PID 演算法(比例-積分-微分控制器)是工業自動化與嵌入式控制中最廣泛應用的回授控制演算法。從 ESP32 的溫度控制、STM32 的馬達轉速調節,到無人機的姿態穩定,PID 控制器幾乎無所不在。
本文從 PID 控制的基本原理出發,帶你理解 P、I、D 三個參數各司什麼職,並以 ESP32 和 STM32 的實際程式碼教你如何實作與調整 PID 控制器。
什麼是 PID 控制?
PID 控制器是一種回授控制機制:它讀取感測器數值(Process Variable, PV),與目標值(Setpoint, SP)比較得到誤差 e(t),然後根據誤差計算控制輸出 u(t),驅動被控系統(Plant)趨近目標。
PID 公式
u(t) = Kp × e(t) + Ki × ∫e(t)dt + Kd × de(t)/dt
離散化後(嵌入式系統使用):
u[k] = Kp × e[k] + Ki × Σ(e[i] × Δt) + Kd × (e[k] - e[k-1]) / Δt
P、I、D 各自的作用
P — 比例控制(Proportional)
輸出與當前誤差成正比。誤差大 → 輸出大,誤差小 → 輸出小。
- 優點:反應快,誤差一出現立即響應
- 缺點:純 P 控制無法消除穩態誤差(Steady-State Error)
- Kp 太大:系統震盪甚至不穩定
- Kp 太小:響應遲緩,穩態誤差大
I — 積分控制(Integral)
輸出與過去誤差的累積成正比。即使很小的誤差,隨著時間累積也會產生足夠的修正力。
- 優點:消除穩態誤差(零穩態誤差)
- 缺點:積分飽和(Integral Windup)導致超調
- Ki 太大:超調量大,收斂慢
- Ki 太小:穩態誤差消除慢
D — 微分控制(Derivative)
輸出與誤差的變化率成正比。預測誤差的趨勢,提前施加反向力。
- 優點:抑制超調,提高穩定性
- 缺點:對感測器雜訊極度敏感(放大高頻雜訊)
- Kd 太大:系統對雜訊反應過度,反而變得不穩定
- Kd 太小:無明顯效果
PID 參數調整
Ziegler-Nichols 閉環整定法
這是經典的參數整定方法,適合不確定系統模型的場合:
- 設 Ki=0, Kd=0(只留 P 控制)
- 逐步增加 Kp 直到系統產生等幅振盪(持續的固定周期震盪)
- 記錄此時的 Kp = K_u(極限增益),振盪周期 = T_u
- 根據下表設定 PID 參數:
| 控制器類型 | Kp | Ki | Kd |
|---|---|---|---|
| P-only | 0.5 × K_u | — | — |
| PI | 0.45 × K_u | 0.54 × K_u / T_u | — |
| PID | 0.6 × K_u | 1.2 × K_u / T_u | 0.075 × K_u × T_u |
ESP32 PID 實作:馬達轉速控制
以下是以 ESP32 搭配編碼器(Encoder)實現直流馬達 PID 轉速控制的完整程式碼:
// PID 控制器結構體
typedef struct {
float Kp, Ki, Kd;
float integral;
float prev_error;
float dt;
float output_min, output_max; // 輸出限制(Anti-Windup)
} PIDController;
void PID_Init(PIDController *pid, float Kp, float Ki, float Kd, float dt) {
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->integral = 0;
pid->prev_error = 0;
pid->dt = dt;
pid->output_min = 0;
pid->output_max = 255; // PWM 8-bit
}
float PID_Compute(PIDController *pid, float setpoint, float measurement) {
float error = setpoint - measurement;
// P term
float P = pid->Kp * error;
// I term (with anti-windup)
pid->integral += error * pid->dt;
float I = pid->Ki * pid->integral;
// D term (measurement derivative to avoid derivative kick)
float D = pid->Kd * (measurement - pid->prev_measurement) / pid->dt;
// 計算總輸出
float output = P + I - D;
// 輸出限制 + Anti-Windup
if (output > pid->output_max) {
output = pid->output_max;
pid->integral -= error * pid->dt; // 退積分
} else if (output < pid->output_min) {
output = pid->output_min;
pid->integral -= error * pid->dt;
}
pid->prev_error = error;
pid->prev_measurement = measurement;
return output;
}
void setup() {
PIDController motor_pid;
PID_Init(&motor_pid, 2.0, 5.0, 0.1, 0.01); // 100Hz 控制週期
// 每秒讀取編碼器轉速 → PID_Compute → 設定 PWM
}
實作要點
- Anti-Windup:當輸出飽和時停止積分,避免積分飽和
- 微分對量測值(非誤差):避免 Setpoint 突變時的 Derivative Kick
- 固定取樣時間:使用 Timer 中斷確保 dt 固定,避免參數偏移
- 低通濾波:微分項前對量測值做一階低通濾波,抑制雜訊放大
常見問題
積分飽和(Integral Windup)
當輸出已經達到極限(PWM 100%),但誤差仍然存在時,積分項會持續累積。當系統回到目標附近時,這個巨大的累積值會導致嚴重的超調。
解法:上述程式碼中的「退積分」(在輸出飽和時不累積積分)是最常見的 Anti-Windup 實作。
微分放大雜訊
D 項對高頻雜訊極度敏感。如果感測器讀數有 ±1% 的噪聲,微分後可能放大到 ±10% 的輸出變化。
解法:
- 對量測值做移動平均或一階低通濾波
- 使用 D-on-Measurement(對量測值微分,而非對誤差)
- 在 Stm32 中,可以利用硬體 ADC 的 oversampling 功能降低噪聲
取樣時間不固定
如果 PID 計算在 loop() 中執行而非 Timer 中斷,dt 會隨程式負載變動。這導致 I 和 D 項的計算偏移——相同的 Ki 在不同負載下表現不同。
解法:使用硬體 Timer 觸發 PID 計算,或記錄 millis() 差值作為實際 dt。
手動調諧口訣
不需要 Ziegler-Nichols 的場合,可以用這個口訣逐步調整:
- 先調 Kp:增加 Kp 直到系統快速響應但剛開始出現輕微震盪,然後退回 20%
- 再加 Ki:增加 Ki 消除穩態誤差,直到系統恢復到目標但超調可接受
- 最後微調 Kd:增加 Kd 抑制超調,但注意不要引入雜訊
- 重複 1→3,三次迭代後通常能到滿意的結果
注意:每次只調一個參數,觀察響應後再調下一個。
總結
PID 演算法是嵌入式控制的核心技術。理解 P、I、D 各自的角色後,實作並不困難。真正的功力在於參數調整——而這需要對系統的響應特性有直覺。
在 ESP32 和 STM32 上實作 PID 時,記得這三件事:固定取樣時間、做好 Anti-Windup、微分對量測值而非誤差。掌握這三點,你的 PID 控制器就成功了一半。
📖 延伸閱讀:PCB 去藕半徑完全解析 · RS485 通訊教學 · I2C BME280 教學
文章評論