hackflight/pids/
angle.rs

1/*
2   Hackflight angle PID controller support
3
4   Copyright (C) 2022 Simon D. Levy
5
6   MIT License
7 */
8
9use crate::Demands;
10use crate::VehicleState;
11
12use crate::filters;
13
14use crate::utils::constrain_f;
15
16use crate::clock::DT;
17
18const DTERM_LPF1_DYN_MIN_HZ: f32 = 75.0;
19const DTERM_LPF1_DYN_MAX_HZ: f32 = 150.0;
20const DTERM_LPF2_HZ: f32 = 150.0;
21const D_MIN_LOWPASS_HZ: f32 = 35.0;  
22const ITERM_RELAX_CUTOFF: f32 = 15.0;
23const D_MIN_RANGE_HZ: f32 = 85.0;  
24
25// minimum of 5ms between updates
26const DYN_LPF_THROTTLE_UPDATE_DELAY_US: u32 = 5000; 
27
28const DYN_LPF_THROTTLE_STEPS: f32 = 100.0;
29
30const ITERM_LIMIT: f32 = 400.0;
31
32const ITERM_WINDUP_POINT_PERCENT: f32 = 85.0;        
33
34// Full iterm suppression in setpoint mode at high-passed setpoint rate > 40deg/sec
35const ITERM_RELAX_SETPOINT_THRESHOLD: f32 = 40.0;
36
37const D_MIN: f32 = 30.0;
38const D_MIN_GAIN: f32 = 37.0;
39const D_MIN_ADVANCE: f32 = 20.0;
40
41const FEEDFORWARD_MAX_RATE_LIMIT: f32 = 900.0;
42
43const DYN_LPF_CURVE_EXPO: f32 = 5.0;
44
45// PT2 lowpass cutoff to smooth the boost effect
46const D_MIN_GAIN_FACTOR: f32  = 0.00008;
47const D_MIN_SETPOINT_GAIN_FACTOR: f32 = 0.00008;
48
49const RATE_ACCEL_LIMIT: f32 = 0.0;
50const YAW_RATE_ACCEL_LIMIT: f32 = 0.0;
51
52const OUTPUT_SCALING: f32 = 1000.0;
53const  LIMIT_CYCLIC: f32 = 500.0; 
54const  LIMIT_YAW: f32 = 400.0;
55
56const YAW_LOWPASS_HZ: f32 = 100.0;
57
58#[derive(Clone)]
59pub struct Pid { 
60    k_rate_p: f32,
61    k_rate_i: f32,
62    k_rate_d: f32,
63    k_rate_f: f32,
64    k_level_p: f32,
65    usec_prev: u32,
66    roll : CyclicAxis,
67    pitch : CyclicAxis,
68    yaw: Axis,
69    dyn_lpf_previous_quantized_throttle: i32,  
70    pterm_yaw_lpf: filters::Pt1
71}
72
73pub fn make(
74    k_rate_p: f32,
75    k_rate_i: f32,
76    k_rate_d: f32,
77    k_rate_f: f32,
78    k_level_p: f32) -> Pid {
79
80        Pid {
81            k_rate_p: k_rate_p,
82            k_rate_i: k_rate_i,
83            k_rate_d: k_rate_d,
84            k_rate_f: k_rate_f,
85            k_level_p: k_level_p,
86            usec_prev: 0,
87            roll : make_cyclic_axis(),
88            pitch : make_cyclic_axis(),
89            yaw: make_axis(),
90            dyn_lpf_previous_quantized_throttle: 0, 
91            pterm_yaw_lpf : filters::make_pt1(YAW_LOWPASS_HZ)
92        }
93} 
94
95pub fn get_demands(
96    pid: &mut Pid,
97    usec: &u32,
98    demands: &Demands,
99    vstate: &VehicleState,
100    reset: &bool) -> Demands {
101
102        let d_usec = *usec - pid.usec_prev;
103        pid.usec_prev = *usec;
104
105        let roll_demand  = rescale_axis(demands.roll);
106        let pitch_demand = rescale_axis(demands.pitch);
107        let yaw_demand   = rescale_axis(demands.yaw);
108
109        let max_velocity = RATE_ACCEL_LIMIT * 100.0 * DT;
110
111        let roll = 
112            update_cyclic(
113                &mut pid.roll,
114                pid.k_level_p,
115                pid.k_rate_p,
116                pid.k_rate_i,
117                pid.k_rate_d,
118                pid.k_rate_f,
119                roll_demand,
120                vstate.phi,
121                vstate.dphi,
122                max_velocity);
123
124
125        let pitch = 
126            update_cyclic(
127                &mut pid.pitch,
128                pid.k_level_p,
129                pid.k_rate_p,
130                pid.k_rate_i,
131                pid.k_rate_d,
132                pid.k_rate_f,
133                pitch_demand,
134                vstate.theta,
135                vstate.dtheta,
136                max_velocity);
137
138        let yaw = update_yaw(
139            &mut pid.yaw,
140            pid.pterm_yaw_lpf,
141            pid.k_rate_p,
142            pid.k_rate_i,
143            yaw_demand,
144            vstate.dpsi);
145
146        pid.roll.axis.integral = if *reset { 0.0 } else { pid.roll.axis.integral };
147        pid.pitch.axis.integral = if *reset { 0.0 } else { pid.pitch.axis.integral };
148        pid.yaw.integral = if *reset { 0.0 } else { pid.yaw.integral };
149
150        if d_usec >= DYN_LPF_THROTTLE_UPDATE_DELAY_US {
151
152            // Quantize the throttle to reduce the number of filter updates
153            let quantized_throttle = (demands.throttle * DYN_LPF_THROTTLE_STEPS) as i32; 
154
155            if quantized_throttle != pid.dyn_lpf_previous_quantized_throttle {
156
157                // Scale the quantized value back to the throttle range so the
158                // filter cutoff steps are repeatable
159                let dyn_lpf_throttle = (quantized_throttle as f32) / DYN_LPF_THROTTLE_STEPS;
160
161                let cutoff_freq = dyn_lpf_cutoff_freq(dyn_lpf_throttle,
162                    DTERM_LPF1_DYN_MIN_HZ,
163                    DTERM_LPF1_DYN_MAX_HZ,
164                    DYN_LPF_CURVE_EXPO);
165
166                init_lpf1(&mut pid.roll, cutoff_freq);
167                init_lpf1(&mut pid.pitch, cutoff_freq);
168
169                pid.dyn_lpf_previous_quantized_throttle = quantized_throttle;
170            }
171        }
172
173        Demands { 
174            throttle : demands.throttle,
175            roll : constrain_output(roll, LIMIT_CYCLIC),
176            pitch : constrain_output(pitch, LIMIT_CYCLIC),
177            yaw : constrain_output(yaw, LIMIT_YAW)
178        }
179    }
180
181#[derive(Clone,Copy)]
182struct Axis {
183
184    previous_setpoint : f32,
185    integral : f32
186}
187
188#[derive(Clone)]
189struct CyclicAxis {
190
191    axis: Axis,
192    dterm_lpf1 : filters::Pt1,
193    dterm_lpf2 : filters::Pt1,
194    d_min_lpf: filters::Pt2,
195    d_min_range: filters::Pt2,
196    windup_lpf: filters::Pt1,
197    previous_dterm: f32
198}
199
200fn update_cyclic(
201    cyclic_axis: &mut CyclicAxis,
202    k_level_p: f32,
203    k_rate_p: f32,
204    k_rate_i: f32,
205    k_rate_d: f32,
206    k_rate_f: f32,
207    demand: f32,
208    angle: f32,
209    angvel: f32,
210    max_velocity: f32) -> f32
211{
212    let axis: &mut Axis = &mut cyclic_axis.axis;
213
214    let current_setpoint =
215        if max_velocity > 0.0
216            // {acceleration_limit(&mut cyclic_axis.axis, demand, max_velocity)}
217        {acceleration_limit(axis, demand, max_velocity)}
218        else {demand};
219
220    let new_setpoint = level_pid(k_level_p, current_setpoint, angle);
221
222    // -----calculate error rate
223    let error_rate = new_setpoint - angvel;
224
225    let setpoint_lpf = filters::apply_pt1(cyclic_axis.windup_lpf, current_setpoint);
226
227    let setpoint_hpf = (current_setpoint - setpoint_lpf).abs();
228
229    let iterm_relax_factor =
230        (1.0 - setpoint_hpf / ITERM_RELAX_SETPOINT_THRESHOLD).max(0.0);
231
232    let is_decreasing_i =
233        ((axis.integral > 0.0) && (error_rate < 0.0)) ||
234        ((axis.integral < 0.0) && (error_rate > 0.0));
235
236    // Was applyItermRelax in original
237    let iterm_error_rate = error_rate * (if !is_decreasing_i  {iterm_relax_factor} else {1.0} );
238
239    let frequency = 1.0 / DT;
240
241    // Calculate P component --------------------------------------------------
242    let pterm = k_rate_p * error_rate;
243
244    // Calculate I component --------------------------------------------------
245    axis.integral = constrain_f(axis.integral + (k_rate_i * DT) * iterm_error_rate,
246    -ITERM_LIMIT, ITERM_LIMIT);
247
248    // Calculate D component --------------------------------------------------
249
250    let dterm = filters::apply_pt1(
251        cyclic_axis.dterm_lpf2, 
252        filters::apply_pt1(cyclic_axis.dterm_lpf1, angvel));
253
254    // Divide rate change by dT to get differential (ie dr/dt).
255    // dT is fixed and calculated from the target PID loop time
256    // This is done to avoid DTerm spikes that occur with
257    // dynamically calculated deltaT whenever another task causes
258    // the PID loop execution to be delayed.
259    let delta = -(dterm - cyclic_axis.previous_dterm) * frequency;
260
261    let pre_t_pa_d = k_rate_d * delta;
262
263    let d_min_percent = if D_MIN > 0.0 && D_MIN < k_rate_d { D_MIN / k_rate_d } else { 0.0 };
264
265    let demand_delta: f32 = 0.0;
266
267    let d_min_gyro_gain = D_MIN_GAIN * D_MIN_GAIN_FACTOR / D_MIN_LOWPASS_HZ;
268
269    let d_min_gyro_factor = (filters::apply_pt2(cyclic_axis.d_min_range, delta)).abs() * d_min_gyro_gain;
270
271    let d_min_setpoint_gain =
272        D_MIN_GAIN * D_MIN_SETPOINT_GAIN_FACTOR * D_MIN_ADVANCE * frequency /
273        (100.0 * D_MIN_LOWPASS_HZ);
274
275    let d_min_setpoint_factor = (demand_delta).abs() * d_min_setpoint_gain;
276
277    let d_min_factor = 
278        d_min_percent + (1.0 - d_min_percent) * d_min_gyro_factor.max(d_min_setpoint_factor);
279
280    let d_min_factor_filtered = filters::apply_pt2(cyclic_axis.d_min_lpf, d_min_factor);
281
282    let d_min_factor = d_min_factor_filtered.min(1.0);
283
284    cyclic_axis.previous_dterm = dterm;
285
286    // Apply the d_min_factor
287    let dterm = pre_t_pa_d * d_min_factor;
288
289    // Calculate feedforward component -----------------------------------------
290
291    // halve feedforward in Level mode since stick sensitivity is
292    // weaker by about half transition now calculated in
293    // feedforward.c when new RC data arrives 
294    let feed_forward = k_rate_f * demand_delta * frequency;
295
296    let feedforward_max_rate: f32 = 670.0;
297
298    let feedforward_max_rate_limit =
299        feedforward_max_rate * FEEDFORWARD_MAX_RATE_LIMIT * 0.01;
300
301    let fterm = if feedforward_max_rate_limit != 0.0 {
302        apply_feeedforward_limit(
303            feed_forward,
304            current_setpoint,
305            k_rate_p,
306            feedforward_max_rate_limit) }
307    else {
308        feed_forward 
309    };
310
311    pterm + axis.integral + dterm + fterm
312
313} // update_cyclic
314
315
316fn update_yaw(
317    axis: &mut Axis,
318    pterm_lpf: filters::Pt1,
319    kp: f32,
320    ki: f32,
321    demand: f32,
322    angvel: f32) -> f32 {
323
324        // gradually scale back integration when above windup point
325        let iterm_windup_point_inv = 1.0 / (1.0 - (ITERM_WINDUP_POINT_PERCENT / 100.0));
326
327        let dyn_ci = DT * (if iterm_windup_point_inv > 1.0
328            {constrain_f(iterm_windup_point_inv, 0.0, 1.0)}
329            else {1.0});
330
331        let max_velocity = YAW_RATE_ACCEL_LIMIT * 100.0 * DT; 
332
333        let current_setpoint =
334            if max_velocity > 0.0 {acceleration_limit(axis, demand, max_velocity)} else {demand};
335
336        let error_rate = current_setpoint - angvel;
337
338        // -----calculate P component
339        let pterm = filters::apply_pt1(pterm_lpf, kp * error_rate);
340
341        // -----calculate I component, constraining windup
342        axis.integral =
343            constrain_f(axis.integral + (ki * dyn_ci) * error_rate, -ITERM_LIMIT, ITERM_LIMIT);
344
345        pterm + axis.integral
346    }
347
348
349fn make_cyclic_axis() -> CyclicAxis {
350
351    CyclicAxis {
352        axis: make_axis(),
353        dterm_lpf1 : filters::make_pt1(DTERM_LPF1_DYN_MIN_HZ),
354        dterm_lpf2 : filters::make_pt1(DTERM_LPF2_HZ),
355        d_min_lpf: filters::make_pt2(D_MIN_LOWPASS_HZ),
356        d_min_range: filters::make_pt2(D_MIN_RANGE_HZ),
357        windup_lpf: filters::make_pt1(ITERM_RELAX_CUTOFF),
358        previous_dterm: 0.0 }
359}
360
361fn make_axis() -> Axis {
362
363    Axis { previous_setpoint: 0.0, integral: 0.0 }
364}
365
366// [-1,+1] => [-670,+670] with nonlinearity
367fn rescale_axis(command: f32) -> f32 {
368
369    const CTR: f32 = 0.104;
370
371    let expof = command * command.abs();
372    let angle_rate = command * CTR + (1.0 - CTR) * expof;
373
374    670.0 * angle_rate
375}
376
377fn level_pid(k_level_p: f32, current_setpoint: f32, current_angle: f32) -> f32
378{
379    // calculate error angle and limit the angle to the max inclination
380    // rcDeflection in [-1.0, 1.0]
381
382    const LEVEL_ANGLE_LIMIT: f32 = 45.0;
383
384    let angle = constrain_f(LEVEL_ANGLE_LIMIT * current_setpoint,
385        -LEVEL_ANGLE_LIMIT, LEVEL_ANGLE_LIMIT);
386
387    let angle_error = angle - (current_angle / 10.0);
388
389    if k_level_p > 0.0  {angle_error * k_level_p } else {current_setpoint}
390}
391
392fn acceleration_limit(axis: &mut Axis, current_setpoint: f32, max_velocity: f32) -> f32 {
393
394    let current_velocity = current_setpoint - axis.previous_setpoint;
395
396    let new_setpoint = 
397        if current_velocity.abs() > max_velocity 
398        { if current_velocity > 0.0 
399            { axis.previous_setpoint + max_velocity } 
400            else { axis.previous_setpoint - max_velocity } 
401        }
402        else { current_setpoint };
403
404    axis.previous_setpoint = new_setpoint;
405
406    new_setpoint
407}
408
409
410fn apply_feeedforward_limit(
411    value: f32,
412    current_setpoint: f32,
413    k_rate_p : f32,
414    max_rate_limit: f32) -> f32 {
415
416        if value * current_setpoint > 0.0 {
417            if current_setpoint.abs() <= max_rate_limit
418            { constrain_f(value, (-max_rate_limit -
419                    current_setpoint) * k_rate_p,
420                    (max_rate_limit - current_setpoint) * k_rate_p)}
421            else {
422                0.0
423            }
424        }
425        else {
426            0.0
427        }
428    }
429
430fn init_lpf1(cyclic_axis: &mut CyclicAxis, cutoff_freq: f32) {
431
432    filters::adjust_pt1_gain(cyclic_axis.dterm_lpf1, cutoff_freq);
433}
434
435fn dyn_lpf_cutoff_freq(throttle: f32, dyn_lpf_min: f32, dyn_lpf_max: f32, expo: f32) -> f32 {
436    let expof = expo / 10.0;
437    let curve = throttle * (1.0 - throttle) * expof + throttle;
438
439    (dyn_lpf_max - dyn_lpf_min) * curve + dyn_lpf_min
440}
441
442
443fn constrain_output(demand: f32, limit: f32) -> f32 {
444
445    constrain_f(demand, -limit, limit) / OUTPUT_SCALING
446}