1use crate::pid::{compute_cascade_angle, compute_rate, CascadeAngleControlData, RateControlData};
11use crate::{CascadeBlendingConfig, FlightStabilizer, FlightStabilizerConfig, Number};
12use piddiy::PidController;
13
14pub struct Angle2Stabilizer<T: Number> {
17 angle_roll_pid: PidController<T, CascadeAngleControlData<T>>,
18 angle_pitch_pid: PidController<T, CascadeAngleControlData<T>>,
19 angle_i_limit: T,
20 angle_scale: T,
21 rate_roll_pid: PidController<T, RateControlData<T>>,
22 rate_pitch_pid: PidController<T, RateControlData<T>>,
23 rate_yaw_pid: PidController<T, RateControlData<T>>,
24 rate_i_limit: T,
25 rate_scale: T,
26 kl: T, beta_roll: T, beta_pitch: T, blending_limit: T, prev_set_point_roll: T,
31 prev_set_point_pitch: T,
32 prev_imu_roll: T,
33 prev_imu_pitch: T,
34}
35
36impl<T: Number> Default for Angle2Stabilizer<T> {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl<T: Number> Angle2Stabilizer<T> {
43 pub fn with_config(
45 angle_config: FlightStabilizerConfig<T>,
46 rate_config: FlightStabilizerConfig<T>,
47 blending_config: CascadeBlendingConfig<T, 2>,
48 ) -> Self {
49 let mut angle_roll_pid = PidController::new();
50 angle_roll_pid
51 .compute_fn(compute_cascade_angle)
52 .set_point(angle_config.set_point_roll)
53 .kp(angle_config.kp_roll)
54 .ki(angle_config.ki_roll)
55 .kd(angle_config.kd_roll);
56
57 let mut angle_pitch_pid = PidController::new();
58 angle_pitch_pid
59 .compute_fn(compute_cascade_angle)
60 .set_point(angle_config.set_point_pitch)
61 .kp(angle_config.kp_pitch)
62 .ki(angle_config.ki_pitch)
63 .kd(angle_config.kd_pitch);
64
65 let mut rate_roll_pid = PidController::new();
66 rate_roll_pid
67 .compute_fn(compute_rate)
68 .set_point(rate_config.set_point_roll)
69 .kp(rate_config.kp_roll)
70 .ki(rate_config.ki_roll)
71 .kd(rate_config.kd_roll);
72
73 let mut rate_pitch_pid = PidController::new();
74 rate_pitch_pid
75 .compute_fn(compute_rate)
76 .set_point(rate_config.set_point_pitch)
77 .kp(rate_config.kp_pitch)
78 .ki(rate_config.ki_pitch)
79 .kd(rate_config.kd_pitch);
80
81 let mut rate_yaw_pid = PidController::new();
82 rate_yaw_pid
83 .compute_fn(compute_rate)
84 .set_point(rate_config.set_point_yaw)
85 .kp(rate_config.kp_yaw)
86 .ki(rate_config.ki_yaw)
87 .kd(rate_config.kd_yaw);
88
89 Angle2Stabilizer {
90 angle_roll_pid,
91 angle_pitch_pid,
92 angle_i_limit: angle_config.i_limit,
93 angle_scale: angle_config.scale,
94 rate_roll_pid,
95 rate_pitch_pid,
96 rate_yaw_pid,
97 rate_i_limit: rate_config.i_limit,
98 rate_scale: rate_config.scale,
99 kl: blending_config.k,
100 beta_roll: blending_config.beta[0],
101 beta_pitch: blending_config.beta[1],
102 blending_limit: blending_config.limit,
103 prev_set_point_roll: angle_config.set_point_roll,
104 prev_set_point_pitch: angle_config.set_point_pitch,
105 prev_imu_roll: T::zero(),
106 prev_imu_pitch: T::zero(),
107 }
108 }
109
110 pub fn new() -> Self {
112 Self::with_config(
113 FlightStabilizerConfig::new(),
114 FlightStabilizerConfig::new(),
115 CascadeBlendingConfig::new(),
116 )
117 }
118
119 fn blend(&self, set_point: T, prev_set_point: T, beta: T) -> T {
120 let result = (set_point * self.kl).clamp(-self.blending_limit, self.blending_limit);
121 beta * result + (T::one() - beta) * prev_set_point
122 }
123}
124
125impl<T: Number> FlightStabilizer<T> for Angle2Stabilizer<T> {
126 fn control(
127 &mut self,
128 set_point: (T, T, T),
129 imu_attitude: (T, T, T),
130 gyro_rate: (T, T, T),
131 dt: T,
132 low_throttle: bool,
133 ) -> (T, T, T) {
134 let (set_point_roll, set_point_pitch, set_point_yaw) = set_point;
136 self.angle_roll_pid.set_point(set_point_roll);
137 self.angle_pitch_pid.set_point(set_point_pitch);
138
139 let (imu_roll, imu_pitch, _) = imu_attitude;
141 let (gyro_roll, gyro_pitch, gyro_yaw) = gyro_rate;
142 let angle_roll_data = CascadeAngleControlData {
143 measurement: imu_roll,
144 prev_measurement: self.prev_imu_roll,
145 rate: gyro_roll,
146 dt,
147 integral_limit: self.angle_i_limit,
148 reset_integral: low_throttle,
149 };
150 let angle_pitch_data = CascadeAngleControlData {
151 measurement: imu_pitch,
152 prev_measurement: self.prev_imu_pitch,
153 rate: gyro_pitch,
154 dt,
155 integral_limit: self.angle_i_limit,
156 reset_integral: low_throttle,
157 };
158
159 let mut adjusted_set_point_roll =
161 self.angle_scale * self.angle_roll_pid.compute(angle_roll_data);
162 let mut adjusted_set_point_pitch =
163 self.angle_scale * self.angle_pitch_pid.compute(angle_pitch_data);
164
165 adjusted_set_point_roll = self.blend(
167 adjusted_set_point_roll,
168 self.prev_set_point_roll,
169 self.beta_roll,
170 );
171 adjusted_set_point_pitch = self.blend(
172 adjusted_set_point_pitch,
173 self.prev_set_point_pitch,
174 self.beta_pitch,
175 );
176
177 self.rate_roll_pid.set_point(adjusted_set_point_roll);
179 self.rate_pitch_pid.set_point(adjusted_set_point_pitch);
180 self.rate_yaw_pid.set_point(set_point_yaw);
181
182 let rate_roll_data = RateControlData {
184 rate: gyro_roll,
185 dt,
186 integral_limit: self.rate_i_limit,
187 reset_integral: low_throttle,
188 };
189 let rate_pitch_data = RateControlData {
190 rate: gyro_pitch,
191 dt,
192 integral_limit: self.rate_i_limit,
193 reset_integral: low_throttle,
194 };
195 let rate_yaw_data = RateControlData {
196 rate: gyro_yaw,
197 dt,
198 integral_limit: self.rate_i_limit,
199 reset_integral: low_throttle,
200 };
201 let roll_output = self.rate_scale * self.rate_roll_pid.compute(rate_roll_data);
203 let pitch_output = self.rate_scale * self.rate_pitch_pid.compute(rate_pitch_data);
204 let yaw_output = self.rate_scale * self.rate_yaw_pid.compute(rate_yaw_data);
205
206 self.prev_set_point_roll = adjusted_set_point_roll;
208 self.prev_set_point_pitch = adjusted_set_point_pitch;
209 self.prev_imu_roll = imu_roll;
210 self.prev_imu_pitch = imu_pitch;
211
212 (roll_output, pitch_output, yaw_output)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::test_utils::*;
220
221 fn default_config() -> (
223 FlightStabilizerConfig<f32>,
224 FlightStabilizerConfig<f32>,
225 CascadeBlendingConfig<f32, 2>,
226 ) {
227 let mut angle_config = FlightStabilizerConfig::new();
229
230 angle_config.kp_roll = 0.2;
232 angle_config.ki_roll = 0.3;
233 angle_config.kd_roll = 0.0; angle_config.kp_pitch = angle_config.kp_roll;
236 angle_config.ki_pitch = angle_config.ki_roll;
237 angle_config.kd_pitch = angle_config.kd_roll;
238
239 angle_config.kp_yaw = 0.3;
240 angle_config.ki_yaw = 0.05;
241 angle_config.kd_yaw = 0.00015;
242
243 angle_config.scale = 1.0;
245
246 angle_config.set_point_roll = 0.0;
249 angle_config.set_point_pitch = 0.0;
250 angle_config.set_point_yaw = 0.0;
251
252 angle_config.i_limit = 25.0;
254
255 let mut rate_config = angle_config;
257
258 rate_config.kp_roll = 0.15;
260 rate_config.ki_roll = 0.2;
261 rate_config.kd_roll = 0.0002;
262
263 rate_config.kp_pitch = rate_config.kp_roll;
264 rate_config.ki_pitch = rate_config.ki_roll;
265 rate_config.kd_pitch = rate_config.kd_roll;
266
267 rate_config.kp_yaw = 0.3;
268 rate_config.ki_yaw = 0.05;
269 rate_config.kd_yaw = 0.00015;
270
271 rate_config.scale = 0.01;
273
274 let mut blending_config = CascadeBlendingConfig::new();
276
277 blending_config.beta = [0.9; 2];
278 blending_config.k = 30.0;
279 blending_config.limit = 240.0;
280
281 (angle_config, rate_config, blending_config)
282 }
283
284 #[test]
286 fn test_stabilizer_angle2_initialization_with_default_config() {
287 let (angle_config, rate_config, blending_config) = default_config();
288 let stabilizer = Angle2Stabilizer::with_config(angle_config, rate_config, blending_config);
289
290 assert_eq!(stabilizer.angle_roll_pid.kp, angle_config.kp_roll);
291 assert_eq!(stabilizer.angle_pitch_pid.kp, angle_config.kp_pitch);
292 assert_eq!(stabilizer.rate_roll_pid.kp, rate_config.kp_roll);
293 assert_eq!(stabilizer.rate_pitch_pid.kp, rate_config.kp_pitch);
294 assert_eq!(stabilizer.rate_yaw_pid.kp, rate_config.kp_yaw);
295 assert_eq!(stabilizer.beta_roll, blending_config.beta[0]);
296 }
297
298 #[test]
300 fn test_stabilizer_angle2_integrator_saturation() {
301 let (angle_config, rate_config, blending_config) = default_config();
302 let mut stabilizer =
303 Angle2Stabilizer::with_config(angle_config, rate_config, blending_config);
304
305 let set_point = (500.0, -500.0, 50.0); let imu_attitude = (0.0, 0.0, 0.0); let gyro_rate = (0.0, 0.0, 0.0); let dt = 0.01; let low_throttle = false;
311
312 for _ in 0..100 {
314 let _ = stabilizer.control(set_point, imu_attitude, gyro_rate, dt, low_throttle);
315 }
316
317 let angle_integrals = (
318 stabilizer.angle_roll_pid.integral,
319 stabilizer.angle_pitch_pid.integral,
320 0.0,
321 );
322 let expected_angle_integrals = (angle_config.i_limit, -angle_config.i_limit, 0.0);
323 assert!(
324 vector_close(expected_angle_integrals, angle_integrals),
325 "Integrals should be capped."
326 );
327 let rate_integrals = (
328 stabilizer.rate_roll_pid.integral,
329 stabilizer.rate_pitch_pid.integral,
330 stabilizer.rate_yaw_pid.integral,
331 );
332 let expected_rate_integrals = (
333 rate_config.i_limit,
334 -rate_config.i_limit,
335 rate_config.i_limit,
336 );
337 assert!(
338 vector_close(expected_rate_integrals, rate_integrals),
339 "Integrals should be capped."
340 );
341 }
342
343 #[test]
345 fn test_stabilizer_angle2_low_throttle_integrator_reset() {
346 let (angle_config, rate_config, blending_config) = default_config();
347 let mut stabilizer =
348 Angle2Stabilizer::with_config(angle_config, rate_config, blending_config);
349
350 let set_point = (10.0, 0.0, 10.0); let imu_attitude = (5.0, 5.0, 0.0); let gyro_rate = (1.0, -1.0, -1.0); let dt = 0.01; let _ = stabilizer.control(set_point, imu_attitude, gyro_rate, dt, false);
358 let angle_integrals = (
359 stabilizer.angle_roll_pid.integral,
360 stabilizer.angle_pitch_pid.integral,
361 1.0, );
363 let unexpected_angle_integrals = (0.0, 0.0, 0.0);
364 assert!(
365 vector_not_close(unexpected_angle_integrals, angle_integrals),
366 "Integrals should not be zero."
367 );
368 let rate_integrals = (
369 stabilizer.rate_roll_pid.integral,
370 stabilizer.rate_pitch_pid.integral,
371 stabilizer.rate_yaw_pid.integral,
372 );
373 let unexpected_rate_integrals = (0.0, 0.0, 0.0);
374 assert!(
375 vector_not_close(unexpected_rate_integrals, rate_integrals),
376 "Integrals should not be zero."
377 );
378
379 let _ = stabilizer.control(set_point, imu_attitude, gyro_rate, dt, true);
381 let angle_integrals = (
382 stabilizer.angle_roll_pid.integral,
383 stabilizer.angle_pitch_pid.integral,
384 0.0, );
386 let expected_angle_integrals = (0.0, 0.0, 0.0);
387 assert!(
388 vector_close(expected_angle_integrals, angle_integrals),
389 "Integrals should be zero."
390 );
391 let rate_integrals = (
392 stabilizer.rate_roll_pid.integral,
393 stabilizer.rate_pitch_pid.integral,
394 stabilizer.rate_yaw_pid.integral,
395 );
396 let expected_rate_integrals = (0.0, 0.0, 0.0);
397 assert!(
398 vector_close(expected_rate_integrals, rate_integrals),
399 "Integrals should be zero."
400 );
401 }
402
403 #[test]
405 fn test_stabilizer_angle2_no_error() {
406 let (angle_config, rate_config, blending_config) = default_config();
407 let mut stabilizer =
408 Angle2Stabilizer::with_config(angle_config, rate_config, blending_config);
409
410 let set_point = (0.0, 0.0, 0.0); let imu_attitude = (0.0, 0.0, 0.0); let gyro_rate = (0.0, 0.0, 0.0); let dt = 0.01; let low_throttle = false;
416
417 let output = stabilizer.control(set_point, imu_attitude, gyro_rate, dt, low_throttle);
419 let expected_output = (0.0, 0.0, 0.0);
420
421 assert!(
422 vector_close(expected_output, output),
423 "Outputs should be zero as there is no error."
424 );
425 }
426
427 #[test]
429 fn test_stabilizer_angle2_specific_pid_output() {
430 const PREV_ANGLE_ROLL_INTEGRAL: f32 = 5.0;
432 const PREV_ANGLE_PITCH_INTEGRAL: f32 = -5.0;
433 const PREV_RATE_ROLL_INTEGRAL: f32 = 0.2;
434 const PREV_RATE_PITCH_INTEGRAL: f32 = -0.2;
435 const PREV_RATE_YAW_INTEGRAL: f32 = 0.2;
436 const PREV_SET_POINT_ROLL: f32 = 50.0;
437 const PREV_SET_POINT_PITCH: f32 = -50.0;
438 let (angle_config, rate_config, blending_config) = default_config();
439
440 let set_point = (10.0, 0.0, 10.0); let imu_attitude = (5.0, 5.0, 0.0); let gyro_rate = (1.0, -1.0, -1.0); let dt = 0.01; let low_throttle = false;
446
447 let mut stabilizer =
448 Angle2Stabilizer::with_config(angle_config, rate_config, blending_config);
449 stabilizer.angle_roll_pid.integral = PREV_ANGLE_ROLL_INTEGRAL;
450 stabilizer.angle_pitch_pid.integral = PREV_ANGLE_PITCH_INTEGRAL;
451 stabilizer.rate_roll_pid.integral = PREV_RATE_ROLL_INTEGRAL;
452 stabilizer.rate_pitch_pid.integral = PREV_RATE_PITCH_INTEGRAL;
453 stabilizer.rate_yaw_pid.integral = PREV_RATE_YAW_INTEGRAL;
454 stabilizer.prev_set_point_roll = PREV_SET_POINT_ROLL;
455 stabilizer.prev_set_point_pitch = PREV_SET_POINT_PITCH;
456
457 let angle_roll_data = CascadeAngleControlData {
459 measurement: imu_attitude.0,
460 prev_measurement: imu_attitude.0,
461 rate: gyro_rate.0,
462 dt: dt,
463 integral_limit: angle_config.i_limit,
464 reset_integral: low_throttle,
465 };
466 let angle_pitch_data = CascadeAngleControlData {
467 measurement: imu_attitude.1,
468 prev_measurement: imu_attitude.1,
469 rate: gyro_rate.1,
470 dt: dt,
471 integral_limit: angle_config.i_limit,
472 reset_integral: low_throttle,
473 };
474
475 stabilizer.angle_roll_pid.set_point(set_point.0);
477 let (roll_error, roll_integral, roll_derivative) =
478 compute_cascade_angle(&mut stabilizer.angle_roll_pid, angle_roll_data);
479 let mut adjusted_set_point_roll = angle_config.scale
480 * (angle_config.kp_roll * roll_error
481 + angle_config.ki_roll * roll_integral
482 + angle_config.kd_roll * roll_derivative);
483 adjusted_set_point_roll = stabilizer.blend(
484 adjusted_set_point_roll,
485 PREV_SET_POINT_ROLL,
486 blending_config.beta[0],
487 );
488
489 stabilizer.angle_pitch_pid.set_point(set_point.1);
491 let (pitch_error, pitch_integral, pitch_derivative) =
492 compute_cascade_angle(&mut stabilizer.angle_pitch_pid, angle_pitch_data);
493 let mut adjusted_set_point_pitch = angle_config.scale
494 * (angle_config.kp_pitch * pitch_error
495 + angle_config.ki_pitch * pitch_integral
496 + angle_config.kd_pitch * pitch_derivative);
497 adjusted_set_point_pitch = stabilizer.blend(
498 adjusted_set_point_pitch,
499 PREV_SET_POINT_PITCH,
500 blending_config.beta[1],
501 );
502
503 assert_eq!(roll_error, -pitch_error);
505 assert_eq!(roll_integral, -pitch_integral);
506 assert_eq!(roll_derivative, -pitch_derivative);
507
508 let adjusted_set_points = (adjusted_set_point_roll, adjusted_set_point_pitch, 0.0);
510 let expected_adjusted_set_points = (72.905, -72.905, 0.0);
511 assert!(
512 vector_close(expected_adjusted_set_points, adjusted_set_points),
513 "Adjusted set points should match specific values."
514 );
515
516 let output = stabilizer.control(set_point, imu_attitude, gyro_rate, dt, low_throttle);
518 let expected_output = (0.124076605, -0.124076605, 0.034805);
519 assert!(
520 vector_close(expected_output, output),
521 "PID outputs should match specific values."
522 );
523 }
524}