1. 先讲清楚一个真相:S7-1200的多路PID,限制从来不在“指令数量”
接到“20多路闭环控制”这种需求,第一反应往往是换更大控制器,或者从PLC价格上给自己找个说服理由。但我做过的项目里,S7-1200跑24路PID很常见,稳定性和4路、8路并没有本质差别。问题从来不在于PID指令能用几次,而在于你用什么程序结构去承接“多路”这件事。
1.1 复制粘贴官方PID块,为什么会让多回路项目越来越别扭
从博途里拖出PID_Compact,编译后自动生成一个背景数据块,填好输入输出,再组态一个HMI画面,这是大多数工程师用得最熟的套路。单回路、两三路,这种做法没有任何问题。但一旦回路数超过8路,事情就会开始失控:
- 每个回路都有一份独立的背景DB,参数只能一个个打开块接口去核对;
- 一旦要批量修改参数,没法对背景DB做数组化访问,只能逐个赋值;
- 回路之间的工艺分组、启停顺序、故障处理逻辑散落在OB1各处,维护成本成倍增长;
- 24个背景DB加上24份功能块调用,占用的程序内存和代码存储空间非常可观。
还有一个容易被忽视的问题:PID_Compact内部集成了自整定、抗积分饱和、手动/自动切换、前馈、PWM输出等一大堆功能。这些功能单看都是好东西,但当24路复制出来时,每一路都要为它保留完整的中间变量和运行状态。S7-1200的工作内存本身就不是标称型号那种只有几十到一百多KB的规模,塞进去以后,留给触摸屏配方、报警缓冲、通信数据区的空间就非常紧张了。
1.2 “不受回路限制”真正指什么
从软件机制上讲,S7-1200从来没有一条“最多只能做8路PID”的硬限制。你完全可以放置多个实例。所以标题里“不受回路限制”这句话,正确的理解应该是:当你不再把“每个回路”理解成“复制一份完整指令”,而是把PID控制逻辑收敛成一套算法、把回路参数整理成一个数组之后,回路数量就不再受“一个块、一个背景DB、一份中间变量”这种概念限制,数据块可以开多大、扫描周期能匀出多少时间,才决定最终能带几路。
我实测下来,24路只是一个很保守的数字。更准确地说,一个S7-1200能否承得住,要看三个预算:数据块占用空间、PID计算在循环中断OB里的耗时、AI/AO模块通道刷新周期。这三项都留有余量,跑30路也不算夸张。后面我会把这三项逐一算给你看。
另外,官方PID块在OB1里挨个执行的方式,也会造成一种隐性隐患:采样周期无法严格固定。OB1循环扫描本身会被通信、运动控制、触摸屏请求打断,导致每次调用PID块的时间间隔忽长忽短。对于积分和微分计算来说,采样周期抖动会让整定参数失去意义。这也是许多“多路PID”项目跑起来后总觉得不稳定的根源之一——不是算法不行,而是执行节拍本身就不均匀。
2. 用一份全局DB加一个结构体,把24路回路装进同一套数据空间
定完基调之后,我们直接看怎么落地。核心思路只有一个:所有回路共享同一段算法逻辑,区别只体现在数据上。这和把24台设备各配一个运维人员相比,相当于只配了一个运维团队,但每个人手里都有一份设备台账。
2.1 设计一个能覆盖所有回路的UDT结构体
在博途里,我们先用UDT(用户自定义数据类型)定义单个回路的“台账字段”。你不用一次把所有字段都想全,但下面这几个是基本盘:开关、运行模式、设定值、反馈值、输出值、PID参数、积分/微分内部状态、PWM输出相关字段。
结构体设计成下面这样,名字随意,我习惯用UDT_PID_Channel:
TYPE UDT_PID_Channel VERSION : 0.1 STRUCT Enable : Bool; // 回路总开关 Mode : Int; // 0=手动,1=自动 SetPoint : Real; // 设定值,工程单位 ProcessValue : Real; // 当前反馈,工程单位 OutputValue : Real; // PID计算输出,0~100% Kp : Real; // 比例增益 Ti : Real; // 积分时间,秒,0表示无积分 Td : Real; // 微分时间,秒,0表示无微分 DeadBand : Real; // 死区,工程单位 OutputMin : Real := 0.0; // 输出下限 OutputMax : Real := 100.0; // 输出上限 Integral : Real; // 积分累加值,内部使用 LastProcessValue : Real; // 上一个周期的反馈,内部使用 LastDiffValue : Real; // 微分项滤波历史值,内部使用 PWM_Period : Real := 10.0; // PWM输出周期,秒 PWM_OnTimeAccum : Real; // PWM导通时间累计 END_STRUCT END_TYPE有人可能会问:把PID参数和运行状态放在同一个结构体里,是不是不太讲究?现场需要修改PID参数时,直接通过HMI把整个字段组写进DB,好处是地址规律、方便批量映射。你把参数和状态分开成两个结构体,也不会错,但数组化处理时地址会变得绕,没必要。
2.2 在全局数据块里按数组展开
接着建一个全局数据块,假设叫DB_MultiPID:
DATA_BLOCK "DB_MultiPID" STRUCT Channels : ARRAY[0..23] OF "UDT_PID_Channel"; SampleTime : REAL := 0.1; // 统一采样周期,秒 ActiveChannels : INT := 24; // 实际投入回路数 END_STRUCT END_DATA_BLOCK这段结构有什么好处?好处在于:无论你有1路还是24路,处理程序永远只需要写一遍。启用几路,就通过ActiveChannels来控制;某个回路不用,让Enable置FALSE即可。以后设备升级到32路,只需要把ARRAY上限改成31,重新编译一次,算法部分一行都不用动。
这一步恰恰是“不受回路限制”的根基。很多工程师最初接触时,容易以为数组化只是把DB做得更整齐,事实上它改变的是扩展方式和调试方式。新回路加入后,所有需要被PID算法处理的输入输出、参数、状态都在同一个结构里,随手就能映射到触摸屏,不用再为一路新回路单独复制一个完整控制块。
3. 统一PID算法代码:位置式积分、抗饱和、无扰切换一次性写对
结构搭建好了,接下来就是算法本体。我建议用SCL而不是LAD或FBD写这个函数块。原因很简单:循环中断里要对24路执行同一套逻辑,SCL的FOR循环和数组访问是最自然的表达方式;用LAD去重复连接24路接口,光连线就够让人崩溃。
3.1 为什么选位置式而非增量式
很多老规矩习惯写增量式PID,输出是“相对上次的变化量”,执行机构自带记忆,程序不用维护积分项。这套思路在单回路、执行器是变频器或电动阀的场合很实用。但到24路规模,我反而推荐位置式,也就是输出直接表示0~100%的阀门/功率百分比。
位置式的优势在于:输出限幅、手动/自动切换、上位机强制输出、故障安全输出,全都在同一个实数上操作,逻辑直观,故障排查一目了然。缺点是需要自己管理积分累加,但这恰恰是统一算法里最容易被验证和复制的地方。你只要保证三条规矩——积分有界、输出有界、切换时预置积分——就不会出现漂移。
多回路项目最大的风险不是算法本身有多复杂,而是成百上千个接线点、变量名、地址各自为政。统一位置式把所有回路的输出语义拉到同一层面,能最大程度减少这种混乱。
3.2 一个能直接抄的SCL函数块模板
下面是一个单回路PID执行体,通过VAR_IN_OUT传入结构体,执行完自动更新结构体里的输出和内部状态。注意三点:微分是对测量值做差分,而不是对偏差做差分,防止设定值阶跃引发微分爆炸;积分在输出饱和时会自动反算限位;手动模式下积分跟随输出,保证切自动时无扰动。
FUNCTION_BLOCK "FB_PID_Single" VAR_IN_OUT Ch : "UDT_PID_Channel"; END_VAR VAR_INPUT Ts : REAL; // 采样周期,秒 END_VAR VAR_TEMP Error : REAL; dMeasure : REAL; PComponent : REAL; IComponent : REAL; DComponent : REAL; OutBefore : REAL; END_VAR BEGIN // 回路未启用,保持现状并清空内部状态 IF NOT #Ch.Enable THEN #Ch.Integral := 0.0; #Ch.LastProcessValue := #Ch.ProcessValue; RETURN; END_IF; // 手动模式:积分跟随输出,方便无扰切自动 IF #Ch.Mode = 0 THEN #Ch.Integral := #Ch.OutputValue; #Ch.LastProcessValue := #Ch.ProcessValue; RETURN; END_IF; // 自动模式主计算 #Error := #Ch.SetPoint - #Ch.ProcessValue; // 死区:小偏差范围内比例项归零,避免频繁动作 IF ABS(#Error) <= #Ch.DeadBand THEN #Error := 0.0; END_IF; // 比例项 #PComponent := #Ch.Kp * #Error; // 测量值变化量,供微分计算使用 #dMeasure := #Ch.ProcessValue - #Ch.LastProcessValue; // 积分项:Ti大于0时累加 #IComponent := #Ch.Integral + #Ch.Kp * #Ts / #Ch.Ti * #Error; IF #Ch.Ti > 0.0 THEN #Ch.Integral := #IComponent; ELSE #Ch.Integral := 0.0; END_IF; // 微分项:对测量值变化量做差分,并加一阶滤波 #DComponent := 0.0; IF #Ch.Td > 0.0 AND ABS(#dMeasure) < 100000.0 THEN #DComponent := #Ch.Kp * #Ch.Td / #Ts * #dMeasure; #DComponent := 0.2 * #DComponent + 0.8 * #Ch.LastDiffValue; END_IF; // 合成输出并限幅 #OutBefore := #PComponent + #Ch.Integral + #DComponent; IF #OutBefore > #Ch.OutputMax THEN #OutBefore := #Ch.OutputMax; END_IF; IF #OutBefore < #Ch.OutputMin THEN #OutBefore := #Ch.OutputMin; END_IF; #Ch.OutputValue := #OutBefore; // 抗积分饱和:输出达到上限且误差为正时,积分不再正向累积 IF #OutBefore >= #Ch.OutputMax AND #Error > 0.0 THEN #Ch.Integral := #Ch.OutputMax - #PComponent - #DComponent; ELSIF #OutBefore <= #Ch.OutputMin AND #Error < 0.0 THEN #Ch.Integral := #Ch.OutputMin - #PComponent - #DComponent; END_IF; // 记录本次状态 #Ch.LastProcessValue := #Ch.ProcessValue; #Ch.LastDiffValue := #DComponent; END_FUNCTION_BLOCK这段代码是按常见工程惯例补全的,强调三个细节:
- 死区只清零比例项,不冻结积分。如果死区把整个计算都冻结,那么在小偏差区间里积分会停住,过程值往往最终卡在死区边缘,反而制造稳态误差。
- 微分滤波系数0.2是起步值。现场噪声大的时候可以降到0.05~0.1,噪声不大的场合拉高到0.3~0.5会让微分响应更直接。
- 抗积分饱和用的是“饱和后反算积分值”的方案。它不是最简单的“饱和就停止积分”,但更稳妥:偏差反向时,积分值距离合理位置不远,输出能立即转换方向。
3.3 在循环中断OB里统一批量执行
算法块写好后,不要在OB1里逐个调用,建一个循环中断组织块,比如OB30,周期设成100ms,在里面写:
FOR i := 0 TO 23 DO "FB_PID_Single".Instance(i)( Ch := "DB_MultiPID".Channels[i], Ts := "DB_MultiPID".SampleTime ); END_FOR;这里“Instance(i)”是示意写法,实际项目中要么你为每个通道单独建一个多重背景实例,要么直接把算法内联进一个统一FB。最方便的做法是建一个“FB_MultiPIDs”容器块,内部持有数组元素引用,然后在OB30里调用这个容器块一次。这样OB30代码干净,未来扩展也只在容器块内部调整。
要注意:循环中断OB里不要塞通信指令、配方读写、触摸屏批量传输这类任务。循环中断是给实时控制用的,里面做任何可变耗时的操作都会直接影响下一轮采样的准时性。S7-1200对这类问题不会直接报错,但现场表现就是某几路输出周期偶尔抖动一下。
4. 稳定性不是玄学:采样节拍、抗饱和、故障安全这些细节决定成败
24路PID听起来数量大,但只要把下面几个关键点处理好,整体稳定性和单回路没有本质区别。工程上那些“多回路跑不稳”的案例,绝大多数都能追溯到采样节拍、积分管理、故障处理这三个环节。
4.1 采样时间统一,是参数可复用的第一步
前面说过,PID的积分和微分计算都依赖精确的采样周期。一个回路如果一会儿10ms算一次、一会儿30ms算一次,那么同样的Kp、Ti、Td在数学上已经不对应同一个控制行为。所以我会把所有回路放进同一个循环中断OB,让它们共享同一个Ts。这个做法带来的额外好处是:整定好的参数可以在非常相似的回路之间直接复制。
现实中常常会有“快回路”和“慢回路”混在一个项目里的场景。比如温度回路用200ms足够,流量回路可能要50ms,电机转速回路可能要求10ms。这种情况下不要硬把全部回路塞进一个OB,也不要图省事只建一个“万能100ms”。更合理的做法是拆两个循环中断OB,一个处理快回路,一个处理慢回路,但两个OB内部调用的是同一个算法容器,只是传入不同的Ts参数。S7-1200支持多个循环中断OB,数量完全够用。
在循环周期选择上,有个原则:采样时间至少要比被控对象的时间常数快5~10倍。普通电加热温度对象,时间常数可能在几十秒甚至几分钟,100ms采样完全够;电磁阀或比例阀控制压力,时间常数短,就得把采样周期压到10~50ms。别一味追求快,因为模拟量模块的转换时间、信号滤波延迟会把多余的速度全部吃掉,反而引入噪声。
4.2 多回路耦合:输出功率管理比PID本身更值得关注
24路共用一个热源、一台水泵或一路电源时,会出现所谓“多回路耦合”。举例来说,一个多温区加热箱,8个区同时升温,总功率是有限的。每个回路的PID看到自己温度偏低,都各自把输出推到100%,实际供电功率却不可能无限供应,于是所有回路都在抢能量。哪个区抢得多,纯粹取决于开始升温时谁先触发,随机性极强,现场表现就是温度高低不平、个别区波动。
解决思路不是调高PID参数,而是在算法外面加一层输出功率管理。我的习惯做法是:每个回路算完OutputValue后,不直接交给输出模块,而是先经过一个“总功率限制”函数。比如设定总输出上限为80%,当前8个回路的理论输出加起来是150%,那就按各回路实际需要的比例缩放到总上限。这个过程只在前置通道执行,不动PID本身,调试时可以单独关掉协调逻辑,不影响基础控制。
这种“PID + 功率分配”的分层结构,在多回路项目里几乎是必须的。它会避免因为一方饱和而把整个系统带向振荡。如果你只想最快速度跑通24路,至少也要为共用电源的回路加一个简单的输出限幅和错峰PWM。
4.3 无扰切换与故障安全三态
现场调试最常发生的事,是工艺员把某一路从自动切到手动,手动开了一阵阀门,再切回自动。如果PID切回自动瞬间,积分项还停留在很久以前的累计值,输出会产生阶跃,过程值猛地一冲,后面要花很长时间才能稳定回来。
回看上面代码,手动模式里有一条:#Ch.Integral := #Ch.OutputValue。这条指令的含义就是:手动状态下,期望输出是多少,就让积分项等于多少。这样切回自动时,PID输出从当前值附近开始变化,无扰动、无阶跃。这套逻辑在单回路和多回路中完全一致,但多回路里尤其重要,因为24路中任何一路切换动作都不应该惊动其他回路。
故障安全则建议做一个独立判断层。每个回路的UDT里可以再加一个“Fault”布尔量,由主程序判断传感器断线、执行器反馈异常等条件后置位。一旦Fault为TRUE,算法立即强制输出安全值,通常是0%,并把积分清零。不要让故障回路的ProcessValue继续参与计算,否则断线瞬间AI值跳到极值,微分项会瞬间冲到一个巨大数值,对其他回路的执行和上位画面都是灾难。
5. 实测资源账:24路到底吃掉多少内存、多少扫描周期
理论讲了半天,如果不算一笔账,很多朋友还是不放心。下面给出一个可参考的数量级评估。不同CPU型号存在差异,但整体结论可以套用。
5.1 数据块和中断耗时的估算法
先算数据块。一个UDT_PID_Channel里面大约有十几个Real、两个Bool和几个Int,按工程实践含内部状态在内约40字节。24路也就是1KB出头。哪怕你再往结构体里扩充报警阈值、量程上下限、滤波系数等字段,总数据量也就在几KB这个量级,对S7-1200的数据区压力很小。
再算计算时间。上面那段SCL算法,单回路执行大约二三十行浮点运算和几次比较赋值。S7-1200执行浮点运算折算下来大概微秒级,单回路约几微秒到十几微秒。24路在100ms中断里全部计算完,耗时通常远小于1ms,对整个CPU负载率的影响往往不到1%。真正吃时间的是模拟量模块本身的转换周期、通信任务和HMI访问,而不是PID算法本身。
这也就解释了为什么“理论上支持24路以上”是有底气的:算法轻量、数据有序、调度清晰。前提是别用24份PID_Compact实例——那个单个调用开销比自写的SCL要高不少,24份叠加之后,已经开始明显拖累中断和OB1了。
下面这张表是简易预算,具体型号会略有波动:
| 回路数 | UDT数据占用 | 100ms采样下PID中断耗时估算 | 建议最低采样周期 |
|---|---|---|---|
| 8路 | 约0.5~1KB | 0.1~0.3ms | 25~50ms |
| 16路 | 约1~2KB | 0.2~0.8ms | 50~100ms |
| 24路 | 约1.5~3KB | 0.3~1.2ms | 100~200ms |
| 32路 | 约2~4KB | 0.5~1.8ms | 200ms起 |
从这张表能看出,真正限制回路数的第一瓶颈不是PLC内部程序空间,而是外围I/O。模拟量AI模块每一路都有转换时间,把一个AI模块所有通道依次转换完,可能需要数毫秒甚至更久;如果你需要24路AI和24路AO同步刷新,那么硬件本身就已经决定了采样周期不可能无限缩短。
5.2 不用官方自整定后的整定方法:先典型、再分组
很多人担心:不用PID_Compact自带自整定功能,24路参数怎么整定?其实多回路项目的工艺通道往往高度类似——一台热压机的上下左右几个区、一条烘箱的8个加热段,结构、功率、传感器位置几乎一样。完全没有必要逐路整定。正确姿势是先挑一个“代表回路”整定出一套参数,再复制给同组其他回路,随后做微调。
标准且常用的工程整定法是临界比例度法:把Ti调得很大、Td设为0,让系统只保留比例控制;逐步增大Kp,直到测量值出现等幅振荡,记下此时的Kp(记为Ku)和振荡周期(记为Tu),然后按下表计算参数:
| 控制方式 | Kp | Ti | Td |
|---|---|---|---|
| P | 0.5 × Ku | 无穷大 | 0 |
| PI | 0.45 × Ku | 0.83 × Tu | 0 |
| PID | 0.6 × Ku | 0.5 × Tu | 0.125 × Tu |
实际调试时,我会在算出来的Kp上先乘0.7~0.8的安全系数,然后再跑一两次设定值阶跃确认。多回路耦合环境下,临界振荡法测出来的Ku可能受到相邻回路干扰,所以它只能作为起点,不要求一步到位。
还有一个我给所有项目都建议的“笨办法”:24路回路开始时全部切手动,逐个检查传感器数值是否正常、执行器动作方向是否正确,确认无误后,再一路一路切自动。每切一路自动,观察无扰切换是否平稳、设定值阶跃响应有没有明显超调。这个过程看起来慢,实际却是排查多回路问题最高效的方式,因为一旦所有回路同时投自动,任何一路异常都会被其他回路掩盖,排查难度会成倍上升。
5.3 循环时间吃紧时的优化顺序
实测如果发现中断OB占了总周期规划的大头,按下面顺序砍消耗,基本不会出问题:
- 把采样周期从100ms放宽到200ms。温度、液位这类惯性大的回路几乎没有影响,但中断内剩余时间直接翻倍。
- 把微分滤波系数从0.2调低到0.1,或者结合现场噪声评估是否需要微分。有些回路Td直接设0,微分项完全跳过。
- 确认OB30里没有通信指令、配方读写、HMI批量传输。这类任务挪到OB1或慢速OB里。
- 把PV滤波从循环中断挪到普通OB里执行,中断里只读滤波结果。
- 如果还不够,才考虑把部分慢回路单独放到另一个循环中断OB,以不同的Ts运行。
按照这个顺序处理,绝大多数“中断爆满”的现场都能平衡下来。我曾遇到一个16路案例,原本把PID和通信都塞在OB30里,CPU报警几乎常亮;把通信挪出去之后,什么都没改,中断负载直接降了七成。
6. 二十四路PID到底可以用在哪些工程方向
从技术验证回到工程落地,这套结构能覆盖的范围比想象中大得多。标题末尾那句“可以用向……”后面通常会接一个具体场景,我自己做过的主要集中在三类。
6.1 多温区分区控温
热压机、平板硫化机、烘箱、注塑机料筒这类设备,工艺上常常需要把一个平面或筒体分成十几个甚至二十几个独立温区。每个区要独立升温、独立控温、独立显示。这套结构做这类项目几乎是量身定制:一个通道对应一路热电偶输入和一路SSR(固态继电器)输出。
PID输出虽然是0~100%,但SSR不能接受模拟量,因此要加PWM输出逻辑。具体做法是在UDT里维护一个PWM导通时间累计量,按固定周期(比如10秒)计算导通/断开;同时给每个分区设置不同的错峰相位。24个SSR如果不做相位错开,会在同一瞬间全部导通,对供电变压器冲击很大。我用过的错峰方法是按回路号计算相位偏移量,例如360°除以24路,每路间隔15度,也就是每路导通起点相对上一路错开15度。实测下来电网电压波动明显减小,温区之间也不会互相干扰。
6.2 反应釜、试验台上的温度-压力-流量联调
比多温区更复杂的工况,是把温度、压力、流量、液位多种物理量揉在一个系统里。典型例子是试验台上有多个反应釜,每个釜同时控制搅拌转速、夹套温度、进料流量、釜内压力,各回路之间互相影响。
统一数组方案的强大之处在这里体现得很明显:因为每个回路只是结构体数组里的一个元素,级联控制自然得不得了。温度环的OutputValue直接作为流量环的SetPoint,在程序里不过是一行赋值。后续做顺序控制、联锁控制、批量控制,都是对数组的遍历和条件判断,不需要为每一路单独写互动逻辑。
这种项目调试时,我的习惯是分批次投运。先投温度回路,等温度稳定了再投压力,压力稳定后再投流量。同时把每个通道的实时曲线都放到HMI上,哪一路先出现振荡,直接缩小范围,不会出现24路一起波动、抓不到源头的情况。数组化DB的地址规律,让触摸屏变量也能用数组批量映射,不用为每路单独建画面元素,上位组态工作量小很多。
6.3 扩展方向:从24路以后再往上走,边界在哪里
这套结构继续扩到32路、40路是可行的,但有三条边界必须盯住:
第一,循环中断OB里的计算时间占比不要超过30%。超过这个比例,S7-1200的其他中断和OB1扫描都会感到压力,通信延迟也会变明显。
第二,模拟量模块的通道数量和转换时间决定了回路的吞吐上限。PLC程序算得再快,外围模块刷新不过来,回路数量照样提不上去。选型时要把AI/AO模块数量和转换周期一起算进预算,不要只盯着CPU型号。
第三,HMI和上位机通信。如果每100ms要同时刷新40多个通道的曲线,通信负载会显著上升。可以把趋势数据改为批量变量访问,或者把刷新周期拉长到200~500ms。控制周期和显示周期没有必要完全一致。
如果需求真的超过40路,我一般会建议直接换S7-1500,或者把控制任务拆分到多个S7-1200里。拆分的思路是:每台控制器负责一组工艺段,控制器之间用通信交换设定值、状态和联动信号。这样单台压力不大,现场故障定位也更清晰。毕竟硬把一台设备压着极限跑,即使编译能过,后期维护和排查问题的时间成本也会成倍增加。
最后分享一个我自己调试多回路时特别受用的经验,算是这套方案的收尾体会:多回路PID项目的难点往往不在数学,而在组织。一直用复制粘贴的方式做控制逻辑,迟早会陷入“改了这个参数忘了改另一个”的泥潭。数组加统一算法把规则集中到了一处,所有回路共用一套逻辑,剩下的工作就是接线、整定、观察趋势,这比什么都重要。如果你正在为多回路控制发愁,不妨先搁置一下官方块,试着把通道当数据、把算法当公共服务来思考,你会发现S7-1200的能力远比你最初想象的宽裕。