简介:面向西门子S7-1200与S7-1500 PLC工程师及自动化控制学习者的增量式PID程序实例,聚焦闭环控制中的输出调节、误差处理与参数整定等实际问题,可参考其编程结构用于温度、流量、压力等控制场景。压缩包共49个文件、约1.26MB,主体为可打开的TIA博图工程,包含工程配置xml、数据块db、SCL源程序以及索引缓存等辅助文件,另有少量图标、界面与日志素材,项目组织相对完整,便于按工程模块定位,并非零散代码片段。已有752人学习下载。程序无密码且带有注释,V15.1及以上博图版本可打开,便于读者查阅增量式PID的误差差值计算、积分项更新和P/I/D参数设定,也能对照实际项目理解SCL编程组织、控制量刷新与调试要点,适合需要快速搭建闭环控制程序框架或查漏补缺的工程人员学习借鉴。
1. 一条温度回路的启停冲击,把我逼到了增量式
某产线的热压工序,温度回路原来用位置式PID,每次设备重启,操作工都要盯着阀门开度手动补一把——位置式输出是积分项的累加,上电时积分从零起步,阀门会从当前实际开度瞬间跳到理论计算值,冲击肉眼可见。换成增量式之后,输出只跟最近三次偏差有关,上电从保持的当前输出值继续累加,阀门纹丝不动。这套方案就是给S7-1200和S7-1500写一个可直接复用的增量式PID功能块,包含系数预计算、输出限幅、手动自动无扰切换,已经在实际项目的温度、压力、流量回路上跑过。适合正在做PLC课程设计、毕业设计,或者要给现场补一个PID功能块的人。
2. 位置式与增量式的取舍:从三个偏差看懂递推公式
2.1 位置式在PLC里为什么会顶死
位置式的输出是绝对量,公式写成离散形式是 u(k) = Kp·e(k) + Ki·Σe(j)·Ts + Kd·[e(k)-e(k-1)]/Ts。它的输出直接对应阀门开度、变频器频率这类绝对执行量,物理意义清楚,但有一个在PLC上特别容易放大的毛病:积分项是历史偏差的累加,只要偏差方向不变,积分就一路往上堆。阀门已经全开、温度还上不去,积分照样涨,等温度回头,积分要花很长时间才退下来,表现就是超调大、回落慢。
有人用"积分分离"或"遇限削弱"去补,逻辑越写越长,调试时反而说不清到底是哪一段在起作用。位置式不是不能用,西门子自带的FB41就是位置式,功能齐全,但它要配一堆抗饱和参数,对课程设计这种要讲清楚每一行代码在干什么的场景,负担偏重。
增量式换了个思路:不直接算输出,算输出的变化量 Δu(k),执行机构自己累加。公式展开后只跟最近三次偏差有关:
Δu(k) = Kp·[e(k)-e(k-1)] + Ki·Ts·e(k) + (Kd/Ts)·[e(k)-2e(k-1)+e(k-2)]
把同类项合并,得到工程上更常用的三系数形式:
Δu(k) = A·e(k) + B·e(k-1) + C·e(k-2) A = Kp·(1 + Ts/Ti + Td/Ts) B = -Kp·(1 + 2·Td/Ts) C = Kp·Td/Ts
这里的 Ti 是积分时间(秒),Td 是微分时间(秒),Ts 是采样周期(秒)。A、B、C 三个系数在参数不变时是常量,可以在功能块里预计算一次,每个扫描周期只做三次乘加,运算量比位置式小,在S7-1200这种资源有限的CPU上差别很明显。
2.2 增量式的两个天然优势和一个代价
第一个优势是上电无冲击。增量式的输出存在一个掉电保持的数据块里,上电后从上次的值继续累加,不会像位置式那样积分从零重建。这一点在间歇生产的设备上价值很大,操作工不用每次开机都手动对阀门。
第二个优势是抗饱和简单。输出到了上限,增量自然被钳住,偏差反向时增量立刻变号,退出饱和比位置式快。不需要单独写一套积分分离逻辑,代码量少一半。
代价是增量式只适合带积分环节的执行机构,比如电动调节阀、变频器、伺服。如果执行机构本身是绝对位置型、每次都要给全量(某些定位器就是这种),增量式反而不合适,得在功能块外面再加一层累加器,那就失去意义了。选型时先确认执行机构是"给增量"还是"给全量",这一步搞错,后面全是白费。
位置式和增量式的差别可以用一张表说清楚:
| 对比项 | 位置式 | 增量式 |
|---|---|---|
| 输出含义 | 绝对量 | 变化量 |
| 积分饱和 | 需要专门抗饱和逻辑 | 天然抑制,限幅即可 |
| 上电冲击 | 积分从零重建,有冲击 | 从保持值累加,无冲击 |
| 运算量 | 每次要做累加和乘法 | 三次乘加,预计算系数 |
| 适用执行机构 | 任意 | 带积分环节的阀门/变频器 |
| 手动切换 | 需要跟踪积分项 | 跟踪输出值和偏差历史即可 |
2.3 采样周期必须和中断OB绑死
增量式对 Ts 的准确性比位置式敏感。公式里 Ti 和 Td 都被 Ts 除了或者乘了,Ts 一变,实际的积分时间和微分时间就跟着变。很多人把PID放在OB1主循环里调用,觉得"反正每个周期都算一次",但OB1的扫描周期受程序量、通信负载影响,忽长忽短,标定好的 Ti 实际可能偏差30%以上。
通常的做法是把PID计算放进循环中断OB,S7-1200和S7-1500都支持OB30系列循环中断,在硬件组态里把周期设成固定值,比如100ms,然后把同一个值写进功能块的 Ts 输入。这样每次调用的间隔是硬件保证的,Ti 和 Td 才有意义。
注意:OB30的周期设成100ms,功能块的Ts也必须是0.1秒,两个地方要人工对齐,没有自动校验。这一点在后面的避坑章节还会展开。
3. 程序架构设计:一个功能块、两个数据块和一条中断链
3.1 整体结构:OB1负责调度,OB30负责节拍
程序分成三层。最上面是OB1主循环,负责模式判断、调用使能、把工艺数据块里的设定值送到功能块。中间是OB30循环中断,周期固定100ms,里面只放PID功能块的调用,保证计算节拍稳定。最下面是功能块本身和它的背景数据块,功能块只做算法,不碰任何具体的工艺变量,方便在多个回路之间复用。
数据块分两个。一个是功能块的背景DB,由TIA Portal在调用时自动生成,存偏差历史、预计算系数这些内部状态。另一个是工艺数据块,按回路建数组,比如温度回路一个结构体、压力回路一个结构体,每个结构体里放设定值、实际值、输出值、手动值、使能位。工艺数据块里的输出值要勾选"保持性",掉电重启后增量式才能从上次的值继续累加,这是上电无冲击的前提。
功能块和外部的关系只有两组接口:输入是设定值、实际值、三个整定参数、采样周期、上下限、手动标志和手动值;输出是累计后的输出值和几个状态位。功能块内部不引用任何全局变量,这样同一个FB可以实例化出十几个回路,互不干扰。
3.2 接口参数定义:哪些进输入,哪些进静态变量
接口划分有一条原则:每个扫描周期由外部给的进输入,跨周期要保持的进静态变量。设定值、实际值、Kp、Ti、Td、Ts、上下限、手动标志这些每个周期都可能变,放 VAR_INPUT。偏差历史 e(k-1)、e(k-2)、预计算系数 A/B/C、上一次的输出值,这些必须跨周期记住,放 VAR 静态区。累计输出值放 VAR_IN_OUT,让外部传工艺数据块里的变量进来,功能块直接读写,省一次赋值。
参数定义整理成表,方便对照:
| 参数 | 类型 | 含义 | 单位 | 典型值 |
|---|---|---|---|---|
| Setpoint | Real | 设定值 | 工艺单位 | 按工艺定 |
| ActualValue | Real | 过程实际值 | 工艺单位 | 按工艺定 |
| Kp | Real | 比例增益 | 无量纲 | 1.0~8.0 |
| Ti | Real | 积分时间 | 秒 | 20~300 |
| Td | Real | 微分时间 | 秒 | 0~50 |
| Ts | Real | 采样周期 | 秒 | 0.1 |
| OutMax | Real | 输出上限 | % | 100.0 |
| OutMin | Real | 输出下限 | % | 0.0 |
| Manual | Bool | 手动标志 | — | FALSE |
| ManualValue | Real | 手动输出值 | % | 0.0 |
Ti 设成0表示关掉积分,Td 设成0表示关掉微分,这两个判断在系数预计算里做分支,不要让用户去填一个"无穷大"的积分时间,那种用法在现场一定会被填错。
3.3 手动自动切换与限幅的设计
手动自动切换要做无扰,核心是两件事:手动时输出直接等于手动值,同时偏差历史照常更新。这样切回自动的瞬间,e(k-1)、e(k-2) 是最新的,算出来的增量反映的是当前的真实趋势,输出从手动值平滑接上去,不会跳。
限幅分两层。第一层在功能块内部,算完增量后先加到一个临时变量上,再和上下限比较,超了就钳住,然后把钳位后的值写回输出。第二层在下游的执行机构块,比如阀门输出块自己有量程换算,那里通常不需要再限一次。两层都限、而且限的值不一样,是现场很常见的翻车点,后面单独说。
抗饱和在增量式里不需要单独写逻辑,但有一个细节要处理:输出被钳位的那一拍,偏差历史仍然要更新。如果不更新,下一拍算增量时用的还是钳位前的旧偏差,微分项会算出一个虚假的跳变。这一点在代码里体现为"限幅判断放在历史更新之前,但历史更新不因限幅而跳过"。
4. SCL核心实现:系数预计算、限幅与无扰切换的代码落地
4.1 功能块声明与静态变量
用SCL写功能块,接口按上一章的表来。静态变量里除了偏差历史和三个系数,再加一个限幅标志位,方便在线监控时看回路是不是长期顶在限幅上。
FUNCTION_BLOCK "FB_PID_Incr" { S7_Optimized_Access := 'TRUE' } VAR_INPUT Setpoint : Real; // 设定值 ActualValue : Real; // 过程实际值 Kp : Real; // 比例增益 Ti : Real; // 积分时间(s),0=关闭积分 Td : Real; // 微分时间(s),0=关闭微分 Ts : Real; // 采样周期(s),必须与OB30周期一致 OutMax : Real; // 输出上限 OutMin : Real; // 输出下限 Manual : Bool; // TRUE=手动 ManualValue : Real; // 手动输出值 Reset : Bool; // 复位,清历史并输出0 END_VAR VAR_IN_OUT Output : Real; // 累计输出,需设为保持性 END_VAR VAR err : Real; // 当前偏差 e(k) err1 : Real; // 上一拍偏差 e(k-1) err2 : Real; // 上上拍偏差 e(k-2) coefA : Real; // e(k) 系数 coefB : Real; // e(k-1) 系数 coefC : Real; // e(k-2) 系数 delta : Real; // 本拍增量 outTmp : Real; // 限幅前的临时输出 limited : Bool; // 限幅标志,供监控 END_VAR声明里有两个地方值得说。Ts 放输入而不是写死常量,是为了让同一个FB能用在100ms和200ms两种中断里,换周期只改调用处的实参。Output 放 VAR_IN_OUT 而不是 VAR_OUTPUT,是为了让外部直接把工艺DB里的变量接进来读写,避免每个周期多一次拷贝,也避免忘记把输出写回保持区。
4.2 系数预计算与三乘加递推
系数在每个扫描周期重算一次。有人觉得参数不变就不用重算,加个"参数变化才重算"的判断,结果现场改参数时忘了触发,系数还是旧的,这类问题查起来很费时间。每个周期算三个乘法,对S7-1200来说负担可以忽略,不值得为这点性能埋一个坑。
// 复位优先,清空全部历史 IF #Reset THEN #err := 0.0; #err1 := 0.0; #err2 := 0.0; #Output := 0.0; #limited := FALSE; RETURN; END_IF; // 系数预计算:Ti=0 或 Td=0 时走无积分/无微分分支 #coefC := #Kp * #Td / #Ts; #coefB := -#Kp * (1.0 + 2.0 * #Td / #Ts); IF #Ti > 0.0 THEN #coefA := #Kp * (1.0 + #Ts / #Ti + #Td / #Ts); ELSE #coefA := #Kp * (1.0 + #Td / #Ts); END_IF; // 当前偏差 #err := #Setpoint - #ActualValue; // 增量 = A*e(k) + B*e(k-1) + C*e(k-2) #delta := #coefA * #err + #coefB * #err1 + #coefC * #err2;参数说明:coefA 对应比例加积分加微分的合并项,coefB 和 coefC 是微分展开后的负项和延迟项。Ti 为0时把积分项从 coefA 里去掉,等价于纯PD;Td 为0时 coefC 为0,coefB 退化成 -Kp,等价于PI。这两个分支让一个FB覆盖P、PI、PD、PID四种组合,课程设计里要演示不同控制规律,改两个参数就能切换。
4.3 手动切换、限幅与历史更新
手动自动的处理顺序是:先算偏差和增量,再判断手动标志。手动时输出直接取手动值,偏差历史照常右移;自动时输出累加增量,然后限幅。
IF #Manual THEN // 手动:输出跟随手动值,偏差历史照常更新,保证切换无扰 #Output := #ManualValue; #limited := FALSE; ELSE // 自动:累加增量后限幅 #outTmp := #Output + #delta; IF #outTmp > #OutMax THEN #outTmp := #OutMax; #limited := TRUE; ELSIF #outTmp < #OutMin THEN #outTmp := #OutMin; #limited := TRUE; ELSE #limited := FALSE; END_IF; #Output := #outTmp; END_IF; // 历史右移,无论手动自动、无论是否限幅都要执行 #err2 := #err1; #err1 := #err;历史右移放在最后、放在条件分支外面,是这段代码的关键。手动时如果不更新历史,切回自动的第一拍 e(k-1) 还是切换前的旧值,增量会算出一个尖峰;限幅时如果不更新历史,退出限幅的第一拍微分项会跳。这两个细节在梯形图里用MOVE指令实现时很容易漏,SCL写在一处反而不容易出错。
提示:Output 接的工艺DB变量一定要在数据块属性里勾选"保持性",否则掉电后输出归零,增量式就退化成位置式的上电冲击,白折腾一场。
4.4 在OB30里调用与参数整定
功能块写好后在OB30里调用,每个回路一个背景DB。调用时把工艺DB里的变量接到实参上,Ts 填0.1,和OB30的100ms周期对齐。
// OB30 循环中断,周期100ms "FB_PID_Incr_DB_Temp"( Setpoint := "DB_Process".Temp.Setpoint, ActualValue := "DB_Process".Temp.ActualValue, Kp := "DB_Process".Temp.Kp, Ti := "DB_Process".Temp.Ti, Td := "DB_Process".Temp.Td, Ts := 0.1, OutMax := 100.0, OutMin := 0.0, Manual := "DB_Process".Temp.Manual, ManualValue := "DB_Process".Temp.ManualValue, Reset := "DB_Process".Temp.Reset, Output := "DB_Process".Temp.Output);整定顺序我一般按这个来:先把 Ti 和 Td 设成0,只留 Kp,从小往大加,加到输出开始等幅振荡,记下这个临界增益 Ku 和振荡周期 Tu;然后按齐格勒-尼科尔斯经验公式给一组初值,Kp=0.6·Ku,Ti=0.5·Tu,Td=0.125·Tu;最后在阶跃响应上看超调,超调超过20%就把 Kp 往下压10%到20%,Ti 往上加。这套方法不保证最优,但能给出一组不发散的起点,比盲目试凑快得多。
增量式的 Kp 和位置式的 Kp 在数值上不完全等价,从位置式迁移过来时不能直接抄参数,通常要把 Kp 调小一些,因为增量式的输出是累加的,同样的 Kp 等效增益更高。这一点在替换老设备的老程序时特别容易翻车。
5. 避坑与排查:增量式PID现场最容易翻车的五个点
5.1 采样周期和OB实际周期对不上
现象:回路怎么调都调不好,超调大,积分时间调长调短都别扭,示波器抓输出能看到周期性的小台阶。
原因:功能块的 Ts 填了0.1,但OB30的周期在硬件组态里被改成了200ms,或者OB30里还塞了别的耗时逻辑,实际执行间隔变成150ms以上。公式里的 Ti/Ts 和 Td/Ts 全部按错误的周期换算,标称的积分时间实际缩水一半。
解决:OB30里只放PID调用,不要在同一个中断里做通信、文件读写这类耗时操作。Ts 不要手填,在OB30里用#Ts := 0.1这种常量赋值,改周期时只改一处。调试阶段可以在OB30里加一个计数器,在线看每秒计数值,确认实际节拍。
5.2 双重限幅把积分顶死
现象:温度长时间停在设定值以下,阀门已经100%开度,输出值显示100,但偏差一直不收敛,等温度上来后又迟迟不回落。
原因:功能块内部限幅到100,下游的阀门输出块又限了一次,而且下游的限幅值可能是95。功能块以为输出到100了,实际上阀门只开到95,剩下5%的缺口让偏差一直存在,增量持续为正,输出一直顶在100,退不下来。
解决:限幅只在一个地方做。要么功能块内部限,下游直接用量程换算;要么功能块不限,下游限,功能块通过一个"实际输出反馈"输入知道被限了,停止累加。我一般选第一种,功能块内限幅,下游只做单位换算,不做数值钳位。
5.3 输出变量用了INT或没有保持性
现象:小信号时输出跳变,阀门动作一格一格的;或者每次断电重启,阀门先关到零再慢慢开。
原因:输出变量定义成INT,增量是Real,赋值时被截断,0.3的增量变成0,累计几拍才动一格。或者工艺DB没勾保持性,掉电后Output归零,上电从零累加。
解决:输出、增量、偏差全部用Real,中间不要经过任何整型转换,直到最后送模拟量输出通道时才做一次线性换算。工艺DB里Output所在的结构体勾选保持性,下载前确认一次,这个属性在块接口里看不到,只在DB属性里。
5.4 手动切自动有扰动
现象:操作工手动把阀门调到60%,切自动的瞬间阀门跳到70%再回来,或者切过去以后要晃好几拍才稳。
原因:手动期间偏差历史没有更新,切自动第一拍用的 e(k-1)、e(k-2) 是切换前的旧值,增量算出来偏大。或者手动时输出和偏差历史都对,但切换瞬间设定值和实际值差得远,增量本身就有跳变。
解决:按4.3的写法,手动时历史照常右移。如果切换瞬间偏差确实大,加一个切换后的前两拍增量减半的软过渡,或者让操作工先把设定值拉到接近实际值再切。前者是代码里解决,后者是操作规程里解决,两条都写上更稳。
5.5 微分项放大噪声导致输出抖动
现象:Td 一加上去,输出就开始高频抖动,阀门嗡嗡响,实际值本身波动不大但输出抖得厉害。
原因:实际值来自模拟量输入,本身有几位跳动,微分项对偏差做差分,噪声被放大 Td/Ts 倍。Ts 越小放大倍数越大,100ms周期下 Td=10s 时放大100倍,几个LSB的噪声就变成满量程抖动。
解决:先给实际值加一阶滤波,滤波时间取 Td 的1/5到1/10;或者改用微分先行,只对实际值做微分,设定值突变时不产生微分冲击。工程上还有一个简单办法,Td 设成0,多数温度、压力回路纯PI就够用,微分留给有明确滞后补偿需求的回路。不要为了"看起来完整"硬加微分。
6. 验收与答辩:用阶跃响应和跟踪测试证明回路能用
6.1 四组测试,把功能块的功能点全覆盖
验收不能只看"能跑起来",要按功能点逐条测。我做这类功能块一般准备四组测试,每组都有明确的通过标准,测完把曲线截图存进报告,答辩时直接放。
| 测试项 | 操作方法 | 通过标准 |
|---|---|---|
| 阶跃响应 | 设定值从50跳到60,记录输出和实际值 | 超调≤20%,稳态误差≤1%,2倍积分时间内稳定 |
| 手动自动切换 | 手动输出60%稳定后切自动,再切回手动 | 切换瞬间输出变化≤2%,无持续振荡 |
| 限幅与退饱和 | 设定值设到明显超出能力范围,保持2分钟后恢复 | 输出稳定在限幅值,恢复后30秒内退出饱和 |
| 掉电重启 | 运行中断电,重新上电 | 输出从保持值继续,无跳变,实际值无冲击 |
阶跃响应那条,重点是看三个量:上升时间、超调量、稳态误差。上升时间反映 Kp 够不够,超调反映 Kp 和 Ti 的配比,稳态误差反映积分有没有效。三条曲线放在一张图里,答辩时一眼就能说清楚每个参数在起什么作用。
手动自动切换那条,示波器或者趋势图上要能看到切换点的输出是平的。如果有一个明显的台阶,回去查5.4。
6.2 答辩时最容易被问的三个问题
第一个问题通常是"为什么用增量式不用位置式"。回答不要背优缺点,讲你自己项目的场景:执行机构是电动调节阀,带积分环节,上电希望无冲击,抗饱和希望简单,所以选增量式。如果评委追问位置式行不行,就说位置式也能做,但要额外写抗饱和和上电跟踪,代码量更大,课程设计里不容易讲清楚每一行。
第二个问题是"采样周期怎么定的"。不要说"经验值",要给出依据:被控对象的时间常数大约多少,按每个时间常数采样6到10次的原则,取100ms。再把OB30的组态截图放出来,证明 Ts 和实际周期一致。
第三个问题是"参数怎么整定的"。按4.4的顺序讲一遍,重点讲临界增益那一步是怎么测的,超调大了往哪个方向调。评委想听的是你有没有一套可复现的流程,而不是一组碰巧能用的数字。
6.3 一个我踩过的坑,留给你当后悔药
第一次把增量式用到现场,调试一切正常,运行两周后操作工反映"偶尔阀门会自己动一下"。查了三天,最后发现是工艺DB里Output所在的数组在某个不相关的写入操作里被越界写了一次,增量式的输出是累加的,被写坏一次就再也回不到正确的值。位置式每次重算,被写坏一次下一拍就恢复了,增量式没有这个自愈能力。
从那以后我每次做增量式,都强制在功能块里加一个输出合理性检查:输出超出上下限一定范围就报警并强制回到限幅值,同时在工艺DB里把Output单独放一个结构体,前后各留一个保留字,越界写会先撞到保留字,在线监控一眼能看出来。这个习惯后来救过两次,一次是通信写入越界,一次是HMI脚本写错地址。增量式好用,但它把状态存在输出里,状态的完整性要自己守住。希望帮到你。
本文还有配套的精品资源,点击获取