资讯动态

S7-1200/1500增量式PID的SCL功能块实现与整定避坑

发布时间:2026/10/26 10:29:11 来源:尧图企业网站定制
简介面向自动化控制方向工程师与PLC编程学习者的西门子增量式PID程序实例可在S7-1200与S7-1500系列PLC上直接使用并已在真实项目中验证运行。程序以博图TIA Portal工程形式组织V15.1及以上版本均可打开核心逻辑位于SCL源码与背景数据块中配套触摸屏、符号表等工程文件注释完整、无密码保护便于对照理解比例、积分、微分三项参数的整定过程以及增量式算法对当前误差与前次误差差值的处理思路。压缩包共49个文件以工程归档与配置类文件cfs、xml、del、cfg等、图标与索引类资源、scl源码及db数据块为主整体约1.26MB属于轻量级、易移植的工程包。目前已有752人学习下载适合希望从理论过渡到现场调试、需要一份可直接改用的PID模板的读者参考。1. 从一个“阀门嗡嗡响”的现场说起增量式 PID 在 S7-1200/1500 上到底解决什么问题去年冬天配合调试一个换热站循环泵的调节阀在目标温度附近来回蹭一小时能把执行器磨出响声。翻程序发现用的是位置式 PID每个扫描周期重新计算一次绝对开度手动切自动那一下直接给到 80%积分项还在后台悄悄累积等温度回落时想收都收不回来。后来换成增量式只算“这一步该比上一步多加多少”阀门立刻稳住了。这份程序就是干这个的一套用 SCL 写的增量式 PID 功能块S7-1200 和 S7-1500 通用已经在实际项目里跑过温度、压力、流量几个回路。它适合手上已经有 PLC、被现场抖动和超调折腾过的自控工程师也适合刚学 SCL 想找个能直接改的模板的新手。2. 程序骨架与选型为什么增量式更适合 1200/1500 的循环中断2.1 位置式与增量式的差别落在代码上是什么样位置式的输出公式是 u(k) Kp·e(k) Kp·Ts/Ti·Σe Kp·Td/Ts·[e(k)-e(k-1)]每个周期都要把历史偏差全加一遍再乘系数输出是一个绝对值。这种做法的问题不在于算不出来而在于“记忆”藏在积分累加里一旦输出被限幅、被手动干预累加值就和实际执行机构脱节了切回自动那一刻必然有一脚冲击。增量式算的是 Δu(k) u(k) - u(k-1)展开后偏差的历史项会被差分掉只留下最近两三次的偏差输出靠 u(k) u(k-1) Δu(k) 一步步累加。这个结构带来三个现场特别受用的性质执行机构本身就是一个积分器阀门开度、变频器频率增量式与它天然匹配限幅只需要卡住累加结果抗饱和几乎不用额外写逻辑手动切自动时只要把 u(k-1) 追成当前实际输出就是无扰切换。代价也有——它没有“绝对位置”的概念一旦累加变量被清零或者断电没保持住输出会从 0 爬起这一点后面避坑章节会专门说。2.2 FB 接口清单输入、输出、静态变量怎么划接口设计决定了这个块能不能被第二个项目直接拿走用。我一般把接口分成三类输入是设定值、过程值、手自动开关、手动输出值和五个整定参数输出是实际输出、当前偏差、三项分量的中间值方便在线看是哪一项在捣乱、饱和标志静态变量只放真正需要跨周期记忆的东西——上一次和上上次的偏差、上两次的 PV、上一次的输出、首次扫描标志、上一次的手自动状态。参数单位必须一次性定死否则后面谁都看不懂。这套程序约定Kp 无量纲Ti 是积分时间单位为秒Td 是微分时间单位为秒Ts 是采样周期单位为秒OutMax/OutMin 与 SP、PV 同一工程量纲一般是 0~100 的百分比DBW 是死区宽度。习惯用积分增益 Ki单位 1/s的人把 Ti 换成 1/Ki 就行但一个项目里只能选一种混着用必翻车。接口类型单位说明SP / PVReal 输入工程量设定值与反馈值必须同量纲AutoBool 输入-1 自动0 手动ManualOutReal 输入%手动模式下直接给到输出的值Kp / Ti / TdReal 输入- / s / s整定参数Ti 填 0 视为关积分TsReal 输入s必须与调用它的 OB 周期一致OutMax / OutMinReal 输入%输出上下限对应执行机构量程DBWReal 输入工程量死区宽度抗小信号抖动OutReal 输出%最终输出接标定环节P_Term / I_Term / D_TermReal 输出%三项分量调试时看这个SatBool 输出-输出到达限幅时置位2.3 执行载体OB30 循环中断与采样周期对齐PID 必须跑在固定周期里这是硬要求。放在 OB1 里调用扫描周期随程序量变化Ts 就变成一个随时在变的谎言积分和微分的量纲全乱。S7-1200/1500 上标准做法是开一个 OB30 循环中断周期设 100 ms把 FB 的调用放进去Ts 填 0.1两者严格对应。如果回路响应很快比如压力闭环100 ms 可能太慢可以把 OB30 改成 20 ms 或 50 ms如果回路是温度这种大惯性200 ms 甚至 500 ms 都够用还能省 CPU。改周期的时候记得同步改 Ts这两个数不一致是后面排查章节里最常见的一条。// OB30 循环中断硬件组态里把循环时间设为 100 ms // Ts 必须与这里的 100 ms 一致改一个就要改另一个 FB_PID_Inc_DB_1( Run : Tag_PID_Run, Auto : Tag_PID_Auto, SP : Tag_SP_Temp, PV : Tag_PV_Temp, ManualOut : Tag_Manual_Out, Kp : 2.5, Ti : 45.0, // 积分时间 45 s Td : 6.0, // 微分时间 6 s Ts : 0.1, // 与 OB30 周期严格对齐 OutMax : 100.0, OutMin : 0.0, DBW : 0.3);这段是调用示例参数值来自一个温度回路只能当起点用不能照抄。Run 用来做联锁工艺没准备好时置 0块内部会把输出压到 OutMin 并复位记忆量Auto 接操作面板上的手自动旋钮DBW 填 0.3 意思是偏差在 ±0.3 摄氏度以内就不动作这个值要大于传感器的抖动幅度、小于工艺允许的稳态误差取太小等于没加取太大稳态精度不够。多数情况下一个 OB30 里可以放多个回路的多重背景调用互不干扰但每个回路的 Ts 必须和自己的实际调用周期一致——如果几个回路被放在同一个 OB30 的不同网络里周期是一样的那 Ts 也就一样这点不用纠结。3. 核心代码拆解一个扫描周期里 PID 到底算了什么3.1 偏差、死区与工程量归一化偏差计算看着简单坑都在量纲上。SP 和 PV 必须来自同一个标定链路如果 SP 是面板上设的 0~100%PV 是从模拟量通道读回来又 SCALE_X 成 0~100% 的那就直接相减如果 PV 还是原始的 0~27648减出来的偏差会大到让 Kp 完全失去意义。这套程序默认两者都是工程量百分比标定环节放在 FB 外面做FB 只负责算这样块本身和硬件解耦换通道不用动 PID。死区放在偏差算完之后、三项计算之前。写法很简单但位置很重要如果放在输出之后做死区积分项照样在累加只是最后被砍掉输出会在死区边界上“蓄力”一出门就窜出去。// 偏差SP 与 PV 必须同量纲标定在 FB 外部完成 #e_k : #SP - #PV; // 死区偏差绝对值小于 DBW 时按 0 处理 // 目的不是提高精度而是让执行机构在稳态时彻底停下来 IF ABS(#e_k) #DBW THEN #e_k : 0.0; END_IF;这两行之后#e_k 就是本周期真正参与运算的偏差。注意死区里把偏差直接置 0 而不是置成 ±DBW是为了让积分项在死区内完全停止累加——增量式的积分项正比于 e(k)e(k)0 就等于这一步不加稳态不会漂。3.2 增量公式落地P、I、D 三项分别怎么写增量式的三项各自对应“偏差变化了多少”“偏差累积了多少”“偏差变化得快不快”。P 项是 Kp 乘以本次偏差与上次偏差之差I 项是 Kp·Ts/Ti 乘以本次偏差D 项这里做了微分先行只对 PV 求二阶差分不对偏差求原因是设定值被人为改动时比如面板上从 60 改成 65偏差会瞬间跳变标准微分形式会算出一个巨大的尖峰打到阀门上微分先行把这一棍子躲掉了。// P 项偏差的变化量反映误差正在扩大还是收敛 #P_Term : #Kp * (#e_k - #e_k1); // I 项Ti 单位为秒Ti 填 0 或负数视为关闭积分 IF #Ti 0.001 THEN #I_Term : #Kp * #Ts / #Ti * #e_k; ELSE #I_Term : 0.0; END_IF; // D 项微分先行只对 PV 做二阶差分 // 等价于 -Kp*Td/Ts * [PV(k) - 2*PV(k-1) PV(k-2)] #D_Term : (#Kp * #Td / #Ts) * (2.0 * #pv_k1 - #pv_k - #pv_k2); // 本周期应该加多少 #DeltaU : #P_Term #I_Term #D_Term;参数含义再明确一遍Kp 越大响应越快、越容易振荡Ti 越大积分越弱、消除静差越慢Ti 填 0 退化成纯比例Td 越大对变化越敏感但 PV 上有噪声时会把噪声放大成输出抖动这时候要么减小 Td要么在 PV 进 FB 之前加一个一阶低通滤波常见做法是 PV_f PV_f 0.2·(PV - PV_f)系数 0.2 对应大约 5 个周期的平滑。还有一个容易忽略的细节D 项用到的 #pv_k2 是上上次的 PV第一次扫描时它还是 0直接代入会算出一个巨大的负数增量。所以首次扫描必须把三个历史量全部初始化成当前 PV这一条放在 3.4 节里做。3.3 输出限幅与抗积分饱和增量式在抗饱和这件事上占了大便宜。因为输出是累加的只要每次把限幅之后的值回写进 u(k-1)累加器本身就被钉死在 [OutMin, OutMax] 之间不会像位置式那样在后台攒出一个天文数字。这就是我说的“限幅即抗饱和”——不需要额外的积分分离逻辑也不需要单独判断饱和方向去扣积分。// 累加本周期增量 #u_raw : #u_k1 #DeltaU; // 限幅并把限幅后的值回写天然抗积分饱和 IF #u_raw #OutMax THEN #Out : #OutMax; #Sat : TRUE; ELSIF #u_raw #OutMin THEN #Out : #OutMin; #Sat : TRUE; ELSE #Out : #u_raw; #Sat : FALSE; END_IF; #u_k1 : #Out; // 关键回写的是限幅后的值#u_raw 是未限幅的原始累加结果#Sat 输出给上位机做报警用——如果 Sat 长时间为 TRUE说明要么参数太激进要么执行机构选小了这时候该调的是工艺和选型不是继续加大 Kp。#u_k1 回写限幅值这一行是整个抗饱和的核心删掉它块在高负荷工况下就会表现出手动一切回自动就冲顶的经典症状。如果工艺上要求输出变化率也不能太快比如防止阀门电机过载可以在限幅之后再加一层速率限制|Out - u_k1| 超过每周期允许的最大变化量时按最大变化量走。这一层不是必须的但做阀门和变频器的时候我一般会加。3.4 手动/自动无扰切换与首次扫描初始化无扰切换是这套程序最实用的地方。逻辑只有一句检测到 Auto 从 0 变 1 的上升沿时把 u_k1 赋值成当前的 ManualOut同时把三个历史量刷成当前值。这样切回自动的第一个周期Δu 只反映当前工况不会因为历史残留而跳变。手动模式下的处理也要顺手做掉Out 直接等于 ManualOutu_k1 同步跟踪 ManualOut。很多人只做了前半句结果手动期间 u_k1 还停在切手动之前的值一切回自动就回到旧开度这就是典型的翻车现场。// 首次扫描把历史量全部拉平到当前值 IF #FirstScan THEN #e_k1 : #SP - #PV; #pv_k1 : #PV; #pv_k2 : #PV; #u_k1 : #ManualOut; #FirstScan : FALSE; END_IF; // 手自动上升沿无扰切换 IF #Auto AND NOT #LastAuto THEN #u_k1 : #ManualOut; // 以当前实际输出为累加起点 #e_k1 : #SP - #PV; #pv_k1 : #PV; #pv_k2 : #PV; END_IF; #LastAuto : #Auto; // 手动模式输出跟随手动值并同步累加器 IF NOT #Auto THEN #Out : #ManualOut; #u_k1 : #ManualOut; #Sat : FALSE; RETURN; // 手动时不执行后面的三项计算 END_IF; // ---- 以下为自动模式的偏差、三项、限幅计算 ---- // ---- 最后统一更新历史量 ---- #e_k1 : #e_k; #pv_k2 : #pv_k1; #pv_k1 : #PV;这段里有几个顺序问题必须说清楚。RETURN 放在手动分支末尾是为了跳过三项计算否则手动期间 P 项和 I 项还在算虽然输出被覆盖看不出来但历史量被污染了切回自动的瞬间会冒出来。历史量更新必须放在最后用本周期算出来的 #e_k 和 #PV 去覆盖顺序反了就等于每个周期都在拿旧值算。FirstScan 标志声明成静态变量并给初值 TRUE块实例第一次执行时进一次初始化如果还希望在 PLC 冷启动后重新初始化比如断电重启后执行机构已经归位可以把这段逻辑挪到 OB100 启动组织块里或者干脆把 u_k1 设成保持性让它在断电后接着上次的位置走——两种做法选一种取决于工艺允不允许设备自己动。4. 从下载到跑起来在 TIA Portal 里建块、调用与整定4.1 建 FB、配背景 DB 与保持性设置新建一个函数块语言选 SCL编号随意名字叫 FB_PID_Inc。把 2.2 节的接口按 VAR_INPUT / VAR_OUTPUT / VAR 三段填进去注意 VAR 段里只放跨周期变量临时变量Temp里放 u_raw、e_k、DeltaU 这些用完就丢的中间量。块属性里勾上“优化的块访问”S7-1200 固件 4.0 以上和 S7-1500 都支持访问速度快也不用记绝对地址。调用方式有两种单回路就用一个独立的背景 DB在 OB30 里直接拖块、确认生成 DB多回路就用多重背景把它塞进一个 FB 的静态变量里在一个 OB30 网络里循环调用。前者上手快后者适合做模板批量复制第 6 章会展开讲。保持性这块要分清楚如果工艺要求断电后输出保持在原位就把背景 DB 里的 u_k1、e_k1、pv_k1、pv_k2 勾上“保持”S7-1500 在实例 DB 的变量表里可以直接设S7-1200 也支持对实例 DB 的变量设置保持性如果不希望设备自己动就别勾靠 FirstScan 或 OB100 重新初始化。两种都合理但一定要主动选默认状态是最容易出事的。下载之后先在 OB1 里把 Auto 置 0、ManualOut 给一个中间值观察 Out 是不是等于 ManualOut再置 1观察 Out 有没有跳变。这一步花不了五分钟能省掉后面在现场返工的两小时。4.2 输出标定Real 到模拟量通道的换算FB 输出的 Out 是 0~100 的百分比真正写进模拟量输出通道还得换成 0~27648。这一步看着机械但精度问题全在这里。// 百分比 - 模拟量原始值 // 先用 Real 算最后一步再四舍五入转 Int #TempReal : FB_PID_Inc_DB_1.Out * 27648.0 / 100.0; #TempInt : REAL_TO_INT(ROUND(#TempReal)); // 通道地址按实际硬件组态填这里只是示例 QW64 : #TempInt;关键是 ROUND 这一下。很多人图省事用 TRUNC 直接截断0.9% 的输出会变成 0整条曲线整体偏低接近 1%而且因为是单向截断误差不会互相抵消最后稳态静差怎么调都消不掉还以为是积分参数的问题。用 ROUND 之后误差在正负半个 LSB 之间摆动肉眼看不出来。如果项目里习惯用 NORM_X / SCALE_X 这对指令也可以写 SCALE_X(OUT #TempInt, MIN 0, MAX 27648, VALUE Out)效果等价可读性更好。但要注意 NORM_X 的 VALUE 类型和 MIN/MAX 类型要匹配S7-1200 上混用 Int 和 Real 会在编译期报类型不兼容得先把 Out 显式转成 Real 之外的中间量这一点新手很容易卡住。写 QW 之前别忘了做范围钳位虽然 FB 内部已经限幅了但万一有人手动改了 DB 里的 Out 值写出去的超范围数据会让输出模块报错。4.3 用 Trace 做阶跃测试和参数整定参数整定不要靠猜用 Trace 记录 SP、PV、Out 三条曲线一次阶跃看三件事上升时间、超调量、是否有稳态误差。测试前把执行机构切到手动让 PV 稳定在工况中点然后切自动、把 SP 阶跃 5%~10%不要太大会撞限幅Trace 采样周期设成和 OB30 一样录 10 分钟。整定顺序我习惯这样走先把 Ti 设成一个很大的值相当于关掉积分、Td 设 0只剩纯比例。慢慢加 Kp加到 PV 出现等幅振荡记下这时的 Kp 为 Ku振荡周期为 Tu。按临界比例度法取初值Kp 0.6·KuTi 0.5·TuTd 0.125·Tu。这套经验公式给的是一个偏保守的起点实际还要微调。如果超调大、回落慢先加大 Ti减弱积分再看如果稳态始终差一点减小 Ti。最后加 Td从 0.1·Ti 开始加到能压住超调但不放大噪声为止。温度回路 Td 可以给大一点流量和压力回路通常给很小甚至给 0。每次只改一个参数改完重跑一次阶跃这是规矩。同时改三个参数然后盯着曲线猜是哪一个起的作用属于玄学调试出来的结果也没法复现到别的回路上。5. 避坑与排查五个在现场真踩过的坑现象一输出一直在 0 和 100 之间来回打像是开关量控制。原因是 Ts 填错了。有人把 OB30 周期改成 20 ms 提响应Ts 还留着 0.1程序以为每个周期过了 100 ms积分项被放大了 5 倍D 项被缩小了 5 倍整个块的表现就变得又快又猛。解决方式是把 Ts 和硬件组态里的循环中断周期严格对齐或者更保险一点在 OB30 里用 RUNTIME 指令测出上一次到这一次的实际间隔直接喂给 Ts这样即使有人改了组态也不会失配。现象二手动切自动的一瞬间阀门猛冲到顶再慢慢回来。原因是没有做无扰切换u_k1 还停在上次自动时的值而这段时间执行机构已经被手动操作到别的位置。解决方式是加 Auto 上升沿检测把 u_k1 和三个历史量全部刷成当前值见 3.4 节同时手动分支里让 u_k1 跟踪 ManualOut。这两处少一处都不行。现象三改了 Ki或 Ti参数输出毫无反应。大概率是单位理解错了。仪表手册里的“积分时间”单位是秒有些 DCS 里的“积分增益”单位是 1/s两者互为倒数。如果按 1/s 的习惯往 Ti 里填了 0.5等于积分时间半秒积分项瞬间变成主导输出会顶到限幅反过来把 Ti 当成增益填了个 200等于几乎关了积分当然没反应。解决办法是在接口注释里把单位写死块里加一个 Ti 的上下限判断超范围直接按关闭积分处理别让它静默地跑偏。现象四输出写下去变频器频率和画面显示对不上差 1% 左右。这是 REAL_TO_INT 用了截断而不是四舍五入属于典型的精度血泪账。改成 ROUND 就好。另外要注意模拟量输出模块的分辨率12 位模块的 27648 对应满量程实际能分辨的最小步进就是 1/27648别指望它能表现出 0.01% 的变化。现象五设备断电重启后输出从 0 开始爬工艺参数波动好几分钟。静态变量没设保持性重启后全部归零u_k1 从 0 开始累加。解决办法二选一需要保持的就把 u_k1 和三个历史量勾上保持性不需要保持的就在 OB100 里显式初始化成安全的启动值比如阀门的安全开度别让它自己从 0 猜。最怕的是两个都没做那就真成了黑匣子。6. 二次开发把单回路 FB 改成能批量复用的模板单个回路调通之后接下来多半是复制到第二个、第三个回路。直接拖块生成一堆背景 DB 的做法到第十个回路就管不住了——参数散在各个 DB 里改一次配方要挨个打开。我的做法是把整定参数抽成一个 UDT叫 “UDT_PID_Param”里面就五个 RealKp、Ti、Td、OutMax、OutMin再配一个全局的“参数配方” DB用数组存成 UDT_PID_Param 的数组索引就是回路号。// 参数配方 DB一个数组管住所有回路 // DB_PID_Param.Param[1..16] 的类型是 UDT_PID_Param FOR #i : 1 TO 16 DO FB_PID_Multi[#i]( Run : Tag_Run[#i], Auto : Tag_Auto[#i], SP : Tag_SP[#i], PV : Tag_PV[#i], ManualOut : Tag_Manual[#i], Kp : DB_PID_Param.Param[#i].Kp, Ti : DB_PID_Param.Param[#i].Ti, Td : DB_PID_Param.Param[#i].Td, Ts : 0.1, OutMax : DB_PID_Param.Param[#i].OutMax, OutMin : DB_PID_Param.Param[#i].OutMin, DBW : DB_PID_Param.Param[#i].DBW); END_FOR;这段循环调用放在 OB30 里FB_PID_Multi 是多重背景数组一次扫描跑完全部回路。配方切换就变成“把另一组参数写进数组”这么简单HMI 上做一个配方选择按钮后台调用 WSTRING 到参数的映射表就行。注意几个地方Ts 对所有回路一致因为它们共用一个 OB30数组长度要留余量别卡着实际回路数声明循环里的索引必须和 Tag 数组的下标严格对应错一位就是把 A 回路的输出写到 B 回路这种事故在现场很难第一眼发现。再往深一层可以给 FB 加一个“自整定”的简易版本在手动模式下给一个小的阶跃扰动记录 PV 的响应曲线用两点法算出过程增益和滞后时间反算出一组 Kp/Ti/Td 填进参数里。这属于锦上添花但实现起来不难关键是扰动幅度要小3% 左右而且只在设备停产的调试窗口里做别在满负荷生产时自动触发。改造完一定要做回归验证把每个回路单独切一次手动/自动看有没有跳变把参数配方来回切两遍看输出是否平滑过渡最后断电重启一次确认保持性设置和你的预期一致。这三步走完模板才敢往下一个项目上搬。我早年图省事改完参数直接在现场投运结果一个回路的微分项符号写反阀门和设定值反向跑追了半个下午才发现。从那以后我每次改完 PID 代码都会先在仿真里跑一遍阶跃把 SP、PV、Out 三条线拉出来肉眼扫一遍确认方向对、限幅有效、手自动平滑再碰真实设备。这个习惯看着慢其实是最省时间的。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑