Ver código fonte

add:增加 Air8000 modbus TCP 主站示例代码

马梦阳 3 meses atrás
pai
commit
a0d1589006

+ 151 - 0
module/Air8000/demo/modbus/tcp_master/main.lua

@@ -0,0 +1,151 @@
+--[[
+@module  main
+@summary LuatOS 用户应用脚本文件入口,总体调度应用逻辑
+@version 1.0
+@date    2025.12.18
+@author  马梦阳
+@usage
+
+本 demo 演示的核心功能为:
+1、将设备配置为 modbus TCP 主站模式
+2、与从站 1 和 从站 2 进行通信
+    1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
+    2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
+
+注意事项:
+1、该示例程序需要搭配 exmodbus 扩展库使用
+2、在 main.lua 中 require "param_field" 模块,可以演示标准 modbus TCP 请求报文格式的使用方式
+3、在 main.lua 中 require "raw_frame" 模块,可以演示非标准 modbus TCP 请求报文格式的使用方式
+4、require "param_field" 和 require "raw_frame" 不要同时打开,否则功能会有冲突
+
+特别说明:
+关于 TCP 报文,exmodbus 扩展库支持通过 字段参数 或 原始帧 两种方式进行配置
+这两种配置方式本质都由用户将其放入 table 中在调用接口时传入,区别如下:
+1、字段参数方式
+    这种方式需要用户将请求报文进行解析后,将其放入 table 中,例如:
+    读取请求:
+    local config = {
+        slave_id = 1,                         -- 从站地址
+        reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+        start_addr = 0x0000,                  -- 寄存器起始地址
+        reg_count = 0x0002,                   -- 寄存器数量
+        timeout = 1000                        -- 超时时间
+    }
+    写入请求:
+    local config = {
+        slave_id = 2,                         -- 从站地址
+        reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+        start_addr = 0x0000,                  -- 寄存器起始地址
+        reg_count = 0x0002,                   -- 寄存器数量
+        data = {
+            [start_addr] = 0x0012,            -- 寄存器 0 的值
+            [start_addr + 1] = 0x0034,        -- 寄存器 1 的值
+        }
+        force_multiple = true, -- 是否强制使用多个寄存器写入操作(写多个线圈功能码:0x0F;写多个寄存器功能码:0x10)
+        timeout = 1000                        -- 超时时间
+    }
+    
+2、原始帧方式
+    这种方式只需要用户将原始请求报文放入 table 中,例如:
+    读取请求:
+    local config = {
+        raw_request = string.char(
+            0x00, 0x01, -- 事务标识符
+            0x00, 0x00, -- 协议标识符
+            0x00, 0x06, -- 长度
+            0x01,       -- 单元标识符(从站地址)
+            0x03,       -- 功能码:读取保持寄存器
+            0x00, 0x00, -- 寄存器起始地址
+            0x00, 0x02  -- 寄存器数量
+        ),
+        timeout = 1000  -- 超时时间 1000 ms
+    }
+    写入请求:
+    local config = {
+        raw_request = string.char(
+            0x00, 0x02, -- 事务标识符
+            0x00, 0x00, -- 协议标识符
+            0x00, 0x0B, -- 长度
+            0x02,       -- 单元标识符(从站地址)
+            0x10,       -- 功能码:写入保持寄存器
+            0x00, 0x00, -- 寄存器起始地址
+            0x00, 0x02, -- 寄存器数量
+            0x04,       -- 字节数量
+            0x00, 0x12, -- 寄存器 0 的值
+            0x00, 0x34  -- 寄存器 1 的值
+        ),
+        timeout = 1000  -- 超时时间 1000 ms
+    }
+如果你需要发送的请求报文是符合 modbus TCP 标准格式,可以使用 字段参数 或者 原始帧 方式
+如果你需要发送的请求报文是非标准格式,必须使用 原始帧 方式,使用 字段参数 方式会导致解析的数据不正确
+更多说明参考本目录下的 readme.md 文件;
+]]
+
+--[[
+必须定义PROJECT和VERSION变量,Luatools工具会用到这两个变量,远程升级功能也会用到这两个变量
+PROJECT:项目名,ascii string类型
+        可以随便定义,只要不使用,就行
+VERSION:项目版本号,ascii string类型
+        如果使用合宙iot.openluat.com进行远程升级,必须按照"XXX.YYY.ZZZ"三段格式定义:
+            X、Y、Z各表示1位数字,三个X表示的数字可以相同,也可以不同,同理三个Y和三个Z表示的数字也是可以相同,可以不同
+            因为历史原因,YYY这三位数字必须存在,但是没有任何用处,可以一直写为000
+        如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
+]]
+PROJECT = "TCP_MASTER"
+VERSION = "001.000.000"
+
+
+-- 在日志中打印项目名和项目版本号
+log.info("main", PROJECT, VERSION)
+
+
+-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
+-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
+if wdt then
+    --配置喂狗超时时间为9秒钟
+    wdt.init(9000)
+    --启动一个循环定时器,每隔3秒钟喂一次狗
+    sys.timerLoopStart(wdt.feed, 3000)
+end
+
+
+-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
+-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
+-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
+-- 启动errDump日志存储并且上传功能,600秒上传一次
+-- if errDump then
+--     errDump.config(true, 600)
+-- end
+
+
+-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
+-- 可以使用合宙的iot.openluat.com平台进行远程升级
+-- 也可以使用客户自己搭建的平台进行远程升级
+-- 远程升级的详细用法,可以参考fota的demo进行使用
+
+
+-- 启动一个循环定时器
+-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
+-- 方便分析内存使用是否有异常
+-- sys.timerLoopStart(function()
+--     log.info("mem.lua", rtos.meminfo())
+--     log.info("mem.sys", rtos.meminfo("sys"))
+-- end, 3000)
+
+
+-- 开启以太网wan(默认使用静态 IP 地址)
+require "netdrv_eth_spi"
+
+-- 以下两种方式只能选择其中一种打开,否则会导致功能冲突
+
+-- 加载 TCP 主站应用模块(字段参数方式)
+require "param_field"
+
+-- 加载 TCP 主站应用模块(原始帧方式)
+-- require "raw_frame"
+
+
+-- 用户代码已结束---------------------------------------------
+-- 结尾总是这一句
+sys.run()
+-- sys.run()之后后面不要加任何语句!!!!!

+ 81 - 0
module/Air8000/demo/modbus/tcp_master/netdrv_eth_spi.lua

@@ -0,0 +1,81 @@
+--[[
+@module  netdrv_eth_spi
+@summary “通过SPI外挂CH390H芯片的以太网卡”驱动模块
+@version 1.0
+@date    2025.12.18
+@author  马梦阳
+@usage
+本文件为“通过SPI外挂CH390H芯片的以太网卡”驱动模块,核心业务逻辑为:
+1、打开CH390H芯片供电开关;
+2、初始化spi1,初始化以太网卡,并且在以太网卡上开启DHCP(动态主机配置协议);
+3、以太网卡的连接状态发生变化时,在日志中进行打印;
+
+直接使用Air8000开发板硬件测试即可;
+
+本文件没有对外接口,直接在其他功能模块中require "netdrv_eth_spi"就可以加载运行;
+]]
+
+local exnetif = require "exnetif"
+
+local function ip_ready_func(ip, adapter)
+    if adapter == socket.LWIP_ETH then
+        -- 在位置1和2设置自定义的DNS服务器ip地址:
+        -- "223.5.5.5",这个DNS服务器IP地址是阿里云提供的DNS服务器IP地址;
+        -- "114.114.114.114",这个DNS服务器IP地址是国内通用的DNS服务器IP地址;
+        -- 可以加上以下两行代码,在自动获取的DNS服务器工作不稳定的情况下,这两个新增的DNS服务器会使DNS服务更加稳定可靠;
+        -- 如果使用专网卡,不要使用这两行代码;
+        -- 如果使用国外的网络,不要使用这两行代码;
+        socket.setDNS(adapter, 1, "223.5.5.5")
+        socket.setDNS(adapter, 2, "114.114.114.114")
+
+        log.info("netdrv_eth_spi.ip_ready_func", "IP_READY", socket.localIP(socket.LWIP_ETH))
+    end
+end
+
+local function ip_lose_func(adapter)
+    if adapter == socket.LWIP_ETH then
+        log.warn("netdrv_eth_spi.ip_lose_func", "IP_LOSE")
+    end
+end
+
+
+-- 以太网联网成功(成功连接路由器,并且获取到了IP地址)后,内核固件会产生一个"IP_READY"消息
+-- 各个功能模块可以订阅"IP_READY"消息实时处理以太网联网成功的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+-- 以太网断网后,内核固件会产生一个"IP_LOSE"消息
+-- 各个功能模块可以订阅"IP_LOSE"消息实时处理以太网断网的事件
+-- 也可以在任何时刻调用socket.adapter(socket.LWIP_ETH)来获取以太网是否连接成功
+
+--此处订阅"IP_READY"和"IP_LOSE"两种消息
+--在消息的处理函数中,仅仅打印了一些信息,便于实时观察“通过SPI外挂CH390H芯片的以太网卡”的连接状态
+--也可以根据自己的项目需求,在消息处理函数中增加自己的业务逻辑控制,例如可以在连网状态发生改变时更新网络图标
+sys.subscribe("IP_READY", ip_ready_func)
+sys.subscribe("IP_LOSE", ip_lose_func)
+
+
+-- 配置SPI外接以太网芯片CH390H的单网卡,exnetif.set_priority_order使用的网卡编号为socket.LWIP_ETH
+-- 本demo使用Air8000开发板测试,开发板上的硬件配置为:
+-- GPIO140为CH390H以太网芯片的供电使能控制引脚
+-- 使用spi1,片选引脚使用GPIO12
+-- 如果使用的硬件不是Air8000开发板,根据自己的硬件配置修改以下参数
+local function netdrv_task_func()
+    exnetif.set_priority_order({
+        {
+            ETHERNET = {
+                pwrpin = 140,
+                tp = netdrv.CH390,
+                opts = { spi = 1, cs = 12 },
+                -- 此处设置为静态 IP 地址
+                -- 如果不设置为静态 IP 地址,默认会使用 DHCP 协议动态获取 IP 地址
+                static_ip = {
+                    ipv4 = "192.168.1.183",
+                    mark = "255.255.255.0",
+                    gw = "192.168.1.1"
+                }
+            }
+        }
+    })
+end
+
+sys.taskInit(netdrv_task_func)

+ 149 - 0
module/Air8000/demo/modbus/tcp_master/param_field.lua

@@ -0,0 +1,149 @@
+--[[
+@module  param_field
+@summary TCP 主站应用模块(字段参数方式)
+@version 1.0
+@date    2025.12.18
+@author  马梦阳
+@usage
+本功能模块演示的内容为:
+1、将设备配置为 modbus TCP 主站模式
+2、与从站 1 和 从站 2 进行通信
+    1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
+    2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
+
+注意事项:
+1、该示例程序需要搭配 exmodbus 扩展库使用
+2、本功能模块适合使用标准 modbus TCP 请求报文格式的用户
+3、如果你使用的是非标准 modbus TCP 请求报文格式,请参考 raw_frame 功能模块
+
+本文件没有对外接口,直接在 main.lua 中 require "param_field" 就可以加载运行;
+]]
+
+local exmodbus = require("exmodbus")
+
+-- 创建 TCP 主站配置参数
+-- 说明:创建 TCP 主站时只需要配置如下参数即可
+local create_config = {
+    -- 网络参数配置
+    mode = exmodbus.TCP_MASTER,   -- 通信模式:TCP主站
+    adapter = socket.LWIP_ETH,    -- 网卡 ID:LwIP 协议栈的以太网卡
+    ip_address = "192.168.1.100", -- 从站 IP 地址
+    port = 6000,                  -- 从站端口号
+}
+
+-- 初始化从站 1 数据结构
+-- 用于记录从站 1 保持寄存器 0-1 的值
+local slave1_data = {}
+
+-- 读取从站 1 保持寄存器 0-1 的值时,配置读命令的字段参数;
+local read_config = {
+    slave_id = 1,                         -- 从站地址 1
+    reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+    start_addr = 0x0000,                  -- 起始地址 0
+    reg_count = 0x0002,                   -- 读取 2 个寄存器
+    timeout = 1000                        -- 超时时间 1000 ms
+}
+
+
+-- 初始化从站 2 数据结构;
+local slave2_data = {
+    data1 = 123,
+    data2 = 456
+}
+
+-- 定义从站 2 保持寄存器的起始地址;
+local start_addr = 0x0000
+
+-- 写入从站 2 保持寄存器 0-1 的值时,配置写命令的字段参数;
+local write_config = {
+    slave_id = 2,                                            -- 从站地址 2
+    reg_type = exmodbus.HOLDING_REGISTER,                    -- 寄存器类型:保持寄存器
+    start_addr = start_addr,                                 -- 起始地址 0
+    reg_count = 0x0002,                                      -- 写入 2 个寄存器
+    data = {
+        [start_addr] = slave2_data.data1,                    -- 第一个寄存器值
+        [start_addr + 1] = slave2_data.data2,                -- 第二个寄存器值
+    },                                                       -- 写入寄存器值
+    force_multiple = true,                                   -- 强制使用写多个功能码
+                                                                -- 设置为 true 时,写单个或多个线圈时强制功能码为 0x0F,写单个或多个保持寄存器时强制功能码为 0x10
+                                                                -- 设置为 false 时,写单个线圈时功能码为 0x05,写单个保持寄存器时功能码为 0x06,写多个线圈时功能码为 0x0F,写多个保持寄存器时功能码为 0x10
+    timeout = 1000                                           -- 超时时间 1000 ms
+}
+
+
+-- 创建 TCP 主站实例
+local tcp_master = exmodbus.create(create_config)
+
+-- 判断主站是否创建成功并记录日志
+if not tcp_master then
+    log.info("exmodbus_test", "tcp_master 创建失败")
+else
+    log.info("exmodbus_test", "tcp_master 创建成功")
+end
+
+
+-- 读取从站 1 保持寄存器数据的函数
+local function read_slave1_holding_registers()
+
+    log.info("exmodbus_test", "开始读取从站 1 保持寄存器 0-1 的值")
+
+    -- 执行读取操作
+    local read_result = tcp_master:read(read_config)
+
+    -- 根据返回状态处理结果
+    if read_result.status == exmodbus.STATUS_SUCCESS then
+        slave1_data.data1 = read_result.data[read_config.start_addr]
+        slave1_data.data2 = read_result.data[read_config.start_addr + 1]
+        log.info("exmodbus_test", "成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为", slave1_data.data1, ",寄存器 1 数值为", slave1_data.data2)
+    elseif read_result.status == exmodbus.STATUS_DATA_INVALID then
+        log.info("exmodbus_test", "收到从站 1 的响应数据但数据损坏/校验失败")
+    elseif read_result.status == exmodbus.STATUS_EXCEPTION then
+        log.info("exmodbus_test", "收到从站 1 的 modbus 标准异常响应,异常码为", read_result.execption_code)
+    elseif read_result.status == exmodbus.STATUS_TIMEOUT then
+        log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
+    end
+end
+
+-- 写入从站 2 保持寄存器数据的函数
+local function write_slave2_holding_registers()
+
+    log.info("exmodbus_test", "开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为", slave2_data.data1, ",寄存器 1 数值为", slave2_data.data2)
+
+    -- 执行写入操作
+    local write_result = tcp_master:write(write_config)
+
+    -- 根据返回状态处理结果
+    if write_result.status == exmodbus.STATUS_SUCCESS then
+        log.info("exmodbus_test", "成功写入从站 2 保持寄存器 0-1 的值")
+    elseif write_result.status == exmodbus.STATUS_DATA_INVALID then
+        log.info("exmodbus_test", "收到从站 2 的响应数据但数据损坏/校验失败")
+    elseif write_result.status == exmodbus.STATUS_EXCEPTION then
+        log.info("exmodbus_test", "收到从站 2 的 modbus 标准异常响应,异常码为", write_result.execption_code)
+    elseif write_result.status == exmodbus.STATUS_TIMEOUT then
+        log.info("exmodbus_test", "未收到从站 2 的响应(超时)")
+    end
+end
+
+-- 定时任务函数:每 2 秒调用一次读取函数,每 4 秒调用一次写入函数
+local function task()
+
+    local count = 0 -- 计数器
+
+    while true do
+        if tcp_master then
+            -- 每 2 秒调用一次读取函数
+            read_slave1_holding_registers()
+            if count == 0 then
+                -- 每 4 秒调用一次写入函数
+                write_slave2_holding_registers()
+            end
+            count = (count + 1) % 2
+        else
+            log.info("exmodbus_test", "tcp_master 未创建,无法执行 read_slave1_holding_registers()")
+        end
+        sys.wait(2000)
+    end
+end
+
+-- 初始化任务
+sys.taskInit(task)

+ 258 - 0
module/Air8000/demo/modbus/tcp_master/raw_frame.lua

@@ -0,0 +1,258 @@
+--[[
+@module  raw_frame
+@summary TCP 主站应用模块(原始帧方式)
+@version 1.0
+@date    2025.12.18
+@author  马梦阳
+@usage
+本功能模块演示的内容为:
+1、将设备配置为 modbus TCP 主站模式
+2、与从站 1 和 从站 2 进行通信
+    1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
+    2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
+
+注意事项:
+1、该示例程序需要搭配 exmodbus 扩展库使用
+2、本功能模块只适合使用非标准 modbus TCP 请求报文格式的用户
+3、如果你使用的是标准 modbus TCP 请求报文格式,请参考 param_field 功能模块
+
+本文件没有对外接口,直接在 main.lua 中 require "raw_frame" 就可以加载运行;
+]]
+
+local exmodbus = require("exmodbus")
+
+-- 创建 TCP 主站配置参数
+-- 说明:创建 TCP 主站时只需要配置如下参数即可
+local create_config = {
+    -- 网络参数配置
+    mode = exmodbus.TCP_MASTER,   -- 通信模式:TCP主站
+    adapter = socket.LWIP_ETH,    -- 网卡 ID:LwIP 协议栈的以太网卡
+    ip_address = "192.168.1.100", -- 从站 IP 地址
+    port = 6000,                  -- 从站端口号
+}
+
+-- 初始化从站 1 数据结构
+-- 用于记录从站 1 保持寄存器 0-1 的值;
+local slave1_data = {}
+
+-- 配置读取从站 1 保持寄存器 0-1 的值;
+local read_config = {
+    raw_request = string.char(
+        0x00, 0x01, -- 事务标识符
+        0x00, 0x00, -- 协议标识符
+        0x00, 0x06, -- 长度
+        0x01,       -- 单元标识符(从站地址)
+        0x03,       -- 功能码:读取保持寄存器
+        0x00, 0x00, -- 寄存器起始地址
+        0x00, 0x02  -- 寄存器数量
+    ),
+    timeout = 1000  -- 超时时间 1000 ms
+}
+
+-- 配置写入从站 2 保持寄存器 0-1 的值;
+local write_config = {
+    raw_request = string.char(
+        0x00, 0x02, -- 事务标识符
+        0x00, 0x00, -- 协议标识符
+        0x00, 0x0B, -- 长度
+        0x02,       -- 单元标识符(从站地址)
+        0x10,       -- 功能码:写入保持寄存器
+        0x00, 0x00, -- 寄存器起始地址
+        0x00, 0x02, -- 寄存器数量
+        0x04,       -- 字节数量
+        0x00, 0x12, -- 寄存器 0 的值
+        0x00, 0x34  -- 寄存器 1 的值
+    ),
+    timeout = 1000  -- 超时时间 1000 ms
+}
+
+
+-- 创建 TCP 主站实例
+local tcp_master = exmodbus.create(create_config)
+
+-- 判断主站是否创建成功并记录日志
+if not tcp_master then
+    log.info("exmodbus_test", "tcp_master 创建失败")
+else
+    log.info("exmodbus_test", "tcp_master 创建成功")
+end
+
+
+-- 读取从站 1 保持寄存器数据的函数
+local function read_slave1_holding_registers()
+    log.info("exmodbus_test", "开始读取从站 1 保持寄存器 0-1 的值")
+
+    -- 执行读取操作
+    local read_result = tcp_master:read(read_config)
+
+    -- 根据返回状态处理结果
+    if read_result.status == exmodbus.STATUS_SUCCESS then
+        local resp = read_result.raw_response
+
+        -- 特别说明:
+        -- 接下来的判断是针对 modbus TCP 标准响应格式的应答原始帧来解析的
+        -- 在实际项目中,应根据自己项目中的实际应答原始帧格式进行解析
+        -- 如果实际格式与此处演示的格式不一致,需要修改接下来的解析代码
+
+        -- 1. 检查总长度:必须为 13 字节(7 MBAP头 + 1 功能码 + 1 字节数 + 4 数据)
+        if #resp ~= 13 then
+            log.info("exmodbus_test", "响应长度错误,期望 13 字节,实际:", #resp)
+            return
+        end
+
+        -- 2. 检查事务标识符是否与请求一致
+        local req_trans_id = string.unpack(">I2", read_config.raw_request, 1)
+        local resp_trans_id = string.unpack(">I2", resp, 1)
+        if req_trans_id ~= resp_trans_id then
+            log.info("exmodbus_test", "事务标识符不一致,期望:", req_trans_id, "实际:", resp_trans_id)
+            return
+        end
+
+        -- 3. 检查协议标识符是否为 0x0000
+        if string.unpack(">I2", resp, 3) ~= 0x0000 then
+            log.info("exmodbus_test", "协议标识符错误,期望 0x0000,实际:", string.unpack(">I2", resp, 3))
+            return
+        end
+
+        -- 4. 检查单元标识符(从站地址)是否与请求一致
+        local req_unit_id = string.byte(read_config.raw_request, 7)
+        local resp_unit_id = string.byte(resp, 7)
+        if req_unit_id ~= resp_unit_id then
+            log.info("exmodbus_test", "单元标识符不一致,期望:", req_unit_id, "实际:", resp_unit_id)
+            return
+        end
+
+        -- 5. 检查功能码是否与请求一致
+        local req_func_code = string.byte(read_config.raw_request, 8)
+        local resp_func_code = string.byte(resp, 8)
+        if req_func_code ~= resp_func_code then
+            log.info("exmodbus_test", "功能码不一致,期望:", req_func_code, "实际:", resp_func_code)
+            return
+        end
+
+        -- 6. 检查字节数字段是否正确
+        local byte_count = string.byte(resp, 9)
+        if byte_count ~= 4 then
+            log.info("exmodbus_test", "字节数字段错误,期望 4 字节,实际:", byte_count)
+            return
+        end
+
+        -- 7. 解析寄存器数据(从第 10 字节开始,大端序)
+        local data1 = string.unpack(">I2", resp, 10) -- 寄存器 0,偏移 10
+        local data2 = string.unpack(">I2", resp, 12) -- 寄存器 1,偏移 12
+
+        -- 8. 记录数据
+        slave1_data[0] = data1
+        slave1_data[1] = data2
+
+        -- 9. 记录日志
+        log.info("exmodbus_test", "成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为", slave1_data[0], ",寄存器 1 数值为", slave1_data[1])
+    elseif read_result.status == exmodbus.STATUS_TIMEOUT then
+        log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
+    elseif read_result.status == exmodbus.STATUS_PARAM_INVALID then
+        log.info("exmodbus_test", "读取从站 1 参数无效")
+    end
+end
+
+-- 写入从站 2 保持寄存器数据的函数
+local function write_slave2_holding_registers()
+
+    log.info("exmodbus_test", "开始写入从站 2 保持寄存器 0-1 的值")
+
+    -- 执行写入操作
+    local write_result = tcp_master:write(write_config)
+
+    -- 根据返回状态处理结果
+    if write_result.status == exmodbus.STATUS_SUCCESS then
+        local resp = write_result.raw_response
+
+        -- 特别说明:
+        -- 接下来的判断是针对 modbus TCP 标准响应格式的应答原始帧来解析的
+        -- 在实际项目中,应根据自己项目中的实际应答原始帧格式进行解析
+        -- 如果实际格式与此处演示的格式不一致,需要修改接下来的解析代码
+
+        -- 1. 检查总长度:必须为 12 字节(7 MBAP头 + 1 功能码 + 2 起始地址 + 2 寄存器数量)
+        if #resp ~= 12 then
+            log.info("exmodbus_test", "响应长度错误,期望 12 字节,实际:", #resp)
+            return
+        end
+
+        -- 2. 检查事务标识符是否与请求一致
+        local req_trans_id = string.unpack(">I2", write_config.raw_request, 1)
+        local resp_trans_id = string.unpack(">I2", resp, 1)
+        if req_trans_id ~= resp_trans_id then
+            log.info("exmodbus_test", "事务标识符不一致,期望:", req_trans_id, "实际:", resp_trans_id)
+            return
+        end
+
+        -- 3. 检查协议标识符是否为 0x0000
+        if string.unpack(">I2", resp, 3) ~= 0x0000 then
+            log.info("exmodbus_test", "协议标识符错误,期望 0x0000,实际:", string.unpack(">I2", resp, 3))
+            return
+        end
+
+        -- 4. 检查单元标识符(从站地址)是否与请求一致
+        local req_unit_id = string.byte(write_config.raw_request, 7)
+        local resp_unit_id = string.byte(resp, 7)
+        if req_unit_id ~= resp_unit_id then
+            log.info("exmodbus_test", "单元标识符不一致,期望:", req_unit_id, "实际:", resp_unit_id)
+            return
+        end
+
+        -- 5. 检查功能码是否与请求一致
+        local req_func_code = string.byte(write_config.raw_request, 8)
+        local resp_func_code = string.byte(resp, 8)
+        if req_func_code ~= resp_func_code then
+            log.info("exmodbus_test", "功能码不一致,期望:", req_func_code, "实际:", resp_func_code)
+            return
+        end
+
+        -- 6. 检查起始地址是否与请求一致
+        local req_start_addr = string.unpack(">I2", write_config.raw_request, 9)
+        local resp_start_addr = string.unpack(">I2", resp, 9)
+        if req_start_addr ~= resp_start_addr then
+            log.info("exmodbus_test", "起始地址不一致,期望:", req_start_addr, "实际:", resp_start_addr)
+            return
+        end
+
+        -- 7. 检查寄存器数量是否与请求一致
+        local req_reg_count = string.unpack(">I2", write_config.raw_request, 11)
+        local resp_reg_count = string.unpack(">I2", resp, 11)
+        if req_reg_count ~= resp_reg_count then
+            log.info("exmodbus_test", "寄存器数量不一致,期望:", req_reg_count, "实际:", resp_reg_count)
+            return
+        end
+
+        log.info("exmodbus_test", "成功写入从站 2 保持寄存器 0-1")
+    elseif write_result.status == exmodbus.STATUS_TIMEOUT then
+        log.info("exmodbus_test", "未收到从站 2 的响应(超时)")
+    elseif write_result.status == exmodbus.STATUS_PARAM_INVALID then
+        log.info("exmodbus_test", "写入从站 2 参数无效")
+    end
+end
+
+
+-- 定时任务函数:每 2 秒调用一次读取函数,每 4 秒调用一次写入函数
+local function task()
+
+    local count = 0 -- 计数器
+
+    while true do
+        if tcp_master then
+            -- 每 2 秒调用一次读取函数
+            read_slave1_holding_registers()
+            if count == 0 then
+                -- 每 4 秒调用一次写入函数
+                write_slave2_holding_registers()
+            end
+            count = (count + 1) % 2
+        else
+            log.info("exmodbus_test", "tcp_master 未创建,无法执行 read_slave1_holding_registers()")
+        end
+        sys.wait(2000)
+    end
+end
+
+
+-- 初始化任务
+sys.taskInit(task)

+ 406 - 0
module/Air8000/demo/modbus/tcp_master/readme.md

@@ -0,0 +1,406 @@
+## 演示模块概述
+
+1、main.lua:主程序入口;
+
+2、param_field.lua:TCP 主站应用模块(字段参数方式);
+
+3、raw_frame.lua:TCP 主站应用模块(原始帧方式);
+
+4、netdrv_eth_spi.lua:“通过SPI外挂CH390H芯片的以太网卡”驱动模块;
+
+## 演示功能概述
+
+本 demo 演示的核心功能为:
+
+1、将设备配置为 modbus TCP 主站模式
+
+2、与从站 1 和 从站 2 进行通信
+
+- 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
+- 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
+
+
+
+注意事项:
+
+1、该示例程序需要搭配 exmodbus 扩展库使用
+
+2、在 main.lua 中 require "param_field" 模块,可以演示标准 modbus TCP 请求报文格式的使用方式
+
+3、在 main.lua 中 require "raw_frame" 模块,可以演示非标准 modbus TCP 请求报文格式的使用方式
+
+4、require "param_field" 和 require "raw_frame" 不要同时打开,否则功能会有冲突
+
+
+
+特别说明:
+
+关于 TCP 报文,exmodbus 扩展库支持通过 字段参数 或 原始帧 两种方式进行配置
+
+这两种配置方式本质都由用户将其放入 table 中在调用接口时传入,区别如下:
+
+1、字段参数方式
+
+  这种方式需要用户将请求报文进行解析后,将其放入 table 中,例如:
+
+```lua
+-- 读取请求:
+local config = {
+    slave_id = 1,                         -- 从站地址
+    reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+    start_addr = 0x0000,                  -- 寄存器起始地址
+    reg_count = 0x0002,                   -- 寄存器数量
+    timeout = 1000                        -- 超时时间
+}
+
+-- 写入请求:
+local config = {
+    slave_id = 2,                         -- 从站地址
+    reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+    start_addr = 0x0000,                  -- 寄存器起始地址
+    reg_count = 0x0002,                   -- 寄存器数量
+    data = {
+        [start_addr] = 0x0012,            -- 寄存器 0 的值
+        [start_addr + 1] = 0x0034,        -- 寄存器 1 的值
+    }
+    force_multiple = true, -- 是否强制使用多个寄存器写入操作(写多个线圈功能码:0x0F;写多个寄存器功能码:0x10)
+    timeout = 1000                        -- 超时时间
+}
+```
+
+2、原始帧方式
+
+  这种方式只需要用户将原始请求报文放入 table 中,例如:
+
+```lua
+-- 读取请求:
+local config = {
+    raw_request = string.char(
+        0x00, 0x01, -- 事务标识符
+        0x00, 0x00, -- 协议标识符
+        0x00, 0x06, -- 长度
+        0x01,       -- 单元标识符(从站地址)
+        0x03,       -- 功能码:读取保持寄存器
+        0x00, 0x00, -- 寄存器起始地址
+        0x00, 0x02  -- 寄存器数量
+    ),
+    timeout = 1000  -- 超时时间 1000 ms
+}
+
+-- 写入请求:
+local config = {
+    raw_request = string.char(
+        0x00, 0x02, -- 事务标识符
+        0x00, 0x00, -- 协议标识符
+        0x00, 0x0B, -- 长度
+        0x02,       -- 单元标识符(从站地址)
+        0x10,       -- 功能码:写入保持寄存器
+        0x00, 0x00, -- 寄存器起始地址
+        0x00, 0x02, -- 寄存器数量
+        0x04,       -- 字节数量
+        0x00, 0x12, -- 寄存器 0 的值
+        0x00, 0x34  -- 寄存器 1 的值
+    ),
+    timeout = 1000  -- 超时时间 1000 ms
+}
+```
+
+如果你需要发送的请求报文是符合 modbus TCP 标准格式,可以使用 字段参数 或者 原始帧 方式
+
+如果你需要发送的请求报文是非标准格式,必须使用 原始帧 方式,使用 字段参数 方式会导致解析的数据不正确
+
+## 演示硬件环境
+
+1、Air8000 开发板一块
+
+2、TYPE-C USB数据线一根
+
+3、网线两根(一根开发板使用,一根电脑使用)
+
+![](https://docs.openluat.com/cdn/image/Air8000_tcp1.png)
+
+## 演示软件环境
+
+1、[Luatools下载调试工具](https://docs.openluat.com/air8000/luatos/common/download/)
+
+2、[Air8000 V2018 版本](https://docs.openluat.com/air8000/luatos/firmware/)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录 V2018-1 固件对比验证)
+
+3、[摩尔信使(MThings)官网](https://www.gulink.cn/)(用于模拟 modbus 从站设备)
+
+## 演示核心步骤
+
+### TCP 主站应用模块(字段参数方式,对应 param_field.lua)
+
+1、搭建硬件环境
+
+- 将 TYPE-C USB 数据线一端接在 Air8000 开发板上,另一端接在电脑上
+- 将网线一端接在 Air8000 开发板网口上,另一端接在路由器/交换机上
+
+- 将另一根网线一端接在电脑网口上,另一端接在同一个路由器/交换机上
+
+- 参考图见 演示硬件环境
+
+2、在摩尔信使上配置模拟 TCP 从站设备环境
+
+- 点击左上角的 “通道管理” 按钮,在 “通道管理” 窗口点击 “网络通道” 按钮,点击 NET000 通道后面的 “配置” 按钮,在 “网络参数配置” 窗口配置网络参数,操作流程如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/41.png)
+
+- 点击左上角的 “添加设备”按钮,在 “添加设备” 窗口对通信设备参数进行配置,配置好后点击 “添加” 按钮,左侧栏即为添加后的效果,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/42.png)
+
+- 点击左侧的第一个从站(我这里显示为 “NET000-001”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/43.png)
+
+- 点击左侧的第二个从站(我这里显示为 “NET000-002”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/44.png)
+
+- 此时在摩尔信使上的配置操作已经完成,如果需要在摩尔信使上查看报文,那么操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/45.png)
+
+3、调整软件代码
+
+- 打开 require "param_field" ,注释掉 require "raw_frame" ,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/modbus/4.png)
+  
+- 在 ”param_field.lua“ 文件中修改对应的 IP 地址和端口号(与上位机保持一致)
+
+  ![img](https://docs.openluat.com/cdn/image/modbus/6.png)
+
+4、打开 Luatools 工具,根据要求烧录本次所需要的内核固件和脚本代码
+
+5、烧录成功后,自动开机运行
+
+6、开机运行后 Luatools 工具上记录的日志如下:
+
+```
+[2025-12-18 15:04:33.822][000000002.471] I/user.exmodbus 连接服务器成功
+[2025-12-18 15:04:34.174][000000002.845] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:34.236][000000002.893] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:36.234][000000004.893] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:36.250][000000004.906] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:36.264][000000004.906] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:36.280][000000004.925] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 15:04:38.069][000000006.667] I/mobile sim0 sms ready
+[2025-12-18 15:04:38.071][000000006.668] D/mobile cid1, state0
+[2025-12-18 15:04:38.079][000000006.668] D/mobile bearer act 0, result 0
+[2025-12-18 15:04:38.087][000000006.669] D/mobile NETIF_LINK_ON -> IP_READY
+[2025-12-18 15:04:38.090][000000006.669] I/user.dnsproxy 开始监听
+[2025-12-18 15:04:38.092][000000006.745] D/mobile TIME_SYNC 0
+[2025-12-18 15:04:38.255][000000006.925] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:38.273][000000006.939] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:40.272][000000008.940] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:40.284][000000008.953] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:40.294][000000008.953] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:40.310][000000008.966] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 15:04:42.297][000000010.966] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:42.327][000000010.979] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:42.899][000000011.563] I/user.exmodbus 连接断开
+[2025-12-18 15:04:44.319][000000012.979] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:44.325][000000012.980] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:44.330][000000012.981] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 15:04:44.338][000000012.981] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:44.344][000000012.983] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:44.352][000000012.983] I/user.exmodbus_test 未收到从站 2 的响应(超时)
+[2025-12-18 15:04:46.314][000000014.984] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:46.335][000000014.985] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:46.365][000000014.986] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 15:04:47.901][000000016.566] D/socket connect to 192.168.1.100,6000
+[2025-12-18 15:04:47.911][000000016.567] D/net adapter 4 connect 192.168.1.100:6000 TCP
+[2025-12-18 15:04:48.320][000000016.987] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:48.326][000000016.988] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:48.333][000000016.989] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 15:04:48.346][000000016.989] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:48.350][000000016.991] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:48.356][000000016.991] I/user.exmodbus_test 未收到从站 2 的响应(超时)
+[2025-12-18 15:04:50.324][000000018.992] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:50.328][000000018.993] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:50.333][000000018.994] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 15:04:50.913][000000019.573] I/user.exmodbus 连接服务器成功
+[2025-12-18 15:04:52.335][000000020.994] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:52.343][000000021.006] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:52.352][000000021.007] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:52.359][000000021.019] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 15:04:54.357][000000023.020] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:54.384][000000023.032] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:56.371][000000025.033] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:56.410][000000025.045] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:56.426][000000025.046] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:56.441][000000025.063] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
+```
+
+7、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的网络通道关闭后,此时 Air8000 在发送请求时便会收不到响应,Luatools 工具上显示的日志如下:
+
+![img](https://docs.openluat.com/cdn/image/MThings/46.png)
+
+```
+[2025-12-18 15:04:42.899][000000011.563] I/user.exmodbus 连接断开
+[2025-12-18 15:04:44.319][000000012.979] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:44.325][000000012.980] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:44.330][000000012.981] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 15:04:44.338][000000012.981] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:44.344][000000012.983] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 15:04:44.352][000000012.983] I/user.exmodbus_test 未收到从站 2 的响应(超时)
+```
+
+8、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的网络通道打开后,此时 Air8000 在发送请求时便会接收到响应,Luatools 工具与摩尔信使上显示的日志如下:
+
+> 程序设计为每隔 2 秒执行一次读取,每隔 4 秒执行一次写入
+
+![img](https://docs.openluat.com/cdn/image/MThings/47.png)
+
+```
+[2025-12-18 15:04:50.913][000000019.573] I/user.exmodbus 连接服务器成功
+[2025-12-18 15:04:52.335][000000020.994] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 15:04:52.343][000000021.006] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 15:04:52.352][000000021.007] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
+[2025-12-18 15:04:52.359][000000021.019] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
+```
+
+![img](https://docs.openluat.com/cdn/image/MThings/48.png)
+
+9、关于 Air8000 执行读取和写入请求后,摩尔信使上位机的数值变化如下图所示:
+
+![img](https://docs.openluat.com/cdn/image/MThings/49.png)
+
+![img](https://docs.openluat.com/cdn/image/MThings/50.png)
+
+### TCP 主站应用模块(原始帧方式,对应 raw_frame.lua)
+
+1、搭建硬件环境
+
+- 将 TYPE-C USB 数据线一端接在 Air8000 开发板上,另一端接在电脑上
+- 将网线一端接在 Air8000 开发板网口上,另一端接在路由器/交换机上
+
+- 将另一根网线一端接在电脑网口上,另一端接在同一个路由器/交换机上
+
+- 参考图见 演示硬件环境
+
+2、在摩尔信使上配置模拟 TCP 从站设备环境
+
+- 点击左上角的 “通道管理” 按钮,在 “通道管理” 窗口点击 “网络通道” 按钮,点击 NET000 通道后面的 “配置” 按钮,在 “网络参数配置” 窗口配置网络参数,操作流程如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/41.png)
+
+- 点击左上角的 “添加设备”按钮,在 “添加设备” 窗口对通信设备参数进行配置,配置好后点击 “添加” 按钮,左侧栏即为添加后的效果,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/42.png)
+
+- 点击左侧的第一个从站(我这里显示为 “NET000-001”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/43.png)
+
+- 点击左侧的第二个从站(我这里显示为 “NET000-002”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/44.png)
+
+- 此时在摩尔信使上的配置操作已经完成,如果需要在摩尔信使上查看报文,那么操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/MThings/45.png)
+
+3、调整软件代码
+
+- 打开 require "raw_frame" ,注释掉 require "param_field" ,操作流程图如下:
+
+  ![img](https://docs.openluat.com/cdn/image/modbus/5.png)
+  
+- 在 ”raw_frame.lua“ 文件中修改对应的 IP 地址和端口号(与上位机保持一致)
+
+  ![img](https://docs.openluat.com/cdn/image/modbus/7.png)
+
+4、打开 Luatools 工具,根据要求烧录本次所需要的内核固件和脚本代码
+
+5、烧录成功后,自动开机运行
+
+6、开机运行后 Luatools 工具上记录的日志如下:
+
+```
+[2025-12-18 16:13:55.214][000000002.707] I/user.exmodbus 连接服务器成功
+[2025-12-18 16:13:55.371][000000002.858] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:13:55.405][000000002.869] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:13:57.371][000000004.870] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:13:57.380][000000004.881] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:13:57.388][000000004.882] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:13:57.396][000000004.893] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
+[2025-12-18 16:13:59.400][000000006.894] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:13:59.416][000000006.907] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:01.409][000000008.907] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:01.419][000000008.918] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:01.429][000000008.919] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:01.434][000000008.930] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
+[2025-12-18 16:14:03.432][000000010.931] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:03.443][000000010.942] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:04.549][000000012.037] I/user.exmodbus 连接断开
+[2025-12-18 16:14:05.442][000000012.943] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:05.453][000000012.944] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:05.462][000000012.945] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 16:14:05.471][000000012.945] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:05.479][000000012.946] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:05.488][000000012.947] I/user.exmodbus_test 未收到从站 2 的响应(超时)
+[2025-12-18 16:14:07.452][000000014.947] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:07.459][000000014.948] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:07.463][000000014.949] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 16:14:09.447][000000016.949] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:09.460][000000016.950] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:09.464][000000016.950] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 16:14:09.467][000000016.951] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:09.475][000000016.952] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:09.478][000000016.952] I/user.exmodbus_test 未收到从站 2 的响应(超时)
+[2025-12-18 16:14:09.539][000000017.040] D/socket connect to 192.168.1.100,6000
+[2025-12-18 16:14:09.544][000000017.040] D/net adapter 4 connect 192.168.1.100:6000 TCP
+[2025-12-18 16:14:11.460][000000018.953] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:11.467][000000018.954] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:11.475][000000018.954] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 16:14:12.549][000000020.047] I/user.exmodbus 连接服务器成功
+[2025-12-18 16:14:13.454][000000020.955] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:13.470][000000020.966] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:13.472][000000020.966] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:13.482][000000020.978] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
+[2025-12-18 16:14:15.483][000000022.979] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:15.499][000000022.990] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:17.502][000000024.991] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:17.513][000000025.003] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:17.520][000000025.003] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:17.529][000000025.016] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
+```
+
+7、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的网络通道关闭后,此时 Air8000 在发送请求时便会收不到响应,Luatools 工具上显示的日志如下:
+
+![img](https://docs.openluat.com/cdn/image/MThings/46.png)
+
+```
+[2025-12-18 16:14:04.549][000000012.037] I/user.exmodbus 连接断开
+[2025-12-18 16:14:05.442][000000012.943] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:05.453][000000012.944] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:05.462][000000012.945] I/user.exmodbus_test 未收到从站 1 的响应(超时)
+[2025-12-18 16:14:05.471][000000012.945] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:05.479][000000012.946] E/user.exmodbus TCP 连接未建立或已断开,无法发送请求
+[2025-12-18 16:14:05.488][000000012.947] I/user.exmodbus_test 未收到从站 2 的响应(超时)
+```
+
+8、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的网络通道打开后,此时 Air8000 在发送请求时便会接收到响应,Luatools 工具与摩尔信使上显示的日志如下:
+
+> 程序设计为每隔 2 秒执行一次读取,每隔 4 秒执行一次写入
+
+![img](https://docs.openluat.com/cdn/image/MThings/47.png)
+
+```
+[2025-12-18 16:14:12.549][000000020.047] I/user.exmodbus 连接服务器成功
+[2025-12-18 16:14:13.454][000000020.955] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
+[2025-12-18 16:14:13.470][000000020.966] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
+[2025-12-18 16:14:13.472][000000020.966] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
+[2025-12-18 16:14:13.482][000000020.978] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
+```
+
+![img](https://docs.openluat.com/cdn/image/MThings/48.png)
+
+9、关于 Air8000 执行读取和写入请求后,摩尔信使上位机的数值变化如下图所示:
+
+![img](https://docs.openluat.com/cdn/image/MThings/49.png)
+
+![img](https://docs.openluat.com/cdn/image/MThings/50.png)