$$G(s) = K_p + \frac{K_i}{s} + K_d s$$
$$u(t) = K_p e(t) + K_i \int_0^t e(\tau)d\tau + K_d \frac{de(t)}{dt}$$
$$\Delta u(k) = u(k) - u(k-1) = K_p[e(k)-e(k-1)] + K_i T e(k) + \frac{K_d}{T}[e(k)-2e(k-1)+e(k-2)]$$
整理得:
$$u(k) = u(k-1) + a_0 e(k) - a_1 e(k-1) + a_2 e(k-2)$$
其中:
PID.h —— 型別定義、巨集預設值、介面宣告
PID.c —— 演算法核心實現
amain.c —— 應用層初始化與中斷服務程式調用
#if (PID_DEBUG)
/* 調試階段:即時計算 a0,a1,a2 */
float a0 = p->Kp + p->Ki*p->T + p->Kd/p->T;
...
#else
/* 正式階段:直接使用預計算的 a0,a1,a2 */
p->Out = p->Out_1 + p->a0*p->Err - p->a1*p->Err_1 + p->a2*p->Err_2;
#endif
調試時開啟 `PID_DEBUG=1` 方便觀察中間變量;量產時設 `0`,消除除法與乘法開銷,關鍵路徑僅保留 3 次乘加。
typedef struct {
float Ref, Fdb; /* 輸入 */
float Kp, Ki, Kd, T; /* 參數 */
float a0, a1, a2; /* 中間係數 */
float Err, Err_1, Err_2; /* 歷史誤差 */
float Out, Out_1; /* 輸出歷史 */
float OutMax, OutMin; /* 輸出飽和限幅 */
} PID;
單一結構體打包所有狀態,支援多 PID 實例並行;函數以 `PID*` 傳遞,避免結構體複製開銷。
typedef int MyInt; /* 基礎型別別名 */
typedef struct { ... } PID; /* 結構體標籤即型別名 */
typedef void (*PFun)(int); /* 函數指標型別 */
核心規則:**`typedef` 宣告中的標識符,其型別等同於移除 `typedef` 後該標識符作為變數宣告時的型別。** 此技巧統一 `int16_t`/`int32_t` 等平台相關型別,實現原始碼級跨平台。
/* 直接調用 —— C/C++ 通用 */
pid_calc(&ASR);
/* 函數指標調用 —— 僅 C,風格更緊湊 */
typedef void (*CalcFunc)(PID*);
CalcFunc calc = pid_calc;
calc(&ASR); /* 或 ASR.calc(&ASR) 當函數指標作為結構體成員 */
void pid_calc(PID*);
void (*calc)();
calc = (void (*)(unsigned long))pid_calc; /* 顯式轉換,消除警告 */
calc = pid_calc; /* 隱式轉換,C 允許但有風險 */
#define PID_DEFAULTS {0,0, \
0,0,0, \
0.0002, \
...}
#ifndef PID_H
#define PID_H
typedef struct {
float Ref, Fdb;
float Kp, Ki, Kd, T;
float a0, a1, a2;
float Err, Err_1, Err_2;
float Out, Out_1;
float OutMax, OutMin;
void (*calc)(); /* 函數指標成員 */
} PID;
#define PID_DEFAULTS {0,0, 0,0,0, 0.0002, 0,0,0, 0,0,0, 0,0,0,0, (void(*)(unsigned long))pid_calc}
#define PID_DEBUG 1
void pid_calc(PID *p);
#endif
#include "PID.h"
void pid_calc(PID *p) {
#if (PID_DEBUG)
float a0 = p->Kp + p->Ki*p->T + p->Kd/p->T;
float a1 = p->Kp + 2*p->Kd/p->T;
float a2 = p->Kd/p->T;
p->Out = p->Out_1 + a0*p->Err - a1*p->Err_1 + a2*p->Err_2;
#else
p->Out = p->Out_1 + p->a0*p->Err - p->a1*p->Err_1 + p->a2*p->Err_2;
#endif
/* 輸出限幅 */
if (p->Out > p->OutMax) p->Out = p->OutMax;
if (p->Out < p->OutMin) p->Out = p->OutMin;
/* 歷史更新 */
p->Out_1 = p->Out;
p->Err_2 = p->Err_1;
p->Err_1 = p->Err;
}
#include "PID.h"
#define T0 0.0002f
PID ASR = PID_DEFAULTS;
float SpeedKp=2, SpeedKi=1, SpeedLimit=10;
void main() {
ASR.Kp = SpeedKp; ASR.Ki = SpeedKi; ASR.T = T0;
ASR.OutMax = SpeedLimit; ASR.OutMin = -SpeedLimit;
}
interrupt void T1UFINT_ISR(void) {
ASR.Ref = 2; ASR.Fdb = 1;
ASR.Err = ASR.Ref - ASR.Fdb;
ASR.calc(&ASR); /* 函數指調用 */
}