ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PLC编程核心元素:关键字与常数的规范用法解析

PLC编程核心元素:关键字与常数的规范用法解析 干PLC这行的人大概都有过这样的经历程序写多了之后回头翻看发现最耗时间的往往不是那些复杂的算法逻辑而是“这个常数当初为什么设成12.5”“MOTOR_HOLD_TIME到底是哪条产线定的参数”之类的问题。我自己从做数控设备转做CoDeSys平台以来交了不少学费才明白一段ST程序能不能在多年后被自己和同事一眼看懂、安稳改对常常取决于最基础的关键字和常数用得规不规范。这一篇继续“PLC编程公用元素”系列专门把关键字与常数这两个“程序的核心词汇与固定值”掰开揉碎地讲一遍。这两个东西看似基础其实是整套IEC 61131-3标准里最容易被忽略、又最影响工程质量的部分。关键字规定了你“能说什么”常数规定了你“定得死不死”。所以我一直对刚入门的工程师强调先不急着学一堆高级功能块把VAR声明写规范了把常量规划好了你的程序就成功了一半。这篇的内容适合正在学CoDeSys标准编程语法、想从“抄程序”过渡到“写程序”的同行也适合准备在团队里统一编程规范的同学参考。1. 关键字与常数PLC程序的两类“基础设施”1.1 关键字是什么——编译器眼中“有名有姓”的词关键字在CoDeSys里就是一组预先定义好的保留字比如IF、WHILE、VAR、BOOL、TRUE这些它们构成了ST语言的语法框架。你可以把它类比成说话时的“连词、标点、时态标记”——没有这些词句子就组不起来反过来你也绝对不能把“我”这个人名改成“了”这个助词否则整句话就乱套了。从编译器的视角看所有ST源代码都要经历分词和语法解析的流程先拆成一个个最小的词法单元再按语法规则检查结构是否合法。关键字就是这棵语法树的固定分支节点编译器看到IF就知道后面必须跟一个条件表达式看到END_IF就知道这个判断语句收尾了。所以关键字是不能被重新定义的你声明一个变量叫IF编译器会直接报错因为它无法区分这是语法结构还是变量名。CoDeSys的关键字实际上包含两部分第一部分是IEC 61131-3标准定义的公用元素比如VAR、IF、CASE、FOR这些第二部分是CoDeSys自己预留的扩展关键字比如RETAIN、PERSISTENT、AT这类用于内存保持和地址绑定的词。这两类统称为保留字规则都一样——只能按固定含义使用不能另做他用。有个非常实用的鉴别技巧在CoDeSys的编辑环境里默认会把关键字高亮显示。我在工程实战中几乎每天都用这个特征——写代码时只要看到一个标识符被系统自动变了颜色立刻就知道这是保留字不会再把它当变量名去用。不同CoDeSys版本的高亮配色会略有差别但逻辑一致本质就是编译器在用颜色悄悄告诉你红线范围。1.2 常数为什么是程序质量的“隐形分水岭”常数分两类一类是字面常数就是直接在代码里写的数字、TRUE、T#2s这种另一类是命名常数也就是先在声明区里给一个名字、再关联一个固定值。初学者往往会觉得字面常数写起来更快但随着程序规模变大字面常数到处散落会成为维护的噩梦。打个生活化的比方常数就像你家墙上贴的电器标签写清楚“空调插座220V”当然能用但如果在每个插座旁边都只写个“220V”等你要插电饭煲的时候还得逐个回忆哪一路是厨房的。同理代码里到处写1500、3s、32这种“魔法数字”三个月后你自己回来看都会发懵更别说交接给同事了。命名常数真正解决的是“一改全改”的问题。现场调试时经常要调延时时间、限位行程、报警阈值如果你把这些值用命名常数统一收口在一处声明区改的时候只需要动一个地方逻辑代码完全不用碰。这个概念贯穿整个IEC 61131-3的公共元素体系也是这一篇把关键字和常数放在一起讲的原因——二者一个管语法骨架一个管数据取值配合起来才能写出真正规整的PLC程序。2. CoDeSys关键字全景盘点与使用边界2.1 声明类关键字搭起程序的骨架声明区是ST程序的入口也是初学者最容易小看的部分。VAR关键字用于声明局部变量在功能块或程序内部有效每次调用时默认不保持VAR_INPUT声明输入变量相当于功能块的“入口参数”调用方只能从外部给它赋值VAR_OUTPUT声明输出变量相当于功能块的“返回值通道”VAR_IN_OUT则比较特殊它是输入输出双向的引用参数传递的是变量本身而不是拷贝使用时需要特别谨慎避免在不经意间修改了外部实参。VAR_GLOBAL用于声明全局变量可以在多个POU之间共享数据。这里我必须强调一句PLC编程里滥用全局变量是程序腐化的最快路径。全局变量破坏了模块的封装性A程序改了某个全局量B程序的行为可能莫名其妙跟着变排查难度成倍上升。我个人的习惯是能不用就不用跨POU数据优先通过输入输出参数接口传递。VAR_TEMP声明临时变量在每个扫描周期开始时会使用未初始化的内存区一个周期内有效常用于功能块内部的中间计算。VAR_CONSTANT声明命名常数这是后面第3章要详细展开的内容。VAR_EXTERNAL用于引用外部变量常用于跨工程文件或者库访问。组织层面还有几个关键字需要记住PROGRAM声明一段主程序FUNCTION_BLOCK声明功能块FUNCTION声明函数METHOD和PROPERTY用于面向对象扩展定义功能块的方法和属性TYPE和STRUCT配合用于自定义数据类型和结构体。这一整套声明关键字构成了CoDeSys程序的基本骨架也是后续架构设计的基础。2.2 流程控制类关键字指挥程序的走向流程控制关键字是ST语言的核心执行逻辑骨架。IF-THEN-ELSIF-ELSE-END_IF是最常用的条件判断结构注意ELSIF不是ELSEIF这是IEC标准与很多通用语言不同的拼写踩坑频率极高。CASE-OF-END_CASE适合对整数或枚举值做多分支选择比连续嵌套多个IF要清晰得多。循环方面FOR-TO-BY-DO-END_FOR是确定性循环适合已知循环次数的情况WHILE-DO-END_WHILE是前置判断循环先判断条件再执行REPEAT-UNTIL-END_REPEAT是后置判断循环至少执行一次。这三种循环在CoDeSys的扫描周期模型下都需要谨慎使用尤其是WHILE和REPEAT一旦条件写错程序可能在一个扫描周期内死循环导致整个PLC任务看门狗超时。EXIT关键字用于强制跳出当前循环RETURN则用于提前结束当前POU的执行相当于函数返回。逻辑运算关键字也归在流程控制的大范畴里AND、OR、XOR、NOT是基础逻辑运算还有两个容易忽略的短路关键字AND_THEN和OR_ELSE。普通AND会计算两侧的所有表达式而AND_THEN在左侧条件已经不满足时会直接跳过右侧计算。这个特性在防止数组越界、除数非零判断时尤为关键。比如要判断“索引在有效范围内且对应元素大于5”用普通AND的话即使索引无效元素仍然会被访问——这可能导致运行时错误或读取脏数据用AND_THEN就能实现真正的短路保护。2.3 容易踩线的关键字边界与命名规范CoDeSys中IEC 61131-3标准明确关键字不区分大小写这意味着你不能用IF、If、if三种写法规避保留字限制——无论怎么变体编译器都识别为同一个关键字。这一点和C语言不同很多从C转来的工程师会在这上面栽跟头。关键字类别常见保留字示例数据类型BOOL, BYTE, WORD, DWORD, INT, DINT, REAL, LREAL, TIME, STRING声明区VAR, VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_GLOBAL, VAR_TEMP, VAR_CONSTANT流程控制IF, THEN, ELSIF, ELSE, END_IF, CASE, OF, FOR, TO, BY, DO, WHILE, REPEAT运算逻辑AND, OR, XOR, NOT, MOD, AND_THEN, OR_ELSE程序组织PROGRAM, FUNCTION_BLOCK, FUNCTION, METHOD, PROPERTY特殊值TRUE, FALSE, NULL实际编程时的命名规范我建议遵循行业里比较通用的匈牙利前缀法布尔变量用b开头如bMotorOn整数用i开头如iCounter浮点用r或f开头如rTemperature时间变量用t开头如tDelay字符串用str开头如strProductName常量则统一用大写全称加下划线如MOTOR_RUN_HOLD_TIME。这样的好处是扫一眼变量名就能知道类型和用途逻辑错误在阅读时就能被肉眼拦截大半。还有一类容易踩的坑是CoDeSys扩展保留字比如AT地址分配、RETAIN保持区变量、PERSISTENT持久化变量。这些词在标准里没有但CoDeSys编译器识别它们。有些人是第一次接触就拿来当变量名然后百思不得其解为什么编译报错。遇到这种情况最快的方法是右键该标识符查看系统提示看它是不是被当作保留字高亮了。3. 常数的定义方式与CoDeSys特有语法3.1 数值常数进制、浮点与类型前缀数值常数是程序里最常见的一类固定值。IEC 61131-3标准支持多种进制CoDeSys中十进制直接写即可十六进制用16#前缀二进制用2#前缀八进制用8#前缀。例如16#FF表示2552#1010表示108#17表示15。值得一提的是CoDeSys允许用下划线分组提高可读性比如2#1010_1010这一点在配置IO地址掩码时非常实用。浮点常数默认按REAL类型处理也可以写成科学计数法比如1.5E3表示1500.0。这里有一个隐蔽的坑直接写的整数常数默认按最小适配类型处理但如果你把它直接赋值给一个LREAL或DINT变量可能会触发隐式类型转换警告。要避免这个问题CoDeSys提供了类型化常数的语法在数值前面加上类型前缀并配合#符号例如INT#5、DINT#10、REAL#3.14、LREAL#2.71。这种写法让常数自带类型信息赋值时编译器可以精确匹配极大减少隐式转换带来的精度损失和编译告警。还要特别提醒一句在CoDeSys里把浮点常数赋给整型变量或者反过来把数值常数赋给TIME类型变量都会出现类型不匹配错误。很多新手在定时器参数那里直接写5系统报错后一脸茫然——因为定时器需要的是TIME类型字面量正确写法是T#5s。3.2 布尔、时间与字符串常数布尔常数很简单就是TRUE和FALSE两个关键字但要注意它们也是保留字不能用作变量名。时间常数是PLC编程里最常用、也最容易出格式问题的一类。CoDeSys使用T#或TIME#作为前缀后面跟时间单位组合。毫秒写ms秒写s分钟写m小时写h天写d。例如T#500ms、T#3s、T#2m、T#1h30m20s。组合时间常数可以直连多个单位比如T#1d2h3m4s5ms系统会精确计算出总时长并存储为TIME类型。要注意单位后不加空格小时部分不要写超过23的大数字——正确表达25小时应该用T#1d1h而不是T#25h后者虽然可能被某些版本接受但可读性极差。字符串常数用单引号或双引号包裹。CoDeSys中STRING类型的字面量通常用单引号例如Running和Stopped这样便于在HMI或数组IO中使用。字符串常数在报警信息处理、设备名牌传递、配方名称管理等场景下非常重要。我还见过有人把字符串常数和CHAR类型混淆CHAR是单个字符STRING才是字符串两者声明和赋值语法不同需要区分清楚。3.3 命名常数从魔法数字到工程可维护性命名常数是通过VAR CONSTANT或VAR_GLOBAL CONSTANT声明的常量集合。代码写起来是VAR CONSTANT MOTOR_RUN_HOLD_TIME : TIME : T#3s; MAX_PRODUCT_COUNT : INT : 500; ALARM_MAX_TEMP : REAL : 85.5; END_VAR在VAR CONSTANT块里每个名称一旦被赋予固定值程序运行期间就不能再修改。如果试图在逻辑代码里对它赋值编译器会直接报错。命名常数的好处前面已经提过可读性、可维护性、一处修改全局生效。需要补充的一个机制是常量折叠。CoDeSys编译器在编译阶段会把只包含常量的表达式提前算好结果。比如你在代码里写MAX_PRODUCT_COUNT - 1只要MAX_PRODUCT_COUNT是常量编译器就会直接编译成499运行时没有额外计算消耗。这算是白送的性能优化同时还能让代码语义更清晰。不过硬币的另一面是如果某处本应写成变量的数据被误定义成了常量程序运行过程中该值永远不会变化这种故障排查起来非常隐蔽。所以我的建议是——规则类、配置类的固定值才用命名常数运行期可调、要跟配方走的数据就用变量加HMI绑定千万不要图省事把所有参数都写成常量。4. 联动实战用关键字和常数重写一个电机启保停程序4.1 先看一个“裸写”版本的问题拿一个最常见的电机启保停控制来做实例。原始版本可能是网上抄来的逻辑长这样VAR bStart : BOOL; bStop : BOOL; bRun : BOOL; END_VAR IF bStart THEN bRun : TRUE; END_IF; IF bStop THEN bRun : FALSE; END_IF;这段代码的问题是显而易见的第一两个IF是顺序关系同时按启动和停止时后执行的IF bStop会把bRun清零逻辑上“停止优先”。这个行为本身也许是对的但代码表面上根本看不出来语义全靠藏在执行顺序里的副作用。第二整个逻辑没有互锁机制如果启动按钮粘连或者程序被误写电机可能无法正常停机。第三所有条件都是布尔裸变量没有任何滤波和延时缓冲。对照这篇讲的关键字和常数可以发现这段代码至少应该用CASE或显式互锁结构来重构并且把延时参数提取成命名常数。这就是关键字和常数在实际程序里发挥作用的地方——不只是语法规则更是程序结构的约束。4.2 用声明关键字和常数重构规范版本我在现场写这类逻辑时会先考虑控制需求把参数和状态提炼出来。假定需求是电机上电后按启动按钮运行按停止按钮停止同时要求启动后至少保持运行3秒才能被停止防止频繁启停损坏设备。重构后的代码分成两个部分。第一段是声明区把定时器值和按钮滤波时间全部用命名常数定义VAR CONSTANT MIN_RUN_TIME : TIME : T#3s; BUTTON_FILTER_TIME : TIME : T#50ms; END_VAR VAR bStartRaw : BOOL; bStopRaw : BOOL; bRun : BOOL; tRunTimer : TON; tBtnFilter : TON; END_VAR第二段是核心逻辑用TON定时器实现按钮滤波用时间常数做保持计时。TON功能块的PT端直接填MIN_RUN_TIME常数这样将来现场觉得3秒太短只需要改声明区一行逻辑部分完全不用动。tBtnFilter(IN : bStartRaw, PT : BUTTON_FILTER_TIME); IF tBtnFilter.Q THEN bRun : TRUE; tRunTimer(IN : bRun, PT : MIN_RUN_TIME); END_IF; tRunTimer(IN : bRun, PT : MIN_RUN_TIME); IF bStopRaw THEN IF tRunTimer.Q THEN bRun : FALSE; END_IF; END_IF;这里有几个细节值得说明第一bRun被置TRUE后tRunTimer的IN就为TRUET#3s后Q输出为TRUE此时如果再来停止信号bRun才能被清掉。第二启动用滤波后的按钮信号避免现场抖动造成误触发。第三停止信号直接取原始值保证急停按钮的响应速度不受滤波影响——这是工程上的一个常见决策。对照原来的裸写版本新版本多用了四个命名常数、一个TON功能块、一个略显复杂的IF嵌套结构。代码行数增加了但逻辑清晰度提升了一个档次。尤其是MIN_RUN_TIME这个常数它把“为什么程序要让它多转几秒”这个业务规则直接摆在了声明区后续维护的人一眼就能看到不需要去猜测3秒是哪来的。这正是这个实战想传达的核心思想关键字决定代码能不能跑常数决定程序好不好改。4.3 编译验证与在线调试看板写完代码后在CoDeSys里点编译如果一切正常信息窗口只会有零条错误零条警告。我建议把编译选项里的“严格模式”打开这样所有隐式转换都会被列为警告或错误逼着你把常数类型写规范。比如延迟PT的值如果不写T#3s而直接写3严格模式下立刻会提示类型不匹配从根源堵住隐患。在线调试时我在监视窗口同时观察bStartRaw、bStopRaw、tRunTimer.Q和bRun几个关键变量。启动瞬间看滤波定时器的Q是否按预期延迟停止时看tRunTimer的当前值和Q翻转是否一致。这个调试手法屡试不爽——很多看似莫名其妙的启停故障其实问题都出在定时器还没计时完成就被反复重启上。这个时候也能体会到定义MIN_RUN_TIME常数的好处一旦确定了要延时就不用在多个功能块里到处找时间参数监视窗口一目了然。5. 常见报错与排查技巧实录5.1 关键字相关的编译错误速查错误现象可能原因解决办法提示“未知标识符”变量名使用了保留字修改变量名使用前缀命名法提示“语法错误”或“意外的关键字”IF/END_IF不配对或ELSIF拼错检查结构化语句是否成对建议使用代码折叠辅助提示“需要表达式”THEN后面没有写判断结果检查条件表达式是否完整声明区重复定义同名变量同名标识符在多个声明区出现统一声明到最合适的VAR块下逻辑运算符号不匹配普通AND与AND_THEN混用明确是否要短路优先用短路版本规避风险ELSIF拼错成ELSEIF是我见过最多的一类编译错误。在CoDeSys里正确拼法是ELSIF中间没有E。每次报“意外的关键字”时先检查是不是这里写错了往往能省下十分钟。还有一个和关键字强相关的坑是变量名虽然不撞任何一个保留字但命名风格和关键字看起来高度相似比如把变量命名为TIMER、IO_CHANNEL。虽然编译器不报错但阅读时极容易和关键字TIME、IO变量混淆。团队协作时最好在命名规范里明确所有自定义标识符必须带类型前缀且避免全大写词。5.2 常数使用中的隐蔽陷阱常数类型不匹配是编译期最常见的警告来源。默认的整数字面量在CoDeSys中被视为INT类型如果你把它喂给DINT变量会有隐式转换警告喂给REAL类型则会提示REAL与INT转换可能损失精度。解决办法就是前面说的类型化常数DINT#100、REAL#2.5明确告诉编译器你要的是什么类型。时间常数格式的坑也很隐蔽。T#3s没问题但T#0.5s里的小数点在某些版本里会引发异常正确写法是T#500ms。还有人在表达式里写T#1.5m表示1.5分钟这也是有风险的——应该写成T#1m30s。总之时间常数千万不要在小数和进制上做文章老老实实用整数加多个单位组合表达。然后是共享常量的波及范围问题。项目跨多个POU引用同一个TL等全局常量改动时确实只改一处但这“一处”改完后所有引用点都会悄悄变化。我在实际项目中遇到过把某个延时常量从5s改成8s后产线上有一个吹气阀的动作时间跟着变了当时排查了很久才定位根因。所以命名常量最好在声明处写明影响范围注释改动前先用交叉引用列表查看所有使用位置。5.3 我踩过的最贵的一个坑最后分享一个我自己早年踩过的坑在CoDeSys的FOR循环里把循环变量声明成WORD类型结果循环次数超过32767之后出现了隐式转换问题程序跑到半夜才爆出偶发故障。后来把所有数组下标和循环控制变量统一改成DINT或UDINT并配合常量折叠和显式类型转换这个隐患才算彻底排除。在CoDeSys这个平台上关键字的规则是死的但人的使用习惯可以相差十万八千里。我在团队里推行的习惯是常量必须在声明处写清楚“含义、单位、允许范围、修改影响范围”四要素开头命名用“对象_属性”全大写例如MOTOR_RUN_HOLD_TIME、ALARM_MAX_TEMP。这样任何同事接手都能快速定位。另外坚持一个字面常数出现次数超过两次就转成命名常量的原则配合代码折叠注释程序维护成本会大幅下降。做设备维护时最怕的不是程序跑飞而是别人改了一个全局常量导致整个生产节奏变化却没有改注释。基础的东西守规矩复杂的东西才有机会不翻车。
返回列表