kernelvex/control/feedforward.rs
1//! Feedforward controllers for motion control.
2//!
3//! This module provides feedforward controllers that compute motor output based on
4//! desired velocity and acceleration, without feedback. When combined with PID,
5//! feedforward provides proactive control that anticipates required motor output.
6//!
7//! # Overview
8//!
9//! Feedforward uses a physics-based model to predict the motor voltage needed to
10//! achieve a desired motion:
11//!
12//! ```text
13//! voltage = ks * sign(v) + kv * v + ka * a
14//! ```
15//!
16//! Where:
17//! - `ks`: Static friction compensation (voltage to overcome stiction)
18//! - `kv`: Velocity gain (voltage per unit velocity)
19//! - `ka`: Acceleration gain (voltage per unit acceleration)
20//!
21//! # Controllers
22//!
23//! - [`FeedForward`]: Standard feedforward for linear motion
24//! - [`ArmFeedForward`]: Extended feedforward with gravity compensation for arms
25//!
26//! # Example
27//!
28//! ```no_run
29//! use kernelvex::FeedForward;
30//!
31//! let mut ff = FeedForward::new();
32//! ff.set_ks(0.1); // Static friction
33//! ff.set_kv(0.5); // Velocity gain
34//! ff.set_ka(0.01); // Acceleration gain
35//!
36//! // Calculate voltage for 2 m/s velocity, 0.5 m/s^2 acceleration
37//! let voltage = ff.calculate(2.0, 0.5);
38//! ```
39
40use crate::QAngle;
41
42/// Feedforward controller for linear motion.
43///
44/// Computes motor voltage based on desired velocity and acceleration using:
45///
46/// ```text
47/// voltage = ks * sign(v) + kv * v + ka * a
48/// ```
49///
50/// # Gains
51///
52/// - `ks`: Static friction compensation. The minimum voltage needed to start
53/// moving from rest. Applied in the direction of motion.
54/// - `kv`: Velocity feedforward. Voltage required per unit velocity to maintain
55/// constant speed against friction and back-EMF.
56/// - `ka`: Acceleration feedforward. Voltage required per unit acceleration to
57/// overcome inertia.
58///
59/// # Example
60///
61/// ```no_run
62/// let ff = FeedForward::new()
63/// .set_gains(0.1, 0.5, 0.01);
64///
65/// let voltage = ff.calculate(target_velocity, target_acceleration);
66/// ```
67pub struct FeedForward {
68 /// Static friction compensation (voltage to overcome stiction).
69 ks: f64,
70 /// Velocity feedforward gain (voltage per unit velocity).
71 kv: f64,
72 /// Acceleration feedforward gain (voltage per unit acceleration).
73 ka: f64,
74}
75
76impl FeedForward {
77 /// Creates a new feedforward controller with zero gains.
78 ///
79 /// Use [`set_gains`](Self::set_gains) or individual setters to configure.
80 #[inline]
81 pub const fn new() -> FeedForward {
82 Self {
83 ks: 0.,
84 kv: 0.,
85 ka: 0.,
86 }
87 }
88
89 /// Returns the static friction gain.
90 pub const fn ks(&self) -> f64 {
91 self.ks
92 }
93
94 /// Returns the velocity feedforward gain.
95 pub const fn kv(&self) -> f64 {
96 self.kv
97 }
98
99 /// Returns the acceleration feedforward gain.
100 pub const fn ka(&self) -> f64 {
101 self.ka
102 }
103
104 /// Sets all feedforward gains at once.
105 ///
106 /// # Arguments
107 ///
108 /// * `ks` - Static friction compensation
109 /// * `kv` - Velocity feedforward gain
110 /// * `ka` - Acceleration feedforward gain
111 ///
112 /// # Returns
113 ///
114 /// A new `FeedForward` with the specified gains.
115 pub const fn set_gains(&mut self, ks: f64, kv: f64, ka: f64) -> Self {
116 Self { ks, kv, ka }
117 }
118
119 /// Sets the static friction gain.
120 pub const fn set_ks(&mut self, ks: f64) {
121 self.ks = ks
122 }
123
124 /// Sets the velocity feedforward gain.
125 pub const fn set_kv(&mut self, kv: f64) {
126 self.kv = kv
127 }
128
129 /// Sets the acceleration feedforward gain.
130 pub const fn set_ka(&mut self, ka: f64) {
131 self.ka = ka
132 }
133
134 /// Calculates the feedforward voltage for the given velocity and acceleration.
135 ///
136 /// # Arguments
137 ///
138 /// * `velocity` - Desired velocity (units depend on your system)
139 /// * `acceleration` - Desired acceleration (units depend on your system)
140 ///
141 /// # Returns
142 ///
143 /// The feedforward voltage output.
144 ///
145 /// # Formula
146 ///
147 /// ```text
148 /// voltage = ks * sign(velocity) + kv * velocity + ka * acceleration
149 /// ```
150 pub fn calculate(&self, velocity: f64, acceleration: f64) -> f64 {
151 self.ks * velocity.signum() + self.kv * velocity + self.ka * acceleration
152 }
153}
154
155/// Feedforward controller for arm mechanisms with gravity compensation.
156///
157/// Extends [`FeedForward`] with a gravity compensation term that accounts for
158/// the arm's position. This is essential for arms that must hold position
159/// against gravity.
160///
161/// # Formula
162///
163/// ```text
164/// voltage = ks * sign(v) + kv * v + ka * a + kg * g(angle)
165/// ```
166///
167/// Where `g(angle)` is a function that returns the gravity effect at the given
168/// angle (typically `cos(angle)` for a simple arm).
169///
170/// # Example
171///
172/// ```no_run
173/// use kernelvex::{ArmFeedForward, QAngle};
174///
175/// let ff = ArmFeedForward::new(0.1, 0.5, 0.01, 0.3);
176///
177/// // Calculate with gravity compensation using cosine
178/// let voltage = ff.calculate(
179/// QAngle::from_degrees(45.0),
180/// target_velocity,
181/// target_acceleration,
182/// |angle| angle.cos(),
183/// );
184/// ```
185pub struct ArmFeedForward {
186 /// Static friction compensation.
187 ks: f64,
188 /// Velocity feedforward gain.
189 kv: f64,
190 /// Acceleration feedforward gain.
191 ka: f64,
192 /// Gravity compensation gain.
193 kg: f64,
194}
195
196impl ArmFeedForward {
197 /// Creates a new arm feedforward controller with the given gains.
198 ///
199 /// # Arguments
200 ///
201 /// * `ks` - Static friction compensation
202 /// * `kv` - Velocity feedforward gain
203 /// * `ka` - Acceleration feedforward gain
204 /// * `kg` - Gravity compensation gain
205 #[inline]
206 pub const fn new(ks: f64, kv: f64, ka: f64, kg: f64) -> Self {
207 Self { ks, kv, ka, kg }
208 }
209
210 /// Returns the static friction gain.
211 #[inline]
212 pub const fn ks(&self) -> f64 {
213 self.ks
214 }
215
216 /// Returns the velocity feedforward gain.
217 #[inline]
218 pub const fn kv(&self) -> f64 {
219 self.kv
220 }
221
222 /// Returns the acceleration feedforward gain.
223 #[inline]
224 pub const fn ka(&self) -> f64 {
225 self.ka
226 }
227
228 /// Returns the gravity compensation gain.
229 #[inline]
230 pub const fn kg(&self) -> f64 {
231 self.kg
232 }
233
234 /// Sets all gains at once, returning a new instance.
235 #[inline]
236 pub const fn set_gains(self, ks: f64, kv: f64, ka: f64, kg: f64) -> Self {
237 Self { ks, kv, ka, kg }
238 }
239
240 /// Sets the static friction gain, returning a new instance.
241 #[inline]
242 pub const fn set_ks(self, ks: f64) -> Self {
243 Self { ks, ..self }
244 }
245
246 /// Sets the velocity gain, returning a new instance.
247 #[inline]
248 pub const fn set_kv(self, kv: f64) -> Self {
249 Self { kv, ..self }
250 }
251
252 /// Sets the acceleration gain, returning a new instance.
253 #[inline]
254 pub const fn set_ka(self, ka: f64) -> Self {
255 Self { ka, ..self }
256 }
257
258 /// Sets the gravity gain, returning a new instance.
259 #[inline]
260 pub const fn set_kg(self, kg: f64) -> Self {
261 Self { kg, ..self }
262 }
263
264 /// Calculates the feedforward voltage with gravity compensation.
265 ///
266 /// # Arguments
267 ///
268 /// * `angle` - Current arm angle
269 /// * `velocity` - Desired velocity
270 /// * `acceleration` - Desired acceleration
271 /// * `g` - Gravity function that takes angle and returns gravity effect
272 ///
273 /// # Returns
274 ///
275 /// The feedforward voltage output including gravity compensation.
276 ///
277 /// # Example
278 ///
279 /// ```no_run
280 /// // Using cosine for simple arm
281 /// let voltage = ff.calculate(angle, vel, acc, |a| a.cos());
282 ///
283 /// // Using custom gravity function
284 /// let voltage = ff.calculate(angle, vel, acc, |a| {
285 /// // Custom gravity calculation for 4-bar linkage
286 /// (a + offset).cos() * linkage_factor
287 /// });
288 /// ```
289 #[inline]
290 pub fn calculate(
291 &self,
292 angle: QAngle,
293 velocity: f64,
294 acceleration: f64,
295 g: impl Fn(QAngle) -> f64,
296 ) -> f64 {
297 self.ks * velocity.signum()
298 + self.kv * velocity
299 + self.ka * acceleration
300 + self.kg * g(angle)
301 }
302}