主题
通信与 Lua 协同例程
梯形图管实时 I/O 与安全联锁,Lua 管网络与协议,两边通过 D 寄存器和 M 位共享数据。 本页 6 个例程解决同一件事:两边怎么不打架地配合。
返回 例程总览。
只有支持 Lua 的机型能跑本页
Lua 脚本只在 WIFI / 4G 版(LP3202 / LP3203 / LP3222 / LP3232 / LP3288 / LP8910 等)与软 PLC 模拟器(LP6666)上有, 纯梯形图机型(LP22xx 系列)没有 Lua。这类机型请只用本页里的梯形图部分。 详见 PLC 与 Lua 协同。
先约定:谁写谁读
两边同时写一个寄存器是协同里最常见的故障源 —— 现象是"值莫名其妙跳回去",查起来极费时间。动手前先定好这张表:
| 区域 | 谁写 | 谁读 | 用途 |
|---|---|---|---|
X / Y | 梯形图(X 只读) | Lua 可读 Y、可写 Y(慎用) | 实时输入/输出,输出尽量只由梯形图写 |
M0~M99 | 梯形图 | Lua 只读 | 梯形图的状态标志(运行、故障、完成) |
M100~M199 | Lua | 梯形图只读 | 云指令、网络状态、握手位 |
D0~D99 | 梯形图 | Lua 只读 | 采集值(温度、产量、转速) |
D100~D199 | Lua | 梯形图只读 | 设定值(目标温度、目标产量、命令字) |
D8000+ | 系统 | 双方只读 | 系统状态(扫描时间、RTC、错误码) |
两条铁律:
- 同一个寄存器只允许一方写,另一方只读。
- 握手位由"产生方置位、消费方清除" —— 谁发现事情、谁置位;谁处理完、谁清掉。两边都清或都不清,就会漏事件或重复执行。
例程 1:梯形图采集 → Lua 定时上报(M 元件握手)
场景:产量、计数这类数据在梯形图里产生(要靠高速/实时性),但要送到云端只能交给 Lua。 用一位 M 当"有新数据"的握手信号,Lua 上报完负责清掉,避免在断网期间丢数据。
软元件:X10 产品到位、M0 到位脉冲、D100-D101 累计产量(32 位)、M100 数据已更新(梯形图置 / Lua 清)
梯形图:
LD X10
PLS M0 ; 每件产品一个脉冲
LD M0
DADD D100 K1 D100 ; 产量 +1(32 位加,占 D100-D101)
LD M0
SET M100 ; 通知 Lua:有新数据Lua:
lua
mqttc = mqtt.creat("broker.emqx.io", 1883, "lpplc_001", "", "") -- 必须全局变量
mqtt.start(mqttc)
timer.start(1000, function()
if plc.m(100) == 1 then -- 梯形图说"有新数据"
local n = plc.dd(100) -- 读 32 位累计产量
if mqtt.is_linked(mqttc) == 1 then -- 整数,必须 == 1
mqtt.publish(mqttc, "data/count", tostring(n), 0, 0)
end
plc.m(100, 0) -- 回执:由消费方清除
end
end)要点
- 握手位是
SET(不是OUT):采集可能比 Lua 轮询快,用SET才不会漏掉中间的事件。 - Lua 侧只在上报成功后才清握手位,断网时数据留在 D100 里、
M100保持 ON,恢复后补上报。 plc.dd(100)读 32 位,和梯形图的DADD占位一致;读错成plc.d(100)只能拿到低 16 位。
验证:给 X10 几次脉冲 → 平台收到递增的数字,同时上位机监视 M100 会短暂变 ON 后自动回到 OFF。
注意事项
if plc.m(100) then—— 错。plc.m返回 0/1 两个数,而 Lua 里0也是真值,条件永远成立。必须写== 1。- 忘了
plc.m(100, 0):M100永远 ON,每个周期重复上报同一条数据。 - Lua 里用
while true do ... end轮询:绝对禁止,那会冻死整个事件循环(见 并发模型概述)。
例程 2:云端下发命令 → Lua 写 D → 梯形图执行并回 ACK
场景:平台点"启动/停止",命令经 Lua 落到 D 寄存器,梯形图解析执行,再把"执行完了"回给平台。 命令字 + 应答位是这类需求最稳的结构,比"直接写 Y"安全得多(梯形图仍握着联锁的最终决定权)。
软元件:D200 命令字(1=启动 / 2=停止 / 3=复位)、M200 有新命令(Lua 置 / 梯形图清)、M201 执行完成(梯形图置 / Lua 清)、M10 运行标志、Y0 设备
梯形图:
LD M200
AND= D200 K1 ; 命令 = 启动
SET M10 ; 置运行标志
LD M200
AND= D200 K2 ; 命令 = 停止
RST M10
LD M200
AND= D200 K3 ; 命令 = 复位
ZRST D100 D199 ; 清空累计(按你的分区调整范围)
LD M200
SET M201 ; 应答:命令已处理
RST M200 ; 命令标志由梯形图清除
LD M10
ANI X20 ; 急停/联锁仍然只由梯形图把关
OUT Y0Lua:
lua
mqttc = mqtt.creat("broker.emqx.io", 1883, "lpplc_001", "", "")
mqtt.reg(mqttc, mqtt.CONNECTED, function()
mqtt.subscribe(mqttc, "cmd/lpplc_001", 0) -- 连上后订阅
end)
mqtt.reg(mqttc, mqtt.RECVDATA, function(topic, payload)
local cmd = tonumber(payload) or 0 -- 约定:报文就是命令字
plc.d(200, cmd) -- 写命令字
plc.m(200, 1) -- 置"有新命令"
end)
mqtt.start(mqttc)
timer.start(200, function()
if plc.m(201) == 1 then -- 梯形图执行完了
if mqtt.is_linked(mqttc) == 1 then
mqtt.publish(mqttc, "ack/lpplc_001",
"cmd=" .. plc.d(200) .. ",ok=1", 0, 0)
end
plc.m(201, 0) -- 应答位由 Lua 清除
end
end)要点
- 执行权留在梯形图:Lua 只递命令字,真正让
Y0动作的是梯形图里的LD M10 / ANI X20 / OUT Y0。急停、联锁不会被网络绕过。 mqtt.reg要在mqtt.start之前注册好RECVDATA,否则连上后早到的消息会漏掉。- 客户端对象
mqttc必须是全局变量,局部变量会被垃圾回收,表现是"连上一会儿就断"。
验证:向 cmd/lpplc_001 发 1 → Y0 吸合、ack 主题收到 cmd=1,ok=1;发 2 → Y0 断开。
注意事项
- Lua 直接
plc.y(0, 1)控输出:绕过了梯形图的联锁,急停接进来也停不下来。 - 命令标志清错边:Lua 清
M200会和梯形图的RST M200抢,可能出现命令还没执行就被清掉。产生方置位、消费方清除,别两边都清。 - 命令下发期间
D200被新命令覆盖:命令处理得慢时,先到M201应答再收下一条,或加一个"忙"标志。
例程 3:上电初始化与常 ON 刷新(M8002 / M8000)
场景:设备一上电,D 区是随机值,必须先灌一套默认参数;运行中要持续把系统数据搬到"镜像区"给上位机/Lua 读。 这是唯一一个"必须用 M8002 而不是 M8000"的场景。
软元件:D100 温度设定(×10)、D101 回差(×10)、D500~D509 状态镜像区
LD M8002 ; 只在运行后的第一个扫描周期 ON
MOV K500 D100 ; 默认设定 50.0℃
MOV K20 D101 ; 默认回差 2.0℃
FMOV K0 D500 K10 ; 状态镜像区清零
LD M8000 ; 常 ON:持续刷新镜像
MOV D8010 D510 ; 当前扫描时间(0.1ms)
MOV D8012 D511 ; 最大扫描时间要点
M8002(初始化脉冲)只 ON 一个扫描周期,专做上电初始化;M8000常 ON,做持续动作。- 参数默认值只能用
M8002写一次。写成M8000的话,运行中从上位机或 Lua 改过的设定值会在下一个扫描周期被改回默认值 —— 这就是"参数改不动、改完又跳回去"的真相。
验证:下载后读 D100 = 500、D101 = 20;用上位机把 D100 改成 300,等几秒再读仍是 300(说明没有被反复覆盖)。
注意事项
- 把初始化段写在程序末尾:梯形图按步序执行,末尾的
M8002段同样只跑一次,但前面用到默认值的指令在第一个周期已经读到了旧值,首周期行为可能不对。初始化段放程序最前。 FMOV K0 D500 K10把不该清的镜像区也清了:镜像区地址一旦改,这里的范围要一起改。
例程 4:读 D8000 系列取系统状态
场景:想知道"这台设备现在健康不健康"——扫描周期多长、RTC 对不对、有没有报错、固件什么版本。 这些值固件已经放在 D8000 系列里,梯形图搬一次,Lua 就能直接读,不用另建采集逻辑。
软元件:D510~D517 状态镜像区(梯形图写 / Lua 只读)
梯形图:
LD M8000
MOV D8010 D510 ; 当前扫描时间(单位 0.1ms)
MOV D8012 D511 ; 最大扫描时间
MOV D8013 D512 ; RTC 秒
MOV D8014 D513 ; RTC 分
MOV D8015 D514 ; RTC 时
MOV D8004 D515 ; 错误编号(BCD,0 = 正常)
MOV D8034 D516 ; 固件版本(高 8 位 / 低 8 位)
MOV D8035 D517 ; 固件版本(续)Lua:
lua
timer.start(5000, function()
print("扫描时间(0.1ms) =", plc.d(510), " 最大 =", plc.d(511))
print("RTC =", plc.d(514), ":", plc.d(513), ":", plc.d(512))
print("错误码 =", plc.d(515))
end)要点
- 梯形图只做"搬运",Lua 只做"读 + 上报",两边职责清楚,改上报内容不用动梯形图。
D8004是 M8060~M8067 的BCD 编码错误编号,正常为 0;要看具体是哪一类错,配合 M8060~M8067 判断(见 出错检测机制)。- 扫描时间若持续接近看门狗设定(
D8000),说明程序里有太重的运算或通信,需要拆开。
验证:用上位机读 D510,应为一个几十到几百的数(单位 0.1ms);把 D515 与上位机的诊断页对照,正常为 0。
注意事项
- 往 D8000 系列里写值:这些是只读系统寄存器(少数如
D8000、D8013~D8019可写),写错地址不会报错,只是没效果。 D8034/D8035是LP3U专有,其它平台读到 0,不要当成"版本读不到"去排查。- Lua 里用
plc.df(510)去读扫描时间:那是浮点读法,会把两个寄存器当一个浮点数解,读出来是天文数字。状态量一律用plc.d()。
例程 5:Lua 写设定值、梯形图只读并执行(带回差)
场景:目标温度、目标产量这类"设定值"由平台下发,梯形图拿它做闭环。 关键不是算法,而是回差(滞环):没有回差,继电器会在设定点附近每秒通断好几次,几天就烧触点。
软元件:D0 当前温度(×10,梯形图写)、D100 目标温度(×10,Lua 写)、D101 回差(×10)、D102 上限(= 目标 + 回差)、M10 运行允许、Y0 加热器
梯形图:
LD M8000
ADD D100 D101 D102 ; 上限 = 目标 + 回差(例:50.0 + 2.0 = 52.0℃)
LD M10
AND< D0 D100 ; 当前 < 目标
SET Y0 ; 开始加热
LD M10
AND> D0 D102 ; 当前 > 目标 + 回差
RST Y0 ; 停止加热Lua(下发设定值):
lua
plc.d(100, 250) -- 目标 25.0℃(放大 10 倍,避开浮点)
plc.d(101, 20) -- 回差 2.0℃要点
- 温度统一 ×10 存整数(25.0℃ 存 250):PLC 侧整数运算快、准,浮点比较还容易因精度抖动(见 数值基本知识)。
SET/RST配对构成自保持 + 双阈值:低于目标开、高于目标+回差才关,中间区间保持原状态,这就是回差。D100/D101由 Lua 写、梯形图只读;反过来梯形图若也写D100,平台下发的值会被冲掉。
验证:设目标 250、回差 20,把 D0 从 200 慢慢加到 260:D0=260(26.0℃)以前 Y0 一直 ON;超过 270(27.0℃)才断开;降回 250 以下才会重新吸合。
注意事项
- 用
OUT Y0而不是SET/RST:每个扫描周期都重新判断,结果是在设定点附近高频通断。 - 回差设成 0 或忘了加回差:和上面同一个后果。
- 梯形图和 Lua 都写
D100:典型争用,现象是"平台下发了设定值,过一会又变回旧值"。
例程 6:通信故障指示与恢复判断
场景:现场最常被问的一句"到底通没通"。面板上要有一红一绿两盏灯, 同时把错误码搬到 D 区,Lua 才能把"通信中断"这件事报到平台上去。
软元件:M8062 PLC 通讯错误、M8063 一般通讯错误、D8062 PLC 通讯错误码、D600~D601 错误码镜像、D610 故障次数、M50 计数脉冲、Y20 故障红灯、Y21 正常绿灯
LD M8000
MOV D8062 D600 ; 通讯错误码 → 镜像(0 = 正常)
MOV D8063 D601 ; 一般通讯错误码 → 镜像
LD M8062
OR M8063
OUT Y20 ; 通信故障(红灯)
LD M8062
PLS M50
LD M50
INC D610 ; 故障次数累计(只数 PLC 通讯错误)
LD M8000
ANI M8062
ANI M8063
OUT Y21 ; 通信正常(绿灯)要点
M8062/M8063是只读系统继电器,由固件置位;D8062/D8063给出具体错误码,方便定位是超时、校验错还是参数错。- 绿灯用"两个错误位都为 OFF"来判,比用"通信成功标志"可靠 —— 后者的复位时机往往不直观。
- 故障计数用
PLS取边沿,否则故障持续的每一秒都在加,数字会大到没有参考意义。
验证:拔掉网线(或关掉对端设备)→ 红灯亮、绿灯灭、D610 加 1;恢复后红灯灭、绿灯亮。
注意事项
- 想用
RST M8062把错误清掉:清不掉,它是只读的。错误清除要走专门的方法(见 错误清除方法),通常是排除故障后自动或按流程复位。 - 用
M8004(任一错误发生)来判断通信故障:它明确不含 M8062,通信出错时M8004可能是 OFF。通信要看M8062/M8063。 EXTR/RS指令的通信错误另有标志M8115(读它的.0位),走自由口通信时不要只看M8062。