『7x24小时有问必答』
去年我在佛山一家陶瓷厂,接手一台用了三年的码垛机程序。打开GX Works3,某个FB里一个叫D456_MOVE_TEMP的变量反复出现在23处逻辑里,没有任何注释,连那个“D456”指的是什么轴都查不到。我光梳理这段代码就花了整整一个周末,调试更是噩梦——修一处,炸三处。
后来我强迫自己按一套“代码审查八重境界”重新过了一遍整个项目,把原来2000步的梯形图拆成8个带清晰接口的FB,每个变量加注释和单位,还统一了命名规则。第二次调试?3小时零19分钟结束,一次通过。
今天我就把这八重境界拆给你看。每个境界对应一个审查要点,我踩过的坑、优化的数据、具体的GX Works3操作都写在里了。别担心你是新手——看完最后一个境界,你会恨不得把去年的自己拉出来骂一顿。

第一重:变量命名 —— “一眼就知道它是谁”

你以为
:变量名短一点,写程序快。   
实际上
:三周后你连
M100
是干嘛的都忘了,更别说同事。
这是我最惨的教训。那条码垛机程序里,有个中间继电器M456,后来发现是“夹具在位检测”。如果换成bClampDetect,你还需要查交叉引用吗?
GX Works3里写ST时,我强制自己用这套规则:
前缀示意:  
b   — 布尔(BOOL)
w   — 字(WORD)
dw  — 双字(DWORD)
ud  — 无符号双字(UDINT)
r   — 实数(REAL)
例:
bConveyorRun
wTemperatureADC
rSpeedSetpoint_mm_s
数据
:   
命名方式平均理解时间修改一处平均影响位置数
无规则(M/D随意)4.2分钟7.3处
带前缀+缩写1.1分钟2.1处
踩坑点
:三菱的数据类型
UDINT
在GX Works3里写
UDINT
而不是
DINT
,检查时一定要看变量属性页。   
代码示例
(GX Works3 ST):
``structured text
// 错误示范:无意义变量
M0 := TRUE;
IF M1 THEN
D100 := D100 + 1;
END_IF;
// 正确示范:自解释
bSystemStart := TRUE;
IF bEmergencyStop THEN
udCycleCount := udCycleCount + 1;
// 单位:次
END_IF;
知识点1:三菱MELSEC iQ-R支持CONFIG标签,可以在“工程→工程属性→变量注释”里预设所有变量的注释模板。审查时直接右键“变量一览表”就能导出Excel核对。

第二重:注释规范 —— “别让三个月后的自己猜”

你以为:代码写得像诗,不用注释。  实际上:三个月后连你自己都读不懂,更别说新来的实习生。
有一次我写了一个带PID的梯形图,唯一注释是“PID调节”。后来参数震荡,翻到那个FB,里面竟然有三组不同积分时间,全凭位置顺序区分。我画了整整两小时流程图才搞明白。
GX Works3里注释可以在几个地方写:
1.变量注释:在变量表里右键“编辑注释”,会显示在每个触点旁边。  2.步注释:梯形图中每步都可以加,右键“插入注释行”。  3.FB说明:在FB属性里写“功能说明、输入输出定义、Revision历史”。硬性规定:  
每个变量必须有注释(大纲/单位/范围)。  
每个FB的代码开头写一段文字说明功能。  
修改时必须在Revision历史里写“日期+内容+修改人”。
代码示例(FB说明部分):
structured text
//=======================================================================
// 功能:码垛机Z轴升降速度控制
// 输入:bStart, bTopLimit, bBottomLimit, rTargetSpeed_mm_s
// 输出:rCurrentSpeed_mm_s
// 内部变量说明:
//    udAccelTime_ms — 加速时间(默认500ms)
//    wSpeedRamp — 速度斜坡表指针
// 版本历史:
//    2025-10-01   v1.0   创建(张三)
//    2026-01-15   v1.1   修改加速曲线为S型(李四)
//=======================================================================
知识点2:GX Works3的“工程交叉引用”可以一键搜出所有用到某标签的位置——但前提是你变量名和注释写对了。否则搜出来一堆M地址,等于白搜。

第三重:状态机结构 —— “别用继电器搭逻辑迷宫”

旧方案:一堆
SET RST到处散落,像蜘蛛网。  新方案:用CASE语句或SFC实现状态机。
码垛机原先有个缺陷:当夹具松紧和Z轴升降同时产生互锁时,用梯形图写了17个
RST分支。解决方案是改成状态机——每个状态只允许一个动作,转移条件清晰写在条件段。
GX Works3里状态机有两种实现:
1.ST的CASE结构(推荐用于复杂顺序逻辑):  2.SFC图(适合流程固定的系统)。代码示例(ST状态机):
structured text
CASE udState OF
0: ( 初始化 )
bConveyorRun := FALSE;
bClampOpen := TRUE;
IF bStart THEN
udState := 10;
END_IF;
10: ( 等待工件到位 )
bClampOpen := TRUE;
IF bWorkpieceDetected THEN
udState := 20;
END_IF;
20: ( 夹紧 )
bClampOpen := FALSE;
IF bClampClosed THEN
udState := 30;
END_IF;
30: ( 提升 )
bAxisZUp := TRUE;
IF bAxisZAtTop THEN
udState := 40;
END_IF;
40: ( 移动X轴 )
bAxisXMove := TRUE;
IF bAxisXAtDest THEN
udState := 50;
END_IF;
50: ( 下降 )
bAxisZDown := TRUE;
IF bAxisZAtBottom THEN
udState := 60;
END_IF;
60: ( 松开 )
bClampOpen := TRUE;
IF bWorkpieceReleased THEN
udState := 70;
END_IF;
70: ( 返回起始位 )
bAxisZUp := TRUE;
IF bAxisZAtTop THEN
udState := 80;
END_IF;
80: ( 回X轴原点 )
bAxisXBack := TRUE;
IF bAxisXAtHome THEN
udState := 0;
bDoneCycle := TRUE;
END_IF;
END_CASE;
注意:三菱的CASE语句中,ELSE分支必须写,否则编译器会报错?不,但推荐写一个ELSE处理非法状态(比如udState := 0)。知识点3:状态机把调试时间从3天降到3小时,不是吹的——状态数×转移条件=可测试路径数。比如上面10个状态,路径数大约27条。你每改一次,只需核对受影响的状态转移,而不是全局继电器。

第四重:错误处理 —— “不留给现场惨叫的机会”

痛点:设备停摆后,操作工说“没报警”。我检查程序才发现,一个
INT超限没处理,整个轴直接停止,没任何信息。
GX Works3支持系统报警和用户自定义报警。审查时必须检查:
1.每个数值运算都考虑溢出:比如
ADD后检查进位标志SM729(超限位),或加LIMIT函数。  2.每个输入指令都检查超时:比如轴移动后,在规定时间内未到位,触发超时报警。  3.每个通讯指令都检查错误码:比如G.READ后检查SM1400(通讯错误)。代码示例(带错误处理的ST):
structured text
// 轴移动指令带超时保护
udTarget_Steps := 10000;
MOVEAXIS_A(udStepCount := udTarget_Steps,
bExecute := TRUE);
// 启动超时定时器(2000ms)
TON_Timeout(IN := bExecute,
PT := T#2000ms);
IF TON_Timeout.Q THEN
// 超时未到位
bAlarmAxis1_Timeout := TRUE;
// 停止轴
STOPAXIS_A();
// 记录错误码
wErrorCode := 16#0A01; // 轴超时
// 触发系统报警(使用GX Works3的报警功能)
// 可以在“标签→系统报警”里配置报警变量
END_IF;
// 检查轴状态
IF NOT AXIS_A.STATUS.B_READY THEN
bAlarmAxis1_NotReady := TRUE;
wErrorCode := 16#0A02;
END_IF;
对比:  
无错误处理:故障平均恢复时间40分钟,需要查程序、连线、断电。  
有错误处理:故障恢复时间5分钟,操作工看HMI报警号就知道换什么。
审查时用GX Works3的“程序检查”功能(项目→检查→程序),会自动标出未使用的变量、未连接的输入——但它不管逻辑正确性。所以必须人工审。

第五重:模块化封装 —— “一个FB解决一类问题”

你以为:复用就是复制粘贴。  实际上:改一个参数要改12个副本,漏一个现场炸。
第6台伺服调试时,我发现4个轴的控制代码几乎一样,只是参数不同。但我用了4个不同的FB实例,每个里面硬编码了轴号。后来加第5台轴时,我复制了第4台的FB,忘了改轴号参数,结果两台伺服同时动作。
正确做法:用一个通用FB,通过输入实例化参数。
代码示例(带参数的FB):
structured text
FUNCTION_BLOCK FB_AxisControl
// 输入参数
VAR_INPUT
bEnable : BOOL;
udTargetPos : UDINT;
wAxisID : WORD; // 1~4
rSpeedSetpoint : REAL;
END_VAR
// 输出参数
VAR_OUTPUT
bInPosition : BOOL;
bError : BOOL;
wErrorID : WORD;
END_VAR
// 内部变量
VAR
bStartCmd : BOOL;
tonTimeout : TON;
END_VAR
// 使用wAxisID选择对应的轴控制指令
CASE wAxisID OF
1:
// 调用轴1的定位指令
POSITIONING_AXIS_1(udTargetPos, bStartCmd, ...);
...
2:
POSITIONING_AXIS_2(udTargetPos, bStartCmd, ...);
...
ELSE
bError := TRUE;
wErrorID := 16#00FF; // 无效轴号
END_CASE
END_FUNCTION_BLOCK
知识点4:三菱的FB支持VAR_IN_OUT(输入输出变量),可以用来传递轴状态结构体。审查时重点看:是否有重复代码可以抽成FB;每个FB的变量作用域是否最小。

第六重:版本控制 —— “别只用GX Works3的自动备份”

痛点:今天改了程序,明天现场说“用原来的版本”,你翻遍电脑找不到。GX Works3虽然有历史备份(工程→备份),但默认只保留最近5个。而且备份文件名是时间戳,你根本不知道哪个版本对应哪天改了什么。方案:使用外部版本管理(比如SVN或Git),配合GX Works3的导出功能。每次修改前先导出一份
._PRJ(项目文件)或._CSV(CSV导出),再提交。审查要点:  检查工程文件夹下有没有.svn.git目录。  
查看“工程属性”里的“版本”字段有没有手动更新。  
检查Revision历史注释是否一致。
知识点5:三菱iQ-R的工程文件本质是XML格式的压缩包(.gx3)。你可以用文本编辑器打开备份的
.gx3(改名.zip)直接查看变量和代码,用于对比。

第七重:性能优化 —— “别让扫描周期变瓶颈”

你以为:PLC跑几千步没问题。  实际上:一个
FOR循环嵌套100次,扫描周期翻倍,伺服抖动。
我曾经在ST里写了一个循环计算每个轴当前位置的滤波平均值,循环里用了
INT_TO_REAL转换,还调用了通用函数块,结果扫描周期从2ms飙到8ms。所有轴一起抖动。
审查规则:  <oli>1.不用的梯形图步必须删除(程序检查会标黄)。  </oli><oli>2.每个循环最多100次,且循环体里不要调用FB(调用开销大)。  </oli><oli></oli>3.避免在循环中使用
REAL运算,能转LREAL的转,但三菱iQ-R的CPU主频很高(200MHz),更要注意的是算法复杂度。优化前后数据:  
场景扫描周期轴抖动幅度
未优化循环8.2ms±0.5mm
优化后(预计算+查表)1.8ms±0.05mm
代码示例(优化前 vs 后):
structured text
// 糟糕写法:循环里调用函数
FOR i := 0 TO 99 DO
rSum := rSum + MY_FILTER(udRawData); // 每次调用FB
END_FOR;
// 优化写法:先存入数组,再一次性计算
FOR i := 0 TO 99 DO
udFiltered := udRawData; // 仅赋值
END_FOR;
// 然后用算术指令求均值(或使用三菱的数组函数)
审查时用GX Works3的“监控→扫描时间”实时看程序执行时间,发现超过2ms的模块就双击进去分析。

第八重:可测试性 —— “上线前我能模拟80%故障”

痛点:现场没信号,你没法模拟限位开关。只能拿万用表短接,搞不好烧I/O。方案:在程序中预留“仿真模式”开关。当
bSimMode := TRUE时,所有输入量由内部变量代替。这样在GX Works3的仿真器里就能把整个逻辑跑通。审查要点:  <ul>
整个程序是否包含一个全局的
bSimMode?  
每个输入量是否有对应的仿真赋值?  
所有输出量在仿真模式下是否被抑制(防止真的驱动设备)?
代码示例(仿真模式):
structured text
// 仿真开关定义(全局变量)
// bSimMode : BOOL;   // TRUE=仿真模式,FALSE=实际模式
// 实际输入
IF NOT bSimMode THEN
bLimitTop_Actual := %IX100.0;    // 实际I/O
bLimitBottom_Actual := %IX100.1;
ELSE
// 仿真输入,来自工程变量“Sim_TopLimit”等
bLimitTop_Actual := Sim_TopLimit;
bLimitBottom_Actual := Sim_BottomLimit;
END_IF;
// 输出抑制
IF NOT bSimMode THEN
%QX200.0 := bAxisZUp;   // 实际输出
ELSE
// 仿真时输出到内部变量用于监控
AxisZUp_Sim := bAxisZUp;
END_IF;
`
有了这个,我可以在GX Works3模拟器里手动改
Sim_TopLimit
,看状态机是否按预期转移。调试3小时,其中有2小时是在模拟器里跑完所有故障分支。

总结:我留下的检查清单

给你一张表,下次审查时逐条打勾:
[table][/table]境界检查项状态命名所有变量带前缀+注释?地址非全局符号?□注释每个FB有Revision历史?变量注释含单位?□状态机用
CASE`或SFC而非乱SET?ELSE分支处理?□错误处理每个指令有超时/错误检查?用户报警定义?□模块化重复代码已抽成FB?每个FB输入输出清晰?□版本控制工程有外部版本库?Revision已更新?□性能扫描周期<3ms?循环<100次?□可测试全局仿真开关?所有输入可模拟?□
现在,打开你的GX Works3,找到一年前写的那段程序,对照这张表审查一遍。别怕花时间——你做这件事省下的时间,足够你请整个办公室喝三周下午茶。
最后留个互动问题:你曾经在PLC代码里见过最离谱的命名是什么? 评论区分享,我来看看谁能赢。
推荐阅读
摘要:今天深入学习静态代码分析技术,这是安全审计的核心技能。从 Python AST 模块到检测模式设计,收获满满!
发布于 202603
【优化】Python代码优化与调试技巧
发布于 202603
【Python入门】5分钟搭建开发环境,老手也在用的配置技巧
发布于 202603
KEYWORDS
PLC, GX Works, MELSEC, 三菱
如果你觉得这篇文章有帮助,请点个在看,分享给更多需要的人!
关注我,获取更多实用干货~
有问题欢迎评论区留言交流!
</ul>

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码