VisualHMI - Modbus一主多从

本章节将介绍主从模式下(Modbus/FX2N/FX3U),读写PLC/驱动器的寄存器

以Modbus 为例,项目创建两个从站,第1个从站的站号为10,第2个从站的站号为20,如下所示:

image-20240103170621406

  1. Lua读PLC/驱动器的寄存器
  2. Lua写PLC/驱动器的寄存器
  3. Lua批量写寄存器

适用范围:VisualHMI - HMI&M&Dx系列

例程下载链接:ViusalHMI - 一主多从(点击下载)

1. Lua API说明

1.1.start_read(index,vtype, addr,quantity,cycle,clcye_run,mode)

主机模式下周期性自动读取寄存器的通信控制函数,start_read 是 HMI 在 主机模式(如 Modbus Master、FX2N 主站)下的后台自动轮询机制,用于周期性向从机设备发起读取请求

📊 参数说明

参数 类型 必填 说明
index number 任务索引号:0~127),用于后续 stop_read(index) 停止该任务
vtype number 数据类型:VT_3x, VT_4x
addr number 寄存器地址
quantity number 读取数量:1 ~ 120 个寄存器
cycle number 选填,轮询周期倍数(默认 0 → 每周期都读)
cycle_run number 选填,在周期内的第几次执行(0-based,需 < cycle
mode number 选填,读取模式: 0 = 持续周期读取(默认) 1 = 仅读一次(需先调用 create_resp_que()

⚠️ 注意

  • 该函数无返回值
  • 实际数据需通过 get_uint16get_float 等函数从本地缓存读取;
  • 仅在主机模式下有效(如 Modbus RTU/FX3U)。

💡 示例

以Modbus RTU Master模式为例

1.1.1自动后台轮询
  • 调用 start_read 后,HMI 系统将该读取任务加入通信调度队列
  • 每个通信周期,系统自动发送 Modbus 请求;
  • 返回数据自动写入 HMI 内存镜像区;
  • 脚本通过 get_xx(VT_4x, addr) 直接读缓存无通信延迟
1.1.2.cyclecycle_run 的调度逻辑

HMI 将通信周期划分为多个“子周期”,用于区分高/低频任务

调用示例 行为说明
start_read(0, VT_4x, 0x1000, 10) 每个通信周期都读 4x1000~1009
start_read(1, VT_4x, 0x2000, 10, 3, 2) 每 3 个周期读一次,且在第 3 个周期(即 cycle_run=2,0-based)执行

🔄 调度时序示例:

1周期 #0 (0ms)   → 读 0x1000(高频)
2周期 #1 (50ms)  → 读 0x1000
3周期 #2 (100ms) → 读 0x1000 + 读 0x2000(低频任务触发)
4周期 #3 (150ms) → 读 0x1000
5周期 #4 (200ms) → 读 0x1000
6周期 #5 (250ms) → 读 0x1000 + 读 0x2000
7...

💡 设计目的

  • 高频变量(如速度、温度)→ cycle=0(每周期读)
  • 低频变量(如累计量、状态字)→ cycle=5, cycle_run=4(每 5 周期读一次)→ 降低总线负载,提升系统实时性

1.2.stop_read(index)

停止后台周期性读取任务stop_read(index) 是 HMI 系统在主机模式(如 Modbus Master、FX2N 主站)下提供的通信任务管理函数,用于停止由 start_read 启动的后台周期性读取任务。调用后,HMI 将立即从通信调度队列中移除该任务,不再向从机设备发送对应的读取指令,但本地缓存中的数据保持不变(保留最后一次成功读取的值)。

📊 参数说明

参数 类型 说明
index number 任务索引号(0 ~ 127):必须与 start_read 中注册的 index 一致

核心用途:动态控制通信负载、释放无效轮询、实现按需读取策略。

1.3.stop_all_read()

停止所有脚本启动的后台读取任务stop_all_read() 是 HMI 系统在主机模式(如 Modbus RTU/TCP Master、FX2N 主站等)下提供的全局通信任务停止函数,用于一次性停止所有由 start_read 启动的后台周期性读取任务

核心用途

  • 快速释放所有脚本注册的通信资源;
  • 适用于系统复位、模式切换、紧急停机等场景;
  • 避免逐个调用 stop_read(index) 的繁琐操作。

1.4.set_auto_read(en)

控制“画面绑定变量”自动读取的全局开关set_auto_read(en) 是 HMI 系统在主机模式(如 Modbus Master、FX2N 主站)下提供的全局通信策略控制函数,用于启用或禁用画面控件所绑定寄存器的自动轮询机制

📊 参数说明

参数 类型 说明
en number 使能标志: 1 = 启用画面绑定变量的自动读取(默认) 0 = 禁止自动读取,仅依赖 start_read 等脚本控制

核心用途

  • 精细控制通信行为,将“画面自动读取”与“脚本主动读取”解耦;
  • 在高性能或低带宽场景下,完全由脚本通过 start_read 接管数据更新
  • 避免系统自动生成冗余或冲突的读取指令。

1.5.select_slave(slave_id)

HMI 在多从机通信模式(如 Modbus RTU、FX2N 等总线协议)下,通过select_slave,临时指定从站索引,使得后续的 get_xxx() / set_xxx() 操作时,访问指定从机寄存器。

核心作用:切换指定从站,访问对应从站寄存器。

📊 参数说明

参数 类型 说明
slave_id number 从机索引
0 起始,对应工程中配置的从机列表顺序,非 Modbus 站号

image-20231107094626802

术语 含义 示例
从机索引 (slave_id) HMI 内部从机列表的数组下标(0, 1, 2...) 0 = 第一个从机
Modbus 站号 (Slave Address) 从机设备的物理通信地址 10, 33

💡 映射关系由 HMI 工程配置决定

  • slave_id = 0 → 站号 = 10
  • slave_id = 1 → 站号 = 33

2.Lua 读寄存器

2.1.Lua 脚本

Lua脚本读取从机寄存器值,通常用于条件判断、计算和等。步骤如下所示

  1. 在脚本获取该寄存器时,确保后台有轮询请求,保证获取的值是最新的
  2. 获取寄存器前,要需要判断从机是否在线
  3. 获取寄存器前,需选择对应的从机
--读取保存寄存器
function mb_read_reg_03(slave, addr)

    local onlineState = get_uint16(VT_LW,0x01A3) -- 获取从机在线状态

    if ((onlineState >> slave) & 0x01) == 0x01 -- 读取从机在线状态
    then
        select_slave(slave)-- 选择从机
        return get_uint16(VT_4x, addr)--获取寄存器值
    else
        return false
    end
end

假设需要读取从机1/从机2的电量、电量,并计算总电压电量。如下所示

function on_run(screen)

    local Energy1 = mb_read_reg_03(0, 0x0000)
    local Energy2 = mb_read_reg_03(1, 0x0000)
    if Energy1 ~= false and Energy2 ~= false
    then
        set_uint16(VT_LW, 0x1000, Energy1 + Energy2)
    end

    local vol1 = mb_read_reg_03(0, 0x0001)
    local vol2 = mb_read_reg_03(1, 0x0001)
    if vol1 ~= false and vol2 ~= false
    then
        set_uint16(VT_LW, 0x1001, vol1 + vol2)
    end
end

2.2.运行预览

创建虚拟串口对,用Modbus slave和虚拟屏联机,如下所示,总电压、总电压实时变化:

Video_2024-01-15_151116~2

3.Lua 写寄存器

3.1.Lua 脚本

Lua脚本写从机寄存器值,通常在某个条件下或计算后,设置对应寄存器。步骤如下所示

  1. 获取寄存器前,要需要判断从机是否在线
  2. 获取寄存器前,需选择对应的从机
--写单个保存寄存器
function mb_write_reg_06(slave, addr, value)

    local onlineState = get_uint16(VT_LW,0x01A3)

    if ((onlineState >> slave) & 0x01) == 0x01
    then
        feed_dog()
        select_slave(slave)
        set_uint16(VT_4x, addr, value)
        return true
    else
        return false
    end

end

假设一个称重业务,一共有三种校准模式,如下所示

  • 零点校准:实重 = 57
  • 砝码校准:实重 = 砝码重量
  • 实物校准:实重=标定系数*砝码重量 // 保存码

function on_update(slave,vtype,addr)

    if vtype == VT_LW
    then
        if addr == 0x1004
        then
            local mode = get_uint16(VT_LW,  0x1003) -- 校准

            if mode == 0
            then
                mb_write_reg_06(0, 0x1002, 57)

            elseif mode == 1
            then
                --实重 = 砝码重量
                print('val == 1')
                local weightVal = get_uint16(VT_LW, 0x1005) -- 砝码重量
                mb_write_reg_06(0, 0x1002, weightVal)

            elseif mode == 2
            then
                --if (标定系数>0) && (保存码>0) 
                --then
                --    实重=标定系数*砝码重量 // 保存码
                --end
                local coeffi   = get_uint16(VT_LW, 0x1006) --标定系数
                local saveCode = get_uint16(VT_LW, 0x1007) --保存码

                if coeffi > 0 and saveCode > 0
                then
                    local weightVal = get_uint16(VT_LW, 0x1005) -- 砝码重量
                    mb_write_reg_06(0, 0x1002, (saveCode*weightVal)//coeffi )
                end
            end
        end
    end
end

3.2.运行预览

创建虚拟串口对,用Modbus slave和虚拟屏联机,如下所示,选择不同的校准方式和校准参数,校准结果实时变化:

Video_2024-01-15_153556

4.Lua 批量写线圈

4.1.Lua 脚本

VisualHMI批量写线圈最小单位是短整型,16个线圈对齐。本章节封装了批量写线圈的函数,直接调用以下接口

mb_write_coil_15(slave_id, addr, coilsTb)

  • slave_id:从站号索引
  • addr:起始地址
  • coilsTb:线圈值,字表

[!note|tip:注意] mb_write_coil_15 该函数为自行封装api,非系统api

假设一个业务逻辑,线圈起始地址为0x0000,设置连续14个线圈值,如下所示

coil c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13
val 1 0 1 0 0 0 1 0 1 1 1 1 0 0

按钮按下,执行批量设置寄存器逻辑,如下所示:


--批量写多个线圈
function mb_write_coil_15 (slave,addr,coils)

    if ((addr + #coils) > 0xFFFF ) or (#coils > 120) or (#coils <= 0)
    then
        return
    end

    local onlineState = get_uint16(VT_LW,0x01A3)
    if ((onlineState >> slave) & 0x01) == 0x01
    then
        feed_dog()
        select_slave(slave)
        local cnt = (#coils // 16) + (((#coils % 16) >= 0 ) and 1 or 0) -- 总共个数,16个线圈对齐
        local all = cnt*16
        local idx = (#(coils) + 1)

        if all > 120 --标准modbus 最大读写连续寄存器<=120
        then
            return
        end

        for i = idx, all --16bit对齐,获取实际对齐部分线圈的值
        do
            local ret = mb_read_coil_01(slave, ((addr + i) - 1))
            if ret == false
            then
                return false
            end
            coils[#coils + 1] = ret
       end

        local val = {}
        for i = 1, cnt --合并16位数据
        do
            val[i] = 0x0000
            for j = 0, 15
            do
                 val[i] = set_valbit(val[i], j, coils[(i - 1)*16 + j + 1])
            end
        end
        set_array(VT_0x, addr, val)

        return true
    else
        return false
    end

end

function on_update(slave,vtype,addr)
    if vtype == VT_LW
    then
        if addr == 0x1004
        then
            ......

        elseif addr == 0x2000
        then
            local val = get_uint16(VT_LW,  0x2000) -- 批量写
            if val == 1
            then
                --批量写入0x0000~0x000D, 14个寄存器  5B1A
                mb_write_coil_15(0, 0x0000, {1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0 })
            ....
            end
        end
    end
end

4.2.运行预览

创建虚拟串口对,用Modbus slave和虚拟屏联机,如下所示,点击批量写线圈按钮,0x0000~0x000D线圈对应变化

Video_2024-01-15_180412

5.Lua 批量写寄存器

5.1.Lua 脚本

VisualHMI批量写保持寄存器最小单位是16位数据。本章节封装了批量写寄存器的函数,直接调用以下接口

mb_write_reg_16(slave_id, addr, regsTb)

  • slave_id:从站号索引
  • addr:起始地址
  • regsTb:寄存器值,字表

[!note|tip:注意] mb_write_reg_16 该函数为自行封装api,非系统api

--批量写多个保存寄存器
function mb_write_reg_16 (slave,addr,regs)

    local onlineState = get_uint16(VT_LW,0x01A3)

    if ((onlineState >> slave) & 0x01) == 0x01
    then
        feed_dog()
        select_slave(slave)
        set_array(VT_4x, addr, regs)
        return true
    else
        return false
    end

end

function on_update(slave,vtype,addr)
    if vtype == VT_LW
    then
        if addr == 0x1004
        then
            ......

        elseif addr == 0x2000
        then
            local val = get_uint16(VT_LW,  0x2000) -- 批量写
            if val == 1
            then
                   ....
            elseif val == 2
            then
                --批量写入0x1000~0x1006, 7个寄存器
                mb_write_reg_16(1, 0x1000, {0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007})
            end
        end
    end
end

5.2.运行预览

创建虚拟串口对,用Modbus slave和虚拟屏联机,如下所示,点击批量写寄存器按钮,0x1000~0x1006寄存器对应变化

Video_2024-01-15_184327

Copyright ©Dacai all right reserved,powered by Gitbook该文件修订时间: 2026-02-09 08:52:50

results matching ""

    No results matching ""