PID 控制器完整拆解:從連續時間公式到離散實作與抗飽和
- pid
- feedback-control
- tuning
- anti-windup
- discrete-control
問題定義
PID 控制器要解決的是最基本的反饋控制問題:系統目前的量測值(例如關節角度、輪速、無人機高度)跟目標設定值之間有誤差,要根據這個誤差算出一個控制輸出(例如馬達電壓、力矩指令),讓系統盡快、穩定地趨近目標值。誤差定義為:
其中 是設定值(reference/setpoint)、 是量測值。PID 這個名字直接對應控制輸出的三個組成項:
三個項的物理意義
比例項(P):輸出跟目前誤差成正比。這是最直覺的部分——誤差越大,修正力道越大。但單獨用 P 控制器有個結構性問題:對大多數真實系統(有摩擦力、重力負載等),要讓系統完全停在目標值,需要一個非零的控制輸出去抵消這些常數負載,而純比例控制器在誤差趨近於零時輸出也會趨近於零,兩者矛盾的結果是系統會停在一個穩態誤差(steady-state error)不為零的位置,比例增益 越大,這個穩態誤差越小,但太大的 又會讓系統對雜訊敏感、容易震盪甚至不穩定。
積分項(I):輸出跟誤差的累積量(時間積分)成正比。這一項的作用正是消除比例項留下的穩態誤差——只要誤差沒有完全歸零,積分項就會持續累加,把控制輸出往上(或往下)推,直到累積到剛好能抵消穩態負載為止。代價是積分項在動態響應上會引入額外的相位延遲,容易讓系統的超調(overshoot)變大、甚至造成前面 FAQ 提到的積分飽和問題。
微分項(D):輸出跟誤差的變化率成正比,本質上是在「預測」誤差接下來的走勢——如果誤差正在快速縮小,微分項會提前施加一個反向的煞車力道,避免系統衝過頭。這一項能有效抑制超調、提升響應的阻尼特性,但因為對誤差做微分,天生對量測雜訊非常敏感(雜訊的高頻成分經過微分會被放大),實務上幾乎都要搭配低通濾波器一起使用。
離散時間實作
真實控制系統是以固定取樣週期 執行的離散迴圈(例如 ROS2 控制節點以 1 kHz 執行),連續時間公式必須離散化。積分項用累加近似,微分項用差分近似:
實務實作通常寫成遞迴形式(不需要儲存整個誤差歷史,只需要維護一個累積值),並且會刻意避開兩個常見陷阱:
陷阱一:微分突波(Derivative Kick)
當設定值 突然階躍改變時(例如使用者突然把目標角度從 30° 改成 90°),誤差 會瞬間跳一大步,微分項 會在那一瞬間出現一個不合理的巨大尖峰,導致控制輸出瞬間暴衝。標準解法是把微分項改成對量測值微分,而不是對誤差微分:
因為 ,且設定值改變是階躍(不連續),量測值 本身是連續變化的物理量,不會有這種瞬間跳變,對 微分自然就避開了這個問題,符號取負是因為 ,忽略 的變化率後剩下 。
陷阱二:積分飽和(Integral Windup)與抗飽和
當控制輸出被實際致動器限制(例如馬達電壓上限、PWM 佔空比上限)鉗制在飽和值時,如果誤差持續存在(例如系統物理上暫時無法跟上目標),積分項會不受限制地持續累加,累積出遠超過實際需要的數值。等誤差方向反轉、系統該減速時,膨脹過頭的積分項需要先花時間「退回」合理範圍,這段期間輸出仍然貼著飽和值不放,造成響應嚴重延遲、超調大幅增加。
最常見的抗飽和機制是反算法(back-calculation):當偵測到輸出已經飽和,額外扣除一項跟飽和量成正比的修正量,主動把積分器往回拉:
最後一項就是反算法的修正: 是「實際輸出」與「理論上未飽和輸出」之間的差距, 是抗飽和增益,這個修正項會主動把積分器狀態往飽和邊界拉回,而不是任由它無限膨脹。
Ziegler-Nichols 調參法
Ziegler-Nichols 提供了一套從系統實測響應直接算出 PID 三個增益的經驗法則,最常用的是閉迴路臨界增益法:
- 先把 ,只用純比例控制器,逐步調高 。
- 持續加大 ,直到系統輸出出現等幅、不衰減也不發散的持續震盪,記下這個臨界增益 (ultimate gain)與震盪週期 (ultimate period)。
- 依下表公式算出三個增益:
| 控制器類型 | |||
|---|---|---|---|
| P | — | — | |
| PI | — | ||
| PID |
這組參數對應的響應特性是「四分之一衰減比」——每次震盪的振幅衰減到前一次的四分之一,是相對積極、允許一定超調的調參目標,不一定適合對超調敏感的機器人關節應用,實務上通常拿來當起始點,再依實際需求(更保守/更積極)手動微調。
常見於機器人系統的串級控制(Cascade Control)
機械手臂或移動平台的關節控制很少只用單一個 PID 迴圈,常見做法是串級控制:外層是位置迴圈(PID 或 PD),輸出不是直接的力矩指令,而是「目標速度」,這個目標速度再送進內層更高頻率執行的速度迴圈(通常是 PI),由速度迴圈輸出最終的電流/力矩指令。這樣設計的好處是內層迴圈執行頻率高(可以做到 kHz 等級)、對馬達動態的響應更即時,外層迴圈執行頻率可以相對低,同時內層迴圈能提前吸收速度層級的擾動,不用等到位置誤差累積出來才反應。
程式碼範例
class PIDController:
def __init__(self, kp, ki, kd, output_limits, anti_windup_gain=0.5):
self.kp, self.ki, self.kd = kp, ki, kd
self.out_min, self.out_max = output_limits
self.kaw = anti_windup_gain
self.integral = 0.0
self.prev_measurement = None
def reset(self):
self.integral = 0.0
self.prev_measurement = None
def update(self, setpoint, measurement, dt):
error = setpoint - measurement
# 微分項對量測值微分,避免設定值階躍造成的 derivative kick
if self.prev_measurement is None:
derivative = 0.0
else:
derivative = -(measurement - self.prev_measurement) / dt
self.prev_measurement = measurement
unsaturated = (
self.kp * error
+ self.integral
+ self.kd * derivative
)
output = max(self.out_min, min(self.out_max, unsaturated))
# 反算法抗飽和:用實際輸出與理論輸出的差距修正積分器
self.integral += self.ki * error * dt + self.kaw * (output - unsaturated)
return output
常見錯誤
- 對誤差微分而不是對量測值微分:設定值階躍變化時會產生不合理的微分突波,導致控制輸出瞬間暴衝。
- 積分項沒有抗飽和機制:致動器長時間飽和時(例如系統暫時無法跟上大幅度的目標變化),積分器無限累積,反向時響應嚴重延遲、超調暴增。
- 取樣週期不固定卻假設 是常數:在非即時作業系統上,迴圈執行週期會有抖動(jitter),如果程式碼裡硬編碼一個固定 而不是實際量測每次迴圈的時間差,積分與微分項的計算會系統性偏差。
- 微分項沒有濾波,直接對雜訊量測值微分:感測器雜訊的高頻成分經過微分放大後會讓控制輸出劇烈抖動,實務上幾乎都需要在微分項前加一階低通濾波器。
- 把 Ziegler-Nichols 算出來的參數直接當最終答案,不做後續微調:這組參數是為特定響應特性(四分之一衰減比)設計的通用起始點,不是針對特定應用最佳化的結果。
延伸閱讀
常見問題
- 積分項一定要有嗎?只用 PD 控制器可以嗎?
- 可以,很多機械手臂的關節控制器實際上用的是 PD(比例-微分)而不是完整 PID,尤其在系統本身已經有其他方式補償穩態誤差的情況下(例如重力補償項直接算出並加進控制輸出,不需要依賴積分項慢慢累積誤差來抵消重力)。積分項的價值在於消除穩態誤差——沒有積分項時,系統在有恆定負載或摩擦力的情況下,會停在一個跟設定值有微小但持續存在的偏差的位置;但積分項也帶來飽和(windup)與相位延遲兩個副作用。是否需要積分項取決於系統是否有其他機制能消除穩態誤差,兩者要一起評估,不是「PID 一定比 PD 好」。
- Ziegler-Nichols 調出來的參數為什麼實務上常常要再手動微調?
- Ziegler-Nichols 方法(不管是開迴路階躍響應法還是閉迴路臨界增益法)設計目標是讓系統呈現「四分之一衰減比」(quarter amplitude decay)的響應特性,這個特性對應的是相對積極、允許有一定超調(overshoot)的響應曲線,是 1940 年代為了工業製程控制設計的經驗法則,不是針對特定應用最佳化的結果。機器人關節控制通常更在意超調要小(超調可能代表關節衝過頭撞到機構極限)、或是更在意穩定裕度而不是最快收斂速度,這些偏好跟 Ziegler-Nichols 的預設目標不一致,因此得到的參數通常只能當一個合理的起始點,還是需要根據實際應用的優先順序手動微調。
- 抗飽和(anti-windup)跟直接把積分項輸出限幅有什麼不一樣?
- 直接限幅只是限制了最終輸出範圍,但積分項內部累積的數值(積分器狀態)本身仍然持續增長,等到誤差反向之後,系統要先花時間讓內部累積的積分值消退回合理範圍,才能真正跟著誤差反向調整,這段時間輸出仍然停留在飽和值附近,造成明顯的響應延遲,這就是「windup」這個名字的來源——積分器內部把誤差捲得太多,反向時要先「解捲」。真正的抗飽和機制(例如反算法 back-calculation 或條件式積分 conditional integration)是直接介入積分器的累積值,讓它的成長被輸出端的飽和狀態約束住,而不是任由它無限累積、事後才在輸出端限幅,兩者在輸出真正貼著限幅邊界運作時的動態行為有明顯差異。