资讯详情

三菱PLC ST编程:从常量到枚举,告别写死代码

📅 2026/10/30 14:51:23 | 华诺云谱 👁 阅读
三菱PLC ST编程:从常量到枚举,告别写死代码
干了十来年三菱PLC最近看到群里有人吐槽一个很有意思的标题“三菱ST常量vs枚举1700写死8处漏改3处”。我一看就乐了这简直是PLC程序员最经典的翻车现场之一。所谓“写死”就是在ST语言、梯形图或者结构化文本里直接把数字、编号这种“魔法数字”硬编码在逻辑里。比如IF DI1 1 THEN、REF[1700] : 5这样的代码看着简洁但改起来就是一场灾难。标题里说的“1700写死8处漏改3处”大概率是有人在1000多行代码里用了8个同一个含义的原始数字常量后来设备类型变了需要整体更换编号结果程序里只改了5处剩下3处漏网导致设备死活不对——这种故障在调试现场非常典型看起来像硬件坏了其实全是软件里的老古董数字在作祟。今天这篇文章就不绕弯子专门把三菱ST语言里常量和枚举这两个概念掰开揉碎讲讲为什么“写死”是万恶之源以及怎么用常量和枚举把代码从“能用”提升到“好改”。内容会比较偏实战适合正在用GX Works3开发FX5U、L系列和Q系列的小伙伴参考。1. 先从痛点聊起你写的程序为什么越来越难改先问个特别基础的问题你在写ST程序的时候有没有直接用过“裸数字”我见过太多种写法的了。比如在三菱FX5U的ST程序里有人会直接写IF D100 5 THEN (* 这个5代表什么没人知道 *) END_IF;再比如报警代码比较设备状态判断甚至轴号的选择全都直接塞数字。这在一开始是没有问题的写着还很快因为不需要动脑子去定义名字。但是当程序到1000行、2000行当这个逻辑被维护半年、一年之后那个“5”到底是“等待5秒”还是“模式5”就只有天知道了。1.1 为什么说“写死”的代码会越改越乱在标题的场景里“1700写死8处漏改3处”这种事故核心原因就是数字散落各地彼此没有任何关联。举个例子假设你在一台设备上用了气缸伸出位置的到位传感器。不同工位可能对应不同传感器地址编号整套设备换了型号后可能所有传感器的接线编号都变了。如果你在一百个地方直接写了IF X60 THEN那么换型时你要一个个去找X60找得到就改找不到就漏。但是如果你用的是常量VAR CONSTANT CYL_A_AT_POS : WORD : 1600; (* 地址1600 *) END_VAR程序里只需要引用CYL_A_AT_POS那么换型的时候你就只有一个地方要改那就是常量定义区。“写死”最可怕的地方在于它不是改起来难而是“漏改”了你根本发现不了。改错数字程序编译可能会报错。漏改数字程序编译不会报错但是设备动作就是不对而且极难排查。1.2 你以为你是在写程序其实你是在埋雷我们做一个很简单的推理。假如一个项目中有5个设备编号需要用到同一个“设备类型”数字0x03。代码里直接写IF DEV_CODE 3 THEN用了8次。突然有一天你发现实际设备类型编号标准更新了3要改成7你会怎么做你会按下CtrlF查找“DEV_CODE”可惜你程序里根本没这个名字。你只能查找“ 3”但是结果会让你崩溃程序里还有大量的D[3]、#3、TIMER_3、M3等无关结果。你在一片噪声里找了半天改了其中8个里的5个第6个在子程序里你没看见第7个在第2个功能块里第8个在FB的局部注释底下藏着。最后的结果就是标题说的1700行代码8处写死漏改3处设备彻底乱套。所以“写死”这个问题的本质不是代码量而是可搜索性和唯一性。常量解决的就是这两个问题程序里出现的名字是唯一的你搜索CYL_A_AT_POS一搜一个准绝不会搜到无关的“1600”。2. 三菱ST语言里的常量最朴素的解药2.1 GX Works3里常量怎么定义在三菱ST语言GX Works3中常量Constant的定义非常明确分为“全局常量”和“局部常量”。在全局标签编辑器里你可以直接建一个标签把“数据类型”定义为“CONSTANT”或使用“VAR CONSTANT”语法。在ST程序里最常用的写法是在变量声明区VAR CONSTANT C_MAX_SPEED : REAL : 60.5; C_NODE_ID : WORD : 5; C_ALARM_TIME : TIME : T#5S; END_VAR这里的关键词是VAR CONSTANT ... END_VAR。定义了之后程序正文里只能引用不能再赋值。如果你不小心写了C_NODE_ID : 10;编译系统会直接报错“表达式必须含有常量值”之类的提示。2.2 定义常量的三个潜规则实际工程中我建议遵循三条规则第一常量名要有明确前缀。我习惯用C_开头因为后面还要配合全局标签和局部标签用一个统一前缀能在搜索的时候减少噪声。否则你搜“SPEED”能搜出几十个变量。第二单位要写在注释里。三菱的ST变量注释虽然功能简单但足够好用。在常量后面加上单位(* mm/s *)能避免以后维护的人猜来猜去。第三常量要集中放在一起。尽量把所有工程相关的常量放在同一个GTD全局标签的结构化分组里而不是散在若干个FB里。这样设计变更时打开标签表一眼就能看到哪些是需要改的。2.3 常量的致命短板改了数值但改不断逻辑关系有一个非常容易踩的坑常量虽然在“搜索”和“唯一性”上比裸数字好但它本质上还是一个“共享变化值”。什么意思还是举例。你定义了VAR CONSTANT C_STATE_IDLE : WORD : 0; C_STATE_RUN : WORD : 1; C_STATE_STOP : WORD : 2; END_VAR然后在ST里写IF CurrentState C_STATE_RUN THEN看起来很完美。但是如果你某一天手滑把C_STATE_RUN的数值改成3那么所有引用它的地方都会跟着变成3。可怕的是程序编译不会报错因为3也是合法的WORD值。也就是说常量能防“漏改”但防不了“手工篡改”和“误赋值”。再说一个更尴尬的场景假设你有8个不同的状态量它们都用WORD类型存着你都用常量给它们起了名字。但有一天你要在设备上增加第9个状态你发现所有常量都在一个池子里没有约束。你的CurrentState变量既可以等于C_STATE_RUN也可以等于C_STATE_STOP编译器根本不关心它们各自是什么。这就引入了枚举——它才是真正的类型安全工具。3. 枚举把“允许的取值”锁进盒子里3.1 枚举是干什么的“枚举”这个词其实很直白就是把一组相关的常量“收集”成一个类型。你在ST里定义了一个枚举类型后续凡是声明为这个类型的变量就只能在这个列表里取值。超出列表的值编译器直接拒绝。在三菱GX Works3里枚举类型是通过“全局标签”或者“共用标签”里的“数据类型”创建来实现的。操作路径大概是工程项目 → 全局标签 → 数据类型 → 新建 → 选择“枚举类型ENUM”。举例我们可以创建一个名为MODE_TYPE的枚举TYPE MODE_TYPE : ( MODE_MANUAL, (* 手动 *) MODE_AUTO, (* 自动 *) MODE_STEP, (* 步进 *) MODE_RECOVER (* 复位恢复 *) ); END_TYPE定义之后你在FB或者程序里就可以声明一个变量VAR CurrentMode : MODE_TYPE; END_VAR然后你可以赋值CurrentMode : MODE_AUTO;但如果你试图写CurrentMode : 3;编译就会报错因为3不是MODE_TYPE类型。3.2 枚举和常量到底在内核上差在哪这就要说到标题的核心了。“常量 vs 枚举”都是一组名字对应一组数字但它们的本质区别在于类型契约。常量是“全世界都可以用”的裸值。它没有一个“类型”作为容器IDE不知道你到底该把哪个常量给哪个变量。枚举则自带类型约束你的变量一旦声明成某个枚举类型编辑器就会在编译阶段帮你把关。用一句大白话说常量防的是“找不到”但防不了“用错”。枚举防的是“用错”把错误拦截在编译期。比如你在ST里有1700行代码其中8处直接写了“4”这个数字它们可能有的含义是“模式4”有的是“第4号轴”有的是“4mm/s”。这种情况下用常量你只是把“4”变成了MODE_4、AXIS_4、SPEED_4从搜索角度来说方便了但你还是有可能把AXIS_4误赋给一个Mode变量。而用枚举AXIS_4的变量类型是AXIS_NO_ENUMMode变量是MODE_TYPE它们之间根本没有赋值通道你连错误的机会都没有。3.3 三菱ST枚举的三点实战经验第一别把枚举值直接和整型做算术运算。GX Works3对枚举类型的算术支持比较弱。不能直接写CurrentMode : CurrentMode 1;来切换下一个模式。如果你要循环切换建议加一个转换函数或者定义一个WORD辅助变量替代。第二枚举在HMI关联时要小心。人机界面上的下拉列表通常需要整数编号。你在触摸屏上选择的“模式1”最终可能会以WORD类型的数值发给PLC。这时可以在PLC内部做一个映射IF RawModeWord 0 THEN CurrentMode : MODE_MANUAL; ELSIF RawModeWord 1 THEN CurrentMode : MODE_AUTO; END_IF;这样可以避免在HMI上直接“裸写”枚举编号。第三枚举定义的位置很关键。我强烈建议在全局标签里定义枚举数据类型而不是在FB内部定义。因为如果你在功能块里定义的类型外部程序是无法用来声明变量的将来无法做统一管理和扩展。全局定义了别的FB和主程序才能共同使用多人协作时大家的基础类型才会统一。4. 光会用还不够你得会选常量还是枚举4.1 一张表说清怎么选型每次做内部培训时我都会画一张类似的表这次直接分享出来。维度裸数字魔法数常量枚举查找修改极难噪声多容易集中改动容易集中改动防漏改完全没有基本可以有命名提醒完全可以防止错误赋值完全没有较弱类型还是INT/WORD极强类型锁死编译检查能力无无约束力强约束力HMI通信友好度很高较高中等需手动转换适合场景临时脚本、快速调试全局配置、传感器编号、固定参数模式、状态、报警码、类型标识代码阅读体验很差一般偏上极佳从这张表能看出来常量适合“这个数字是个物理属性”的场景比如速度上限、轴脉冲数、设备地址编号传感器接点地址。枚举适合“这个数字是个分类/模式/状态”的场景比如手动、自动、回原点、异常报警类型A、B、C。4.2 实际工程里我会怎么做以三菱FX5U的项目为例。我一般会在全局标签区做三组定义第一组物理性常量用VAR CONSTANT直接定义系统参数和设备连接编号。比如VAR CONSTANT C_FEED_SPEED_1 : REAL : 500.0; (* mm/min *) C_SETTLE_TIME : TIME : T#500MS; C_CONV_ADDR_MAIN : WORD : 1; END_VAR第二组设备状态逻辑用枚举类型。我会在全局标签里新建枚举STATION_STATE包含STS_INIT、STS_READY、STS_BUSY、STS_DONE、STS_ALARM。第三组报警码用另一个枚举ALARM_CODE包含ALM_ESTOP、ALM_OVERRUN、ALM_MAINTENANCE等。这样整套程序的“数字”全部都退居幕后程序正文里只看到语义化的名字。后期如果要修改编号规则只动对应枚举的赋值就好几乎是改一处完成全部。4.3 什么时候可以反向操作用常量给枚举做底层数值有一种高级玩法推荐给有一定基础的工程师。在GX Works3中BCL三菱MELSEC的ST的枚举类型定义通常是不带显式数值的只靠顺序。但在某些需要把枚举值直接映射到HMI或者通讯协议的项目里就得用到“常量枚举”的组合。做法是定义一个常量表VAR CONSTANT C_MODE_MANUAL : WORD : 16#0100; C_MODE_AUTO : WORD : 16#0200; END_VAR然后在程序中把通讯原始值WORD映射到枚举IF RawMode C_MODE_AUTO THEN CurrentMode : MODE_AUTO; END_IF;这种做法的好处是协议上的编号和程序逻辑解耦。如果通讯协议升级了编号变了只改C_MODE_XXXX这一处程序逻辑全部不变。而CurrentMode继续享受枚举的类型安全检查。5. 实操复盘从1700行代码里挖出“8处写死”并改造5.1 第一步先全面盘查找出所有魔法数字拿到旧的ST代码不要急着写新代码。先老老实实用查找工具把程序里所有的数字量筛一遍。重点查两类IF xx 数字 THEN这种比较逻辑xx : 数字;这种赋值逻辑F3快捷键在GX Works3的ST编辑器里是查找。查找时先查一些常见的小数字0、1、2、3、4、5、8、16。这些数字是重灾区。另外要查16#前缀的十六进制数比如16#03这种往往是通讯协议、报警码、硬件地址。我一般会建一个Excel表格把行号、代码、所在程序块、疑似含义列出来。防止后面改乱。5.2 第二步按“物理参数”和“状态类别”归类命名查出来的数字不要急着全部替换。先分两个篮子物理参数类速度、时间、距离、地址编号→ 定义为常量。状态、模式、报警类型、设备动作类型 → 定义为枚举。这一步非常关键。如果分错后面会出现“拿枚举做参数运算”的尴尬。比如程序里有MODE : 3这明显是“模式”含义命名成MODE_AUTO走枚举。如果是D100 : 3000030000是脉冲频率定义成常量C_PULSE_HIGH。5.3 第三步分批替换一次只改一个FB替换数字是有风险的。如果是1600行代码一次性全部替换编译报错会把你淹没。我习惯把涉及的数字按功能块分批处理。操作顺序先把全局常量和枚举类型定义好。编译一次确保类型定义无误。然后逐个打开功能块把IF X60改成IF C_SENSOR_AT_POS。每改完一个FB编译一次。所有FB都改完后用CtrlF再搜一次原来的数字确认没有遗漏。替换过程中最需要注意的是“数字是不是被用到了数组下标”。比如REF[5]、ARRAY_DATA[2]这种如果5代表第5号配方我建议把它定义成常量C_RECIPE_INDEX_5而不是枚举因为数组下标本质上还是整数运算。5.4 第四步把“黑话”注释补上防住未来的自己改造完不是万事大吉。在整个项目的关键函数入口、状态机切换处务必补上一段注释。三菱ST支持(* ... *)注释写清楚这段逻辑用的“状态枚举”到底名字是什么。我见过有人定义了枚举然而注释还是写的“(* 模式 3 *)”这就等于白弄。注释要和代码里语义化名字完全一致不能脱节。这段改造过程我自己实测大概1900行代码差不多一个下午可以全部改完。花时间最多的是查数字而不是改数字。只要前期规划命名合理中期逐个FB编译后期基本不会出幺蛾子。6. 常见问题与排查技巧实录6.1 编译报错“表达式必须含有常量值”这个报错我在GX Works3中见得太多了。通常出现在两种情况一是你把常量赋值给其他变量然后试图在数组下标里用那个变量。数组下标必须是编译期可确定的常量值。比如C_INDEX : WORD : 5; ARRAY_DATA[C_INDEX] : 100; (* 如果C_INDEX是常量可能可以通过 *)但如果C_INDEX是普通变量这行就报“表达式必须含有常量值”。解决方法是要么用VAR CONSTANT定义真正的常量要么先把变量值复制到一个固定变量再用固定变量赋值。二是使用枚举类型时尝试直接用数字给枚举变量赋值。同样的“表达式必须含有常量值”。正确写法是CurrentMode : MODE_AUTO; (* 不能用 CurrentMode : 1 *)6.2 枚举变量莫名其妙变成“未知值”有时候你从HMI或者通信收到一个整数经过映射写入枚举变量但程序运行中发现枚举变量的值不在枚举列表里。调试的时候不要只盯着枚举变量本身。要看原始WORD值。GX Works3里可以在“标签监视”窗口把枚举类型临时修改为WORD类型如果允许的话或者另外添加一个WORD辅助变量来监视原始值。这个问题的根源多半是HMI下拉列表的索引和你枚举顺序没对上。你枚举是0开始HMI那个控件可能是1开始直接错位。对策很简单在HMI和PLC之间定义一个人机接口专用WORD变量做枚举和WORD的显式转换。不要依赖HMI直接写枚举。6.3 删除魔法数字后触摸屏无法联动很多人把PLC内部数字全部语义化之后发现触摸屏本来能直接读写D区数字现在没法直接读写“枚举”了。因为GX Works3的标记监控虽然能看到枚举类型但实际外部通讯如MODBUS/TCP、三菱MC协议看到的是一个16位整数地址。你在触摸屏上的“数值显示”控件需要指定数据地址比如D100然后它读回来一个整数。这时PLC内要做反向映射RawValue : CurrentMode; (* 可以隐式转换吗要看具体型号 *)如果编译器不让你直接把枚举传给WORD可以用辅助变量RawValue : WORD_ENUM_TO_WORD(CurrentMode);或者自己写一个简单选择分支。注意如果你在GX Works3里建的枚举类型占用的字大小需要配置正确否则可能会出现地址错位。FX5U里枚举类型在标签表上默认占用一个字16位一般够用。6.4 组态软件报警里看不到枚举名最后一个小坑你用枚举定义了几十个报警码但在三菱GX Works3的报警监控或者GT Designer的报警浏览中可能只能显示整数编号看不到对应的文本。因为报警文本是需要单独在GOT画面的系统报警里配置的它不会自动读取你在PLC里建的枚举类型名称。如果要在HMI上显示“手动模式启动中”这种文本你需要单独在GT Designer的“报警”选项卡里把数值和文本一一对应。这个映射关系最好和PLC里的枚举顺序完全一致否则将来维护时对不上号很麻烦。我个人的建议是快速维护型的报警可以直接用枚举但HMI的报警配置仍然要走一遍标准流程两个文件的一致性靠注释和版本管理来保证。7. 最后的工程经验分享说了这么多回归到标题那台1700行的设备上。8处写死漏改3处这种问题说大不大说小不小但它暴露的是整个工作流中缺少“抽象”的环节。我们写ST代码本质上是在给设备造一套数字规则但数字本身没有意义只有它们背后的“模式”“状态”“参数”才有意义。用常量和枚举不是为了让代码变得花哨而是为了让错误尽早爆发。写死在设备上的数字往往要等设备动起来才能发现错了写死在编译器的类型不对当场就报错你根本不需要上电。把一部分调试时间省下来去做设备工艺优化不是更好吗我的习惯是从项目第一天就用枚举建状态用常量建参数。一天两天看不出差别项目超过三个月、代码超过一千行的时候维护成本差别会越来越明显。实测下来这种习惯能帮我把调试时间压缩30%以上而且接手项目的人基本不会再打电话问我“这个5代表什么意思”。最后再分享一个小技巧每次你给PLC程序做常量/枚举改造时顺手把Excel版本的变量映射表导出来归档在项目文件夹里。半年后你要改设备编号根本不用打开电脑屏伸手去翻代码直接在Excel里查好语义名再去GX Works3里搜那个名字一种全项目都能改干净。这套组合拳打下来“1700写死8处漏改3处”这种事情会离你越来越远。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑