首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

PID 控制器數位化實現與 C 語法深度解析

     編輯:關於C語言

PID 控制器數位化推導

連續域傳遞函數

$$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}$$

離散化(向後差分近似)

  • 積分:$\int e(t)dt \approx T \sum_{k=0}^n e(k)$
  • 微分:$\frac{de}{dt} \approx \frac{e(k) - e(k-1)}{T}$

增量式差分方程

$$\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)$$

其中:

  • $a_0 = K_p + K_i T + K_d/T$
  • $a_1 = K_p + 2K_d/T$
  • $a_2 = K_d/T$

模組化程式架構

PID.h  —— 型別定義、巨集預設值、介面宣告
PID.c  —— 演算法核心實現
amain.c —— 應用層初始化與中斷服務程式調用

關鍵 C 語法深度解析

1. 條件編譯:功能切換與編譯期優化

#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 次乘加。

2. 結構體封裝與指標傳遞

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*` 傳遞,避免結構體複製開銷。

3. typedef 型別別名與跨平台移植

typedef int MyInt;                 /* 基礎型別別名 */
typedef struct { ... } PID;        /* 結構體標籤即型別名 */
typedef void (*PFun)(int);         /* 函數指標型別 */

核心規則:**`typedef` 宣告中的標識符,其型別等同於移除 `typedef` 後該標識符作為變數宣告時的型別。** 此技巧統一 `int16_t`/`int32_t` 等平台相關型別,實現原始碼級跨平台。

4. 空參數列表 vs `void` 參數列表

  • C++:`void f()` ≡ `void f(void)`
  • C:`void f()` 表示參數不定(關閉型別檢查),`void f(void)` 明確無參數(開啟檢查)。C 程式嚴禁使用空參數列表。

5. 函數指標調用風格

/* 直接調用 —— C/C++ 通用 */
pid_calc(&ASR);

/* 函數指標調用 —— 僅 C,風格更緊湊 */
typedef void (*CalcFunc)(PID*);
CalcFunc calc = pid_calc;
calc(&ASR);  /* 或 ASR.calc(&ASR) 當函數指標作為結構體成員 */

6. 強制型別轉換與函數指標相容性

void pid_calc(PID*);
void (*calc)();
calc = (void (*)(unsigned long))pid_calc; /* 顯式轉換,消除警告 */
calc = pid_calc;                          /* 隱式轉換,C 允許但有風險 */

7. 多行巨集續行符 `\`

#define PID_DEFAULTS {0,0, \
    0,0,0, \
    0.0002, \
    ...}

完整原始碼附件(節選)

PID.h

#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

PID.c

#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;
}

amain.c(中斷服務程式)

#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);      /* 函數指調用 */
}

工程落地建議

  • 採用**增量式 PID**,輸出直接為控制量增量,僅需對輸出限幅,積分項天然抗飽和。
  • 微分項建議加**一階低通濾波**,抑制高頻雜訊放大。
  • 參數整定流程:先關閉 I、D,調 P 至微震;再加 I 消除靜差;最後加 D 改善超調。
  • 關鍵路徑務必用 `#if PID_DEBUG` 隔離除法,確保 ISR 執行時間確定性。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved