4 bool ModeAcro::_enter()
6 acro_state
.locked_roll
= false;
7 acro_state
.locked_pitch
= false;
8 IGNORE_RETURN(ahrs
.get_quaternion(acro_state
.q
));
12 void ModeAcro::update()
14 // handle locked/unlocked control
15 if (acro_state
.locked_roll
) {
16 plane
.nav_roll_cd
= acro_state
.locked_roll_err
;
18 plane
.nav_roll_cd
= ahrs
.roll_sensor
;
20 if (acro_state
.locked_pitch
) {
21 plane
.nav_pitch_cd
= acro_state
.locked_pitch_cd
;
23 plane
.nav_pitch_cd
= ahrs
.pitch_sensor
;
29 output_pilot_throttle();
31 if (plane
.g
.acro_locking
== 2 && plane
.g
.acro_yaw_rate
> 0 &&
32 plane
.yawController
.rate_control_enabled()) {
33 // we can do 3D acro locking
34 stabilize_quaternion();
43 this is the ACRO mode stabilization function. It does rate
44 stabilization on roll and pitch axes
46 void ModeAcro::stabilize()
48 const float speed_scaler
= plane
.get_speed_scaler();
49 const float rexpo
= plane
.roll_in_expo(true);
50 const float pexpo
= plane
.pitch_in_expo(true);
51 float roll_rate
= (rexpo
/SERVO_MAX
) * plane
.g
.acro_roll_rate
;
52 float pitch_rate
= (pexpo
/SERVO_MAX
) * plane
.g
.acro_pitch_rate
;
54 IGNORE_RETURN(ahrs
.get_quaternion(acro_state
.q
));
57 check for special roll handling near the pitch poles
59 if (plane
.g
.acro_locking
&& is_zero(roll_rate
)) {
61 we have no roll stick input, so we will enter "roll locked"
62 mode, and hold the roll we had when the stick was released
64 if (!acro_state
.locked_roll
) {
65 acro_state
.locked_roll
= true;
66 acro_state
.locked_roll_err
= 0;
68 acro_state
.locked_roll_err
+= ahrs
.get_gyro().x
* plane
.G_Dt
;
70 int32_t roll_error_cd
= -ToDeg(acro_state
.locked_roll_err
)*100;
71 plane
.nav_roll_cd
= ahrs
.roll_sensor
+ roll_error_cd
;
72 // try to reduce the integrated angular error to zero. We set
73 // 'stabilize' to true, which disables the roll integrator
74 SRV_Channels::set_output_scaled(SRV_Channel::k_aileron
, plane
.rollController
.get_servo_out(roll_error_cd
,
79 aileron stick is non-zero, use pure rate control until the
80 user releases the stick
82 acro_state
.locked_roll
= false;
83 SRV_Channels::set_output_scaled(SRV_Channel::k_aileron
, plane
.rollController
.get_rate_out(roll_rate
, speed_scaler
));
86 if (plane
.g
.acro_locking
&& is_zero(pitch_rate
)) {
88 user has zero pitch stick input, so we lock pitch at the
89 point they release the stick
91 if (!acro_state
.locked_pitch
) {
92 acro_state
.locked_pitch
= true;
93 acro_state
.locked_pitch_cd
= ahrs
.pitch_sensor
;
95 // try to hold the locked pitch. Note that we have the pitch
96 // integrator enabled, which helps with inverted flight
97 plane
.nav_pitch_cd
= acro_state
.locked_pitch_cd
;
98 SRV_Channels::set_output_scaled(SRV_Channel::k_elevator
, plane
.pitchController
.get_servo_out(plane
.nav_pitch_cd
- ahrs
.pitch_sensor
,
103 user has non-zero pitch input, use a pure rate controller
105 acro_state
.locked_pitch
= false;
106 SRV_Channels::set_output_scaled(SRV_Channel::k_elevator
, plane
.pitchController
.get_rate_out(pitch_rate
, speed_scaler
));
110 if (plane
.g
.acro_yaw_rate
> 0 && plane
.yawController
.rate_control_enabled()) {
111 // user has asked for yaw rate control with yaw rate scaled by ACRO_YAW_RATE
112 const float rudd_expo
= plane
.rudder_in_expo(true);
113 const float yaw_rate
= (rudd_expo
/SERVO_MAX
) * plane
.g
.acro_yaw_rate
;
114 rudder_output
= plane
.yawController
.get_rate_out(yaw_rate
, speed_scaler
, false);
115 } else if (plane
.flight_option_enabled(FlightOptions::ACRO_YAW_DAMPER
)) {
116 // use yaw controller
117 rudder_output
= plane
.calc_nav_yaw_coordinated();
122 rudder_output
= plane
.rudder_input();
125 output_rudder_and_steering(rudder_output
);
130 quaternion based acro stabilization with continuous locking. Enabled with ACRO_LOCKING=2
132 void ModeAcro::stabilize_quaternion()
134 const float speed_scaler
= plane
.get_speed_scaler();
135 auto &q
= acro_state
.q
;
136 const float rexpo
= plane
.roll_in_expo(true);
137 const float pexpo
= plane
.pitch_in_expo(true);
138 const float yexpo
= plane
.rudder_in_expo(true);
140 // get pilot desired rates
141 float roll_rate
= (rexpo
/SERVO_MAX
) * plane
.g
.acro_roll_rate
;
142 float pitch_rate
= (pexpo
/SERVO_MAX
) * plane
.g
.acro_pitch_rate
;
143 float yaw_rate
= (yexpo
/SERVO_MAX
) * plane
.g
.acro_yaw_rate
;
144 bool roll_active
= !is_zero(roll_rate
);
145 bool pitch_active
= !is_zero(pitch_rate
);
146 bool yaw_active
= !is_zero(yaw_rate
);
148 // integrate target attitude
149 Vector3f r
{ float(radians(roll_rate
)), float(radians(pitch_rate
)), float(radians(yaw_rate
)) };
154 // fill in target roll/pitch for GCS/logs
155 plane
.nav_roll_cd
= degrees(q
.get_euler_roll())*100;
156 plane
.nav_pitch_cd
= degrees(q
.get_euler_pitch())*100;
160 IGNORE_RETURN(ahrs
.get_quaternion(ahrs_q
));
162 // zero target if not flying, no stick input and zero throttle
163 if (is_zero(plane
.get_throttle_input()) &&
164 !plane
.is_flying() &&
165 is_zero(roll_rate
) &&
166 is_zero(pitch_rate
) &&
168 // cope with sitting on the ground with neutral sticks, no throttle
172 // get error in attitude
173 Quaternion error_quat
= ahrs_q
.inverse() * q
;
174 Vector3f error_angle1
;
175 error_quat
.to_axis_angle(error_angle1
);
177 // don't let too much error build up, limit to 0.2s
178 const float max_error_t
= 0.2;
179 float max_err_roll_rad
= radians(plane
.g
.acro_roll_rate
*max_error_t
);
180 float max_err_pitch_rad
= radians(plane
.g
.acro_pitch_rate
*max_error_t
);
181 float max_err_yaw_rad
= radians(plane
.g
.acro_yaw_rate
*max_error_t
);
183 if (!roll_active
&& acro_state
.roll_active_last
) {
184 max_err_roll_rad
= 0;
186 if (!pitch_active
&& acro_state
.pitch_active_last
) {
187 max_err_pitch_rad
= 0;
189 if (!yaw_active
&& acro_state
.yaw_active_last
) {
193 Vector3f desired_rates
= error_angle1
;
194 desired_rates
.x
= constrain_float(desired_rates
.x
, -max_err_roll_rad
, max_err_roll_rad
);
195 desired_rates
.y
= constrain_float(desired_rates
.y
, -max_err_pitch_rad
, max_err_pitch_rad
);
196 desired_rates
.z
= constrain_float(desired_rates
.z
, -max_err_yaw_rad
, max_err_yaw_rad
);
198 // correct target based on max error
199 q
.rotate_fast(desired_rates
- error_angle1
);
202 // convert to desired body rates
203 desired_rates
.x
/= plane
.rollController
.tau();
204 desired_rates
.y
/= plane
.pitchController
.tau();
205 desired_rates
.z
/= plane
.pitchController
.tau(); // no yaw tau parameter, use pitch
207 desired_rates
*= degrees(1.0);
210 desired_rates
.x
= roll_rate
;
213 desired_rates
.y
= pitch_rate
;
216 desired_rates
.z
= yaw_rate
;
219 // call to rate controllers
220 SRV_Channels::set_output_scaled(SRV_Channel::k_aileron
, plane
.rollController
.get_rate_out(desired_rates
.x
, speed_scaler
));
221 SRV_Channels::set_output_scaled(SRV_Channel::k_elevator
, plane
.pitchController
.get_rate_out(desired_rates
.y
, speed_scaler
));
222 output_rudder_and_steering(plane
.yawController
.get_rate_out(desired_rates
.z
, speed_scaler
, false));
224 acro_state
.roll_active_last
= roll_active
;
225 acro_state
.pitch_active_last
= pitch_active
;
226 acro_state
.yaw_active_last
= yaw_active
;