rust_robotics_control/
pid_controller.rs1use rust_robotics_core::Controller;
3
4#[derive(Debug, Clone, Copy)]
6pub struct PIDConfig {
7 pub kp: f64,
9 pub ki: f64,
11 pub kd: f64,
13 pub dt: f64,
15 pub max_integral: f64,
17 pub max_output: f64,
19}
20
21impl Default for PIDConfig {
22 fn default() -> Self {
23 Self {
24 kp: 1.0,
25 ki: 0.0,
26 kd: 0.0,
27 dt: 0.01,
28 max_integral: 10.0,
29 max_output: f64::INFINITY,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
50pub struct PIDController {
51 pub config: PIDConfig,
53 integral: f64,
54 prev_error: f64,
55}
56
57impl PIDController {
58 pub fn new(config: PIDConfig) -> Self {
60 Self {
61 config,
62 integral: 0.0,
63 prev_error: 0.0,
64 }
65 }
66
67 pub fn with_gains(kp: f64, ki: f64, kd: f64, dt: f64) -> Self {
69 Self::new(PIDConfig {
70 kp,
71 ki,
72 kd,
73 dt,
74 ..PIDConfig::default()
75 })
76 }
77
78 pub fn step(&mut self, error: f64) -> f64 {
83 let state = 0.0_f64;
84 let reference = error;
85 self.compute(&state, &reference)
86 }
87}
88
89impl Controller for PIDController {
90 type State = f64;
91 type Reference = f64;
92 type Output = f64;
93
94 fn compute(&mut self, state: &f64, reference: &f64) -> f64 {
100 let error = reference - state;
101
102 self.integral = (self.integral + error * self.config.dt)
103 .clamp(-self.config.max_integral, self.config.max_integral);
104
105 let derivative = (error - self.prev_error) / self.config.dt;
106 self.prev_error = error;
107
108 let output =
109 self.config.kp * error + self.config.ki * self.integral + self.config.kd * derivative;
110
111 output.clamp(-self.config.max_output, self.config.max_output)
112 }
113
114 fn reset(&mut self) {
116 self.integral = 0.0;
117 self.prev_error = 0.0;
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use rust_robotics_core::Controller;
125
126 #[test]
127 fn test_proportional_only() {
128 let mut pid = PIDController::with_gains(2.0, 0.0, 0.0, 0.01);
129 let output = pid.compute(&0.0, &3.0);
130 assert!((output - 6.0).abs() < 1e-10, "expected 6.0, got {output}");
131 }
132
133 #[test]
134 fn test_integral_accumulation() {
135 let mut pid = PIDController::with_gains(0.0, 1.0, 0.0, 0.1);
136 for _ in 0..5 {
139 pid.compute(&0.0, &1.0);
140 }
141 let output = pid.compute(&0.0, &1.0);
142 assert!((output - 0.6).abs() < 1e-10, "expected 0.6, got {output}");
144 }
145
146 #[test]
147 fn test_anti_windup() {
148 let config = PIDConfig {
149 kp: 0.0,
150 ki: 1.0,
151 kd: 0.0,
152 dt: 1.0,
153 max_integral: 5.0,
154 max_output: f64::INFINITY,
155 };
156 let mut pid = PIDController::new(config);
157 for _ in 0..20 {
159 pid.compute(&0.0, &1.0);
160 }
161 let output = pid.compute(&0.0, &0.0);
163 assert!(
164 output.abs() <= 5.0 + 1e-10,
165 "anti-windup failed: output {output} exceeded max_integral"
166 );
167 }
168
169 #[test]
170 fn test_derivative() {
171 let mut pid = PIDController::with_gains(0.0, 0.0, 1.0, 0.1);
172 let output = pid.compute(&0.0, &1.0);
174 assert!((output - 10.0).abs() < 1e-10, "expected 10.0, got {output}");
175 let output2 = pid.compute(&0.0, &1.0);
177 assert!(output2.abs() < 1e-10, "expected 0.0, got {output2}");
178 }
179
180 #[test]
181 fn test_reset() {
182 let mut pid = PIDController::with_gains(1.0, 1.0, 1.0, 0.1);
183 for _ in 0..10 {
184 pid.compute(&0.0, &1.0);
185 }
186 pid.reset();
187 let output = pid.compute(&0.0, &2.0);
189 let expected = 1.0 * 2.0 + 1.0 * (2.0 * 0.1) + 1.0 * (2.0 / 0.1);
193 assert!(
194 (output - expected).abs() < 1e-10,
195 "expected {expected}, got {output}"
196 );
197 }
198}