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范围内。