工业PID功能块SCL源码解析:量纲、饱和与手自动切换
简介面向西门子自动化工程师及PLC编程学习者的博途PID SCL源代码聚焦在TIA Portal中用结构化控制语言实现自定义PID功能块解决反馈控制中比例、积分、微分参数配置与手动/自动切换、积分保持及初始化等实际问题。压缩包共1个doc文件约19KB内容以功能块接口与变量说明为主便于对照输入输出定义理解控制逻辑。已有4187人学习下载说明该主题在工控实践中关注度较高。读者可从中获取完整的PID功能块框架包括COM_RST、MAN_ON、P_SEL/I_SEL/D_SEL、CYCLE、SP_INT、PV_IN、GAIN、TI、TD、DEADB_W等参数设置以及LMN、LMN_PER、LMN_P/I/D、PV等输出含义并了解S7_Optimized_Access优化访问的作用结合死区、上下限和干扰变量DISV可进一步掌握参数整定与抗扰、限幅、初始化等排错思路适合作为博途SCL控制程序编写与调试的参考素材。1. 一个 PID 功能块的接口就是一份现场踩坑清单第一次读到这份 PID 的 SCL 源代码多数人会有个落差四十多个接口变量里真正跟教科书公式沾边的只有GAIN、TI、TD三个剩下全是PV_FAC、DEADB_W、TM_LAG、QLMN_HLM这类看起来跟控制算法毫无关系的东西。结论先说工业 PID 的难点从来不是公式而是公式之外的三件事——量纲、饱和、切换。教科书给的是理想连续系统下的解现场给的是字长有限、噪声满地、操作工会随手切手动的离散系统。这个块为什么值得逐行拆就在于它把那些“理想条件下不存在、现场天天发生”的问题全部固化成了接口参数和静态变量。2. 归一化与离散化为什么 PID 运算前要先走一遍量纲换算功能块声明本身信息量就很大。VAR_INPUT里既有PV_INReal工程量又有PV_PERWord外设原始字还有PV_FAC、PV_OFF一对缩放参数——三套机制同时存在说明设计者假定“调用者的数据来源不确定”。这是工程块和教学代码最典型的分野教学代码假定输入已经完美工程块假定输入永远需要收拾。FUNCTION_BLOCK PID { S7_Optimized_Access : TRUE } VERSION : 0.1 VAR_INPUT COM_RST : Bool; // 完全重启 MAN_ON : Bool; // 手动值打开 PVPE_ON : Bool; // 外设过程变量打开 P_SEL : Bool; // 比例作用打开 I_SEL : Bool; // 积分作用打开 D_SEL : Bool; // 微分作用打开 CYCLE : Time; // 采样时间 SP_INT : Real; // 内部设定值 PV_IN : Real; // 过程变量值输入 PV_PER : Word; // 外设过程变量 GAIN : Real; // 比例增益 TI : Time; // 积分时间 TD : Time; // 微分时间 TM_LAG : Time; // 微分作用延时 END_VAR上面节选的是接口骨架注意{ S7_Optimized_Access : TRUE }这一行属性优化块访问意味着块内变量不再按绝对偏移寻址而是按符号访问编译期就能做类型检查。对 PID 这种声明了几十个 Real 的块来说这直接决定了单个实例占用的装载内存大小也意味着VAR_IN_OUT的ER必须真实接线不能靠偏移量“蒙混”过去。2.1 外设字到工程量WORD→INT→REAL 为什么要分两步模拟量输入通道送上来的是一个 16 位字常见约定是 0 到 27648 对应 0% 到 100%双极性时是 -27648 到 27648 对应 -100% 到 100%。这个数字是位模式没有符号语义直接转 Real 会把负值当成很大的正数回路立刻反向。所以转换必须走两级// 第一级把位模式按有符号 16 位解释 // WORD_TO_INT 只改解释方式不改变位负值在这里被正确还原 // 第二级整数域除到工程百分比全程 Real避免整数除法截断 IF PVPE_ON THEN Istwert : INT_TO_REAL(WORD_TO_INT(PV_PER)) * 100.0 / 27648.0; ELSE Istwert : PV_IN; // 调用者已换算好工程量直接透传 END_IF; // 工程量修正增益 偏置 // 例变送器量程 -20~80 ℃PV_FAC 100.0 / 27648.0PV_OFF -20.0 PV : Istwert * PV_FAC PV_OFF; // 偏差同时经 VAR_IN_OUT 送出供趋势、报警、上位机复用 ER : SP_INT - PV;WORD_TO_INT和INT_TO_REAL这一步不能省。前者的语义是“同一组位换一种解释”后者才是数值搬运。很多现场遇到的“温度显示 6553 度”就是省掉第一级的结果。PV_FAC与PV_OFF的价值在于把量程换算从“每个调用点手写一遍”变成“块内统一处理一次”十个回路共用一套标定逻辑改量程时只改实例参数不动程序。注意PVPE_ON为真时PV_IN会被彻底忽略。调试时盯着PV_IN的数值找不到问题先确认这个开关的状态。2.2 CYCLE 决定了 TI 和 TD 的物理意义连续域的积分是 ∫e·dt离散后变成 Σe·Δt。这个 Δt 就是CYCLE。它如果传错TI就不再是“积分时间多少秒”而变成一个没有物理含义的系数——你按经验设的 30 秒实际可能是 3 秒或 300 秒。这是自动化调试里最容易被忽略、也最容易把参数整成玄学的一条。// Time 类型底层是毫秒整数除以 1000 得到秒后续所有增量按秒计 IF CYCLE T#0ms THEN rCycle : TIME_TO_REAL(CYCLE) / 1000.0; ELSE rCycle : 0.1; // 兜底未接线时按 100 ms 处理防止除零和积分爆炸 END_IF; // 积分时间同样转成秒与 rCycle 保持同一量纲 rTi : TIME_TO_REAL(TI) / 1000.0; rTd : TIME_TO_REAL(TD) / 1000.0;CYCLE应该传什么如果块被放在循环中断 OB 里调用就传那个 OB 的固定循环时间如果放在主循环里就得传一个足够保守的估计值。有些实现会让CYCLE 0时自动取当前循环时间但这份块没有这么做——判断分支里用兜底值行为可预测代价是调用者必须知道自己在传什么。这是明确的设计取舍宁可让你配错也不让块偷偷帮你猜。量纲统一之后TI才算真正回到“秒”这个单位上。之后调整参数才有可复现性改采样周期不用重调参数因为积分增量里已经乘了rCycle采样变快积分步长自动变小。3. 三个分量各自的坑P 的噪声、I 的饱和、D 的抖动PID 三个分量教科书上地位平等现场地位完全不等。比例项最稳问题只在整定积分项最容易出事因为它会“记住”历史微分项最危险因为它放大噪声而现场的信号从来没有干净的。这份块的变量表里Iant、sLanteilAlt、sRestint、RueckDiff、sRueck全是围绕积分饱和准备的TM_LAG和rTmLag则是给微分准备的减震器。3.1 比例分量与死区DEADB_W 为什么作用在偏差上而不是输出上比例项本身只有一行GAIN * ER难点在死区。执行机构阀门、变频器有回差测量有噪声如果偏差在零附近来回抖输出就会持续小幅变化阀门频繁动作寿命和工艺都受不了。死区的做法是把“偏差绝对值小于DEADB_W”视为零偏差// 死区处理偏差落在带宽内时视为 0输出保持上一周期的结果 IF ABS(ER) DEADB_W THEN ErKP : 0.0; // 比例分量归零避免在死区内继续推输出 ErTot : 0.0; // 送进积分和微分的偏差也一并归零 ELSE ErKP : GAIN * ER; ErTot : ER; END_IF;注意死区处理的位置作用在偏差ER上而不是作用在最终输出LMN上。如果作用在输出上输出被削掉一小段积分项会继续累积这段差值等偏差真正超出死区时累积量会一次性释放形成台阶。作用在偏差上积分器看到的就是零不会累积。这个差别很小但决定了死区到底是在“消抖”还是在“制造滞后”。DEADB_W的量纲跟偏差一致。SP_INT和PV都在工程量域死区就必须按工程量给比如温度回路给 0.5压力回路给 0.02。给成“百分数”或者“原始字”都会导致死区范围与实际需求差几个数量级。3.2 积分分量与抗饱和sRueck、RueckDiff 这批变量的真实用途积分项的离散形式是增量累加// 积分增量GAIN * (rCycle / rTi) * 偏差 // rCycle/rTi 就是离散化后的积分步长比例量纲为 1 IF I_SEL AND NOT INT_HOLD THEN Iant : GAIN * rCycle / rTi * ErTot; Ianteil : Ianteil Iant; END_IF; // 积分初始化只在 I_ITL_ON 为真的那个周期生效 IF I_ITL_ON THEN Ianteil : GAIN * I_ITL_VAL; // 以输出量纲预置积分器不是以偏差量纲 END_IF;问题出在“累加会一直加”。如果输出已经顶到LMN_HLM而偏差仍然为正积分项会继续往上累累到远超上限的位置。等偏差反向积分项要先“消化”掉这段超出的部分才能让输出回落表现就是超调、反调、迟迟不回。这就是积分饱和现场一多半的“PID 调不下来”都是这个原因。抗饱和的常见做法有三类积分分离、条件积分、回算back-calculation。这份块里sRueck、RueckDiff、RueckAlt、sLmn、sbArwHLmOn、sbArwLLmOn这一组变量指向的是回算路线——把“输出被削掉多少”这个差值反向送进积分器// 上一周期输出被限幅的部分期望输出未限幅与实际输出之差 RueckDiff : dLmn - sLmn; // 只要上一周期处在饱和状态就把这段差值从积分器里扣掉 // sbArwHLmOn / sbArwLLmOn 记录的是上一周期的限幅状态避免同周期反复抖动 IF sbArwHLmOn OR sbArwLLmOn THEN Ianteil : Ianteil - RueckDiff; END_IF;参数含义dLmn是限幅前的求和结果sLmn是上一周期真正送出去的输出两者相减就是“被限幅吃掉的那部分”。把它从积分项里减回去积分器实际上就停在了“刚好让输出等于上限”的位置不会越界。这比简单地“饱和时停止积分”更平滑条件积分在饱和边缘容易产生一阶不连续回算则是连续的。sRestint这个变量的注释写着“浮点数计算偏差”对应的是另一种更细的问题Real 是 32 位浮点累加几百个周期后小量加大量会丢精度积分器数值会缓慢漂移。常见做法是保留一个残差变量把每次丢失的低位累积起来等攒够一个可表示的量再补偿回去。这类处理在教科书里完全不存在却是长期运行的回路能不能稳住的细节。3.3 微分先行与 TM_LAG为什么 D 项从 PV 而不是 ER 取微分的物理意义是“预测误差的变化趋势”但误差 设定值 - 测量值设定值一旦阶跃变化误差瞬间跳变微分项就会打出一个巨大的尖峰输出瞬间冲到极限。操作工改一次设定值阀门猛地全开再回来这就是微分冲击。解决办法是微分先行derivative on measurement只对测量值求微分不对误差求微分。设定值变化时PV是连续的D 项不会跳。// 微分先行用 PV 的变化率而不是偏差的变化率 // sInvAlt 保存上一周期的 PV 值 Danteil : -GAIN * rTd * (PV - sInvAlt) / rCycle / (rTmLag rTd); sInvAlt : PV;除以rCycle把差分变成变化率这是噪声放大的源头采样周期越小两次采样之间的差值越小但相除之后噪声的相对占比反而更大。所以 D 项几乎必须滤波。TM_LAG就是那个一阶惯性环节的时间常数// 一阶惯性滤波D_f D_prev K * (D_raw - D_prev) // K TD / (TM_LAG TD)TM_LAG 越大滤波越强相位滞后也越大 Danteil : sLanteilAlt (rTd / (rTmLag rTd)) * (Danteil - sLanteilAlt); sLanteilAlt : Danteil;参数说明TM_LAG 0时K 1等于不滤波纯微分噪声原样放大TM_LAG远大于TD时微分作用被削得几乎不剩。工程上一般取TM_LAG在TD的 1/5 到 1/10 之间既压住高频噪声又保留趋势预测能力。D_SEL关闭时要把Danteil和sLanteilAlt一并清零否则重新打开时上一周期的残留值会跳出来。4. 手自动切换与复位三个“清零”语义完全不同调用顺序在这个块里是有硬约束的。手动分支必须最先处理因为它决定了本周期输出是“算出来的”还是“给定的”复位必须更早因为它决定所有静态变量是否还有意义。顺序错了症状很难查——输出会在切换瞬间跳变或者复位之后仍然残留上一轮的值。4.1 MAN_ON 无扰切换靠积分跟踪而不是靠输出钳位手动模式最简单的实现是IF MAN_ON THEN LMN : MAN;问题是切回自动的瞬间积分器还停留在手动之前的值输出会立刻跳到那个值上执行机构猛地一动。这叫有扰切换。IF MAN_ON THEN LMN : MAN; // 手动值直接作为输出 // 关键让积分器跟踪当前输出保证切回自动时输出连续 // 反推公式Ianteil LMN - P分量 - D分量 Ianteil : LMN - ErKP - Danteil; sLmn : LMN; // 同步上一周期输出供回算使用 ELSE // 自动分支先算 P、I、D再求和、限幅 dLmn : ErKP Ianteil Danteil DISV; END_IF;参数说明DISV是干扰变量作为前馈直接叠加到输出上不走 PID 运算常用于已知扰动比如上游流量波动的补偿。反推Ianteil那一步是无扰切换的核心——它让积分器的值恰好等于“如果一直处于自动输出应该是多少”切换瞬间三个分量之和不变。4.2 COM_RST、I_ITL_ON、INT_HOLD三种清零不能混用变量作用范围典型使用场景常见误用COM_RST清空所有静态变量、上一周期值、限幅标志设备启动的第一个周期由启动 OB 触发常置 TRUE导致每周期都重置输出永远起不来I_ITL_ON只重置积分器到I_ITL_VAL工艺要求从指定初始值开始累加当作总复位用比例项和微分项的历史值仍残留INT_HOLD冻结积分累加但不清零保压、保温阶段暂停积分当作复位用一松开就从冻结处继续累加COM_RST必须只执行一个周期。原因是它要把sInvAlt、sLanteilAlt、sLmn、sbArwHLmOn全部归零如果每个周期都归零微分项永远是 0因为上一周期 PV 每周期都被清掉回算永远认为“上周期没饱和”。写进启动 OB 里、或者在程序里做上升沿触发两种方式都行行业里的血泪经验是——一定要做上升沿不要直接接一个常 TRUE 的变量。5. 排查那些看起来像“参数玄学”的翻车现场PID 出了问题现象往往只有一种输出不对。但原因分散在量纲、时序、状态三个层面。下面几条按“现象 → 原因 → 解决”整理都是这类块在实际工程里反复出现的坑。5.1 参数与量纲配置层面的三个典型翻车现象一回路怎么调都不稳参数改到边界也一样。原因CYCLE传的单位不对。传了T#100ms但块内按秒换算实际积分步长是设定值的 1000 倍TI的物理意义完全失效。或者反过来主循环里传了一个拍脑袋的T#1s实际循环时间是 10 ms积分速度比预期快 100 倍。 解决先打印rCycle的实际值确认它在预期范围内。放在固定循环中断 OB 里调用时直接把那个 OB 的循环时间填给CYCLE放在主循环里则要用保守值并且接受调参结果不可复现的代价。现象二输出长期贴在 0 或 100%无论怎么改GAIN。原因LMN_HLM和LMN_LLM未初始化默认为 0限幅把输出死死压住或者LMN_FAC默认为 0最终输出等于LMN_OFF。 解决块实例化时必须显式给限幅赋初值。工程上默认给LMN_HLM 100.0、LMN_LLM 0.0、LMN_FAC 1.0、LMN_OFF 0.0即使当前工艺不需要限幅也写上去让“默认 0 值”这个坑消失。现象三温度读数整体偏了几十度且偏差恒定。原因PV_FAC、PV_OFF与外部已经做过的量程换算重复了一遍。变送器出来的值已经转成工程量再乘一次PV_FAC就重复放大了或者外部没换算PV_FAC也没给PV直接等于原始字的百分比。 解决明确一处换算。用PV_PER通道时PV_FAC/PV_OFF是唯一的量纲修正点用PV_IN通道时PV_FAC应该给 1.0、PV_OFF给 0.0把所有换算留在外部。5.2 时序与状态切换瞬间的跳变从哪来现象四手动切自动的一瞬间阀门猛地全开再回来。原因手自动切换时没有做积分跟踪。手动期间积分器还在后台按旧偏差累加或者干脆没累加、停在手动之前的值上切回自动时三个分量之和不等于当前输出。 解决在手动分支里反推积分项Ianteil : LMN - ErKP - Danteil让分量之和等于输出。同时把sLmn同步成当前输出避免下一周期的回算用错基准。现象五设定值一改输出立刻出现一个尖峰。原因微分项取的是误差的变化率而不是测量值的变化率。设定值阶跃时误差瞬间跳变微分项直接顶到限幅。 解决改成微分先行差分对象换成PV同时确认TM_LAG不为 0。如果工艺允许设定值本身也应该做斜坡化处理让跳变变成持续的斜坡从源头上消除冲击。现象六调试软件里看到LMN_I一直在涨但输出不动。原因已经进入积分饱和且回算没有生效。可能是sbArwHLmOn这个标志没有在限幅判断之后更新导致下一周期的回算看不到饱和状态也可能是RueckDiff用错了基准拿的是限幅后的输出相互比较。 解决检查限幅判断与标志赋值是否在同一周期末尾完成sLmn必须在限幅之后赋值。回算的减量要基于“限幅前 - 限幅后”不是“本周期 - 上周期”。注意sLmn、sbArwHLmOn这类带s前缀的变量是静态变量跨周期保存。把它们当临时变量在块内随手写症状是“时好时坏”这类问题几乎无法通过在线监控定位只能靠梳理周期边界。6. 设计取舍限幅为什么必须放在最后一步这份块的结构顺序是归一化 → 偏差与死区 → 三个分量 → 求和 → 限幅 → 输出。限幅放在最后不是随意排的。如果放在中间比如先限幅再算微分微分项看到的就是被削平的值趋势预测失真如果先限幅再回算回算拿到的差值就是 0抗饱和直接失效。只有把限幅放在求和之后、输出之前回算才有正确的基准“被削掉多少”这个信息才完整。另一个取舍是抗饱和用回算而不是积分分离。积分分离实现简单超限就把I_SEL拉低但输出在限幅边界附近会出现一阶不连续工艺上表现为轻微的喘振。回算是连续的代价是要多维护四五个静态变量和一组跨周期的标志位。这份块选了后者说明目标是长期运行的稳定性而不是实现上的省事。还有一个更隐蔽的选择LMN走Real、LMN_PER走Word两条输出通道同时存在。这意味着块不假设执行机构的接口形态由调用者决定用哪一路。代价是LMN_FAC、LMN_OFF这组参数必须和通道选择保持一致否则会出现“监控画面上数值对、现场执行器动作错”的情况。从那以后我每次拿到一个现成的 PID 块第一件事不再是看公式而是先把接口按“量纲、饱和、切换”三堆分开逐个确认默认值和复位语义再上电试运行。这套流程看着慢但比在现场靠改参数试出来快得多。希望帮到你。本文还有配套的精品资源点击获取