2. 增量式PID控制器实现

任务目标

实现增量式PID控制器,输入目标值与当前反馈值,输出控制增量Δu(k),并对比位置式PID的输出形式与适用场景差异。

核心实现

#include <stdio.h>
#include <stdint.h>

typedef struct {
    float Kp;           // 比例系数
    float Ki;           // 积分系数
    float Kd;           // 微分系数
    float prev_err;     // e(k-1)
    float prev2_err;    // e(k-2)
    float dt;           // 采样周期(s)
} IncrementalPID;

// 初始化增量式PID
void pid_init(IncrementalPID *pid, float Kp, float Ki, float Kd, float dt) {
    pid->Kp = Kp;
    pid->Ki = Ki;
    pid->Kd = Kd;
    pid->dt = dt;
    pid->prev_err = 0.0f;
    pid->prev2_err = 0.0f;
}

// 计算控制增量 Δu(k) = Kp*[e(k)-e(k-1)] + Ki*e(k)*dt + Kd*[e(k)-2e(k-1)+e(k-2)]/dt
float pid_calc_delta(IncrementalPID *pid, float setpoint, float feedback) {
    float err = setpoint - feedback;
    float delta_u = pid->Kp * (err - pid->prev_err)
                  + pid->Ki * err * pid->dt
                  + pid->Kd * (err - 2.0f*pid->prev_err + pid->prev2_err) / pid->dt;
    // 更新误差历史
    pid->prev2_err = pid->prev_err;
    pid->prev_err = err;
    return delta_u;
}

// 位置式PID(用于对比)
typedef struct {
    float Kp, Ki, Kd;
    float integral;
    float prev_err;
    float dt;
} PositionalPID;

void pos_pid_init(PositionalPID *pid, float Kp, float Ki, float Kd, float dt) {
    pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd;
    pid->dt = dt; pid->integral = 0.0f; pid->prev_err = 0.0f;
}

float pos_pid_calc(PositionalPID *pid, float setpoint, float feedback) {
    float err = setpoint - feedback;
    pid->integral += err * pid->dt;
    float derivative = (err - pid->prev_err) / pid->dt;
    pid->prev_err = err;
    return pid->Kp*err + pid->Ki*pid->integral + pid->Kd*derivative;
}

// 模拟被控对象:一阶惯性环节 + 延迟
float plant_model(float u, float *y, float dt) {
    static const float tau = 0.5f;   // 时间常数
    static const float K = 1.0f;     // 稳态增益
    *y += (K * u - *y) * dt / tau;
    return *y;
}

int main() {
    // 增量式PID参数
    IncrementalPID inc_pid;
    pid_init(&inc_pid, 2.0f, 1.5f, 0.1f, 0.01f);
    
    // 位置式PID参数(相同增益)
    PositionalPID pos_pid;
    pos_pid_init(&pos_pid, 2.0f, 1.5f, 0.1f, 0.01f);
    
    float setpoint = 1.0f;       // 目标值
    float y_inc = 0.0f, y_pos = 0.0f;  // 两个控制器各自的系统输出
    float u_inc = 0.0f, u_pos = 0.0f;  // 控制量(位置式输出控制量)
    float delta_u = 0.0f;        // 增量式输出增量
    
    printf("步数 | 增量式Δu | 增量式u累计 | 位置式u | 增量式y | 位置式y\n");
    printf("-----|----------|------------|---------|---------|---------\n");
    
    for (int i = 0; i < 200; i++) {
        // 增量式:输出增量,需外部累加得到实际控制量
        delta_u = pid_calc_delta(&inc_pid, setpoint, y_inc);
        u_inc += delta_u;
        plant_model(u_inc, &y_inc, 0.01f);
        
        // 位置式:直接输出控制量
        u_pos = pos_pid_calc(&pos_pid, setpoint, y_pos);
        plant_model(u_pos, &y_pos, 0.01f);
        
        if (i % 20 == 0 || i == 199) {
            printf("%4d | %8.4f | %10.4f | %7.4f | %7.4f | %7.4f\n",
                   i, delta_u, u_inc, u_pos, y_inc, y_pos);
        }
    }
    return 0;
}

编译与运行

gcc -o pid_demo pid_demo.c -lm
./pid_demo

验证方法

观察程序输出的两个控制器响应曲线数据:

  • 收敛性:增量式与位置式控制的系统输出(y_inc、y_pos)最终都应接近目标值1.0,稳态误差趋近于0。
  • 输出形式差异:增量式输出Δu为小幅度增量(正负交替衰减),需累加得到实际控制量;位置式直接输出控制量u_pos,数值较大且始终为正。
  • 抗扰动特性:若在运行中手动将y_inc或y_pos置为突变值(如第100步令y=0.5),增量式因只输出增量,控制量波动更平缓;位置式因积分项累积,控制量会出现更大跳变。可在代码中临时加入扰动验证此差异。

两种算法差异对比

对比项增量式PID位置式PID
输出量控制增量Δu(k)控制量u(k)
积分作用隐含在增量累加中,无积分饱和显式积分项,需抗饱和处理
执行器需求需带积分保持的执行器(如步进电机)可直接驱动比例执行器(如阀门)
故障影响单次输出错误影响小单次输出错误影响大
手动/自动切换无需初始化,切换无冲击需先置积分初值,否则有冲击

预期输出(前几行示例):系统从0开始跟踪目标1.0,增量式Δu先正后负逐渐收敛,u_inc单调上升至稳态值附近;位置式u_pos直接输出较大控制量后回落。两者最终y均稳定在0.99~1.01范围内。