我先问个问题:你写PLC程序多少年了?如果现在有人问你,IEC 61131到底包含哪几个部分,各部分管什么,软件模型里的任务和程序组织单元是怎么回事,你能立刻答上来吗?我见过不少干了七八年工控的老工程师,梯形图画得很顺,现场排故障也利索,但被问到这些时,通常是愣一下,然后含含糊糊说一句“就是PLC编程标准嘛”。
这个反应很真实,但也很可惜。IEC 61131不是用来应付考试的知识点,它是整个PLC行业共同的语言底座。不管是选型、编程、调试、交接,还是跨平台迁移,最终都会绕回这套标准。今天这篇文章,我想把IEC 61131的完整版图和PLC编程中最核心的基础知识一次性讲透,重点放在IEC 61131-3的软件模型和五种编程语言上,最后再结合我这些年实际跑项目的经验,聊一聊哪些坑值得注意、哪些习惯值得养成。如果你是刚入行的新人,这篇文章能帮你建立一套标准的知识框架;如果你已经有几年经验,我希望你能从中找到一些之前忽略的细节。
1. 写在前面:IEC 61131不是考试知识点,而是PLC行业的通用语言
很多人觉得,反正我日常用的是某品牌PLC自带的那套编程软件,按厂家的手册来就行,干嘛要研究标准?这个问题我当年也这么想过,直到吃了几个亏,才意识到标准思维和工具思维的差距。
1.1 三个典型的现实场景,让你看清标准的价值
第一个场景是项目交接。我接过一个用某欧系品牌PLC写的老项目,程序写得比较随性,没有模块拆分,没有命名规范,两千条指令堆在一个任务里。接手的人只能靠注释猜逻辑,改一行代码怕引起连锁反应,根本不敢动。但如果当初按IEC 61131-3的软件模型来做,把设备封装成功能块,变量按作用域归类,交接时只需要说明“哪个程序段管哪台设备”,完全不用逐行猜。
第二个场景是跨品牌迁移。我之前帮朋友把一个项目从A品牌PLC迁移到B品牌PLC,表面上看两家都属于IEC 61131-3兼容产品,但因为原工程里大量使用了厂商扩展指令,没有任何结构和命名规划,迁移过程基本等于重写。反观另一个同事的项目,因为一开始就用ST(结构化文本)写核心算法、用变量隐藏硬件地址,迁移时只改了硬件映射部分,逻辑部分几乎原封不动搬了过去。
第三个场景是团队协作。同一套产线控制系统,有人用梯形图,有人爱用功能块图,有人偏好在ST里写数学运算。如果没有统一标准,代码风格会变得五花八门,审查和排故障都很痛苦。而IEC 61131-3正好定义了统一的编程模型和语言框架,大家各写各擅长的语言,最后集成到一个工程里,变量、数据类型、调用规则完全一致。
1.2 “标准是约束”其实是最大的误解
我经常听到一种说法:IEC 61131限制了厂家的发挥,照着标准写程序反而绑手绑脚。这完全理解反了。标准给的是最小公约数,是互通的底层逻辑,厂家在这之上做扩展没有任何问题。对工程师来说,标准思维最大的作用不是让你去背条款,而是让你在面对任意一个PLC平台时,都能快速把工程上的问题映射成“任务、程序、功能块、变量”这些标准元件,再用平台提供的语言去实现。平台会换代,品牌会更换,但软件模型和语言思路是连续的,这才是这份经验能不断积累的根本原因。
2. IEC 61131九个部分的完整版图:它不是单一标准,而是一套标准族
很多人口中的“IEC 61131标准”,严格来说是IEC 61131系列标准族。我知道的公开正式版本一共覆盖九个部分,你可以把它理解成一套“可编程控制器”的全生命周期规范,从硬件到软件、从编程到通信、从安全到应用指导全都涉及。
2.1 九个部分分别管什么事
我整理了一张表,把它拆开看很清晰:
| 部分 | 名称 | 核心内容 |
|---|---|---|
| IEC 61131-1 | 通用信息 | 定义可编程控制器的术语、基本功能、选型和用户需求 |
| IEC 61131-2 | 设备要求与测试 | 硬件性能指标、电气特性、机械要求、EMC、测试条件 |
| IEC 61131-3 | 编程语言 | 五种编程语言、软件模型、数据类型,是整个标准的核心 |
| IEC 61131-4 | 用户指引 | 给最终用户和系统集成商的使用指导,偏向工程实践 |
| IEC 61131-5 | 通信 | 规定PLC与其他设备之间的通信模型和服务 |
| IEC 61131-6 | 功能安全 | 面向安全相关系统的要求,与功能安全领域其他标准衔接 |
| IEC 61131-7 | 模糊控制编程 | 定义模糊逻辑在PLC中的编程方法和可用功能块 |
| IEC 61131-8 | 应用指南 | 针对-3中各种语言的实现和应用提供更细的指导 |
| IEC 61131-9 | 数据交换与通信 | 面向分布式控制场景的通信与数据交换规范 |
2.2 为什么第3部分是所有工程师的必修课
上面九部分里,只有IEC 61131-3是绝大多数工程师真正每天都会接触到的标准。因为它规定了三个东西:一是软件模型,也就是程序在PLC里如何组织、如何分层;二是数据类型和变量类型,以及变量在不同程序组织单元之间如何传递;三是五种编程语言的语法与语义,以及如何将不同语言组合在同一个任务中。
举一个最直观的例子:你在某品牌PLC里写VAR_GLOBAL还是局部VAR,声明为BOOL还是INT,程序是放在PRG里还是FB里,这背后全是IEC 61131-3的影子。哪怕是平台界面做得再“傻瓜”,底层逻辑跑不出这个框架。所以后面三个章节我会把-3的内容展开讲清楚,这是整篇文章的核心部分。
2.3 从-3到-8,标准如何帮助你做设计决策
还有一个容易被忽视的点是-8。很多人写PLC程序时间久了,会形成一套自己的风格,但并不知道标准里其实有官方推荐的应用方法。-8针对每种语言给出了大量编程示例和推荐使用场景,比如哪种场合适合用顺序功能图,哪种场合适合用结构化文本,什么时候应避免使用指令表。我建议有点基础的朋友找来看一下,尤其是对“语言选型”犹豫不决的时候,-8的指导比我个人的经验更成体系。
3. IEC 61131-3软件模型拆解:程序组织单元、变量作用域与任务调度
IEC 61131-3的定义里有一个“软件模型”的概念,很多人第一次看到会觉得很抽象,因为PLC工程师习惯了打开编程软件就拖梯形图,很少去想这套层级结构。但要理解标准,这是绕不过去的一道门。
3.1 用一家工厂的比喻理解完整软件模型
标准的软件模型可以抽象成四层:配置对应一家工厂,资源对应车间,任务对应车间里的生产计划,程序组织单元对应具体的工人或设备。
- 配置(Configuration):一个控制系统的最高层,它定义了系统里有几个PLC、网络怎么连、全局变量怎么分配。对应到编程软件里,就是一个完整的工程。
- 资源(Resource):一个控制器内部的运行单元,通常对应一个CPU处理单元或一个特别定义的运行环境。资源可以拥有自己的全局变量和任务。
- 任务(Task):任务是为程序组织单元提供周期性或事件触发执行机会的调度器。这是很多工程师忽略的部分,但恰恰决定了程序段的执行频率和优先级。
- 程序组织单元(POU):包括程序、功能块和函数。程序是最上层调用单元,功能块有内部状态和记忆能力,函数则没有内部记忆,同样的输入一定得到同样的输出。
这种层级关系的好处是什么?它逼你在写代码之前,先想清楚“哪个变量归谁管、哪段程序多久跑一次”,而不是把所有逻辑堆在一个执行循环里。
3.2 程序、功能块、函数三种POU的区别,你必须彻底搞明白
程序(Program,简称PRG)是整个用户程序的主入口级单元,可以有多个程序并行运行,每个程序被某个任务调度。程序之间通过全局变量或直接变量访问来交换数据。
功能块(Function Block,简称FB)是实战中最常用的封装单位。FB有输入、输出,有内部变量,且每次调用结束后内部状态会保留。这就意味着同一个FB实例可以在程序里被多处调用,每个实例维护属于自己的状态,互不干扰。最典型的就是电机控制块:你把启动、停止、故障复位、超时保护这些逻辑全部封装进一个FB里,然后给每个电机创建一个FB实例,程序瞬间就变得清爽了。
函数(Function,简称F)没有内部状态,同输入必同输出。比如类型转换、数学运算、比较运算,这些都是函数。标准里已经定义了大量标准函数和标准功能块,我们日常用到的定时器TON、计数器CTU,其实是标准库里的功能块,不是语法自带的魔法。
3.3 变量作用域:从VAR到VAR_GLOBAL,变量到底该放在哪
变量类型是IEC 61131-3里非常基础但也非常实用的内容,我建议每个初学者都把下表记住:
| 变量声明 | 作用域 | 典型用途 |
|---|---|---|
| VAR | 仅当前POU内部,退出不保留 | 临时中间量、局部计算值 |
| VAR_INPUT | 由调用方传入 | 功能块的设定参数、命令 |
| VAR_OUTPUT | 由本POU输出给调用方 | 状态、计算结果 |
| VAR_IN_OUT | 双向传递且引用同一地址 | 需要读写外部变量的场合 |
| VAR_EXTERNAL | 引用已声明的全局变量 | 在POU中访问全局变量 |
| VAR_GLOBAL | 全配置范围内可见 | 跨程序共享的设备状态 |
| VAR_PERSISTENT | 掉电保持 | 工艺参数、配方、累计值 |
| VAR_TEMP | 资源内临时存储,初始化不确定 | 任务内的临时中转数据 |
这里有一条实战经验:全局变量不要乱用。很多老程序最后变成天书,很大一个原因就是所有变量都声明成VAR_GLOBAL,然后在几十个POU里互相改写,整个程序的调用关系就成了一个完全无法追踪的网。正确的做法是让数据尽量靠近它所属的模块,跨设备交互的数据才用全局变量,而且最好集中在配置层集中声明,并加注释说明它的用途和来源。
3.4 任务(Task)到底是什么,为何它与实时性强相关
任务在标准里是可以设置触发方式的调度器,有两种基本类型:周期型任务和事件触发型任务。周期型任务隔一个固定时间运行一次,比如每10ms执行一次高速逻辑;事件触发型任务只在特定事件发生时执行,比如某个中断信号到来。
这一点对理解PLC的实时性非常重要。很多人说PLC是实时系统,但实时性不是“跑得快”,而是“可预期”。任务调度的意义在于:你可以把高速、高优先级的逻辑放在一个短周期任务里,把慢速、低优先级的逻辑放在另一个长周期任务里,互不干扰。如果不分任务,所有程序都挤在一个主扫描周期里跑,那么伺服控制和温度测量都会受同一份负担拖累。
在我实际接触的项目里,常见的做法是:一个高速任务跑运动控制和现场总线刷新,周期5ms到10ms;一个中等任务跑设备逻辑,周期20ms到50ms;一个慢速任务跑报警、统计和配方处理,周期100ms到500ms。任务分得好,后面调实时性问题能少一大半。
4. 五种编程语言怎么选:梯形图、功能块图、结构化文本、顺序功能图和指令表
IEC 61131-3定义了五种编程语言:梯形图(LD)、功能块图(FBD)、结构化文本(ST)、顺序功能图(SFC)和指令表(IL)。这五种不是简单的五选一,而是各有脾气,应用场景也不同。更关键的是,标准允许它们混用,这才真正体现了这套体系的灵活性。
4.1 梯形图(LD):电气工程师的共同语言
梯形图是所有语言里最直观的,它直接还原了继电器控制电路的表达方式。左母线是火线,右母线是零线,常开触点、常闭触点、线圈,这些概念和传统电气图几乎一一对应。所以老一代工程师几乎都能无门槛上手LD。
但LD也有它的短板:当逻辑复杂度上去之后,一个网络里几十个触点串联并联,可读性会迅速下降。尤其是涉及数学计算、数据处理、字符串操作时,LD写起来非常痛苦。我的建议是:设备联锁、安全回路、手动操作逻辑这些“触点决定通断”的场景,放心用LD;但涉及数据计算、复杂分支的场合,别硬用LD,老老实实切ST。
4.2 功能块图(FBD):信号流表达,适合过程控制
FBD的表达方式有点像画电路框图:函数和功能块以图块形式连接,数据从左往右流动。对于模拟量处理、PID回路、通信数据处理这类“数据流动”特征明显的逻辑,FBD比LD更好用,因为你能一眼看出信号从哪里来、到哪里去。
在很多过程控制项目里,FBD是默认语言。它和LD一样属于图形化编程,调试时需要逐个功能块监视,变量追踪相对直观。劣势在于,如果数据流分支太多,画面会越画越乱,而且太长的连线也会造成视觉混乱。我一般建议FBD用在单路信号处理链上,比如一个模拟量输入经过滤波、量程转换、限幅、报警判断,再送给PID功能块,这种链路用FBD会非常清晰。
4.3 结构化文本(ST):写着最爽,也是算法最稳的选择
ST是五种语言里最接近通用高级编程语言的一种,语法类似Pascal。它的优势非常明显:数学计算、字符串处理、数组遍历、条件分支、循环,全部能轻松表达;一段几百字的算法在LD里可能要画半天,在ST里几十行就写完了。
我个人的主力语言就是ST,特别是涉及下列场景时:
- 模拟量标定、滤波、闭环控制计算
- 数据打包、拆包、通信协议解析
- 多个设备的状态机逻辑
- 需要大量重复运算的批处理逻辑
ST的劣势是不如图形化语言直观,不熟悉编程思维的电气工程师可能一开始不太适应。但只要你写过几个项目,就会发现它的表达密度和可维护性远高于图形化语言。下面是一个典型ST代码片段,实现双输入取平均加越限报警的逻辑:
FUNCTION_BLOCK FB_AvgAlarm VAR_INPUT rValue1 : REAL; rValue2 : REAL; rLimit : REAL; END_VAR VAR_OUTPUT rAvg : REAL; xAlarmHigh : BOOL; END_VAR VAR rSum : REAL; END_VAR rSum := rValue1 + rValue2; rAvg := rSum / 2.0; IF rAvg > rLimit THEN xAlarmHigh := TRUE; ELSE xAlarmHigh := FALSE; END_IF;4.4 顺序功能图(SFC):把工艺流程画成状态图
SFC特别适合描述离散制造中的顺序控制,比如一条流水线要依次完成上料、定位、夹紧、加工、松开、下料这些步骤。SFC的基本元素是步、转换和动作:每个步代表一个稳定状态,步与步之间用转换条件连接,满足条件就跳到下一步。
SFC有一个很实用的特性是它对并行分支和选择分支的支持。比如一个托盘进入工位后,两个机械臂需要同时动作,这就是并行分支;随后到了下一个决策点,根据传感器信号决定走A流程还是B流程,这就是选择分支。用梯形图写这种逻辑,需要大量中间变量和互锁;用SFC写,结构天生就是清晰的。
但SFC也有使用门槛:把连续逻辑拆成步骤需要一定的抽象能力,初学者容易把状态拆得过多或过少。另外,SFC的调试不能像LD那样直接看触点,更多是观察当前激活步。所以我的建议是,SFC用于规划和可视化层,步内部的具体动作还是用LD或ST来实现,两者配合效果最好。
4.5 指令表(IL):还在用但逐步边缘化
IL是一种类似汇编的语言,每行一条指令,比如:
LD xStart ANDN xStop ST xMotor这实现了“启动按钮按下且停止按钮未按下时,电机运行”的逻辑。IL的好处是结构紧凑、执行效率高,早期存储空间紧张的时代非常流行。但在今天的绝大多数PLC平台上,IL的使用已经越来越少,新手完全没有必要花太多精力去学。不过理解它的基本格式还是有价值的——很多平台的在线监控、简单的脚本表达式,其实还留着IL的思路。
4.6 语言混用才是工程常态,不要一棵树上吊死
既然是IEC 61131-3标准,最重要的特性之一就是允许不同语言混合使用。一个工程里,你完全可以在同一个程序中用LD写联锁回路、用ST算模拟量、用SFC搭工艺顺序流程。在实际工程项目里,我见过最舒服的程序结构是:SFC定义总工艺流程,FB封装设备动作,FB内部用LD/ST混合实现,算法部分用ST。这种组合既保证了可读性,又保证了计算能力。
5. 扫描周期是PLC程序的隐形骨架:执行模型与实时性
关于PLC编程,有一个概念如果你是零基础,必须尽早搞明白,那就是扫描周期。标准的软件模型定义了任务,而任务的执行在一个可编程控制器内部,本质上仍然遵循“扫描”这个基本节奏。
5.1 一次扫描里到底发生了什么
传统PLC的执行模型是循环扫描,典型的一次扫描包括以下几个阶段:读取输入状态、执行用户程序、写入输出状态、系统诊断与通信服务。输入刷新的意思是,把所有输入端子上的0/1状态统一采集到输入映像区;程序执行过程中读取的是这个映像,而不是直接读物理端子;程序结束后,把输出映像统一写到物理输出端子。
这个设计有一个非常重要的结果:在一个扫描周期内,所有逻辑看到的输入状态是一致的、执行顺序是确定性的。不会出现“同一时刻前半段程序读到的输入和后半段程序读到的输入不一样”的问题,这保证了逻辑的可复现性。代价是输入变化的响应会有延迟,最长可能需要一个扫描周期才能被捕捉到。所以,如果某个外部信号需要更快的响应,就必须用硬件中断或专门的快速任务去处理。
5.2 扫描时间从哪来,怎么预算
扫描时间由几个部分构成:I/O更新时间、程序执行时间、系统通信时间、自诊断时间。其中大头通常是程序执行时间。程序指令越复杂、调用的功能块越多、通信报文越大,扫描时间就越长。
很多工程师有一个误解,认为扫描时间是越小越好。实际上,只要满足你的控制实时性要求,扫描时间稍长一点并没有问题。过分钟情于把扫描时间压得很短,反而会牺牲程序的丰富性和可维护性。对常规设备控制来说,10ms到50ms的扫描周期已经能满足绝大多数需求;只有伺服插补、高速计数器响应这些场合,才需要真正毫秒级甚至更短的任务周期。
预算扫描时间有一个土办法:先把程序空跑一遍测基准时间,然后逐步加入功能块,每加入一块就记录时间增量,最后留出30%到50%的余量,防止后续加功能导致超时。这种方法虽然原始,但比拍脑袋强得多。
5.3 任务的优先级和看门狗,标准里面是怎么处理的
在IEC 61131-3的框架里,任务是可以设置优先级的。优先级高的任务先运行,同优先级任务按配置的顺序运行。如果低优先级任务执行到一半,高优先级任务需要执行,就会发生抢占。所以当你把两个任务放在同一个资源里时,必须明确它们之间的优先级和周期关系,否则会出现难以复现的时序问题。
还有一个和任务相关的实用机制是看门狗。任务可以设置看门狗时间,如果这个任务异常导致运行时间超过设定值,系统会触发相应的故障处理。这个机制在工程上非常有用,尤其当程序里出现死循环或用例外的跳转把执行时序搞乱时,看门狗能第一时间抓住问题。我自己写程序时,每个周期型任务都会记得打开看门狗,并预留足够的正常执行余量,避免误报。
6. 从“画梯形图”到“写程序”:变量命名、模块封装与状态机
这一章我想谈点带“手感”的东西。很多PLC工程师能把逻辑写通,但程序到了一定规模之后变得极其难维护,原因往往不是逻辑本身有问题,而是对结构的理解停留在“画图”层面上。从“画梯形图”升级到“写程序”,我总结了几条最核心的实践原则。
6.1 变量命名:让人一眼看懂变量的类型和用途
取名这件事看似小事,项目后期承担的成本差别是巨大的。我比较推荐的写法是“类型前缀+语义名称”。比如:
- bStart:布尔量“启动”
- iTemp:整型“温度”
- rPressure:实型“压力”
- udtMotor:用户自定义类型“电机结构体”
- tDelayTime:时间量“延时时间”
这样在梯形图或者ST里看到一个变量时,不需要跳转到声明处,就能推断出它的类型和大致用途。很多人喜欢用拼音缩写或者随便按几个字母,当时自己知道,三个月后自己都看不懂。
另外一个很多人忽略的规则:一个变量只负责一个含义。不要为了省变量,把“暂停”和“复位”共用一个布尔量,或者把一个整型变量的不同位段都拿来代表不同状态。省变量省出来的空间,远不够后期排障花掉的精力。
6.2 程序组织:把代码按功能分段,并固定段号
在一些成熟的工程里,程序段规划是有规矩的。典型做法是:把程序按功能划分成若干区段,每个区段固定段号。比如100到199是输入处理,200到299是核心工艺逻辑,300到399是设备动作逻辑,400到499是报警处理,500到599是通信与数据维护。段号规划表写在程序开头的大注释里,任何人接手都能快速定位。
这样做的核心目的不是好看,而是把“修改影响范围”降下来。你改报警段代码,不需要担心影响运动控制段;你在工艺逻辑里新增一个步骤,也不用手忙脚乱去处理全局变量冲突。PLC程序的结构化程度,本质上是和人力维护成本成反比的。
6.3 把设备封装成FB:电机控制块的设计实例
FB封装是模块化编程最实用的手段。我拿最常规的电机控制来举个例子。
一个电机控制块需要哪些输入?启动命令、停止命令、远程使能、故障复位信号、实际的运行反馈、允许启动的联锁条件。需要哪些输出?运行状态、故障状态、就绪状态、正在启动的标志。内部需要什么状态?上次命令的记忆、启动超时定时器、掉电保持的运行标志。
如果你把上述所有逻辑全部铺在主程序里,一台电机二三十个变量,二十台电机就是四五百个变量,程序想不乱都难。但如果你封装成一个FB_MotorCtrl,每台电机就是一行实例化代码,输入输出一目了然。程序的可维护性完全不在一个量级上。
6.4 用状态机替代乱七八糟的互锁
复杂设备控制里,最忌讳用一堆互相置位复位的位变量来搭“隐形状态机”。例如气缸控制,你可以用“伸出命令、缩回命令、到位信号、延时、报警”一堆变量来实现,但一旦动作流程改了,互相纠缠的置位复位逻辑会让你改一个点坏三个点。
更好的做法是显式地用状态机来表达。SFC语言天然就是干这个事的;如果平台不习惯用SFC,也可以用ST结合CASE语句做状态机:
CASE byStep OF 0: (* idle state *) IF bStart THEN byStep := 10; END_IF; 10: (* extend *) xCylOut := TRUE; IF xCylOutPos THEN byStep := 20; END_IF; 20: (* hold *) xCylOut := TRUE; IF tHoldTime <= T#0S THEN byStep := 30; END_IF; 30: (* retract *) xCylOut := FALSE; IF xCylInPos THEN byStep := 0; END_IF; END_CASE;这种写法把动作流程变成了一目了然的步骤,每步做什么、满足什么条件跳转,清楚明白,而且天然避免了变量之间“你置位我复位”的江湖恩怨。
6.5 把硬件地址藏起来,程序才能跨平台
我强烈建议不要让逻辑代码直接引用物理I/O地址。比如不要直接在梯形图里写X0.0、I:1.0、%IW10这种硬件地址,而是先用一个符号变量去映射它。比如压力变送器的模拟量输入,你先定义一个全局变量rPressRaw,然后在硬件映射区把它关联到物理通道,之后所有逻辑代码都只认rPressRaw这个符号。
这样做有两个巨大的好处:第一,现场换通道、换模块时,只需要改映射区,逻辑代码一个字符都不用动;第二,将来做跨平台迁移时,符号化的逻辑代码基本可以原样平移,只需要重做硬件映射。这个习惯,我在所有项目里都坚持,回报率非常高。
7. 我踩过的一些坑:IEC 61131常见的误解与实操教训
文章最后,我想集中分享一些真实踩过的坑和发现的问题。这些内容都不是标准文档里写的,但实战中几乎每个人都遇到过。
7.1 误解:“IEC 61131兼容的产品,程序一定能互相移植”
这是最大的一个误区。即便两家PLC都宣称兼容IEC 61131-3,它们对标准的实现深度依然不同:有的支持完整软件模型,有的简化了任务模型;数据类型定义有细微差异;标准库功能块的参数可能不同;厂商扩展指令更不可能通用。我的经验是,把IEC 61131-3理解成“共同的语言框架”而不是“完全一致的执行环境”更准确。能做到平台无关的,是程序结构和核心算法的设计,而不是工程文件本身。
7.2 坑:VAR_PERSISTENT不是万能的,掉电恢复也有讲究
VAR_PERSISTENT变量掉电会保持,但恢复时机和顺序有讲究。有些平台在上电瞬间先把持久化变量恢复到初始值,再加载备份值,如果程序在两者之间执行了某些逻辑,就可能产生不必要的边沿触发。我在一个现场就碰到过:一个重置计数器用了VAR_PERSISTENT,断电重启后计数是恢复了,但“计数器到达设定值”的那次上升沿没有重新产生,导致后续动作没有执行。这种问题非常隐蔽,解决方式是让恢复后的状态在程序里显式判断,而不是依靠边沿脉冲。
7.3 误解:“任务周期设得越小,实时性越好”
如果你把两个任务周期都设成1ms,但系统CPU根本没法在1ms内跑完所有程序,会发生什么?结果是任务发生溢出,系统反复触发看门狗,或者部分周期被跳过,实际行为比不设还差。任务周期应结合程序实际执行时间和实时需求来定,不是越小越好。我见过有人把报警扫描任务也设成5ms,纯粹是浪费CPU,完全没有必要。
7.4 坑:全局变量赋值混乱,程序时间一长彻底失控
前面讲过全局变量别乱用,这里再补一个具体案例。有次我排查一个设备偶发误动作的问题,发现一个运行状态位在程序里被六个不同的FB分别读取和改写,其中两个FB的动作互相矛盾,而且谁修改它完全取决于调用顺序。最终状态变成了“最后一个写入者说了算”,但这种“最后写入者”关系在梯形图里几乎不可见,排查花了整整两天。从那以后,我对全局变量实行了严格纪律:谁定义、谁写入、谁读取,注释写清楚;跨设备状态必须通过接口传递,而不是共享内存。
7.5 误解:“ST是全能的,所以只学ST就够了”
ST的确很强大,但它在某些场合是低效的。比如一个很简单的安全联锁回路,在梯形图里五秒钟就能画出来,任何电气工程师一看就懂;用ST写也行,但可读性不如LD直观。而且,在现场维护的工程师不一定熟悉ST,图形化语言对他们的友好度更高。我的建议是:作为个人能力,ST必须精通;但作为工程项目,语言选型要考虑团队的维护习惯和你交付后的长期可维护性。
7.6 坑:模拟量滤波时假设扫描周期固定,结果精度莫名其妙变差
这是我自己早年犯过的错误。我在ST里写了一个一阶低通滤波,公式是按照固定采样时间推导的,但程序被放在了一个不固定的扫描周期环境里,导致滤波系数失真,输出值波动异常。后来把滤波程序放进了固定的周期任务里,并显式读取任务周期时间参与计算,问题才消失。这里要记一句话:任何依赖时间的算法,必须先确认采样周期是否固定;如果不固定,请用系统时钟来计算实际的间隔时间。
7.7 关于调试工具的坦诚分享
标准的软件模型和编程语言在纸面上很优雅,但实际调试时,不同平台的在线监控效果差异很大。有的平台支持在ST代码里直接修改变量并继续运行,不行得先停止再修改再下载;有的平台对FB实例在线监控做得很好,有的则只能看到实例整体值而看不到内部变量。我建议在项目选型阶段就把调试体验纳入考察范围,否则后期调试效率会很难受。这个点标准没有规定,但工程体验上非常真实。
写到这,我想把结尾落在最实在的地方。我自己的习惯是,新项目拿到手,先不急着打开编程软件,而是用一张纸把整个控制系统按“配置—任务—程序—FB”的层级画一遍:哪些功能放高速任务,哪些放慢速任务,哪些设备需要建FB,哪些数据需要持久化,硬件地址和符号变量如何映射。这张纸往往半小时内就能写完,但它决定了后面几个月的写代码和调试体验。标准的意义从来不是写代码时多一个框框,而是在你还没写第一行指令之前,帮你把思路摆到正确的位置上。希望这篇文章能帮你把IEC 61131的基础框架搭扎实,下次再有人问起这个标准,你可以从容地告诉他:这不是条条框框,这是PLC工程师共用的语言。