PID 控制算法: 位置式与增量式对比 第二篇(进阶)
- 嵌入式开发
- 6小时前
- 70热度
- 0评论
PID 进阶实战:位置式与增量式完全对比
在上一篇《PID 控制算法 第一篇(基础篇)》中,介绍了 PID 控制的基础概念、三个参数的物理意义以及基本的调参方法。如果你还没有阅读过第一篇,建议先回顾基础知识再来阅读本文。
本篇作为 PID 系列的第二篇,将深入探讨 PID 控制器在工程实践中最核心的两种离散化实现方式——位置式 PID 和增量式 PID。我们会从数学推导、代码实现、优缺点对比、工程选型到面试高频问题,进行一次全面而深入的剖析。掌握这两种实现的本质区别和适用场景,是从"了解 PID"到"能用好 PID"的关键一步。
一、位置式 PID —— 输出绝对控制量
1.1 核心思想
位置式 PID 的核心思想是:每次计算都直接输出控制量的绝对值。控制器根据当前误差、历史误差积分和误差变化率,一步到位地算出执行器应该处于的"位置"(如 PWM 占空比、阀门开度等)。
基本公式:
u\lbrack k\rbrack = K_p \cdot e\lbrack k\rbrack + K_i \cdot \sum_{i=0}^{k} e\lbrack i\rbrack + K_d \cdot (e\lbrack k\rbrack - e\lbrack k-1\rbrack)将 P、I、D 三个分量分别拆开来看:
比例项(P):
P\lbrack k\rbrack = K_p \cdot e\lbrack k\rbrack积分项(I):
I\lbrack k\rbrack = K_i \cdot \sum_{i=0}^{k} e\lbrack i\rbrack = I\lbrack k-1\rbrack + K_i \cdot e\lbrack k\rbrack微分项(D):
D\lbrack k\rbrack = K_d \cdot (e\lbrack k\rbrack - e\lbrack k-1\rbrack)最终输出:
u\lbrack k\rbrack = P\lbrack k\rbrack + I\lbrack k\rbrack + D\lbrack k\rbrack**关键理解:** u\lbrack k\rbrack 是直接作用于执行器的目标值。例如在温度控制中,u\lbrack k\rbrack 直接就是加热器的 PWM 占空比(0%~100%);在伺服电机控制中,u\lbrack k\rbrack 就是电机应该到达的目标位置或目标电压。控制器每次都给出一个"绝对答案"。
1.2 优缺点分析
优点:
| 序号 | 优点 | 说明 |
|---|---|---|
| 1 | 输出直观 | 输出值直接对应执行器的目标状态,便于理解和调试 |
| 2 | 精确控制 | 可以实现对被控量的精确绝对值控制,无累积误差 |
| 3 | 实现简单 | 公式直观,代码逻辑清晰,初学者容易上手 |
| 4 | 输出稳定 | 输出直接由 PID 公式决定,不依赖历史输出值的累加 |
缺点:
| 序号 | 缺点 | 说明 |
|---|---|---|
| 1 | 积分饱和 | 误差长期累积导致积分项过大,引起超调和振荡 |
| 2 | 切换冲击 | 手动/自动模式切换时,输出会发生突变,产生冲击 |
| 3 | 误动作影响大 | 一次计算错误直接输出到执行器,可能造成严重后果 |
| 4 | 内存占用较大 | 需要保存完整的误差积分值,积分项随时间持续增长 |
1.3 代码实现
基础版本:
/* ========================================
* 位置式 PID 控制器 - 基础版本
* ======================================== */
typedef struct {
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
float integral; // 积分累加值
float prev_error; // 上一次误差
float output_max; // 输出上限
float output_min; // 输出下限
} PID_Position_t;
/**
* @brief 初始化位置式 PID 控制器
*/
void PID_Position_Init(PID_Position_t *pid,
float kp, float ki, float kd,
float out_min, float out_max)
{
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->integral = 0.0f;
pid->prev_error = 0.0f;
pid->output_max = out_max;
pid->output_min = out_min;
}
/**
* @brief 位置式 PID 计算
* @param setpoint 目标值
* @param measured 实际测量值
* @return 控制输出(绝对值)
*/
float PID_Position_Calculate(PID_Position_t *pid,
float setpoint, float measured)
{
float error = setpoint - measured;
/* P 比例项 */
float P = pid->Kp * error;
/* I 积分项(累加) */
pid->integral += error;
float I = pid->Ki * pid->integral;
/* D 微分项 */
float D = pid->Kd * (error - pid->prev_error);
/* 计算总输出 */
float output = P + I + D;
/* 输出限幅 */
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;
/* 保存本次误差 */
pid->prev_error = error;
return output;
}
使用示例(温度控制):
/* 温度控制应用示例 */
PID_Position_t temp_pid;
void Temperature_Control_Init(void)
{
// Kp=2.0, Ki=0.1, Kd=0.5, 输出范围 0~100(PWM占空比)
PID_Position_Init(&temp_pid, 2.0f, 0.1f, 0.5f, 0.0f, 100.0f);
}
void Temperature_Control_Loop(void)
{
float target_temp = 50.0f; // 目标温度 50°C
float current_temp = Read_Temperature(); // 读取当前温度
// PID 计算,输出即为 PWM 占空比
float pwm_duty = PID_Position_Calculate(&temp_pid,
target_temp,
current_temp);
Set_Heater_PWM(pwm_duty); // 直接设置加热器占空比
}
完整优化版本(含积分分离 + 微分滤波):
/* ========================================
* 位置式 PID 控制器 - 工程优化版
* 特性:积分分离 + 微分低通滤波 + 积分限幅
* ======================================== */
typedef struct {
float Kp, Ki, Kd;
float integral; // 积分累加
float prev_error; // 上一次误差
float prev_deriv; // 上一次微分值(用于滤波)
float output_max; // 输出上限
float output_min; // 输出下限
float integral_max; // 积分限幅上限
float integral_min; // 积分限幅下限
float integral_sep_threshold; // 积分分离阈值
float deriv_filter_alpha; // 微分滤波系数 (0~1)
} PID_Position_Adv_t;
void PID_Position_Adv_Init(PID_Position_Adv_t *pid,
float kp, float ki, float kd,
float out_min, float out_max)
{
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->integral = 0.0f;
pid->prev_error = 0.0f;
pid->prev_deriv = 0.0f;
pid->output_max = out_max;
pid->output_min = out_min;
pid->integral_max = out_max * 0.8f; // 积分限幅取输出的80%
pid->integral_min = out_min * 0.8f;
pid->integral_sep_threshold = 50.0f; // 默认积分分离阈值
pid->deriv_filter_alpha = 0.3f; // 微分滤波系数
}
float PID_Position_Adv_Calculate(PID_Position_Adv_t *pid,
float setpoint, float measured)
{
float error = setpoint - measured;
/* ---- P 比例项 ---- */
float P = pid->Kp * error;
/* ---- I 积分项(积分分离) ---- */
// 误差较大时停止积分,防止积分饱和
if (fabsf(error) < pid->integral_sep_threshold) {
pid->integral += error;
}
// 积分限幅
if (pid->integral > pid->integral_max)
pid->integral = pid->integral_max;
if (pid->integral < pid->integral_min)
pid->integral = pid->integral_min;
float I = pid->Ki * pid->integral;
/* ---- D 微分项(一阶低通滤波) ---- */
float raw_deriv = error - pid->prev_error;
float filtered_deriv = pid->deriv_filter_alpha * raw_deriv
+ (1.0f - pid->deriv_filter_alpha) * pid->prev_deriv;
float D = pid->Kd * filtered_deriv;
pid->prev_deriv = filtered_deriv;
/* ---- 总输出 + 限幅 ---- */
float output = P + I + D;
if (output > pid->output_max) output = pid->output_max;
if (output < pid->output_min) output = pid->output_min;
pid->prev_error = error;
return output;
}
1.4 适用场景
| 应用场景 | 说明 | 为什么选位置式 |
|---|---|---|
| 伺服电机位置控制 | 控制电机转到指定角度 | 需要输出精确的目标位置值 |
| 温度控制 | 恒温箱、烤箱等 | 输出直接对应加热功率百分比 |
| 速度闭环控制 | 直流电机恒速 | 输出对应 PWM 占空比,直观 |
| 压力/流量控制 | 工业过程控制 | 输出直接对应阀门开度或泵速 |
| 液位控制 | 水箱液位恒定 | 需要精确对应进水阀的绝对开度 |
二、增量式 PID —— 输出控制增量
2.1 核心思想
增量式 PID 的核心思想是:每次计算只输出控制量的变化量(增量),而非绝对值。控制器告诉执行器"在上一次的基础上增加或减少多少",而不是"应该到达什么位置"。
核心关系:
\Delta u\lbrack k\rbrack = u\lbrack k\rbrack - u\lbrack k-1\rbrack实际输出需要在外部进行累加:
u\lbrack k\rbrack = u\lbrack k-1\rbrack + \Delta u\lbrack k\rbrack**注意区分:** 增量式 PID 本身只输出 \Delta u\lbrack k\rbrack(增量)。如果需要绝对值,必须在外部自行累加。这正是增量式的精髓所在——PID 控制器内部不保存累加值,安全性更高。
2.2 数学推导
增量式 PID 公式并非凭空产生,而是通过位置式 PID 推导而来。下面进行完整推导。
第 k 拍的位置式 PID:
u\lbrack k\rbrack = K_p \cdot e\lbrack k\rbrack + K_i \cdot \sum_{i=0}^{k} e\lbrack i\rbrack + K_d \cdot (e\lbrack k\rbrack - e\lbrack k-1\rbrack)第 k-1 拍的位置式 PID:
u\lbrack k-1\rbrack = K_p \cdot e\lbrack k-1\rbrack + K_i \cdot \sum_{i=0}^{k-1} e\lbrack i\rbrack + K_d \cdot (e\lbrack k-1\rbrack - e\lbrack k-2\rbrack)两式相减得到增量:
\Delta u\lbrack k\rbrack = u\lbrack k\rbrack - u\lbrack k-1\rbrack \Delta u\lbrack k\rbrack = K_p \cdot (e\lbrack k\rbrack - e\lbrack k-1\rbrack) + K_i \cdot e\lbrack k\rbrack + K_d \cdot (e\lbrack k\rbrack - 2e\lbrack k-1\rbrack + e\lbrack k-2\rbrack)令系数简化:
A = K_p + K_i + K_d B = -(K_p + 2K_d) C = K_d则增量式 PID 的最终简化形式为:
\Delta u\lbrack k\rbrack = A \cdot e\lbrack k\rbrack + B \cdot e\lbrack k-1\rbrack + C \cdot e\lbrack k-2\rbrack**推导的核心收获:** 增量式 PID 的计算只需要保存最近三个采样时刻的误差值:e\lbrack k\rbrack、e\lbrack k-1\rbrack、e\lbrack k-2\rbrack。无需维护累积的积分值,这大大降低了存储需求,也从根本上避免了积分饱和问题。
2.3 优缺点分析
优点:
| 序号 | 优点 | 说明 |
|---|---|---|
| 1 | 安全性高 | 单次计算错误只影响一个增量,不会导致输出突变 |
| 2 | 无积分饱和 | 不显式计算积分项,从根本上避免积分饱和问题 |
| 3 | 手动/自动切换平滑 | 切换时只影响增量而非绝对值,无切换冲击 |
| 4 | 内存占用小 | 只需保存 3 个误差值和 3 个系数 |
| 5 | 计算效率高 | 仅需 3 次乘法和 2 次加法 |
缺点:
| 序号 | 缺点 | 说明 |
|---|---|---|
| 1 | 累加溢出风险 | 外部累加器可能溢出,需要额外的保护措施 |
| 2 | 不适合精确位置控制 | 累加过程中可能引入舍入误差,长期运行偏差积累 |
| 3 | 调试困难 | 输出是增量而非绝对值,不如位置式直观,调参难度更大 |
2.4 代码实现
基础版本:
/* ========================================
* 增量式 PID 控制器 - 基础版本
* ======================================== */
typedef struct {
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
float error[3]; // 误差缓存: [0]=e[k], [1]=e[k-1], [2]=e[k-2]
} PID_Increment_t;
/**
* @brief 初始化增量式 PID 控制器
*/
void PID_Increment_Init(PID_Increment_t *pid,
float kp, float ki, float kd)
{
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->error[0] = 0.0f;
pid->error[1] = 0.0f;
pid->error[2] = 0.0f;
}
/**
* @brief 增量式 PID 计算
* @param setpoint 目标值
* @param measured 实际测量值
* @return 控制增量 Δu(注意:不是绝对值!)
*/
float PID_Increment_Calculate(PID_Increment_t *pid,
float setpoint, float measured)
{
/* 更新误差:滚动移位 */
pid->error[2] = pid->error[1]; // e[k-2] = old e[k-1]
pid->error[1] = pid->error[0]; // e[k-1] = old e[k]
pid->error[0] = setpoint - measured; // e[k] = 当前误差
/* 增量计算:Δu = A*e[k] + B*e[k-1] + C*e[k-2] */
float delta_u = pid->Kp * (pid->error[0] - pid->error[1])
+ pid->Ki * pid->error[0]
+ pid->Kd * (pid->error[0] - 2.0f * pid->error[1]
+ pid->error[2]);
return delta_u;
}
使用示例(电机速度控制 + 外部累加):
/* 电机速度控制应用示例 */
PID_Increment_t motor_pid;
float motor_output = 0.0f; // 外部累加器
#define OUTPUT_MAX 1000.0f
#define OUTPUT_MIN 0.0f
void Motor_Control_Init(void)
{
PID_Increment_Init(&motor_pid, 1.5f, 0.2f, 0.8f);
motor_output = 0.0f;
}
void Motor_Control_Loop(void)
{
float target_speed = 3000.0f; // 目标转速 3000 RPM
float current_speed = Read_Encoder(); // 读取编码器速度
// PID 计算增量
float delta = PID_Increment_Calculate(&motor_pid,
target_speed,
current_speed);
// 外部累加 + 限幅
motor_output += delta;
if (motor_output > OUTPUT_MAX) motor_output = OUTPUT_MAX;
if (motor_output < OUTPUT_MIN) motor_output = OUTPUT_MIN;
Set_Motor_PWM((uint16_t)motor_output);
}
工程优化版本(含增量限幅 + 积分分离):
/* ========================================
* 增量式 PID 控制器 - 工程优化版
* 特性:增量限幅 + 积分分离 + 死区处理
* ======================================== */
typedef struct {
float Kp, Ki, Kd;
float error[3];
float delta_max; // 单次增量上限
float delta_min; // 单次增量下限
float integral_sep_threshold; // 积分分离阈值
float dead_zone; // 死区阈值
} PID_Increment_Adv_t;
void PID_Increment_Adv_Init(PID_Increment_Adv_t *pid,
float kp, float ki, float kd)
{
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->error[0] = pid->error[1] = pid->error[2] = 0.0f;
pid->delta_max = 50.0f;
pid->delta_min = -50.0f;
pid->integral_sep_threshold = 100.0f;
pid->dead_zone = 0.5f;
}
float PID_Increment_Adv_Calculate(PID_Increment_Adv_t *pid,
float setpoint, float measured)
{
pid->error[2] = pid->error[1];
pid->error[1] = pid->error[0];
pid->error[0] = setpoint - measured;
/* 死区处理:误差极小时不输出 */
if (fabsf(pid->error[0]) < pid->dead_zone) {
return 0.0f;
}
/* 比例增量 + 微分增量(始终计算) */
float delta_u = pid->Kp * (pid->error[0] - pid->error[1])
+ pid->Kd * (pid->error[0] - 2.0f * pid->error[1]
+ pid->error[2]);
/* 积分分离:误差较小时才加入积分增量 */
if (fabsf(pid->error[0]) < pid->integral_sep_threshold) {
delta_u += pid->Ki * pid->error[0];
}
/* 增量限幅:防止单次变化过大 */
if (delta_u > pid->delta_max) delta_u = pid->delta_max;
if (delta_u < pid->delta_min) delta_u = pid->delta_min;
return delta_u;
}
2.5 适用场景
| 应用场景 | 说明 | 为什么选增量式 |
|---|---|---|
| 步进电机控制 | 脉冲数增减 | 天然增量驱动,无需绝对值 |
| PWM 增量调节 | 风扇转速、LED 亮度 | 只需微调占空比,增量更自然 |
| 阀门开度调节 | 工业过程控制 | 阀门电机按步进量动作 |
| 资源受限嵌入式 | 8 位 MCU、低内存 | 计算量小,内存占用少 |
| 频繁手动/自动切换 | 工控系统 | 切换无冲击,安全性高 |
三、位置式 vs 增量式 —— 核心对比与选型
3.1 核心对比
| 对比维度 | 位置式 PID | 增量式 PID |
|---|---|---|
| 输出形式 | 绝对控制量 u\lbrack k\rbrack | 控制增量 \Delta u\lbrack k\rbrack |
| 积分项 | 需要累加所有历史误差 | 隐含在公式中,不显式计算 |
| 存储需求 | 需保存积分累加值 + 上一次误差 | 仅需保存最近 3 个误差值 |
| 计算量 | 含累加操作,稍大 | 3 次乘法 + 2 次加法,更小 |
| 误动作影响 | 一次计算错误直接反映在输出上,影响大 | 仅影响当次增量,影响可控 |
| 积分饱和 | 存在,需额外处理 | 天然不存在 |
| 手动/自动切换 | 需要无扰切换处理,否则有冲击 | 天然平滑,无冲击 |
| 输出溢出 | PID 输出直接限幅即可 | 外部累加器可能溢出,需保护 |
| 输出直观性 | 输出即为执行器目标值,非常直观 | 输出是变化量,需外部累加才知道实际值 |
| 典型适用场景 | 伺服定位、温度控制、速度闭环 | 步进电机、阀门调节、资源受限系统 |
3.2 选型决策
**选型指南:**
**选位置式 PID 的场景:**
- 执行器需要接收绝对目标值(如伺服电机角度、PWM 占空比)
- 系统对精度要求极高,不允许累积舍入误差
- 控制对象响应较慢(如温度),不容易产生剧烈振荡
- 调试阶段,需要直观观察输出值
**选增量式 PID 的场景:**
- 执行器是增量驱动型(如步进电机脉冲、阀门步进)
- 系统需要频繁在手动/自动模式间切换
- 对安全性要求高,不允许单次误动作造成大幅输出
- MCU 资源有限,需要最小的计算量和内存占用
- 需要避免积分饱和问题,且不想额外实现抗饱和算法
**两者皆可的场景:**
- 一般的速度闭环控制
- PWM 调光/调速(位置式输出占空比,增量式调节占空比增量)
四、常见问题与解决方案
4.1 积分饱和问题
现象描述: 当系统长期处于误差状态(例如目标值远大于实际值),积分项会持续累加变得非常大。即使误差开始减小,庞大的积分项仍然会推动输出保持在高位,导致严重超调甚至振荡。
**危害:** 积分饱和会导致系统超调量增大、调节时间延长,严重时会引起持续振荡,甚至损坏执行器。这是位置式 PID 在工程应用中最常见的问题。
解决方案一:积分限幅(Integral Clamping)
/* 方法1:积分限幅 —— 最简单直接 */
pid->integral += error;
// 将积分项限制在安全范围内
if (pid->integral > INTEGRAL_MAX)
pid->integral = INTEGRAL_MAX;
if (pid->integral < INTEGRAL_MIN)
pid->integral = INTEGRAL_MIN;
解决方案二:积分分离(Integral Separation)
/* 方法2:积分分离 —— 误差大时关闭积分 */
if (fabsf(error) < SEPARATION_THRESHOLD) {
// 误差在阈值内,正常积分
pid->integral += error;
} else {
// 误差过大,停止积分(可选:清零积分)
// pid->integral = 0; // 激进策略
}
解决方案三:遇限削弱积分(Conditional Integration / Back-calculation)
/* 方法3:遇限削弱积分 —— 输出饱和时停止积分 */
float output = P + I + D;
// 判断输出是否饱和
int is_saturated = (output > OUTPUT_MAX) || (output < OUTPUT_MIN);
// 判断积分方向是否与饱和方向一致
int integral_worsening = ((error > 0) && (output > OUTPUT_MAX)) ||
((error < 0) && (output < OUTPUT_MIN));
// 只有在"不饱和"或"积分方向有助于脱离饱和"时才积分
if (!is_saturated || !integral_worsening) {
pid->integral += error;
}
4.2 微分项噪声放大
微分项计算的是误差的变化率,传感器噪声会被放大,导致输出抖动。
解决方案一:一阶低通滤波
/* 对微分项施加一阶低通滤波 */
float alpha = 0.2f; // 滤波系数,越小越平滑
float raw_deriv = error - pid->prev_error;
float filtered_deriv = alpha * raw_deriv
+ (1.0f - alpha) * pid->prev_deriv;
float D = pid->Kd * filtered_deriv;
pid->prev_deriv = filtered_deriv;
解决方案二:微分先行(Derivative on Measurement)
传统微分项基于误差 e\lbrack k\rbrack 计算。当设定值突然改变时,误差会阶跃变化,微分项输出一个巨大的脉冲。微分先行的做法是对测量值而非误差求微分。
/* 微分先行:对测量值求微分 */
// 传统方式:D = Kd * (error - prev_error)
// 微分先行:D = -Kd * (measured - prev_measured)
// 注意负号!因为 error = setpoint - measured
float D = -pid->Kd * (measured - pid->prev_measured);
pid->prev_measured = measured;
**微分先行的好处:**
- 设定值突变时,微分项不会产生脉冲冲击(因为微分的对象是缓慢变化的测量值)
- 在稳态跟踪和扰动抑制方面,效果与传统微分完全一致
- 特别适合设定值频繁变化的场景(如轨迹跟踪)
4.3 手动/自动切换冲击(位置式)
位置式 PID 在手动模式切回自动模式时,积分项和输出值可能与手动模式下的实际输出不匹配,导致输出突变。
解决方案:无扰切换(Bumpless Transfer)
/**
* @brief 手动/自动无扰切换
* @param manual_output 手动模式下的当前输出值
*/
void PID_Bumpless_Transfer(PID_Position_t *pid, float manual_output)
{
/* 核心思路:让积分项"吸收"手动输出与当前 PD 输出的差值 */
/* 这样切换到自动模式时,PID 的首次输出 = 手动输出 */
float error = 0.0f; // 切换瞬间误差用当前值
float P = pid->Kp * error;
float D = 0.0f; // 切换瞬间微分为零
// 反推积分项,使输出等于手动值
pid->integral = (manual_output - P - D) / pid->Ki;
// 重置误差历史
pid->prev_error = error;
}
/* 使用示例 */
void Switch_To_Auto_Mode(void)
{
float current_manual_output = Get_Manual_Output();
PID_Bumpless_Transfer(&pid, current_manual_output);
// 切换后 PID 首次输出 ≈ manual_output,无突变
}
4.4 超调过大
系统在达到目标值后继续冲过去,产生过大的超调。
解决方案一:降低 Kp,增大 Kd
/* 最直接的方式:降低比例增益,增大微分增益 */
pid->Kp *= 0.7f; // 降低激进程度
pid->Kd *= 1.5f; // 增强预测制动能力
解决方案二:设定值渐变(Setpoint Ramping)
/* 不直接给大阶跃信号,让设定值缓慢变化 */
float ramp_rate = 2.0f; // 每个周期最大变化量
float desired_setpoint = 100.0f;
static float ramped_setpoint = 0.0f;
if (ramped_setpoint < desired_setpoint) {
ramped_setpoint += ramp_rate;
if (ramped_setpoint > desired_setpoint)
ramped_setpoint = desired_setpoint;
} else if (ramped_setpoint > desired_setpoint) {
ramped_setpoint -= ramp_rate;
if (ramped_setpoint < desired_setpoint)
ramped_setpoint = desired_setpoint;
}
// 用渐变后的设定值进行 PID 计算
float output = PID_Calculate(&pid, ramped_setpoint, measured);
解决方案三:积分分离配合(误差大时关闭积分)
/* 配合积分分离:误差大时只用 PD 控制 */
float P = pid->Kp * error;
float D = pid->Kd * (error - pid->prev_error);
float I = 0.0f;
if (fabsf(error) < THRESHOLD) {
pid->integral += error;
I = pid->Ki * pid->integral;
}
float output = P + I + D;
4.5 输出累加溢出(增量式)
增量式 PID 的实际输出需要在外部累加,长时间运行可能导致浮点数精度下降或整型溢出。
解决方案一:累加器限幅
/* 每次累加后立即限幅 */
motor_output += delta_u;
if (motor_output > OUTPUT_MAX) motor_output = OUTPUT_MAX;
if (motor_output < OUTPUT_MIN) motor_output = OUTPUT_MIN;
解决方案二:使用双精度或定期校准
/* 方案A:使用 double 类型累加,减少精度损失 */
static double output_accumulator = 0.0;
output_accumulator += (double)delta_u;
/* 方案B:定期用传感器反馈值校准累加器 */
static int loop_count = 0;
if (++loop_count >= 1000) {
// 每 1000 个周期,用实际测量值重新校准
output_accumulator = (double)Get_Actual_Output();
loop_count = 0;
}
五、面试高频问答
Q1:位置式 PID 和增量式 PID 的本质区别是什么?
**本质区别在于输出量的含义不同:**
- **位置式 PID** 每次计算输出控制量的绝对值,即执行器应该到达的目标状态(如 PWM 占空比 65%)。公式需要累加所有历史误差。
- **增量式 PID** 每次计算输出控制量的变化量(增量),即在上一次输出基础上增加或减少多少(如 PWM 增加 3%)。公式只需要最近三个采样时刻的误差。
从数学上看,增量式是位置式相邻两拍相减的结果。两者在理论上是等价的,但在工程实现中有不同的特性和适用场景。
Q2:什么是积分饱和?怎么解决?
**积分饱和(Integral Windup)** 是指当系统长期存在较大偏差时,位置式 PID 的积分项持续累加,变得非常大。当误差开始减小时,积分项由于惯性仍然维持高值,导致输出无法及时回落,产生严重超调。
**常见解决方法:**
1. **积分限幅:** 对积分项设置上下限,防止无限增长。
2. **积分分离:** 当误差超过设定阈值时暂停积分累加。
3. **遇限削弱积分(Back-calculation):** 当输出饱和时,停止同方向的积分累加。
4. **使用增量式 PID:** 从根本上避免积分饱和问题。
Q3:PID 三个参数各自的作用?如何整定?
**三个参数的作用:**
| 参数 | 作用 | 增大效果 | 减小效果 |
|:---:|:---|:---|:---|
| Kp | 比例增益,决定响应速度 | 响应加快,但可能振荡 | 响应变慢,稳态误差增大 |
| Ki | 积分增益,消除稳态误差 | 消除稳态误差,但增加超调 | 稳态精度下降 |
| Kd | 微分增益,抑制超调和振荡 | 减小超调,但对噪声敏感 | 超调增大,阻尼减弱 |
**常见整定方法:**
1. **试凑法:** 先调 P 使系统振荡,再加 D 抑制振荡,最后加 I 消除稳态误差。
2. **临界比例法(Ziegler-Nichols):** 只用 P 控制找到临界振荡点,根据临界增益和振荡周期套用公式。
3. **衰减曲线法:** 调 P 使系统产生 4:1 衰减比,再确定 I 和 D。
Q4:什么场景选位置式,什么场景选增量式?
**选位置式的典型场景:**
- 伺服电机位置控制(需要精确的绝对位置值)
- 温度控制(输出直接对应加热功率)
- 需要精确绝对值输出的场合
**选增量式的典型场景:**
- 步进电机控制(天然增量驱动)
- 需要频繁手动/自动切换(无冲击)
- 资源受限的嵌入式系统(内存和计算量小)
- 安全性要求高的场合(单次故障影响小)
**核心判断原则:** 如果执行器需要接收"到达某个值"的指令,选位置式;如果执行器需要接收"增加/减少多少"的指令,选增量式。
Q5:微分项对噪声敏感怎么办?
微分项计算的是误差的变化率,高频噪声会被放大。解决方法:
1. **一阶低通滤波:** 对微分项的计算结果施加滤波,公式为 D_{filtered} = \alpha \cdot D_{raw} + (1-\alpha) \cdot D_{prev},其中 \alpha 越小滤波越强。
2. **微分先行(Derivative on Measurement):** 对测量值而非误差求微分。这样设定值的突变不会引起微分脉冲,而且测量值通常比误差信号更平滑。
3. **增加采样周期:** 在不影响控制性能的前提下增大采样间隔,减小高频噪声的影响。
4. **硬件滤波:** 在传感器端加 RC 低通滤波电路。
Q6:增量式 PID 为什么只需要 3 个误差值?
这是由数学推导决定的。增量式 PID 的公式为:
\Delta u\lbrack k\rbrack = K_p(e\lbrack k\rbrack - e\lbrack k-1\rbrack) + K_i \cdot e\lbrack k\rbrack + K_d(e\lbrack k\rbrack - 2e\lbrack k-1\rbrack + e\lbrack k-2\rbrack)展开后可以看到,公式中只包含 e\lbrack k\rbrack、e\lbrack k-1\rbrack、e\lbrack k-2\rbrack 三个误差项。这是因为增量式是位置式相邻两拍做差的结果——积分项的无限求和在做差后变成了单个 e\lbrack k\rbrack,微分项的一阶差分变成了二阶差分(需要 3 个点)。因此,整个计算仅依赖最近 3 个采样时刻的误差值。
Q7:为什么位置式 PID 在手动/自动切换时会有冲击,而增量式不会?
**位置式 PID 的冲击来源:** 在手动模式期间,PID 的积分项没有被更新(或者持续按误差累加),当切回自动模式时,PID 计算出的输出值 u\lbrack k\rbrack 可能与手动模式下执行器的实际状态有很大差距。这个差距直接体现为输出的突变(冲击)。
**增量式 PID 无冲击的原因:** 增量式输出的是变化量 \Delta u\lbrack k\rbrack。切换到自动模式后,PID 只计算一个小的增量叠加到当前的实际输出上。由于起始点就是当前实际输出值,不存在"跳变"到另一个绝对值的问题。第一次计算的增量通常很小,因此过渡平滑。
Q8:解释微分先行的原理及优势
**原理:** 传统 PID 的微分项对误差 e = setpoint - measured 求导。当设定值阶跃变化时,误差瞬间产生巨大变化,微分项输出一个极大的脉冲(理论上为无穷大)。
微分先行将微分对象从误差改为测量值:
D\lbrack k\rbrack = -K_d \cdot (measured\lbrack k\rbrack - measured\lbrack k-1\rbrack)注意负号:因为 e = setpoint - measured,对测量值求导的方向与对误差求导相反。
**优势:**
1. **消除设定值突变冲击:** 设定值阶跃变化时,测量值不会突变(被控对象有惯性),因此微分项输出平稳。
2. **保持扰动抑制能力:** 外部扰动会影响测量值,微分先行依然能检测到并及时响应。
3. **输出更加平滑:** 特别适合设定值频繁变化的轨迹跟踪场景。
4. **实现简单:** 只需将微分对象从 error 改为 measured,加一个负号即可。
六、自测题
选择题
1. 以下关于增量式 PID 的描述,正确的是?
A. 增量式 PID 不含积分作用
B. 增量式 PID 输出的是控制量的绝对值
C. 增量式 PID 只需保存最近 3 个误差值
D. 增量式 PID 不适用于电机控制
**答案:C**
**解析:**
- A 错误:增量式 PID 包含积分作用,只是隐含在公式推导中(K_i \cdot e\lbrack k\rbrack 对应积分增量)。
- B 错误:增量式输出的是控制增量 \Delta u\lbrack k\rbrack,不是绝对值。
- C 正确:由推导可知,增量式公式仅涉及 e\lbrack k\rbrack、e\lbrack k-1\rbrack、e\lbrack k-2\rbrack。
- D 错误:增量式非常适合步进电机等增量驱动型电机控制。
2. 位置式 PID 出现积分饱和的根本原因是?
A. Kp 参数设置过大
B. 积分项长期累加,无法及时消退
C. 微分项放大了噪声
D. 采样周期设置不合理
**答案:B**
**解析:** 积分饱和的根本原因是积分项 \sum e\lbrack i\rbrack 在误差长期存在时持续累加增长。即使误差归零甚至反向,积分项仍然维持巨大的值,需要很长时间才能"消化",导致严重超调。Kp 过大会引起振荡但不是积分饱和,微分噪声是微分项的问题,采样周期不合理可能影响控制效果但不是积分饱和的根本原因。
3. 关于手动/自动切换,以下说法正确的是?
A. 位置式和增量式都会产生切换冲击
B. 位置式不会产生切换冲击,增量式会
C. 增量式天然无切换冲击,位置式需要无扰切换处理
D. 两种方式都不会产生切换冲击
**答案:C**
**解析:** 增量式 PID 输出增量,切换时从当前实际输出开始叠加小增量,天然平滑。位置式 PID 输出绝对值,手动模式期间 PID 内部状态(特别是积分项)与实际输出不同步,切换时产生突变。需要通过无扰切换(Bumpless Transfer)技术来解决,即在切换时反推积分项使 PID 输出匹配手动输出。
填空题
1. 增量式 PID 的简化系数 A、B、C 分别为:A = ______,B = ______,C = ______。
**答案:**
- A = K_p + K_i + K_d
- B = -(K_p + 2K_d)
- C = K_d
2. 微分先行是对 ______ 而非 ______ 进行微分运算,其主要目的是避免 ______ 突变时产生的微分脉冲。
**答案:** 微分先行是对测量值(measured)而非误差(error)进行微分运算,其主要目的是避免设定值(setpoint)突变时产生的微分脉冲。
3. 遇限削弱积分(Back-calculation)的基本策略是:当输出达到 ______ 时,停止 ______ 方向的积分累加。
**答案:** 当输出达到饱和限幅值时,停止与饱和同方向的积分累加。即当输出已经到达上限时,不再累加正误差;当输出到达下限时,不再累加负误差。这样积分项只能向"脱离饱和"的方向变化。
简答题
1. 请从数学推导的角度,说明为什么增量式 PID 天然不存在积分饱和问题。
**参考答案:**
位置式 PID 中,积分项为 I\lbrack k\rbrack = K_i \cdot \sum_{i=0}^{k} e\lbrack i\rbrack,这是一个从系统启动以来所有误差的累加值。当误差长期为正时,该和式持续增长,这就是积分饱和。
在推导增量式 PID 时,我们将第 k 拍与第 k-1 拍的位置式公式相减。积分项相减后:
K_i \cdot \sum_{i=0}^{k} e\lbrack i\rbrack - K_i \cdot \sum_{i=0}^{k-1} e\lbrack i\rbrack = K_i \cdot e\lbrack k\rbrack原本无限增长的求和项消失了,变成了只与当前误差 e\lbrack k\rbrack 相关的有界量。增量式 PID 的计算中不存在对历史误差的显式累加,因此从数学结构上就不可能出现积分饱和。
需要注意的是,增量式的积分作用并没有消失——它隐含在外部的输出累加过程 u\lbrack k\rbrack = u\lbrack k-1\rbrack + \Delta u\lbrack k\rbrack 中。但外部累加器通常有限幅保护,且其值代表的是实际输出而非内部状态,管理起来更加直观和安全。
2. 在一个嵌入式恒温控制系统中,要求温度稳定在 50°C(精度 ±0.5°C),加热器通过 PWM 驱动,系统存在手动/自动切换需求。请分析应该选用位置式还是增量式 PID,并说明理由和需要注意的工程问题。
**参考答案:**
**推荐方案:位置式 PID + 无扰切换处理。**
**理由分析:**
1. **精度要求适合位置式:** 温度控制需要精确到 ±0.5°C,位置式 PID 输出的绝对 PWM 占空比直接对应加热功率,控制精确且直观。增量式的累加过程可能引入浮点舍入误差,长期运行后精度下降。
2. **PWM 输出形式:** 加热器 PWM 驱动接收的是占空比(绝对值),位置式输出可以直接使用,而增量式需要额外的累加器。
3. **温度系统特性:** 温度变化缓慢(大热惯性),不容易出现剧烈振荡,位置式的一些缺点(如误动作影响大)在温度控制中影响较小。
**需要解决的工程问题:**
1. **积分饱和:** 开机时温度与目标差距大,必须加入积分分离或积分限幅。建议设定积分分离阈值为 5°C——误差大于 5°C 时关闭积分。
2. **手动/自动切换冲击:** 这是位置式的主要弱点。需要实现无扰切换(Bumpless Transfer):切换到自动模式时,反推积分项使 PID 输出匹配手动模式下的当前加热功率。
3. **微分噪声:** 温度传感器(如 NTC/PT100)可能有噪声,微分项需要加低通滤波。建议使用微分先行,避免目标温度调整时的微分脉冲。
4. **输出限幅:** PWM 占空比限制在 0%~100%,超出范围无物理意义。
如果手动/自动切换非常频繁且系统安全性要求极高,也可以考虑使用增量式 PID,但需要注意长期累加的精度问题,可以通过定期读取实际温度进行累加器校准来缓解。
七、总结 Checklist
- [ ] 理解位置式 PID 输出绝对控制量,增量式输出控制增量
- [ ] 能够独立推导增量式 PID 公式(位置式相邻两拍相减)
- [ ] 掌握增量式只需 3 个误差值的数学原因
- [ ] 了解积分饱和的成因与三种解决方案(限幅、分离、遇限削弱)
- [ ] 理解微分先行的原理——对测量值而非误差求微分
- [ ] 能够编写位置式和增量式 PID 的 C 语言实现
- [ ] 能根据实际场景合理选择位置式或增量式 PID
- [ ] 掌握无扰切换(Bumpless Transfer)的实现方法
参考资料
- Karl J. Astrom, Tore Hagglund. Advanced PID Control. ISA - The Instrumentation, Systems and Automation Society, 2006.
- 刘金琨. 《先进 PID 控制 MATLAB 仿真(第4版)》. 电子工业出版社, 2016.
- Gene F. Franklin, J. David Powell, Abbas Emami-Naeini. Feedback Control of Dynamic Systems (8th Edition). Pearson, 2019.