一、积分控制的核心思想:误差的历史问责
积分控制的核心公式是:
$$u = K_i \times \int e \, dt$$
其中:
- $K_i$ 是积分系数
- $\int e \, dt$ 是误差随时间的累积面积
1. 积分到底是什么?
积分就是误差随时间的累积。比如洗澡时水温一直偏冷1度:
- 1分钟后,误差累积为 $1^\circ\text{C} \times 60\text{s} = 60^\circ\text{C·s}$
- 10分钟后,误差累积为 $1^\circ\text{C} \times 600\text{s} = 600^\circ\text{C·s}$
积分项等于这个累积量乘以系数$K_i$,误差存在越久,积分值越大,输出的额外加热就越多。
2. P和I的区别:即时反应 vs 历史问责
- P(比例):即时反应,只看当前误差,误差越大调整越多,但永远有余差
- I(积分):历史问责,看误差累积,误差存在越久调整越多,专门消除余差
两者配合的PI控制器,既能快速响应,又能持续纠偏直到误差为零。
二、积分控制的致命陷阱:积分饱和(Windup)
积分控制虽然强大,但有个致命的坑叫积分饱和。如果系统长时间达不到目标,比如加热器坏了,积分项会一直累积到天文数字。等加热器修好后,巨大的积分值会让输出瞬间拉满,造成剧烈超调。
1. 积分饱和的危害
- 系统响应变慢
- 出现剧烈超调
- 甚至导致系统震荡
2. 解决方案:积分限幅(Anti-Windup)
在实际代码中,一定要给积分值设天花板,防止它无限累积。具体做法是:
- 设定积分上下限
- 每次积分后检查,超过上限就设为上限,低于下限就设为下限
三、P vs PI:控制效果对比
我们用洗澡调水温的例子来对比纯P控制和PI控制的效果:
| 控制方式 | 最终温度 | 余差 | 特点 |
|---|---|---|---|
| 纯P控制 | 36.5℃ | 0.5℃ | 快速响应,但永远有余差 |
| PI控制 | 37℃ | 0℃ | 快速响应+消除余差 |
从曲线可以看到:
- 纯P控制最终稳定在36.5℃,离目标37℃差半度
- PI控制开始和P控制一样上升,但因为I持续累积,最终把实际温度推到了37℃整,误差归零,I功成身退
四、嵌入式C语言PI控制器实现:可直接运行的代码
下面是一套基于STM32 HAL库的完整PI控制器实现,包含积分限幅功能,可直接用于温度、电机、液位等控制场景。
1. 头文件定义(pi.h)
#ifndef __PI_H #define __PI_H #include "stm32f1xx_hal.h" // PI控制器结构体 typedef struct { // 核心参数 float Kp; // 比例系数 float Ki; // 积分系数 float Ts; // 采样周期(单位:s) // 限幅参数 float max_integral; // 积分上限 float min_integral; // 积分下限 float max_output; // 输出上限 float min_output; // 输出下限 // 状态变量 float integral; // 积分累加值 float output; // 当前输出 } PI_HandleTypeDef; // 函数声明 void PI_Init(PI_HandleTypeDef *pi, float Kp, float Ki, float Ts, float max_integral, float min_integral, float max_output, float min_output); float PI_Calc(PI_HandleTypeDef *pi, float setpoint, float feedback); void PI_Reset(PI_HandleTypeDef *pi); #endif2. 实现文件(pi.c)
#include "pi.h" #include <math.h> /** @brief PI控制器初始化 @param pi: PI控制器指针 @param Kp: 比例系数 @param Ki: 积分系数 @param Ts: 采样周期(s) @param max_integral: 积分上限 @param min_integral: 积分下限 @param max_output: 输出上限 @param min_output: 输出下限 @retval 无 */ void PI_Init(PI_HandleTypeDef *pi, float Kp, float Ki, float Ts, float max_integral, float min_integral, float max_output, float min_output) { pi->Kp = Kp; pi->Ki = Ki; pi->Ts = Ts; pi->max_integral = max_integral; pi->min_integral = min_integral; pi->max_output = max_output; pi->min_output = min_output; pi->integral = 0.0f; pi->output = 0.0f; } /** @brief PI计算函数 @param pi: PI控制器指针 @param setpoint: 目标设定值 @param feedback: 实际反馈值 @retval PI输出值 */ float PI_Calc(PI_HandleTypeDef *pi, float setpoint, float feedback) { // 1. 计算当前误差 float error = setpoint - feedback; // 2. 比例项 float P_out = pi->Kp * error; // 3. 积分项(带抗饱和限幅) pi->integral += error * pi->Ts; // 积分限幅 if (pi->integral > pi->max_integral) { pi->integral = pi->max_integral; } else if (pi->integral < pi->min_integral) { pi->integral = pi->min_integral; } float I_out = pi->Ki * pi->integral; // 4. 总输出 pi->output = P_out + I_out; // 5. 输出限幅 if (pi->output > pi->max_output) { pi->output = pi->max_output; } else if (pi->output < pi->min_output) { pi->output = pi->min_output; } return pi->output; } /** @brief 重置PI控制器状态 @param pi: PI控制器指针 @retval 无 */ void PI_Reset(PI_HandleTypeDef *pi) { pi->integral = 0.0f; pi->output = 0.0f; }3. 使用示例:温度控制
#include "pi.h" // 定义PI控制器实例 PI_HandleTypeDef temp_pi; // 温度控制初始化 void TempControl_Init(void) { // 初始化PI参数:Kp=2.0, Ki=0.1, 采样周期100ms PI_Init(&temp_pi, 2.0f, 0.1f, 0.1f, 100.0f, -100.0f, // 积分限幅 1000.0f, 0.0f); // 输出限幅(PWM占空比0~1000) } // 温度控制循环(在定时器中断中调用,100ms一次) void TempControl_Loop(void) { float target_temp = 37.0f; // 目标温度37℃ float actual_temp = Get_Temperature(); // 读取实际温度 // 计算PI输出 float pwm_output = PI_Calc(&temp_pi, target_temp, actual_temp); // 设置PWM占空比 Set_PWM_Duty(pwm_output); }五、总结:PI控制器是工程上最常用的组合
PI控制器已经是工程上最常用的组合了:
- P负责快速跟踪
- I负责消除余差
温度控制、电机调速、液位控制——大部分场景PI就够了。
那D微分加进来有什么用?下期我们讲:D是给PI踩刹车的。
代码说明:
- 该实现采用位置式PI,适合大多数嵌入式控制场景
- 包含积分抗饱和和输出限幅功能,防止系统失控
- 采样周期Ts需与实际控制周期一致
- 参数Kp、Ki需根据实际系统进行整定,可先用Ziegler-Nichols方法粗调,再根据响应微调