Skip to content

Commit e3737e5

Browse files
committed
Add further clarifications
1 parent 3d471fb commit e3737e5

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

drivers/advanced/pid-control.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,146 @@ double derivativeTerm() const;
6868
3. **Low-Pass Filtered Derivative**: Reduces noise sensitivity using configurable time constant (tau)
6969
4. **Trapezoidal Integration**: More accurate than simple rectangular integration
7070
71+
### Implementation Details
72+
73+
Understanding why the INDI PID implementation uses specific algorithms:
74+
75+
#### Why Derivative on Measurement Instead of Error?
76+
77+
The implementation calculates derivative on the measurement (process variable) rather than on the error:
78+
79+
```cpp
80+
// From pid.cpp line 152
81+
m_DerivativeTerm = -(2.0 * m_Kd * (measurement - m_PreviousMeasurement) + (2.0 * m_Tau - m_T) * m_DerivativeTerm)
82+
/ (2.0 * m_Tau + m_T);
83+
```
84+
85+
**Reason**: Prevents "derivative kick" during setpoint changes.
86+
87+
When using derivative on error: `d(error)/dt = d(setpoint - measurement)/dt`
88+
89+
If the setpoint changes suddenly (e.g., slewing to a new target):
90+
- Error jumps instantly from small value to large value
91+
- `d(error)/dt` becomes extremely large
92+
- Derivative term produces a huge spike in output
93+
- This causes jerky motion and potential overshoot
94+
95+
When using derivative on measurement: `d(measurement)/dt`
96+
97+
- Measurement changes gradually (limited by physical system dynamics)
98+
- Derivative remains smooth even during setpoint changes
99+
- Only responds to actual process variable changes, not command changes
100+
101+
**Mathematical equivalence during tracking**:
102+
When setpoint is constant: `d(setpoint)/dt = 0`
103+
104+
Therefore: `d(error)/dt = -d(measurement)/dt`
105+
106+
The negative sign in the implementation accounts for this, making it mathematically equivalent to error-based derivative during steady tracking, but smooth during setpoint changes.
107+
108+
#### Why Trapezoidal Integration (Using Both Current and Previous Error)?
109+
110+
The implementation uses trapezoidal rule for integration:
111+
112+
```cpp
113+
// From pid.cpp line 146
114+
m_IntegralTerm = m_IntegralTerm + 0.5 * m_Ki * m_T * (error + m_PreviousError);
115+
```
116+
117+
**Reason**: More accurate approximation of the integral.
118+
119+
**Rectangular integration** (simpler but less accurate):
120+
```
121+
integral += Ki * T * error_current
122+
```
123+
This assumes error is constant over the time interval T, using the current value.
124+
125+
**Trapezoidal integration** (INDI's approach):
126+
```
127+
integral += Ki * T * (error_current + error_previous) / 2
128+
```
129+
This approximates the area under the error curve as a trapezoid, averaging the start and end values.
130+
131+
**Why it's better**:
132+
- More accurate when error changes during the sampling interval
133+
- Second-order accurate (error ~ T²) vs first-order (error ~ T) for rectangular
134+
- Better handles rapidly changing errors
135+
- Accumulated integral more accurately represents true error history
136+
137+
**Visual comparison**:
138+
```
139+
Error vs Time:
140+
| * current
141+
| /|
142+
| / | <- Trapezoid (INDI)
143+
| / |
144+
| * | <- Rectangle (simple method)
145+
|_____|___
146+
T
147+
```
148+
149+
The trapezoid captures the actual error change better than rectangle.
150+
151+
#### Why Low-Pass Filter on Derivative?
152+
153+
The derivative term includes a low-pass filter with time constant `tau`:
154+
155+
```cpp
156+
m_DerivativeTerm = -(2.0 * m_Kd * (measurement - m_PreviousMeasurement) + (2.0 * m_Tau - m_T) * m_DerivativeTerm)
157+
/ (2.0 * m_Tau + m_T);
158+
```
159+
160+
**Reason**: Derivative amplifies high-frequency noise.
161+
162+
Raw derivative: `d(measurement)/dt` is extremely sensitive to measurement noise
163+
- Small random fluctuations in measurement create large derivative values
164+
- Output becomes noisy and can excite system resonances
165+
166+
The filter equation implements a first-order low-pass filter that:
167+
- Smooths out high-frequency noise in the derivative
168+
- Preserves low-frequency (actual) changes
169+
- `tau` parameter controls filter strength: higher tau = more filtering
170+
- Default tau=2 provides good noise rejection without excessive lag
171+
172+
This is why you can increase `Kd` for better damping without making the system noisy.
173+
174+
#### Why Accumulate Capped Integral Instead of Raw Error?
175+
176+
The integrator is limited before accumulation:
177+
178+
```cpp
179+
// Integral term (with trapezoidal integration)
180+
m_IntegralTerm = m_IntegralTerm + 0.5 * m_Ki * m_T * (error + m_PreviousError);
181+
182+
// Clamp Integral (anti-windup for integrator limits)
183+
if (m_IntegratorMin != m_IntegratorMax)
184+
m_IntegralTerm = std::min(m_IntegratorMax, std::max(m_IntegratorMin, m_IntegralTerm));
185+
```
186+
187+
Then later, additional anti-windup when output saturates:
188+
189+
```cpp
190+
// Anti-windup: Back-calculate integral if output is saturated
191+
if (output != outputBeforeSaturation && m_Ki != 0.0)
192+
{
193+
m_IntegralTerm = output - m_ProportionalTerm - m_DerivativeTerm;
194+
}
195+
```
196+
197+
**Reason**: Prevents integrator windup during saturation.
198+
199+
**The problem with unlimited integration**:
200+
1. Error persists because system can't move faster (output saturated)
201+
2. Integral keeps growing to huge values (windup)
202+
3. When error finally reverses, integral takes long time to unwind
203+
4. System overshoots significantly and oscillates
204+
205+
**INDI's two-stage anti-windup**:
206+
1. **Integrator limits**: Prevents integral from growing beyond reasonable bounds
207+
2. **Back-calculation**: When output saturates, recalculates integral to match what actually contributed to output
208+
209+
This keeps the integral term meaningful and prevents overshoot recovery delays.
210+
71211
### Practical Example: Telescope Mount Tracking
72212

73213
The Skywatcher Alt-Az mount driver uses PID controllers for tracking celestial objects. Here's a simplified example:

0 commit comments

Comments
 (0)