☰
位置式PID与增量式PID:原理、选型与C语言实现详解
2026/10/16 1:06:14 网站建设 项目流程

位置式PID和增量式PID,这两个词在控制圈里几乎和“PID”本身一样高频。很多搞嵌入式、写单片机程序的朋友,一上手就会在这两种实现之间反复横跳——到底是直接算出占空比,还是每次只加一点、减一点?说实话,大多数抄来的例程都能跑,但真正出问题的时候,比如温度过冲半天压不下来、电机启动就飞车、手动切自动突然抖一下,十有八九都和这两种实现的本质区别有关。

这篇文章就把位置式PID和增量式PID从公式推导、C代码实现、现场选型到故障排查完整过一遍。适合正在做温控、电机调速、云台稳定、电源恒压恒流的朋友,也适合刚进工业自动化圈子、单片机开发和PLC调试都要沾一点的工程师。看完你至少能回答三件事:两种写法各自在“算什么”、输出到执行器时意味着什么、出问题第一步该查哪里。

先说一个最核心的判断:位置式PID算的是“现在该给多少绝对控制量”,增量式PID算的是“相比上次应该多加还是少加多少”。这个差异会直接影响启动波形、抗积分饱和表现、手动/自动切换手感,甚至决定算法能不能适配你的执行机构。下面我把两条路的细节都掰开。

1. 为什么会有位置式和增量式两种写法

1.1 连续PID与数字PID的分岔点

在模拟调节器时代,并没有“位置式”和“增量式”这个分法。硬件电路里一个运算放大器直接输出连续的电压或电流信号,PID就是一个物理形态。真正让这个家族分家的是数字化。采样周期T出现以后,积分没法再连续做了,微分也没法连续取了,只能拿离散的累加和差分去逼近,于是连续PID公式:

u(t) = Kp·e(t) + Ki·∫e(t)dt + Kd·de(t)/dt

被改写成离散形式。积分项变成从0到k时刻的误差累加,微分项变成相邻两次误差之差。只要你把这三项拼起来,算出一个绝对值去控制执行器,写出来的就是位置式PID。

但如果换个思路:我不直接算绝对控制量,只算下一次应该在当前输出基础上增加多少,把前后两个周期的位置式公式相减,就会得到增量式PID。所以位置式和增量式并不是两种“完全不同的算法”,而是同一个连续PID在数字化时选择了不同的输出口径。这句话早点想明白,后面看各种变体、抗积分饱和、无扰动切换,你就不会头晕。

1.2 两种实现的内存与输出语义差异

写嵌入式的人习惯说“状态”。位置式PID至少要维护两个状态量:一个误差累计值integral,一个上次误差prev_error。它输出的u(k)是一个“绝对值”,比如PWM占空比是0%到100%,u(k)就是具体的百分之多少,给到执行机构就能直接用。

增量式PID也保留误差历史,但通常是两个:e(k-1)、e(k-2)。它输出的是Δu(k),一个“变化量”。如果你面对的执行机构是步进电机,可以把Δu直接当下发步数变化量,硬件自己记住位置;如果你面对的是一台变频器,得先把Δu累加成一个新的频率指令再下发。换言之,增量式把“记忆位置”的责任部分转移给了执行机构或者软件里的累加器。

这个区别会导致一种很典型的现场事故:位置式实现去驱动步进电机,给它绝对转速,可能没问题;但增量式实现去驱动一个直通阀门,如果不在软件里做累加,阀门每次只挪动一个微小增量,系统根本闭环不起来。很多稀奇古怪的控制失败,源头就在这。

2. 位置式PID:直接把绝对控制量算给执行器

2.1 位置式PID公式和C语言实现

位置式PID离散公式写成:

u(k) = Kp·e(k) + Ki·Σe(i) + Kd·[e(k) - e(k-1)]

这里的Σe(i)是从启动到当前的误差累加。代码实现很直白,定义一个结构体保存状态,每次调用函数就输出一个新的绝对控制量。下面给一个最常用的写法:

typedef struct { float Kp; float Ki; float Kd; float target; // 目标值 float integral; // 误差累计 float prev_error; // 上一次误差,用于微分 float out_min; // 输出下限 float out_max; // 输出上限 } PositionPID; float PositionPID_Update(PositionPID *pid, float feedback) { float error = pid->target - feedback; float output; pid->integral += error; // 积分:全部历史误差累加 float derivative = error - pid->prev_error; // 微分:差分代替微分 pid->prev_error = error; output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; if (output > pid->out_max) output = pid->out_max; if (output < pid->out_min) output = pid->out_min; return output; }

这段代码看起来简单,但注意一点:它直接在输出端限幅,并没有限制内部的integral。如果误差一直存在,integral会一直累加,然后输出一直被限幅在最大值,这就是积分饱和的温床。我在第5节会专门讲怎么处理。

2.2 三个绕不开的坑:积分饱和、启动冲击、切换冲击

第一个坑是积分饱和。过程控制系统里最常见,比如加热器要升到200度,初始温度只有25度,误差长期保持在175度,积分项疯狂上涨,输出一直顶在100%。等温度终于到了200度,误差变成0,积分项却还是那个很大的值,控制器需要花很长时间把积分“吐”完,结果就是温度冲过目标很多才回来。这是位置式PID最经典的一种超调来源。

第二个坑是启动冲击。位置式PID在刚上电的瞬间,误差往往是满幅的,三项全部参与计算,输出直接一步跳到最大或最小。如果执行机构是变频器或者伺服驱动器,这种瞬间冲击很容易触发过流保护,甚至给机械结构带来撞击。

第三个坑是手动/自动切换冲击。很多系统允许先手动输出一个值接近目标,再切入自动模式。如果你手动时积分是清零的,切入自动的瞬间,比例项突然作用在剩余误差上,输出会跳变。标准做法是切入自动前把积分项初始化成当前手动输出对应的值,或者利用“积分预装载”功能,否则切换那一下设备就会抖。

2.3 位置式PID适合哪些场景

位置式PID比较适合输出本身就是绝对值的执行机构。比如电动调节阀,你给它一个阀门开度信号,它直接就能开到那个位置,不需要自己记忆位置;再比如可控硅调压器、固态继电器控制的加热棒,输出百分比就是一个绝对占空比,用位置式非常顺手。

温度、压力、液位这类过程量控制,用位置式的比例更多。因为这些对象本身响应慢,采样周期长,积分饱和可以通过合理限幅和抗饱和算法来控制,而且在上位机监控里看到绝对值输出也直观。需要明确一点:位置式PID并不意味着代码“低级”,工业DCS和PLC里的PID功能块,很多核心逻辑就是位置式,只不过外面包了复杂的手动自动切换和抗积分饱和逻辑。

3. 增量式PID:只告诉执行器该加多少

3.1 增量式PID是怎么推导出来的

增量式PID的推导很漂亮。设位置式在k时刻的输出为u(k),在k-1时刻的输出为u(k-1),把两者相减:

Δu(k) = u(k) - u(k-1)

代入位置式公式后,积分项的累加和被消掉,只留下一个误差项;微分项则展开成二阶差分。最后得到:

Δu(k) = Kp·[e(k) - e(k-1)] + Ki·e(k) + Kd·[e(k) - 2e(k-1) + e(k-2)]

对比位置式,增量式里没有Σe(i)这种长期累加项。原因是历史信息被隐含在“上一次输出u(k-1)”里了。控制器只负责算变化量,至于当前位置在哪,要么由执行机构记住,要么由代码里的累加器记住。这个特性让增量式天然有一种“温柔”的感觉:误差大时,每次输出也只变化一个有限增量,而不是直接甩一个满幅绝对值。

3.2 增量式PID的C语言实现与初始化

增量式实现要保存两个历史误差,并用一个内部变量accumulator保存当前的绝对输出。下面是一个典型的实现:

typedef struct { float Kp; float Ki; float Kd; float target; float prev_error; // e(k-1) float prev_prev_error; // e(k-2) float output; // 内部累加得到的绝对输出 float out_min; float out_max; } IncrementPID; float IncrementPID_Update(IncrementPID *pid, float feedback) { float error = pid->target - feedback; float incr; incr = pid->Kp * (error - pid->prev_error) + pid->Ki * error + pid->Kd * (error - 2.0f * pid->prev_error + pid->prev_prev_error); pid->prev_prev_error = pid->prev_error; pid->prev_error = error; pid->output += incr; if (pid->output > pid->out_max) pid->output = pid->out_max; if (pid->output < pid->out_min) pid->output = pid->out_min; return pid->output; }

初始化是个容易被忽略的细节。刚上电时prev_error、prev_prev_error应该清零,output可以初始化为当前手动输出值,也可以从0开始。如果你的执行机构要求先到安全位置再启动控制,那output初值就对应安全位置的比例值。还有一点:如果执行机构本身能记住位置,比如步进电机,那增量式算出来的incr可以直接作为指令下发给驱动器,不需要在控制器里做output累加;但如果执行机构只认绝对信号,比如普通变频器、模拟量阀门,就一定要保留上面这种累加逻辑。

3.3 增量式的优点和它特有的坑

增量式最常被提起的优点有三个。第一,输出变化幅度有限,启动时不会一下拉满,对设备和执行机构更友好,相当于自带一点软启动效果。第二,手动自动切换容易做,从当前输出开始继续加增量就行,切换冲击比位置式小得多。第三,即使控制器短暂故障、增量输出异常甚至变成0,执行机构还能保持在最近的位置,不会直接飞到最大或最小,这在一些安全要求高的场合很重要。

但它也有三个被低估的坑。第一,二阶差分对测量噪声更敏感,Kd项里有个e(k)-2e(k-1)+e(k-2),传感器稍微抖动一下,增量就可能忽大忽小,所以增量式反而比位置式更需要做测量滤波。第二,如果执行机构是比例式的,你必须在代码里累加输出,那这个累加器一旦被噪声灌满,或者长时间饱和后再恢复,依然会出现类似积分饱和的行为。第三,增量式+执行机构自带积分的系统,控制器拿不到执行机构的实际位置,安全监控会变难,需要额外的限位和回零逻辑配合。

4. 位置式和增量式到底怎么选

4.1 一张表看清所有区别

很多朋友习惯用“位置式容易积分饱和,所以增量式更好”来选型,这个判断太粗糙。真实选型牵扯执行机构、控制周期、故障安全策略和调试习惯。先把差异列成一张表:

对比项位置式PID增量式PID
输出含义当前控制量绝对值u(k)当前控制量变化值Δu(k)
维护的历史状态积分累加值integral、上次误差上次误差、上上次误差
执行机构要求适合比例式执行器,给多少就是多少适合积分式执行器,或软件内部累加
启动冲击大,满误差时容易一步拉满小,增量逐步逼近
手动/自动切换需要积分预装载,否则冲击从当前输出叠加增量,冲击小
积分饱和风险高,必须做抗饱和处理低一些,但输出限幅不当同样会饱和
对噪声敏感度主要是一阶差分噪声二阶差分噪声更明显
故障安全表现输出异常可能直接跳到限幅值输出掉零时还能部分保位
典型应用温度、压力、液位、阀位调节电机、伺服、步进、运动控制

4.2 按被控对象和执行机构选型

核心决策逻辑可以简化成三条。看执行机构是不是自带位置记忆。步进电机、伺服电机带编码器、电动执行机构带阀位反馈,这类设备本身就记得自己在哪里,增量式很合适;而可控硅、继电器、模拟量阀门这类只认“当下给多少”的设备,用位置式更直接。

看被控对象的滞后程度。温度、压力这类大滞后过程,控制周期长,位置式配合抗积分饱和算法反而更好调,因为工程师能清楚看到积分项的变化曲线;电机转速这类快速对象,增量式配合伺服环更常见,响应快、启动冲击小。

看你希望故障时候怎么表现。增量式在控制器输出失效时,执行机构因为记住了最后位置,还能保住一个基本状态;位置式一旦输出抖动,执行机构直接跟着大幅摆动。对故障安全要求高的现场,这一点可能一票否决。

4.3 更进一步:位置式和增量式混着用

实际工程里两者经常组合出现。我在伺服类项目里见过一种典型的双环结构:位置外环用位置式PID,输出的是目标速度;速度内环用增量式PID或PI,输出的是力矩增量。位置环算的是绝对目标,速度环算的是平滑变化量,二者各司其职,整体表现比只用一种鲁棒得多。

还有一种变体:增量式PID外挂一个位置环路积分器。增量式负责动态调整,外部积分器负责消除累计位置误差。这种结构在一些云台稳定和机械臂控制里很常见,本质上是用增量式做内环、位置式做外环,取两者长处。所以不必死守“二选一”,而是要把两种算法的输出口径当成积木。

5. 现场最常见的故障与排查手段

5.1 积分饱和:位置式的高发病

前面说过的加热器案例就是典型。判断方法很简单:如果系统升温阶段输出一直顶在100%,温度到达目标后输出却迟迟不降,要过很久才回落,那就基本可以断定是积分饱和。解决手段有两类。一类是积分限幅,在PID结构体里增加integral_min和integral_max,每次更新积分后直接限制累加值,别让它无限增大。另一类是积分分离,误差超过某个阈值时暂停积分,只用比例和微分,等误差进入一个较小的死区再恢复积分。

增量式也会遇到类似问题。虽然它的积分项被差分化了,但如果内部output累加器没有限幅,长时间单向误差同样会把累加值推到很大,之后反向调节就需要额外时间。所以增量式代码里的输出限幅一定不能省,而且最好在饱和时暂停增量叠加,只保留变化量输出,否则退饱和会迟钝。

5.2 微分噪声:两者都躲不掉

微分项放大了传感器噪声,在增量式里还会变成二阶差分噪声,比位置式更剧烈。现象是控制量在稳态时出现高频抖动,执行器频繁来回调整。解决思路有三个:一是在采样前对反馈做滑动平均滤波或一阶低通滤波;二是把微分项里的e(k)-e(k-1)改成只对反馈值取差分,而不是对误差取差分,这样目标值变化时不会产生巨大的微分尖峰;三是对微分输出单独限幅,限制单次微分项的最大值。

其中“对测量值微分而不是对误差微分”是很实用的技巧。原因是目标值通常是个阶跃量,你设定新目标时,误差瞬间变化很大,对误差微分会产生一个理论上无穷大的尖峰;对反馈值微分,即使反馈跳变,幅度也远比误差跳变温和。

5.3 上电飞车和切换抖动

位置式上电飞车的原因我已经说过:误差满幅、输出一步拉满。对策有两个。第一,上电时先把输出初始化为安全值,然后通过渐变逻辑让输出慢慢恢复;第二,把启动阶段分为“比例+微分先行”模式,积分项延迟启动,等误差小于某个范围再参与。如果系统的执行机构支持手动优先,操作员先手动把设备带到接近目标的工作点,再切自动,配合4.1里提到的积分预装载,切换抖动可以压到很小。

增量式切换抖动小的前提是output初值等于当前手动输出。如果你在切换时直接把output清零,那增量式同样会从0开始一步一步爬,反而显得“反应迟钝”。所以增量式不是不需要初始化,而是需要一个“当前实际输出值”作为累加起点。

5.4 参数整定的经验步骤

参数整定不是玄学,但不走弯路的人很少。我最常用的流程是:

第一步,Kd、Ki先设0,只留Kp,从小到大慢慢加。观察系统从振荡到稳定的变化,找到那个开始持续振荡又不发散的比例系数Ku,记录振荡周期Tu。第二步,保持Kp=0.5×Ku左右,把Ki从小到大加,消除静态误差。注意Ki太大主要引起低频振荡,现象是输出整体缓慢来回摆。第三步,最后加Kd,一点一点来,Kd太大会让输出高频抖动。

这三个参数虽然作用不同,但会互相制约。比如Kp调大了,Ki可能就得调小;Kd能抑制超调,但加多了会放大噪声。现场经常需要反复调两三轮才能稳定。

下面是我整理出来的一张整定观察表,适合大部分常规回路:

现象优先调节方向
响应太慢、达不到目标适当增大Kp
系统等幅振荡、发散减小Kp,或减小Ki
稳态误差一直存在增大Ki
超调大、回稳慢先加Kd试,再适当降Kp
稳态时输出高频抖动检查微分项和测量噪声,限制Kd

6. 我的代码习惯和调试经验

6.1 把两种PID封装成一套通用接口

我做过不少单片机项目,最后习惯是把两种算法封装成同一套接口:初始化函数、更新函数、参数设置函数、状态读取函数。结构体里加一个枚举字段,表示当前是位置式还是增量式,更新函数内部按分支处理。这样试验不同算法时,只需要改一个初始化参数,不用到处改业务代码。

调试时要能在运行中读取内部状态。很多人只看最终输出波形,出了超调也不知道是积分大还是比例大。建议在调试接口里同时导出一个结构体,包含error、integral、derivative、output,用上位机或日志把这几路曲线画出来。看到积分曲线在超调阶段还在以很大速度上升,就知道该限幅或加积分分离了。

6.2 先仿真验证,再上硬件试

我强烈建议先在仿真环境里把PID跑通。用简单的一阶惯性对象模型,采样周期和真实系统一致,就能把kp、ki、kd的初始值摸个差不多。直接上硬件试,尤其是驱动大功率电机或加热器,轻则烧管子,重则把机械结构顶坏。仿真时故意给反馈加一点随机噪声,能提前看到微分放大的效果。

控制周期也要在仿真里确定。经验法则是采样周期T尽可能设为被控对象时间常数的十分之一左右。温度对象时间常数几秒,控制周期几百毫秒没问题;电机电流环时间常数不到几毫秒,控制周期就得微秒到毫秒级。同样是PID,换个采样周期,参数完全不可比。

6.3 最后说几句实在话

把位置式和增量式彻底分清楚之后,再看那些PID的衍生话题——模糊PID、自适应PID、史密斯预估器,你会轻松很多。因为它们说到底都是在解决某一类特定对象的缺陷,而位置式和增量式这两条底子,决定了你在哪个基础上做这些事情。

我个人在实际调试中的体会是:位置式适合你能清晰看到“输出到底在哪”的场合,增量式适合你更关心“输出应该怎么变化”的场合。不要因为别人说增量式高级就盲目切换,也不要因为位置式实现简单就忽略抗饱和。真正让控制变好的,不是算法名字,而是你对被控对象、执行机构、采样周期和输出限幅这四个要素的理解。把这四件事想清楚,无论用哪种写法,都不会跑偏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询