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

PID 控制演算法詳解(三):51 單片機 C 語言增量式 PID 完整實作

     編輯:關於C語言

增量式 vs 位置式 PID

特性位置式 PID增量式 PID
輸出絕對控制量控制量增量(ΔU)
公式U_k = Kp·E_k + Ki·ΣE + Kd·(E_k-E_{k-1})ΔU_k = Kp·(E_k-E_{k-1}) + Ki·E_k + Kd·(E_k-2E_{k-1}+E_{k-2})
積分作用顯式累加 ΣE隱含於 ΔU 累加
適用執行器無積分特性(伺服閥、加熱器)自帶積分(步進馬達、電機位置環)
故障安全性輸出直接控制,故障影響大輸出為增量,故障影響較小
啟動衝擊大(需追蹤功能)小(平滑過渡)

本實作採用增量式,適合步進馬達速度環、直流馬達位置環等自帶積分的執行器。

資料結構定義

// pid.h
#ifndef __PID_H__
#define __PID_H__

typedef struct {
    int8_t Kp;        // 比例增益
    int8_t Ki;        // 積分增益
    int8_t Kd;        // 微分增益
    int8_t F;         // 採樣頻率因子 (Ki 除數、Kd 乘數)
    int8_t BITMOV;    // 輸出右移位數 (定點縮放)
    int16_t EK[3];    // 誤差歷史: EK[0]=E_k, EK[1]=E_{k-1}, EK[2]=E_{k-2}
    int32_t UK_REAL;  // 內部高精度累積輸出 (定點)
    int16_t UK;       // 最終輸出 (已縮放、飽和)
    int16_t RK;       // 設定值
    int16_t CK;       // 當前測量值
} PID_t;

void pid_exe(PID_t *pid);

#endif

核心演算法實作

// pid.c
#include "pid.h"
#define MAX_OUT 0xFF  // 輸出飽和限幅 (8-bit PWM)

void pid_exe(PID_t *pid) {
    // 1. 更新誤差歷史
    pid->EK[2] = pid->EK[1];
    pid->EK[1] = pid->EK[0];
    pid->EK[0] = pid->RK - pid->CK;  // E_k = 設定值 - 實測值

    // 2. 增量式 PID 計算 (定點運算)
    // ΔU = Kp*(E_k - E_{k-1}) + Ki*E_k/F + Kd*(E_k - 2E_{k-1} + E_{k-2})*F
    int32_t delta = 0;
    delta += (int32_t)pid->Kp * (pid->EK[0] - pid->EK[1]);           // 比例項 (實為微分後積分)
    delta += (int32_t)pid->Ki * pid->EK[0] / pid->F;                 // 積分項
    delta += (int32_t)pid->Kd * (pid->EK[0] - 2*pid->EK[1] + pid->EK[2]) * pid->F; // 微分項

    // 3. 累積輸出 (位置式輸出 = ΣΔU)
    pid->UK_REAL += delta;

    // 4. 縮放、飽和限幅、輸出
    int16_t out = (int16_t)(pid->UK_REAL >> pid->BITMOV);
    if (out >= MAX_OUT)      pid->UK = MAX_OUT;
    else if (out <= -MAX_OUT) pid->UK = -MAX_OUT;
    else                     pid->UK = out;
}

關鍵實作細節解析

  1. 定點數運算:51 單片機無 FPU,所有參數、中間量用整數。F 為採樣頻率倒數縮放因子(如 100Hz → F=100),BITMOV 為最終輸出右移位數(等效除以 2^BITMOV)。
  2. 增量式公式推導:
    位置式:U_k = Kp·E_k + Ki·ΣE_i + Kd·(E_k-E_{k-1})
    兩式相減得增量式:ΔU_k = U_k - U_{k-1} = Kp·(E_k-E_{k-1}) + Ki·E_k + Kd·(E_k-2E_{k-1}+E_{k-2})
    離散化積分:ΣE_i ≈ ΣE_i·dt = (ΣE_i)/F → 程式中 Ki*E_k/F。
    離散化微分:(E_k-E_{k-1})/dt = (E_k-E_{k-1})·F → 程式中 Kd*(...)*F。
  3. Anti-windup(積分飽和保護):程式通過輸出飽和限幅間接實現。當 UK_REAL 累積導致輸出飽和時,實際輸出 UK 被夾住,但內部 UK_REAL 繼續累積。更嚴格的做法:檢測飽和時凍結積分項累積(此處簡化處理)。
  4. 誤差歷史陣列:EK[3] 滾動更新,節省 RAM。
  5. 符號處理:支援雙向控制(正/負輸出),適用 H 橋驅動、雙向馬達。

使用範例:直流馬達速度環

// main.c
#include "pid.h"

PID_t motor_pid = {
    .Kp = 20, .Ki = 5, .Kd = 2,
    .F = 100,        // 100Hz 採樣
    .BITMOV = 8,     // 內部放大 256 倍,輸出右移 8 位
    .EK = {0}, .UK_REAL = 0, .UK = 0,
    .RK = 3000,      // 目標轉速 3000 RPM
    .CK = 0
};

void timer_isr_100hz() {
    motor_pid.CK = read_encoder_speed(); // 讀取編碼器速度
    pid_exe(&motor_pid);
    set_pwm_duty(motor_pid.UK);          // 輸出 PWM 佔空比
}

調試建議

  1. 先設 Ki=Kd=0,調 Kp 觀察響應。
  2. 用串口輸出 RK, CK, UK, EK[0] 繪製波形分析。
  3. BITMOV 太大 → 輸出解析度低;太小 → 易飽和。嘗試 6~10。
  4. F 必須與實際 ISR 頻率一致,否則積分/微分時基錯誤。

完整專案結構

pid/
├── pid.h       // 介面定義
├── pid.c       // 核心演算法
├── main.c      // 應用層範例
└── defines.h   // 型別定義 (int8_t 等)

此實作經實測可在 51 單片機(12MHz/24MHz)穩定運行,佔用極少 RAM/ROM。移植到 STM32、ESP32 等 32 位 MCU 時,建議改用 float 或 q15_t/q31_t (CMSIS-DSP) 簡化縮放邏輯。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved