博途SCL手写PID功能块:增量式六段实现与参数整定避坑
简介:该资源是西门子博途(TIA Portal)环境下用 SCL 编写的 PID 功能块源代码,面向从事自动化控制的工程师、调试人员及学习 PLC 编程的学生。它可用于在 SCL 中直接定义并调用自定义 PID 控制器,补足标准库功能块在参数可控性与二次开发上的不足。压缩包共 1 个文件,为 doc 文档,大小约 19KB,以文本形式完整呈现 FUNCTION_BLOCK "PID" 的接口与内部变量定义,便于阅读、移植与逐行比对。代码涵盖 COM_RST、MAN_ON、PVPE_ON 等运行控制位,P_SEL、I_SEL、D_SEL 作用选择,SP_INT、PV_IN、PV_PER 设定值与过程量输入,GAIN、TI、TD、TM_LAG、DEADB_W 等整定参数,以及 LMN、LMN_PER、LMN_P、LMN_I、LMN_D、PV 等输出和上周期比例、积分、微分中间量,并启用了 S7_Optimized_Access 优化访问。读者可据此理解比例、积分、微分分量的计算流程,掌握限幅、死区、积分初始化与手动切换的处理思路,并参考变量注释完成参数整定与工程移植。目前已有 4187 人学习下载。
1. 一条温度回路的重现:把 PID 从黑匣子里拽出来
调一条温度回路,工艺对象拖进去、自整定跑完,曲线看着挺漂亮;换个采样周期重新下载,超调又回来了——这种事我碰到过不止一次。后来我把回路拆成看得见的东西:一份用博途 PID SCL 写成的自定义功能块,21 个输入、7 个输出、1 个输入输出引脚,比例、积分、微分三个分量分别引到输出上,采样时间、限幅、死区、手自动全部由引脚决定,不藏在封装里。它解决的不是"能不能控",而是"出了问题查不查得动":曲线抖了,能立刻分辨是微分放大了噪声,还是积分在爬坡;手动切回自动跳一下,能直接定位到积分量没跟随。适合已经会用 PID 指令、但被封装块卡住排查思路的自动化工程师,也适合刚开始写 SCL、想找一个结构完整的功能块当范本的人。
2. 接口先看懂:21 个输入引脚分别落到算法的哪一步
拿到一份只有变量声明、BEGIN ... END_FUNCTION_BLOCK中间空着的东西,很多人第一反应是"缺实现,没法用"。其实恰恰相反:接口定义清楚了,实现只是按顺序填空。真正决定这份代码能不能落地的,是你能不能把每个引脚对应到算法里的具体动作。引脚接错一个,参数调得再准也是白搭。
这份声明的结构很规整:VAR_INPUT里放设定值、过程值、手自动和一组标定系数,VAR_OUTPUT里除了最终调节值,还把 P、I、D 三个分量单独引出来,VAR_IN_OUT只留了一个ER作为偏差信号,VAR里则全是"上周期状态"——sInvAlt、sLanteilAlt、sRestint、sRueck、sLmn这些。看到这些变量名就该明白:这是一个增量式累加的实现,每个周期都要拿上一周期的结果做基准,所以它必须作为静态实例存在,不能写成 FC。
2.1 四组输入:谁决定控制量,谁负责兜底
把 21 个输入按职责分成四组,接线的思路立刻就清楚了。
| 分组 | 引脚 | 职责 |
|---|---|---|
| 控制量 | SP_INT、PV_IN、PV_PER、MAN、DISV | 决定这一拍算出什么 |
| 模式 | MAN_ON、PVPE_ON、P_SEL、I_SEL、D_SEL | 决定这一拍谁参与运算 |
| 标定 | PV_FAC、PV_OFF、LMN_FAC、LMN_OFF | 工程量与通道值的线性映射 |
| 保护 | LMN_HLM、LMN_LLM、DEADB_W、TM_LAG、INT_HOLD、I_ITL_ON、I_ITL_VAL、COM_RST | 限幅、消抖、抗饱和、复位 |
第一组里最容易搞混的是PV_IN和PV_PER。前者是已经换算好的 Real 工程量(比如 85.3 ℃),后者是直接从外设地址读上来的 Word(比如%IW64),两者靠PVPE_ON二选一,绝不能同时接。DISV是前馈干扰量,直接叠加到输出上,做前馈补偿或者阀门预置的时候用,多数单回路把它接 0.0 就行。
第二组里MAN_ON和P_SEL/I_SEL/D_SEL的优先级要理清:MAN_ON为真时,P、I、D 三个选择位全部失效,输出直接走MAN;只有自动模式下,三个选择位才决定哪些分量参与合成。所以"手动模式下调 GAIN 没反应"不是 bug。
2.2PV_PER是 Word 不是 Real:外设字的两次换算
这一步是新手翻车最集中的地方。外设输入字是 16 位整型,满量程 ±27648 对应 −100% 到 +100%,要先转成 Real 再折算成百分比,之后才轮到PV_FAC和PV_OFF做工程量标定。顺序错了,标定系数怎么调都对不上。
// 说明:FB 内部访问局部变量需加 # 前缀,这里为可读性省略,粘贴时补上 // 第一步:外设字 -> 百分比工程量 IF PVPE_ON THEN // WORD 按位解释为有符号 INT,-27648..27648 线性对应 -100.0..100.0 Istwert := INT_TO_REAL(WORD_TO_INT(PV_PER)) * 100.0 / 27648.0; ELSE // 通道值模式:直接用外部换算好的 Real Istwert := PV_IN; END_IF; // 第二步:工程量标定,PV_FAC 为斜率,PV_OFF 为零点偏移 PV := Istwert * PV_FAC + PV_OFF;WORD_TO_INT不能省。PV_PER声明成Word,如果直接强转成Int或者直接参与算术,负值区间会变成 30000 多的大正数,PID 会一路往负方向猛拉,现场表现就是"阀门瞬间全关"。PV_FAC一般填量程跨度(比如传感器 0~150 ℃,对应 0~100% 的百分比,填 1.5),PV_OFF填零点(比如 4~20 mA 对应 −25 ℃ 起始,就填 −25.0)。两个系数拿不准的时候先填 1.0 和 0.0,用监控表看一眼PV是不是落在合理区间,再动标定。
另外注意PV_PER走的是外设直读,不经过过程映像,读取时机与 OB 的调用点有关。如果这个 FB 放在循环中断 OB 里调用,读到的就是那一拍的最新值;放在主循环 OB 里,外设刷新和程序扫描可能错拍,采样一致性会差一点。
2.3 输出引脚与上周期状态:为什么要留那么多中间变量
LMN是最终调节值,LMN_PER是写给外设通道的 Word 形式,两者关系在输出端再算一遍,方向跟输入端刚好相反。LMN_P / LMN_I / LMN_D三个分量单独引出来,价值在于趋势录波:把这三条曲线和LMN一起拖进趋势视图,振荡时一眼就能看出是哪一项在拱。经验上,等幅高频振荡多半是 P 太大或 D 在放大噪声,缓慢爬坡后突降通常是 I 在积分饱和。
QLMN_HLM和QLMN_LLM是到达限幅的标志位,别浪费。接到上位机做报警,或者反过来喂给外部联锁逻辑(比如"输出已经顶到 100% 五分钟还没到温,说明加热功率不足"),比单纯看LMN数值有效得多。
VAR里那一堆sXxx变量看着杂乱,其实只干两件事:一是保存上周期结果供增量计算用,二是记录限幅状态供抗饱和判断用。sRestint和sRestDif的注释写着"浮点数计算偏差",这是有讲究的——积分增量在采样周期很短、积分时间很长的时候会小到 1e-6 量级,Real 类型在这个数量级上做累加,加着加着就加不动了。所以要把不足一个最小有效位的增量"存起来",攒够了下一次再补进去,这就是余数累积。少了这一层,小积分时间常数下的稳态误差会一直消不掉。
ER放在VAR_IN_OUT而不是VAR_OUTPUT,是为了让外部既能读也能写。做串级控制时,副环的偏差可以反写回主环做监视;做前馈时也可以从外部强制给一个偏差值。这个位置的选择是有意为之,别随手改成输出型。
3. 把空壳填满:SCL 主循环的六段写法
接口理清之后,实现就是按顺序把六段逻辑写进去。顺序不能乱:先复位、再采样时间预处理、再标定和死区、再算三个分量、再合成限幅、最后写外设。任何一段提到前面,行为都会变。
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; // 微分作用打开 INT_HOLD : Bool; // 积分作用保持 I_ITL_ON : Bool; // 积分作用初始化 CYCLE : Time; // 采样时间 SP_INT : Real; // 内部设定值 PV_IN : Real; // 过程变量值输入 PV_PER : Word; // 外设过程变量 MAN : Real; // 手动值 GAIN : Real; // 比例增益 TI : Time; // 积分时间 TD : Time; // 微分时间 TM_LAG : Time; // 微分时间作用延时 DEADB_W : Real; // 死区带宽 LMN_HLM : Real; // 调节值上限 LMN_LLM : Real; // 调节值下限 PV_FAC : Real; // 过程变量因子 PV_OFF : Real; // 过程变量偏移量 LMN_FAC : Real; // 调节值因子 LMN_OFF : Real; // 调节值偏移量 I_ITL_VAL : Real; // 积分作用的初始化值 DISV : Real; // 干扰变量 END_VAR VAR_OUTPUT LMN : Real; // 调节值 LMN_PER : Word; // 外设调节值 QLMN_HLM : Bool; // 到达调节值上限 QLMN_LLM : Bool; // 到达调节值下限 LMN_P : Real; // 比例分量 LMN_I : Real; // 积分分量 LMN_D : Real; // 微分分量 PV : Real; // 过程变量值 END_VAR VAR_IN_OUT ER : Real; // 错误信号 END_VAR VAR sInvAlt : Real; // 上周期偏差值 sLanteilAlt : Real; // 上周期积分量 sRestint : Real; // 积分增量余数 sRestDif : Real; // 微分增量余数 sRueck : Real; // 回退量 sLmn : Real; // 上周期调节值 sbArwHLmOn : Bool; // 上周期到达上限 sbArwLLmOn : Bool; // 上周期到达下限 rCycle : Real; // 采样时间秒值 Iant : Real; // 积分增量 Diff : Real; // 微分中间量 Istwert : Real; // 过程变量浮点值 ErKP : Real; // 偏差比例值 rTi : Real; // 积分时间秒值 rTd : Real; // 微分时间秒值 rTmLag : Real; // 微分滞后秒值 Panteil : Real; // 比例值 Ianteil : Real; // 积分值 Danteil : Real; // 微分值 Verstaerk : Real; // 增益中间量 RueckDiff : Real; // 微分回退中间量 RueckAlt : Real; // 上周期回退量 dLmn : Real; // 调节量中间值 gf : Real; // 输出换算中间值 rVal : Real; // 输出标定中间值 END_VAR BEGIN // 六段实现在下面依次展开 END_FUNCTION_BLOCK先把壳摆出来,下面往里填。局部变量保留原声明不变——它们在故障排查时有用,改名字反而失去对照。
3.1 完全重启与采样时间预处理
第一段只干两件事:把上周期状态清干净,把Time类型折算成秒。COM_RST是沿触发的,接一个按钮或者上电初始化块,只在需要的时候给一拍高电平。
// ---- 段 1:完全重启 ---- IF COM_RST THEN sLmn := 0.0; sLanteilAlt := I_ITL_VAL; // 初始化值只在重启时生效一次 sRestint := 0.0; sRestDif := 0.0; sInvAlt := 0.0; Danteil := 0.0; sbArwHLmOn := FALSE; sbArwLLmOn := FALSE; LMN_P := 0.0; LMN_I := 0.0; LMN_D := 0.0; ER := 0.0; END_IF; // ---- 采样时间折算与兜底 ---- rCycle := DINT_TO_REAL(TIME_TO_DINT(CYCLE)) / 1000.0; // ms -> s IF rCycle < 0.001 THEN rCycle := 0.001; // 1 ms 下限,防止积分和微分里的除零 END_IF;TIME_TO_DINT不能省。Time在底层是 32 位毫秒整数,直接DINT_TO_REAL(CYCLE)在某些编译版本里会通不过类型检查,先转DINT再转Real是稳妥写法。除以 1000 是因为后面的GAIN * rCycle / rTi必须单位一致,TI也按秒折算,两边同为秒才能约掉。
rCycle加下限是血泪经验。现场有人把CYCLE忘了接,默认 0,积分段出现除零,CPU 直接报错停机;也有人接了个 1 ms 的循环中断,rCycle小到 0.001,积分增量被浮点精度吃掉,稳态误差怎么都消不掉。1 ms 这个下限不是拍脑袋,是 Real 精度和常见执行机构响应速度之间的折中。
3.2 死区与偏差信号的处理
第二段把过程值算出来,然后做死区。死区的处理方式直接影响稳态表现,写错了会出现"设定值附近来回蹭"的现象。
// ---- 段 2:过程值标定 ---- IF PVPE_ON THEN Istwert := INT_TO_REAL(WORD_TO_INT(PV_PER)) * 100.0 / 27648.0; ELSE Istwert := PV_IN; END_IF; PV := Istwert * PV_FAC + PV_OFF; // ---- 段 3:偏差与死区 ---- ER := SP_INT - PV; IF DEADB_W > 0.0 AND ABS(ER) <= DEADB_W THEN ER := 0.0; // 死区内偏差擦零,死区外保持原值 END_IF;ER = SP_INT - PV这个方向不能反。反了就是正作用,加热回路会变成"温度越高加热越猛",一路冲顶。方向确定之后,执行机构是开阀还是关阀、是加热还是制冷,都要和这个符号对上。
死区这里用的是"擦零"而不是"减去带宽"。擦零的好处是死区内三个分量全部归零,输出稳定不动;如果写成ER := ER - DEADB_W,死区内还会残留一个固定偏差,积分会一直往一个方向爬,最后慢慢蹭出死区。代价是死区边界处有一个小跳变,所以DEADB_W一般取量程的 0.2%~2%,别贪大。
ER是VAR_IN_OUT,赋值之后外部能立刻读到处理过的偏差值。做录波的时候直接监视这个引脚就行,不用再单独算一遍。
3.3 积分段的余数累积与抗饱和
积分是这份代码里最容易写错的一段。三个特殊引脚INT_HOLD、I_ITL_ON、I_ITL_VAL的优先级,加上抗饱和判断,顺序一旦搞反行为就完全不一样。
// ---- 段 4:比例分量 ---- IF P_SEL THEN LMN_P := GAIN * ER; ELSE LMN_P := 0.0; END_IF; // ---- 段 5:积分分量 ---- IF NOT I_SEL THEN LMN_I := 0.0; sRestint := 0.0; ELSIF INT_HOLD THEN LMN_I := sLanteilAlt; // 保持:冻结在上周期积分量 ELSIF I_ITL_ON THEN sLanteilAlt := I_ITL_VAL; // 初始化:强制装入外部值 LMN_I := sLanteilAlt; ELSIF TI > T#0ms THEN rTi := DINT_TO_REAL(TIME_TO_DINT(TI)) / 1000.0; // 抗饱和:上周期已顶到限幅,且偏差仍推向同一方向时冻结累加 IF (sbArwHLmOn AND ER > 0.0) OR (sbArwLLmOn AND ER < 0.0) THEN LMN_I := sLanteilAlt; ELSE Iant := GAIN * rCycle / rTi * ER; sRestint := sRestint + Iant; // 余数累积 IF ABS(sRestint) >= 1.0E-4 THEN sLanteilAlt := sLanteilAlt + sRestint; sRestint := 0.0; END_IF; LMN_I := sLanteilAlt; END_IF; ELSE LMN_I := sLanteilAlt; // TI 为 0 时退化为保持 END_IF;优先级顺序是有讲究的:I_SEL关掉必须清零,INT_HOLD优先于初始化,初始化优先于正常累加。如果INT_HOLD和I_ITL_ON同时为真,保持生效——因为保持是运行中的操作员动作,初始化是启动时的配置动作,运行中的操作不能被覆盖。
余数累积那段用1.0E-4作为门槛。工程量范围在 0~100 时,1e-4 大约是 Real 在这个量级上还能分辨的最小增量。低于这个值就暂存起来,攒够了再补进积分量。这样做的好处是:即便TI设到 600 s、CYCLE只有 10 ms,积分照样能慢慢爬,不会出现"稳态误差永远差 0.5%"的死结。
抗饱和用的是上周期限幅标志sbArwHLmOn / sbArwLLmOn,而不是本周期现算的LMN。原因很简单:此刻LMN还没算出来,用上一拍的状态判断是唯一选择,而且效果一样。冻结积分之后,偏差反向时会立刻恢复累加,退出饱和的速度取决于TI,所以TI别设太大。
3.4 微分段的一阶滞后与输出合成
微分最容易出事。理想微分对噪声是全通放大,实测值上有一点点毛刺,输出就是一片尖峰。TM_LAG存在的意义就是给微分项套一个一阶惯性环节,把高频噪声压下去。
// ---- 段 6:微分分量 ---- IF D_SEL THEN IF TD > T#0ms THEN rTd := DINT_TO_REAL(TIME_TO_DINT(TD)) / 1000.0; Diff := (ER - sInvAlt) / rCycle; // 偏差变化率 RueckDiff := GAIN * rTd * Diff; // 理想微分 IF TM_LAG > T#0ms THEN rTmLag := DINT_TO_REAL(TIME_TO_DINT(TM_LAG)) / 1000.0; // 一阶滞后:alpha = CYCLE / (TM_LAG + CYCLE) Danteil := Danteil + rCycle / (rTmLag + rCycle) * (RueckDiff - Danteil); ELSE Danteil := RueckDiff; END_IF; LMN_D := Danteil; ELSE LMN_D := 0.0; END_IF; ELSE LMN_D := 0.0; Danteil := 0.0; END_IF; sInvAlt := ER; // 无论 D_SEL 是否为真都必须更新,防止切回时微分冲击sInvAlt := ER放在IF D_SEL外面,这一行是很多人会漏的。漏了会怎样?微分关掉一段时间后再打开,sInvAlt还停在很久以前的偏差值,ER - sInvAlt巨大,输出瞬间一个尖峰,现场表现是"微分一投就炸"。养成习惯:所有"上周期值"变量,无论对应功能开没开,每周期都更新。
滞后系数的写法CYCLE / (TM_LAG + CYCLE)是后向差分离散化的结果,TM_LAG越大滤波越重、相位滞后越多。TM_LAG一般取TD的 1/10 到 1 倍,取太小等于没滤,取太大微分就基本没作用了。
输出合成段要处理手自动切换、限幅和外设写出:
// ---- 段 7:输出合成、限幅与手自动 ---- IF MAN_ON THEN dLmn := MAN; // 无扰切换:手动时把剩余量塞进积分器,切回自动不跳变 sLanteilAlt := MAN - LMN_P - LMN_D; LMN_I := sLanteilAlt; ELSE dLmn := LMN_P + LMN_I + LMN_D + DISV; END_IF; // 限幅与标志位 QLMN_HLM := FALSE; QLMN_LLM := FALSE; IF dLmn >= LMN_HLM THEN dLmn := LMN_HLM; QLMN_HLM := TRUE; ELSIF dLmn <= LMN_LLM THEN dLmn := LMN_LLM; QLMN_LLM := TRUE; END_IF; // 记录状态供下一周期抗饱和判断 sbArwHLmOn := QLMN_HLM; sbArwLLmOn := QLMN_LLM; sRueck := dLmn - (LMN_P + LMN_I + LMN_D); // 回退量,调试可监视 sLmn := dLmn; LMN := dLmn; // ---- 段 8:外设输出 ---- rVal := LMN * LMN_FAC / 100.0 + LMN_OFF; gf := rVal * 276.48; // 100.0% 对应 27648 IF gf > 27648.0 THEN gf := 27648.0; ELSIF gf < -27648.0 THEN gf := -27648.0; END_IF; LMN_PER := INT_TO_WORD(REAL_TO_INT(gf));无扰切换那一行sLanteilAlt := MAN - LMN_P - LMN_D是关键。手动期间积分量被强制跟随,切回自动的那一拍,LMN_P + LMN_I + LMN_D加起来刚好等于手动值,输出不跳。前提是MAN和LMN同量纲,都按 0~100 的工程量填。如果有人把MAN填成阀门的原始通道值(0~27648),切回自动会直接飞出量程。
276.48这个系数来自27648 / 100,含义是百分比到通道值的换算。LMN_FAC和LMN_OFF在它之前做工程量标定,两个系数一起用时要想清楚顺序:先按百分比标定,再转通道值。顺序搞反,阀门开度会成倍偏差。
4. 参数整定:GAIN、TI、TD 到底填多少
代码跑起来只是开始,参数不对照样控不住。这份 FB 的参数不多,但每一个都有量纲和取值范围,乱填的后果比想象中严重。
4.1 CYCLE 必须和 OB 调用周期对齐
CYCLE不是用来"设置控制周期"的,它是在告诉 FB"我这个周期有多长"。所以它必须和调用它的 OB 实际周期一致,或者取整数倍。
| 调用位置 | OB 周期 | CYCLE填法 |
|---|---|---|
| 循环中断 OB30 | 100 ms | T#100ms |
| 循环中断 OB32 | 500 ms | T#500ms |
| 主循环 OB1 | 不确定 | 不推荐,或加定时器做分频 |
| 分频调用 | 200 ms(OB30 每两拍调一次) | T#200ms |
填小了,积分和微分按错误的周期计算,等效于把TI和TD都放大,响应会比预期慢;填大了,等效于把TI、TD缩小,容易振荡。这是最常见也最难发现的错误,因为曲线表现和参数整定不当一模一样。我一般会在 FB 里加一个调试用的周期校验:用系统时钟算出实际间隔,和CYCLE对比,偏差超过 10% 就置一个报警位。
INT_HOLD和I_ITL_ON在使用时机上也要注意。I_ITL_ON是电平有效的,一直为真时积分量每个周期都被重装成I_ITL_VAL,等于积分永远不起作用,表现为"怎么调都有稳态误差"。正确的用法是上电初始化时给一拍。INT_HOLD一般接在设备暂停、阀门检修这类场景,保持期间积分冻结,恢复后从冻结点继续。
4.2 从纯比例到 PI 再到 PID 的分步整定
一次把三个参数全填上,振荡了根本分不清是谁的问题。我一般分三步走。
第一步,只投比例。P_SEL := TRUE,I_SEL := FALSE,D_SEL := FALSE,GAIN从 0.5 起,每次翻倍,直到输出出现等幅振荡,记下这个临界增益Ku和振荡周期Tu。这是经典的临界比例度法,实操上比查表准。
第二步,投积分。GAIN退到0.5 * Ku,TI从Tu起往下调,观察阶跃响应。TI太大,稳态误差消得慢;TI太小,超调变大甚至振荡。合格的标准是:阶跃后第一次过冲不超过 10%,两三个TI之内稳到死区里。
第三步,视需要投微分。温度、压力这类大滞后对象,TD取0.1 * TI到0.3 * TI之间,配合TM_LAG一起调。流量、液位这类快过程一般不用微分,噪声太大,投了反而抖。
GAIN的物理含义是"偏差 1 个单位,输出给多少"。工程量是 0~100 百分比时,GAIN = 2.0意味着偏差 1% 输出给 2%,很温和;GAIN = 20就是激进调法。给个参考区间:温度回路 0.5~5,压力回路 1~10,流量回路 2~20,液位回路 0.5~3。超出这个范围要先怀疑量纲或者接线,而不是继续调。
4.3 死区、微分滞后与限幅的取值经验
这三个参数不参与"整定",但直接决定现场表观质量。
| 参数 | 典型取值 | 取值依据 | 填错的后果 |
|---|---|---|---|
DEADB_W | 量程 0.2%~2% | 传感器噪声幅度 | 太大:稳态误差明显;太小:执行机构频繁动作 |
TM_LAG | 0.1 * TD~1.0 * TD | 噪声频率 | 太小:微分放大噪声;太大:微分失效 |
LMN_HLM | 100.0 或执行机构上限 | 阀门/加热器额定 | 太高:执行机构过载;太低:能力浪费 |
LMN_LLM | 0.0 或执行机构下限 | 阀门关断位 | 太低:负输出无意义;太高:关不死 |
PV_FAC | 量程跨度 | 传感器量程 | 错:全过程值偏移 |
PV_OFF | 零点偏移 | 传感器零点 | 错:恒定偏差 |
DEADB_W的确定方法很实在:把P_SEL、I_SEL、D_SEL全关,手动把输出稳在一个值上,看PV的波动幅度,峰峰值除以 2 就是死区下限。低于这个值,执行机构会跟着噪声动。
LMN_HLM和LMN_LLM不要图省事填 100 和 0。加热器有额定功率、阀门有机械行程,限幅要按实际能力设。填 100/0 的时候,输出顶到 100% 说明控制器已经无计可施,但你看不出是"能力刚好用满"还是"严重不足"。把上限设成 90%,运行中QLMN_HLM频繁置位,就说明该加功率或者改工艺了——这是一个很实用的容量告警。
5. 避坑与排查:五个让控制器"看着能用"的细节
代码能编译、曲线能动,不代表能用。下面这几条都是我在联调阶段反复撞上的,每条按现象、原因、解决写清楚,照着对号入座能省不少时间。
5.1 五个高频翻车现象
现象一:输出一路顶到上限不回头。原因是过程值符号搞反或者PV_PER没做WORD_TO_INT,负温度被读成 30000 多,偏差恒为负,积分一路往下积。解决:先把P_SEL/I_SEL/D_SEL全关,手动给MAN一个中间值,看PV是不是跟着实际值走;符号不对就改PV_FAC的正负,别在ER那里取反,取反会让抗饱和方向也反过来。
现象二:切手动再切回自动,输出跳一下。原因是没有做无扰切换,或者MAN的量纲和LMN不一致。解决:确认MAN按 0~100 填;确认手动分支里执行了sLanteilAlt := MAN - LMN_P - LMN_D;如果跳变仍然存在,检查DISV是不是在手动期间也在叠加。
现象三:稳态误差怎么都消不掉,差 0.3% 左右就不动了。原因有两个可能:一是DEADB_W设太大,偏差被擦零;二是积分增量被 Real 精度吃掉,sRestint那段没写或者阈值设太大。解决:先把DEADB_W临时改成 0 观察,如果误差还在,就是积分段的问题,把余数门槛从 1e-4 降到 1e-6 试试。
现象四:输出有规律的高频抖动,周期等于扫描周期。原因是微分太强或者TM_LAG太小,噪声被放大。解决:先把D_SEL关掉,抖动消失就确认是微分问题;把TM_LAG调到TD的 0.5 倍以上,或者干脆不用微分。另一个可能的原因是CYCLE填得比实际周期小很多,等效于放大了微分增益。
现象五:改了CYCLE之后行为完全变了。这不是 bug,是设计使然。CYCLE一变,积分的等效增益CYCLE/TI和微分的等效增益TD/CYCLE都跟着变。解决:改CYCLE之后必须重新整定TI和TD,或者按比例换算:CYCLE翻倍,TI也翻倍,TD减半,能保持等效增益不变。这也是为什么工艺对象自整定出来的参数换个扫描周期就不准。
5.2 联调阶段该盯哪几个量
光看LMN和PV两条曲线,信息量不够。我一般会同时把LMN_P、LMN_I、LMN_D、ER、QLMN_HLM、sbArwHLmOn全部拖进趋势视图,采样周期和 FB 调用周期一致。
看曲线有几个固定套路:三个分量的量级应该大致可比,如果LMN_I比LMN_P大一个数量级,说明积分在主导,TI偏小;如果LMN_D的毛刺比LMN_P的峰还高,微分肯定过头了。QLMN_HLM长时间为真而PV迟迟不涨,说明执行机构能力不够,不是参数问题。ER在死区外频繁正负穿越,说明GAIN太大或者死区太小。
还有一个容易忽略的点:sRueck这个回退量在调试时值得监视。它等于限幅后的输出减去三个分量之和,正常情况下应该接近 0;如果它有大数值,说明某一拍输出被硬拉回来了,抗饱和逻辑在起作用,这时候要回头看是参数问题还是工艺扰动。
6. 进阶:把它做成可复用资产而不是一次性代码
一个 FB 调通之后,真正的价值在于能不能复用到下一个项目。我的做法是把它改成多重背景,放在一个专门的控制 FB 里调用,每个回路一个背景数据块,参数走配方管理。这样同一台设备上十几条回路共用一套逻辑,改算法只改一处,改参数只改数据块。要注意的是多重背景里不能再用COM_RST那种上电沿触发的写法,得改成一个显式的复位引脚,由上一层统一调度,否则每个背景块启动顺序不一样,复位时机对不齐。
验证环节我习惯用一段临时的阶跃测试代码,放在调试用的 OB 里,手动触发:
// 调试用:设定值阶跃 + 记录响应,正式运行时删掉或禁用 IF "StepTest_Trig" THEN "PID_DB".SP_INT := 60.0; // 从当前值阶跃到 60% "StepTest_Trig" := FALSE; END_IF;配合趋势录波,能一次性看清超调量、上升时间、稳态误差三个指标。比凭感觉调参数强得多,也方便把整定结果记进调试报告。
| 验收指标 | 合格线 | 观察位置 |
|---|---|---|
| 超调量 | ≤ 10% 量程 | PV峰值与SP_INT之差 |
| 上升时间 | 按工艺要求 | PV从 10% 到 90% 的时间 |
| 稳态误差 | ≤DEADB_W | PV与SP_INT稳定后之差 |
| 手自动切换跳变 | ≤ 1% 量程 | LMN切换前后差值 |
| 限幅标志 | 常态不置位 | QLMN_HLM/QLMN_LLM |
最后一句忠告:这份代码最容易被跳过的一步,是把CYCLE和 OB 周期对齐再做整定。我早年吃过一次亏,同一个回路在测试台上调得很漂亮,现场换了循环中断的周期,参数原封不动搬过去,温度直接振荡了二十分钟才压下来。从那以后我每次布新回路,都强制先跑一遍周期校验和阶跃测试,两个都过了才允许投自动。希望帮到你。
本文还有配套的精品资源,点击获取