witness_controller/
lib.rs1#![no_std]
15
16const SCALE: i128 = 1 << 24;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20#[repr(C)]
21pub struct Coefficients {
22 pub kp: i32,
24 pub ki_period: i32,
26 pub derivative_decay: i32,
28 pub derivative_gain: i32,
30 pub position_gain: i32,
32 pub velocity_gain: i32,
34 pub reference_gain: i32,
36 pub output_min: i32,
38 pub output_max: i32,
40 pub integral_min: i32,
42 pub integral_max: i32,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct InvalidCoefficients;
49
50impl Coefficients {
51 pub fn validate(&self) -> Result<(), InvalidCoefficients> {
53 if self.output_min > self.output_max
54 || self.integral_min > self.integral_max
55 || self.output_min > 0
56 || self.output_max < 0
57 || self.integral_min > 0
58 || self.integral_max < 0
59 || self.kp < 0
60 || self.ki_period < 0
61 || self.derivative_gain < 0
62 || self.derivative_decay < 0
63 || i128::from(self.derivative_decay) > SCALE
64 {
65 return Err(InvalidCoefficients);
66 }
67 Ok(())
68 }
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73#[repr(C)]
74pub struct Command {
75 pub cycle: u32,
77 pub command: i32,
79 pub integral: i32,
81 pub derivative: i32,
83 pub clipped: bool,
85 pub integral_held: bool,
87}
88
89#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
91#[repr(C)]
92pub struct PidState {
93 pub integral: i32,
95 pub derivative: i32,
97 previous_position: i32,
98 initialized: bool,
99}
100
101fn clamp(value: i128, lower: i32, upper: i32) -> i32 {
103 value.clamp(i128::from(lower), i128::from(upper)) as i32
104}
105
106impl PidState {
107 pub fn reset(&mut self) {
109 *self = Self::default();
110 }
111
112 pub fn step(
120 &mut self,
121 coefficients: &Coefficients,
122 cycle: u32,
123 reference: i32,
124 position: i32,
125 ) -> Result<Command, InvalidCoefficients> {
126 coefficients.validate()?;
127 let error = i128::from(reference) - i128::from(position);
128 let difference = i128::from(position) - i128::from(self.previous_position);
129 let derivative = if self.initialized {
130 clamp(
131 (i128::from(coefficients.derivative_decay) * i128::from(self.derivative)
132 - i128::from(coefficients.derivative_gain) * difference)
133 .div_euclid(SCALE),
134 i32::MIN,
135 i32::MAX,
136 )
137 } else {
138 0
139 };
140 let proposed_integral = clamp(
141 (i128::from(self.integral) * SCALE + i128::from(coefficients.ki_period) * error)
142 .div_euclid(SCALE),
143 coefficients.integral_min,
144 coefficients.integral_max,
145 );
146 let proportional = i128::from(coefficients.kp) * error;
147 let output = |integral| {
148 (proportional + i128::from(integral) * SCALE + i128::from(derivative) * SCALE)
149 .div_euclid(SCALE)
150 };
151 let provisional = output(self.integral);
152 let held = (provisional >= i128::from(coefficients.output_max) && error > 0)
153 || (provisional <= i128::from(coefficients.output_min) && error < 0);
154 let integral = if held {
155 self.integral
156 } else {
157 proposed_integral
158 };
159 let raw = output(integral);
160 self.integral = integral;
161 self.derivative = derivative;
162 self.previous_position = position;
163 self.initialized = true;
164 Ok(Command {
165 cycle,
166 command: clamp(raw, coefficients.output_min, coefficients.output_max),
167 integral,
168 derivative,
169 clipped: raw < i128::from(coefficients.output_min)
170 || raw > i128::from(coefficients.output_max),
171 integral_held: held,
172 })
173 }
174}
175
176pub fn lqr_step(
182 coefficients: &Coefficients,
183 cycle: u32,
184 reference: i32,
185 position: i32,
186 velocity: i32,
187) -> Result<Command, InvalidCoefficients> {
188 coefficients.validate()?;
189 let raw = (i128::from(coefficients.reference_gain) * i128::from(reference)
190 - i128::from(coefficients.position_gain) * i128::from(position)
191 - i128::from(coefficients.velocity_gain) * i128::from(velocity))
192 .div_euclid(SCALE);
193 Ok(Command {
194 cycle,
195 command: clamp(raw, coefficients.output_min, coefficients.output_max),
196 integral: 0,
197 derivative: 0,
198 clipped: raw < i128::from(coefficients.output_min)
199 || raw > i128::from(coefficients.output_max),
200 integral_held: false,
201 })
202}