3. 位置式PID与增量式PID:两种算法原理对比、代码实现、应用场景选择、优缺点分析
PID控制算法,说白了就是一套“纠偏”的数学规则。你给系统一个目标值,它测量当前值,算出偏差,然后通过比例、积分、微分三个环节去修正。但在实际工程落地时,这个算法有两种最常见的实现形式:位置式PID和增量式PID。
很多刚入行的工程师会纠结选哪个。我刚开始做电机控制那会儿,也在这上面栽过跟头。今天咱们就把这两种算法掰开揉碎了讲清楚。
3.1 位置式PID:最直观的“全量输出”
位置式PID,顾名思义,它的输出直接就是执行机构应该到达的绝对位置。比如控制一个阀门开度,它直接输出“开45%”;控制电机转速,它直接输出“占空比60%”。
3.1.1 算法原理
它的数学表达式长这样:
u(k) = Kp * e(k) + Ki * Σe(i) + Kd * [e(k) - e(k-1)]
其中:
- u(k):第k次采样时的控制输出(绝对量)
- e(k):第k次采样时的偏差(目标值 - 当前值)
- Σe(i):从开始到现在的偏差累加和
- Kp, Ki, Kd:比例、积分、微分系数
这里有个关键点——积分项Σe(i)。它记录了系统从启动到现在的所有历史偏差。这意味着,只要系统运行着,这个累加和就会一直增长。
⚠ 我曾经踩过的坑: 位置式PID的积分饱和问题。有一次我做温控系统,启动时偏差很大,积分项疯狂累加。等温度到达目标值时,积分项已经积累了一个巨大的值,导致系统输出迟迟降不下来,温度直接冲过了头。这就是典型的“积分饱和”。
3.1.2 代码实现(C语言)
typedef struct {
float Kp;
float Ki;
float Kd;
float integral; // 积分累加和
float last_error; // 上一次偏差
float output; // 控制输出
float out_max; // 输出上限
float out_min; // 输出下限
} PositionalPID;
float PositionalPID_Calc(PositionalPID *pid, float setpoint, float feedback) {
float error = setpoint - feedback;
// 比例项
float p_out = pid->Kp * error;
// 积分项(累加)
pid->integral += error;
float i_out = pid->Ki * pid->integral;
// 微分项
float d_out = pid->Kd * (error - pid->last_error);
// 计算输出
pid->output = p_out + i_out + d_out;
// 输出限幅(防积分饱和的简单处理)
if (pid->output > pid->out_max) {
pid->output = pid->out_max;
// 这里应该做积分退饱和,后面会讲
} else if (pid->output < pid->out_min) {
pid->output = pid->out_min;
}
// 更新状态
pid->last_error = error;
return pid->output;
}
3.2 增量式PID:只关心“变化量”
增量式PID的思路完全不同。它不输出绝对位置,而是输出当前控制量相对于上一次控制量的增量。说白了,它告诉执行机构:“你再往前走这么多”。
3.2.1 算法原理
增量式PID的公式推导其实是从位置式来的。我们写出第k次和第k-1次的输出,然后相减:
Δu(k) = u(k) - u(k-1)
= Kp * [e(k) - e(k-1)]
+ Ki * e(k)
+ Kd * [e(k) - 2*e(k-1) + e(k-2)]
你看,这个公式里没有积分累加项Σe(i)。它只依赖最近三次的偏差值。这意味着:
- 计算量小,只做加减法
- 没有积分饱和问题
- 输出是增量,误动作影响小
3.2.2 代码实现(C语言)
typedef struct {
float Kp;
float Ki;
float Kd;
float error[3]; // 环形缓冲区,存最近三次偏差
float delta_out; // 输出增量
float output; // 实际输出(需要外部累加)
} IncrementalPID;
float IncrementalPID_Calc(IncrementalPID *pid, float setpoint, float feedback) {
// 更新偏差队列
pid->error[2] = pid->error[1];
pid->error[1] = pid->error[0];
pid->error[0] = setpoint - feedback;
// 计算增量
pid->delta_out = pid->Kp * (pid->error[0] - pid->error[1])
+ pid->Ki * pid->error[0]
+ pid->Kd * (pid->error[0] - 2*pid->error[1] + pid->error[2]);
return pid->delta_out;
}
// 外部调用时,需要自己累加:
// motor_output += IncrementalPID_Calc(&pid, target_speed, current_speed);
💡 我个人习惯: 增量式PID的代码里,我通常会把error数组做成环形缓冲区,这样每次更新只需要移动指针,不用真的搬数据。在STM32上跑,一个周期也就几个微秒。
3.3 核心差异对比
这两种算法到底差在哪?我整理了一张表,你一看就明白:
| 对比维度 | 位置式PID | 增量式PID |
|---|---|---|
| 输出形式 | 绝对位置(如占空比60%) | 增量(如+5%) |
| 积分实现 | 偏差累加和Σe(i) | 无累加,仅用当前偏差 |
| 积分饱和 | 容易发生,需额外处理 | 天生免疫 |
| 计算依赖 | 所有历史偏差 | 最近3次偏差 |
| 误动作影响 | 输出突变,可能造成冲击 | 增量小,影响有限 |
| 手动/自动切换 | 输出会跳变,需做bumpless | 天然无冲击 |
| 存储开销 | 需保存积分累加值 | 只需3个偏差值 |
3.4 应用场景选择:什么时候用哪个?
这个问题没有标准答案,但我可以给你一些实战经验:
3.4.1 优先选增量式PID的场景
- 步进电机/伺服电机位置控制:增量式输出的是“再走多少步”,天然匹配
- 需要频繁切换手动/自动:比如有些设备调试阶段,操作员要切手动调位置,再切回自动。增量式不会导致输出跳变
- 执行机构有记忆功能:比如舵机,你给它一个增量,它自己会记住当前位置
- 系统容易受干扰:增量式误动作小,不会因为一次采样错误导致系统剧烈抖动
3.4.2 优先选位置式PID的场景
- 执行机构需要绝对位置指令:比如比例阀,你告诉它“开50%”,它就去50%
- 积分项需要独立控制:有些高级算法需要单独处理积分项,位置式更方便
- 系统对稳态精度要求极高:位置式的积分项是全局累加,理论上可以消除所有静差
- 输出本身就是绝对量:比如PWM占空比,你直接给一个0-100%的值
🔑 我的选择原则: 如果执行机构是“积分型”(比如步进电机,脉冲数累加),我无脑选增量式。如果执行机构是“比例型”(比如阀门,直接对应开度),我选位置式。但记得一定要处理积分饱和。
3.5 优缺点深度分析
3.5.1 位置式PID的优缺点
优点:
- 物理意义直观,调试时容易理解每个参数的作用
- 积分项可以完全消除静差
- 适合需要绝对位置输出的场景
缺点:
- 积分饱和是老大难问题,需要做抗积分饱和处理(如积分分离、变速积分)
- 手动/自动切换时输出会跳变,需要做无扰切换
- 计算量相对大一些(虽然现在MCU性能都够)
3.5.2 增量式PID的优缺点
优点:
- 没有积分饱和问题,代码实现简单
- 误动作影响小,系统鲁棒性好
- 手动/自动切换天然无冲击
- 计算量小,适合低性能MCU
缺点:
- 输出是增量,需要外部累加,如果累加器溢出或初始化不当会出问题
- 积分作用是通过Ki*e(k)实现的,本质上是一个比例+积分的效果,参数整定思路和位置式略有不同
- 对于需要绝对位置输出的场景,需要额外维护一个累加器
3.6 知识结构图
下面这张图帮你理清两种算法的核心脉络:
3.7 避坑指南
最后,分享几个我实际项目中遇到的坑:
⚠ 我曾经犯过的错: 有一次做四轴飞行器的姿态控制,我用了位置式PID。飞行器起飞时,由于姿态偏差大,积分项迅速饱和。等飞行器姿态回正后,积分项还保留着巨大的值,导致电机输出异常,飞行器直接翻了个跟头。后来我改用了增量式PID,配合限幅,问题就解决了。
💡 如果你非要用位置式: 一定要做积分分离或抗积分饱和处理。我的做法是:当输出达到限幅值时,停止积分累加,或者把积分项清零。这样至少不会让积分项无限增长。
好了,两种PID算法的对比就讲到这里。记住一个核心原则:看你的执行机构是“记位置”还是“不记位置”。记位置的用增量式,不记位置的用位置式。这个选择对了,后面调试会省很多事。