Skip to content

通信与 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~M199Lua梯形图只读云指令、网络状态、握手位
D0~D99梯形图Lua 只读采集值(温度、产量、转速)
D100~D199Lua梯形图只读设定值(目标温度、目标产量、命令字)
D8000+系统双方只读系统状态(扫描时间、RTC、错误码)

两条铁律:

  1. 同一个寄存器只允许一方写,另一方只读。
  2. 握手位由"产生方置位、消费方清除" —— 谁发现事情、谁置位;谁处理完、谁清掉。两边都清或都不清,就会漏事件或重复执行。

例程 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  Y0

Lua:

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。

相关文档 ​

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研