三菱PLC ST语言初值写法:声明赋值与首扫描,哪个防丢数据?

发布时间:2026/9/30 22:38:39
三菱PLC ST语言初值写法:声明赋值与首扫描,哪个防丢数据? 先交代一个现场某天半夜我在调试一台完成品计数设备触摸屏上显示产量从 0 开始后台却明明断电前有三万多件。查了半天问题出在 ST 程序里一行很不起眼的声明赋值——i_done : INT : 0;。产品停了电再上电声明区的初值被重新装载把原本断电保持的累计产量直接压回零。也就是从那天起我在写 MELSEC 的 ST 程序时只要涉及初值都会先问一句这个初值到底应该靠声明赋值还是靠首扫描给这篇文章就把这个问题讲透。包括两种写法在时序上的本质差异、Q/L/iQ-R 系列里各自的实测表现、以及在线修改、断电保持、CPU 复位这些场景下最容易翻车的地方。内容主要针对用 GX Works2 / GX Works3 写三菱 ST 程序的工控工程师做设备调试、程序维护的时候遇到“初值不对”的怪现象基本都能在这找到答案。1. 两种初值写法先看代码长什么样1.1 声明赋值初值写在 VAR 区里ST 语言里最常见的初值写法是把初值直接写在变量声明段VAR i_done : INT : 0; // 累计产量 i_target : INT : 500; // 目标产量 b_alarm_on : BOOL : FALSE; // 报警标志 END_VAR这个写法非常直白声明变量时顺手就给个值。很多从高级语言转过来的工程师会觉得这是最自然的方式C 语言里不也是int x 0;吗1.2 首扫描赋值初始化逻辑放进第一周期另一种写法是变量声明时不写初值在程序开头用一个“只执行一次”的条件把初值赋进去IF SM402 THEN i_done : 0; i_target : 500; b_alarm_on : FALSE; END_IF;SM402是三菱 Q/L 系列里专门做“首次扫描”的特殊继电器CPU 从 STOP 切换到 RUN 后的第一个扫描周期为 ON之后就一直是 OFF。这段逻辑自然只会在第一个周期执行一次。还有一种用声明初值自复位的方式后面会讲在函数块内部特别好用。表面上看两种写法都让变量获得了初值。但“什么时候生效”这件事机制完全不同这也是这个标题下面真正值得花时间的东西。2. 声明赋值的真实写入时机不是“声明”那一刻而是 RUN 启动那一刻2.1 CPU 在 STOP→RUN 时批量装载初值先说结论ST 程序里写在 VAR 区的: 初值并不是每一扫描周期都写一次也不是你编译下载时写进去的。它在 CPU 启动时执行一次——准确说是程序从 STOP 状态切换到 RUN 状态、或者 CPU 复位后重新启动时CPU 会批量完成一次“初值装载”。很多朋友对这个机制有误解以为声明区写了个初值这个变量就一直被“锁”在那个值上。实际上不是。声明区的初值只负责“启动那一刻给变量一个确定的起点”之后的每个扫描周期变量值完全由程序逻辑决定。你可以在第一周期里把它改掉它就永远不回头了。这里有个很有意思的推论声明赋值的初值装载发生在第一扫描周期之前或者说发生在第零个扫描周期。而首扫描段落里的赋值发生在第一周期的某个具体扫描位置。这个时间差看起来微不足道但在某些场景下会直接影响程序行为。举个实际例子。我在一个多程序文件项目里有 A、B 两个 ST 程序。A 程序里声明了一个全局标签g_flag : BOOL : TRUE;然后 B 程序第一周期就判断g_flag来决定是否执行某段联锁释放逻辑。声明初值装载是在程序扫描开始前统一做的所以 B 程序第一周期一进来就能读到TRUE没问题。但如果把初值写在 A 程序内部的首扫描里恰好 A 排在 B 后面执行B 程序第一周期读到的是变量初始化前的随机值这就是两份程序之间隐性的初始化依赖很难查。2.2 保持型软元件与声明初值的优先级冲突这是我在实际项目中踩过最深的一个坑。三菱 PLC 的软元件地址通常有“停电保持”属性具体保持范围取决于 CPU 参数设置。你完全可以把产量、累计运行时间这类数据放到保持区断电后数据不丢。但如果你在 ST 的声明区给某个映射到保持区的标签写了初值那么每次 RUN 启动时CPU 都会用声明的初值覆盖掉保持区里保存的数据。这么说吧一个标签i_total : INT : 0;被映射到一段带有停电保持功能的 D 区地址。你满怀期待地认为断电后i_total会保持三万这个值结果下一次上电声明初值装载阶段直接把 0 写进去保持区救回来的数据瞬间没了。这个问题的本质是“初值装载”和“保持数据恢复”都在 CPU 启动阶段发生但声明初值优先。所以大家在项目里如果确认某些数据必须靠保持区保存的千万记住标签维护里不要给这些变量设置初值。要么用首扫描做“仅在真正需要时初始化”要么完全不在程序里给初值让保持区自己恢复。2.3 在线修改时声明初值不一定会重新写入更隐蔽的场景是“在线修改”也叫 RUN 中写入、程序远程变更。PLC 在 RUN 状态下在线修改程序时声明区的初值会不会重新装载答案不是绝对的。GX Works2 / GX Works3 在线修改时一般会弹出一个对话框里面有类似“变更后进行初始化”之类的选项。你没注意直接点了确定初值可能就会被重新装载一次把当前运行值冲掉。我就犯过这个错。设备正在跑一个配方流程我在线修改了一个无关紧要的报警阈值随手确认。结果整个工序参数全部回到声明初值已经调好的中间状态没了。从那以后我的习惯是在线修改时如果弹窗里有任何关于“初始化”“初始值”的选项默认不勾选除非我明确知道这次改动就是想让变量归零。3. 首扫描的 SM402/SM403 和 iQ-R 初始执行任务实测差异在哪3.1 Q/L 系列里 SM402、SM403 的正确用法在 Q/L 系列GX Works2上首扫描的核心就是特殊继电器 SM402 和 SM403。SM402RUN 后仅第一个扫描周期为 ON之后一直 OFF。SM403RUN 后第一个扫描周期为 OFF之后一直 ON。SM403 用得不多但有一个妙用你可以在程序里用 SM403 加一个下降沿去捕获“第一周期结束”这个时刻。SM402 是“确认我正在第一周期”SM403 是“确认我已经不在第一周期”一正一反处理某些上电握手逻辑很顺手。使用 SM402 有一个要注意的细节它只在 STOP→RUN 时才会置 ON。在线修改、远程 RUN即从 STOP 状态切 RUN都会触发但 CPU 如果在 RUN 中做了一次内部软复位行为可能跟你预期不一样。敏感项目里要确认清楚目标 CPU 的手册定义。3.2 iQ-R 和 FX5U 的“初始执行任务”更干净如果你用的平台是 GX Works3 对应的 iQ-R 系列或 FX5U那还有比 SM402 更干净的做法任务设置里的“初始执行任务”。GX Works3 的工程配置里可以把某个 POU程序组织单元放进“初始执行任务”这个程序体只在 CPU 启动或按设置条件触发时执行一次。它的语义跟“首扫描”几乎一样但好处是不用自己维护 SM402程序体可以集中写初始化逻辑一目了然。这是我在 iQ-R 上更推荐的方式。因为一个项目里如果几十个 POU 都各自写一遍IF SM402 THEN ... END_IF你很难全局管理“哪些程序做了首扫描、哪些没做”。改成初始执行任务后所有初始化代码集中在同一个 POU 里逻辑清晰排查问题也快得多。FX5U 同理。它用 GX Works3也支持初始执行任务。写多了会发现三菱在 Q/L 和 iQ-R/FX5U 上对“首扫描”的支持程度是有代际差异的老平台靠特殊继电器新平台靠任务配置。3.3 不碰 SM402 也可以声明初值 自复位标志做首扫描第三种做法是很多人没意识到“声明赋值和首扫描其实可以配合着用”的典型例子。在函数块FB内部要做一个“实例首次调用时只初始化一次”的逻辑可以直接写VAR b_first : BOOL : TRUE; END_VAR IF b_first THEN b_first : FALSE; // 这里放初始化逻辑 END_IF;原理很简单声明区给b_first赋了 TRUE而声明初值只在启动时装载一次。所以第一个扫描周期进入 IF 体业务赋值执行后置 FALSE从第二周期起b_first永远是 FALSEIF 体再也不会进去。这个写法的好处是不依赖 SM402 这类 CPU 级特殊继电器在 FB 里也能用而且每个 FB 实例都有独立的首扫描状态互不干扰。我经常把它当作“局部首扫描”专门处理那些只在实例建立时需要初始化、但本身又没有独立 POU 首扫描条件的场景。注意这种写法里“声明初值”和“首扫描逻辑”是协同工作的不是对立的。以后别再觉得两者只能选一个很多复杂初始化场景要的就是这种组合。4. 在线修改、断电保持、CPU 复位最容易让初值“失控”的场景4.1 一个真实的故障案例在线修改后全局标签被“洗掉”我调过一台锂电池卷绕机主程序是标准 ST 结构大量使用全局标签并在标签表里设置了初始值。那台设备有个怪毛病工程师在线更新某个配方下载后所有伺服轴的位置清零不算连气缸的使能模式都被重置设备必须重新回原点。查了一晚上最后发现不是逻辑错误而是在线修改时把“标签初始值装载”触发了。全局标签带初值的情况下在线修改一旦执行初始化动作所有标签都会刷回初值正在运行的数据全部归零。设备正在跑的实时数据被一个“看起来只改了某个地址”的在线下载全部推翻。从那以后我定了一条规矩凡是设备运行中不需要归零的数据不要在全局标签里给初值。初值只给“每次上电都应该从标准值开始”的设定类变量至于执行状态、计数结果靠首扫描或显式逻辑在需要时初始化。4.2 在线修改、断电保持和 CPU 复位的行为差异整理三种场景分别怎么表现列表看得更清楚场景声明赋值首扫描赋值STOP→RUN 切换初值重新装载SM402 为 ON初始化逻辑执行在线修改RUN 中写程序取决于弹窗初始化选项选了就覆盖SM402 不置 ON通常不会执行除非勾选对应选项CPU 复位后启动初值重新装载SM402 置 ON初始化逻辑执行断电后重新上电初值重新装载会覆盖保持区的值SM402 置 ON初始化逻辑执行同样会覆盖扫描周期中途初值已装载不影响当前值初始化逻辑已执行不影响当前值重点盯“在线修改”那一行。声明赋值和首扫描赋值在在线修改时默认都不一定会触发具体看 GX Works 弹窗选项。但很多人从没仔细看过那个弹窗这是初值失控的第一大来源。4.3 一个特殊的边界初值装载与保持区恢复的先后再说一次那个最反直觉的结论CPU 启动时声明初值装载通常优先于保持区数据恢复。所以你给一个保持型标签设了初值断电保持实际是失效的。你恢复的不是“断电前最后一次值”而是“程序里写的那个初值”。如果项目的确需要某些数据在断电后恢复唯一的可靠做法是不在标签初值列表里设初值也不在首扫描里无条件赋值。让保持区自己干活。需要“首次上电才清零、断电恢复不清零”这种更精细的控制可以给 CPU 增加一个断电保持的“上电标志”变量首扫描里判断如果是第一次上电标志为 FALSE才做初始化然后置位标志下次掉电再上电标志已经在保持区里为 TRUE就不清零了。这种逻辑在 ST 里写起来也就是几行 IF但要想清楚否则初值早晚坑你一次。5. 怎么选我的判断规则与实战建议5.1 五条选型判断规则我给自己定了几条很朴素的规则写共享前分享出来数据必须断电保持的不设声明初值不要用首扫描无条件清零顶多用“上电标志位”做一次性初始化。每次 RUN 启动都应该回到标准值的例如配方容量、工作模式选择用声明赋值最省事一行就解决。初始化逻辑涉及多个变量、需要条件判断、或需要按次序操作外设的用首扫描或初始执行任务代码可读性强。函数块内部的局部状态声明一个带 TRUE 初值的标志位做自复位首扫描不引用外部特殊继电器。在线修改频繁的项目全局标签初值尽量少用避免一次在线修改把整个工作状态洗白。5.2 一个综合示例生产计数与配方数据的初始化设计拿我前面提到的那台完成品计数设备来举例。假如需求是这样累计产量i_done断电保持不能随意清零。目标产量i_target每次上电恢复为 500。当前批次号i_batch_no每次上电从 1 开始运行中可以修改。设备首次上电时需要把报警历史清空但断电恢复时不清空。我的写法会是VAR i_done : INT; // 无初值靠保持区保存 i_batch_no : INT : 1; // 声明初值每次上电回 1 b_first_pow : BOOL; // 映射到保持位默认保持 b_alarm_hist_cleared : BOOL : FALSE; END_VAR // 目标值在首扫描/初始执行任务里赋值 IF SM402 THEN i_target : 500; END_IF; // 只有首次上电才清报警历史 IF NOT b_first_pow THEN // 清空报警历史 b_first_pow : TRUE; END_IF;注意i_target放在 SM402 里而不是声明区。因为如果在线修改时弹窗勾了“初始化”i_target : 500只在 SM402 为 ON 时执行在线修改通常不触发所以它不会把现场临时改到 800 的目标值拉回 500。而i_batch_no用声明初值: 1反而可能在某些在线修改场景下被重置所以如果批次号运行中很重要建议也挪到首扫描里。这种“混搭”正是我日常做初值设计的方式每个变量按自身业务属性单独决定用哪种初始化形式。5.3 关于初值的三个习惯性动作最后分享三个我调整过很多程序后总结的习惯性动作都是踩了坑才长记性的事。第一下载完程序后先做一次 STOP→RUN再观察无条件初始化的变量是否都符合预期不要直接在 RUN 状态下改程序假装下载成功就完了。第二用 GX Works 的交叉参考表批量查一遍所有带初值的标签凡是关联到保持区的逐个过一遍确认是否真需要初值。第三在线修改之前把弹窗上所有选项都读一遍特别是带“初始值”“初始化”“重启动”字样的没搞清楚前不要点确定。正文完