尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
位置式PID与增量式PID:原理、选型与C语言实现详解
位置式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。第二步保持Kp0.5×Ku左右把Ki从小到大加消除静态误差。注意Ki太大主要引起低频振荡现象是输出整体缓慢来回摆。第三步最后加Kd一点一点来Kd太大会让输出高频抖动。这三个参数虽然作用不同但会互相制约。比如Kp调大了Ki可能就得调小Kd能抑制超调但加多了会放大噪声。现场经常需要反复调两三轮才能稳定。下面是我整理出来的一张整定观察表适合大部分常规回路现象优先调节方向响应太慢、达不到目标适当增大Kp系统等幅振荡、发散减小Kp或减小Ki稳态误差一直存在增大Ki超调大、回稳慢先加Kd试再适当降Kp稳态时输出高频抖动检查微分项和测量噪声限制Kd6. 我的代码习惯和调试经验6.1 把两种PID封装成一套通用接口我做过不少单片机项目最后习惯是把两种算法封装成同一套接口初始化函数、更新函数、参数设置函数、状态读取函数。结构体里加一个枚举字段表示当前是位置式还是增量式更新函数内部按分支处理。这样试验不同算法时只需要改一个初始化参数不用到处改业务代码。调试时要能在运行中读取内部状态。很多人只看最终输出波形出了超调也不知道是积分大还是比例大。建议在调试接口里同时导出一个结构体包含error、integral、derivative、output用上位机或日志把这几路曲线画出来。看到积分曲线在超调阶段还在以很大速度上升就知道该限幅或加积分分离了。6.2 先仿真验证再上硬件试我强烈建议先在仿真环境里把PID跑通。用简单的一阶惯性对象模型采样周期和真实系统一致就能把kp、ki、kd的初始值摸个差不多。直接上硬件试尤其是驱动大功率电机或加热器轻则烧管子重则把机械结构顶坏。仿真时故意给反馈加一点随机噪声能提前看到微分放大的效果。控制周期也要在仿真里确定。经验法则是采样周期T尽可能设为被控对象时间常数的十分之一左右。温度对象时间常数几秒控制周期几百毫秒没问题电机电流环时间常数不到几毫秒控制周期就得微秒到毫秒级。同样是PID换个采样周期参数完全不可比。6.3 最后说几句实在话把位置式和增量式彻底分清楚之后再看那些PID的衍生话题——模糊PID、自适应PID、史密斯预估器你会轻松很多。因为它们说到底都是在解决某一类特定对象的缺陷而位置式和增量式这两条底子决定了你在哪个基础上做这些事情。我个人在实际调试中的体会是位置式适合你能清晰看到“输出到底在哪”的场合增量式适合你更关心“输出应该怎么变化”的场合。不要因为别人说增量式高级就盲目切换也不要因为位置式实现简单就忽略抗饱和。真正让控制变好的不是算法名字而是你对被控对象、执行机构、采样周期和输出限幅这四个要素的理解。把这四件事想清楚无论用哪种写法都不会跑偏。
RELATED

相关推荐

VSCode编译Keil工程:UV4命令行集成配置指南

VSCode编译Keil工程:UV4命令行集成配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/16 1:06:27
GitHub图片404终极解决方案:路径标准化与assets目录规范

GitHub图片404终极解决方案:路径标准化与assets目录规范

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/16 1:06:27
Delphi TStringGrid复制粘贴完整方案:从选区到剪贴板与Excel互操作

Delphi TStringGrid复制粘贴完整方案:从选区到剪贴板与Excel互操作

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/16 1:01:27
MORE NEWS

更多资讯

📰

脑机接口的“事件视界”:技术现状与长寿可能性探析

1. 从“事件视界”说起:为什么脑机接口成了长寿叙事的核心变量我最早看到“第一批活到1000岁的人可能已经诞生”这句话时,第一反应是嗤之以鼻。活到1000岁,这不是科幻小说里的设定吗?但顺着“脑机接口的事件视界”这个角度琢磨了一…

📰

微博发布自动化实战:从选题规划到定时发布与数据复盘

微博发布这件事,放在运营工作里,常被当成最没技术含量的一步:打开输入框,打字,点发送。但如果你需要让一个账号每天稳定输出、持续更新,很快就会意识到——发,从来不是问题;发什么、…

📰

SQL日期函数DATEDIFF详解:从边界条件到性能优化实战

DATEDIFF 应该是 SQL 里最常用也最容易被轻视的函数之一。很多开发者在项目初期用它算个日期差,感觉简单到不需要看文档——两个日期一减,天数出来了,完事。但等你真正处理过跨年数据、排查过慢查询、或者接过一个用了 Oracle 和 PostgreSQL …

📰

gauntlet-loop核心机制揭秘:builder-critic配对与盲评对比如何逼出Agent的极致输出

【免费下载链接】gauntlet-loop Turn any goal into a short prompt that makes your agent set a real quality bar, run builder and critic pairs, compare blind, and loop until it wins. 项目地址: https://gitcode.com/gh_mirrors/ga/gauntlet-loop 点击查看…

📰

基于MP2642GR-Z与PIC18F86K90的两串磷酸铁锂主动均衡方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

美赛C题备赛核心:从数据体检到可解释建模全流程指南

每年到了备赛期,我都会先被同一个问题问住:“C题到底考什么?是不是数据多、能跑模型就能高分?” 这句话对一半,但真正拉开差距的往往不是模型跑了多少,而是你有没有把“问题”翻译成“数学语言”。美赛C题本…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬