init: Air780EPM 官方 LuatOS 项目代码基线
- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码 - 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等) - 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
--[[
|
||||
@module main
|
||||
@summary LuatOS 用户应用脚本文件入口,总体调度应用逻辑
|
||||
@version 1.0
|
||||
@date 2025.12.29
|
||||
@author 马梦阳
|
||||
@usage
|
||||
|
||||
本 demo 演示的核心功能为:
|
||||
1、将设备配置为 modbus RTU 主站模式
|
||||
2、与从站 1 和 从站 2 进行通信
|
||||
1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
|
||||
2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
|
||||
3、读取温湿度传感器数据
|
||||
1. 配置 modbus RTU 主站,读取温湿度传感器数据
|
||||
2. 每 2 秒读取一次传感器数据并解析温度和湿度值
|
||||
|
||||
注意事项:
|
||||
1、该示例程序需要搭配 exmodbus 扩展库使用
|
||||
2、在 main.lua 中 require "param_field" 模块,可以演示标准 modbus RTU 请求报文格式的使用方式
|
||||
3、在 main.lua 中 require "raw_frame" 模块,可以演示非标准 modbus RTU 请求报文格式的使用方式
|
||||
4、在 main.lua 中 require "temp_hum_sensor" 模块,可以演示读取485温湿度传感器数值的使用方式
|
||||
5、require "param_field"、require "raw_frame" 和 require "temp_hum_sensor",不要同时打开,否则功能会有冲突
|
||||
|
||||
特别说明:
|
||||
关于 RTU 报文,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(
|
||||
0x01, -- 从站地址
|
||||
0x03, -- 功能码:读取保持寄存器
|
||||
0x00, 0x00, -- 寄存器起始地址
|
||||
0x00, 0x02, -- 寄存器数量
|
||||
0xC4, 0x0B -- CRC16校验码
|
||||
)
|
||||
timeout = 1000 -- 超时时间
|
||||
}
|
||||
写入请求:
|
||||
local config = {
|
||||
raw_request = string.char(
|
||||
0x02, -- 从站地址
|
||||
0x10, -- 功能码:写入保持寄存器
|
||||
0x00, 0x00, -- 寄存器起始地址
|
||||
0x00, 0x02, -- 寄存器数量
|
||||
0x04, -- 字节数量
|
||||
0x00, 0x12, -- 寄存器 0 的值
|
||||
0x00, 0x34, -- 寄存器 1 的值
|
||||
0x5D, 0x39 -- CRC16校验码
|
||||
)
|
||||
timeout = 1000 -- 超时时间
|
||||
}
|
||||
如果你需要发送的请求报文是符合 modbus RTU 标准格式,可以使用 字段参数 或者 原始帧 方式
|
||||
如果你需要发送的请求报文是非标准格式,必须使用 原始帧 方式,使用 字段参数 方式会导致解析的数据不正确
|
||||
更多说明参考本目录下的 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这三位数字必须存在,但是没有任何用处,可以一直写为999
|
||||
如果不使用合宙iot.openluat.com进行远程升级,根据自己项目的需求,自定义格式即可
|
||||
]]
|
||||
PROJECT = "RTU_MASTER"
|
||||
VERSION = "001.999.000"
|
||||
|
||||
|
||||
-- 在日志中打印项目名和项目版本号
|
||||
log.info("main", PROJECT, VERSION)
|
||||
|
||||
-- 硬件环境配置说明
|
||||
-- 本Demo支持两种硬件环境,请根据实际使用情况修改HARDWARE_ENV变量:
|
||||
-- "DEV_BOARD_V1.3" - V1.3开发板(默认)
|
||||
-- 控制485接收和发送的使能脚:GPIO24
|
||||
-- "DEV_BOARD_V1.2" - V1.2开发板
|
||||
-- 控制485接收和发送的使能脚:GPIO25
|
||||
HARDWARE_ENV = "DEV_BOARD_V1.3" -- 修改此值适配不同开发板版本:"DEV_BOARD_V1.3"=V1.3开发板, "DEV_BOARD_V1.2"=V1.2开发板
|
||||
|
||||
|
||||
|
||||
-- 如果内核固件支持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)
|
||||
|
||||
-- 以下三种方式只能选择其中一种打开,否则会导致功能冲突
|
||||
|
||||
-- 加载 RTU 主站应用模块(字段参数方式)
|
||||
require "param_field"
|
||||
|
||||
-- 加载 RTU 主站应用模块(原始帧方式)
|
||||
-- require "raw_frame"
|
||||
|
||||
-- 加载温湿度传感器模块
|
||||
-- require "temp_hum_sensor"
|
||||
|
||||
-- 用户代码已结束---------------------------------------------
|
||||
-- 结尾总是这一句
|
||||
sys.run()
|
||||
-- sys.run()之后后面不要加任何语句!!!!!
|
||||
@@ -0,0 +1,166 @@
|
||||
--[[
|
||||
@module param_field
|
||||
@summary RTU 主站应用模块(字段参数方式)
|
||||
@version 1.0
|
||||
@date 2025.12.29
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本功能模块演示的内容为:
|
||||
1、将设备配置为 modbus RTU 主站模式
|
||||
2、与从站 1 和 从站 2 进行通信
|
||||
1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
|
||||
2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
|
||||
可通过修改字段参数 force_multiple 为 true 来强制使用写多个功能码(写多个线圈功能码:0x0F;写多个寄存器功能码:0x10)
|
||||
|
||||
注意事项:
|
||||
1、该示例程序需要搭配 exmodbus 扩展库使用
|
||||
2、本功能模块只适合使用标准 modbus RTU 请求报文格式的用户
|
||||
3、如果你使用的是非标准 modbus RTU 请求报文格式,请参考 raw_frame 功能模块
|
||||
|
||||
本文件没有对外接口,直接在 main.lua 中 require "param_field" 就可以加载运行;
|
||||
]]
|
||||
|
||||
local exmodbus = require("exmodbus")
|
||||
|
||||
-- 根据硬件环境配置控制485接收和发送的使能脚
|
||||
local rs485_dir_gpio = 24 -- 默认值
|
||||
|
||||
if HARDWARE_ENV == "DEV_BOARD_V1.2" then
|
||||
rs485_dir_gpio = 25 -- V1.2开发板使用GPIO25
|
||||
elseif HARDWARE_ENV == "DEV_BOARD_V1.3" then
|
||||
rs485_dir_gpio = 24 -- V1.3开发板使用GPIO24
|
||||
end
|
||||
|
||||
gpio.setup(1, 1) -- Air780EPM RS485 芯片供电引脚
|
||||
gpio.setup(23, 1) -- Air780EPM vref 脚拉高
|
||||
|
||||
-- 创建 RTU 主站配置参数;
|
||||
-- 说明:创建 RTU 主站时只需要配置如下参数即可;
|
||||
local create_config = {
|
||||
-- 串口配置参数;
|
||||
mode = exmodbus.RTU_MASTER, -- 通信模式
|
||||
uart_id = 1, -- UART 端口号
|
||||
baud_rate = 115200, -- 波特率
|
||||
data_bits = 8, -- 数据位
|
||||
stop_bits = 1, -- 停止位
|
||||
parity_bits = uart.None, -- 校验位
|
||||
byte_order = uart.LSB, -- 字节顺序
|
||||
rs485_dir_gpio = rs485_dir_gpio, -- RS485 方向引脚
|
||||
rs485_dir_rx_level = 0, -- RS485 接收方向电平
|
||||
}
|
||||
|
||||
-- 初始化从站 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
|
||||
}
|
||||
|
||||
|
||||
-- 创建 RTU 主站实例
|
||||
local rtu_master = exmodbus.create(create_config)
|
||||
|
||||
-- 判断主站是否创建成功并记录日志
|
||||
if not rtu_master then
|
||||
log.info("exmodbus_test", "rtu_master 创建失败")
|
||||
else
|
||||
log.info("exmodbus_test", "rtu_master 创建成功")
|
||||
end
|
||||
|
||||
-- 读取从站 1 保持寄存器数据的函数
|
||||
local function read_slave1_holding_registers()
|
||||
|
||||
log.info("exmodbus_test", "开始读取从站 1 保持寄存器 0-1 的值")
|
||||
|
||||
-- 执行读取操作
|
||||
local read_result = rtu_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 = rtu_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 rtu_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", "rtu_master 未创建,无法执行 read_slave1_holding_registers()")
|
||||
end
|
||||
sys.wait(2000)
|
||||
end
|
||||
end
|
||||
|
||||
-- 初始化任务
|
||||
sys.taskInit(task)
|
||||
@@ -0,0 +1,265 @@
|
||||
--[[
|
||||
@module raw_frame
|
||||
@summary RTU 主站应用模块(原始帧方式)
|
||||
@version 1.0
|
||||
@date 2025.12.29
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本功能模块演示的内容为:
|
||||
1、将设备配置为 modbus RTU 主站模式
|
||||
2、与从站 1 和 从站 2 进行通信
|
||||
1. 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
|
||||
2. 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
|
||||
|
||||
注意事项:
|
||||
1、该示例程序需要搭配 exmodbus 扩展库使用
|
||||
2、本功能模块只适合使用非标准 modbus RTU 请求报文格式的用户
|
||||
3、如果你使用的是标准 modbus RTU 请求报文格式,请参考 param_field 功能模块
|
||||
|
||||
本文件没有对外接口,直接在 main.lua 中 require "raw_frame" 就可以加载运行;
|
||||
]]
|
||||
|
||||
local exmodbus = require("exmodbus")
|
||||
|
||||
-- 根据硬件环境配置控制485接收和发送的使能脚
|
||||
local rs485_dir_gpio = 24 -- 默认值
|
||||
|
||||
if HARDWARE_ENV == "DEV_BOARD_V1.2" then
|
||||
rs485_dir_gpio = 25 -- V1.2开发板使用GPIO25
|
||||
elseif HARDWARE_ENV == "DEV_BOARD_V1.3" then
|
||||
rs485_dir_gpio = 24 -- V1.3开发板使用GPIO24
|
||||
end
|
||||
|
||||
gpio.setup(1, 1) -- Air780EPM RS485 芯片供电引脚
|
||||
gpio.setup(23, 1) -- Air780EPM vref 脚拉高
|
||||
|
||||
|
||||
-- 创建 RTU 主站配置参数;
|
||||
-- 说明:创建 RTU 主站时只需要配置如下参数即可;
|
||||
local create_config = {
|
||||
-- 串口配置参数;
|
||||
mode = exmodbus.RTU_MASTER, -- 通信模式
|
||||
uart_id = 1, -- UART 端口号
|
||||
baud_rate = 115200, -- 波特率
|
||||
data_bits = 8, -- 数据位
|
||||
stop_bits = 1, -- 停止位
|
||||
parity_bits = uart.None, -- 校验位
|
||||
byte_order = uart.LSB, -- 字节顺序
|
||||
rs485_dir_gpio = rs485_dir_gpio, -- RS485 方向引脚
|
||||
rs485_dir_rx_level = 0, -- RS485 接收方向电平
|
||||
}
|
||||
|
||||
-- 初始化从站 1 数据结构
|
||||
-- 用于记录从站 1 保持寄存器 0-1 的值;
|
||||
local slave1_data = {}
|
||||
|
||||
-- 配置读取从站 1 保持寄存器 0-1 的值;
|
||||
local read_config = {
|
||||
raw_request = string.char(
|
||||
0x01, -- 从站地址
|
||||
0x03, -- 功能码:读取保持寄存器
|
||||
0x00, 0x00, -- 寄存器起始地址
|
||||
0x00, 0x02, -- 寄存器数量
|
||||
0xC4, 0x0B -- CRC16校验码
|
||||
),
|
||||
timeout = 1000 -- 超时时间 1000 ms
|
||||
}
|
||||
|
||||
|
||||
-- 配置写入从站 2 保持寄存器 0-1 的值;
|
||||
local write_config = {
|
||||
raw_request = string.char(
|
||||
0x02, -- 从站地址
|
||||
0x10, -- 功能码:写入保持寄存器
|
||||
0x00, 0x00, -- 寄存器起始地址
|
||||
0x00, 0x02, -- 寄存器数量
|
||||
0x04, -- 字节数量
|
||||
0x00, 0x12, -- 寄存器 0 的值
|
||||
0x00, 0x34, -- 寄存器 1 的值
|
||||
0x5D, 0x39 -- CRC16校验码
|
||||
),
|
||||
timeout = 1000, -- 超时时间 1000 ms
|
||||
}
|
||||
|
||||
|
||||
-- 创建 RTU 主站实例
|
||||
local rtu_master = exmodbus.create(create_config)
|
||||
|
||||
|
||||
-- 判断主站是否创建成功并记录日志
|
||||
if not rtu_master then
|
||||
log.info("exmodbus_test", "rtu_master 创建失败")
|
||||
else
|
||||
log.info("exmodbus_test", "rtu_master 创建成功")
|
||||
end
|
||||
|
||||
|
||||
-- 读取从站 1 保持寄存器数据的函数
|
||||
local function read_slave1_holding_registers()
|
||||
|
||||
log.info("exmodbus_test", "开始读取从站 1 保持寄存器 0-1 的值")
|
||||
|
||||
-- 执行读取操作
|
||||
local read_result = rtu_master:read(read_config)
|
||||
|
||||
-- 根据返回状态处理结果
|
||||
if read_result.status == exmodbus.STATUS_SUCCESS then
|
||||
local resp = read_result.raw_response
|
||||
|
||||
-- 特别说明:
|
||||
-- 接下来的判断是针对 modbus RTU 标准响应格式的应答原始帧来解析的
|
||||
-- 在实际项目中,应根据自己项目中的实际应答原始帧格式进行解析
|
||||
-- 如果实际格式与此处演示的格式不一致,需要修改接下来的解析代码
|
||||
|
||||
-- 1. 检查总长度:必须为 9 字节(1 地址 + 1 功能码 + 1 字节数 + 4 数据 + 2 CRC)
|
||||
if #resp ~= 9 then
|
||||
log.info("exmodbus_test", "响应长度错误,期望 9 字节,实际:", #resp)
|
||||
return
|
||||
end
|
||||
|
||||
-- 2. 检查从站地址
|
||||
if string.byte(resp, 1) ~= 0x01 then
|
||||
log.info("exmodbus_test", "从站地址不匹配,收到:", string.byte(resp, 1))
|
||||
return
|
||||
end
|
||||
|
||||
-- 3. 检查功能码
|
||||
local func_code = string.byte(resp, 2)
|
||||
if func_code == 0x83 then
|
||||
local exc_code = string.byte(resp, 3)
|
||||
log.info("exmodbus_test", "从站返回异常响应,异常码:", exc_code)
|
||||
return
|
||||
elseif func_code ~= 0x03 then
|
||||
log.info("exmodbus_test", "功能码错误,收到:", func_code)
|
||||
return
|
||||
end
|
||||
|
||||
-- 4. 检查字节数字段(应为 4)
|
||||
local byte_count = string.byte(resp, 3)
|
||||
if byte_count ~= 4 then
|
||||
log.info("exmodbus_test", "字节数字段错误,期望 4,实际:", byte_count)
|
||||
return
|
||||
end
|
||||
|
||||
-- 5. 校验CRC
|
||||
-- 计算前 7 字节的 CRC
|
||||
local crc_calculated = crypto.crc16_modbus(resp:sub(1, 7))
|
||||
-- 提取接收到的 CRC
|
||||
local crc_received = string.unpack("<I2", resp, 8)
|
||||
-- 比较 CRC
|
||||
if crc_calculated ~= crc_received then
|
||||
log.info("exmodbus_test", "CRC 校验错误,计算值:", crc_calculated, ",接收值:", crc_received)
|
||||
return
|
||||
end
|
||||
|
||||
-- 6. 解析寄存器数据(从第 4 字节开始,大端序)
|
||||
local data1 = string.unpack(">I2", resp, 4) -- 寄存器 0,偏移 4
|
||||
local data2 = string.unpack(">I2", resp, 6) -- 寄存器 1,偏移 6
|
||||
|
||||
-- 7. 记录数据
|
||||
slave1_data[0] = data1
|
||||
slave1_data[1] = data2
|
||||
|
||||
-- 8. 记录日志
|
||||
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 的响应(超时)")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 写入从站 2 保持寄存器数据的函数
|
||||
local function write_slave2_holding_registers()
|
||||
|
||||
log.info("exmodbus_test", "开始写入从站 2 保持寄存器 0-1 的值")
|
||||
|
||||
-- 执行写入操作
|
||||
local write_result = rtu_master:write(write_config)
|
||||
|
||||
-- 根据返回状态处理结果
|
||||
if write_result.status == exmodbus.STATUS_SUCCESS then
|
||||
local resp = write_result.raw_response
|
||||
|
||||
-- 特别说明:
|
||||
-- 接下来的判断是针对 modbus RTU 标准响应格式的应答原始帧来解析的
|
||||
-- 在实际项目中,应根据自己项目中的实际应答原始帧格式进行解析
|
||||
-- 如果实际格式与此处演示的格式不一致,需要修改接下来的解析代码
|
||||
|
||||
-- 1. 检查总长度:必须为 8 字节(1 地址 + 1 功能码 + 2 起始地址 + 2 寄存器数量 + 2 CRC)
|
||||
if #resp ~= 8 then
|
||||
log.info("exmodbus_test", "响应长度错误,期望 8 字节,实际:", #resp)
|
||||
return
|
||||
end
|
||||
|
||||
-- 2. 检查从站地址
|
||||
if string.byte(resp, 1) ~= 0x02 then
|
||||
log.info("exmodbus_test", "从站地址不匹配,收到:", string.byte(resp, 1))
|
||||
return
|
||||
end
|
||||
|
||||
-- 3. 检查功能码
|
||||
local func_code = string.byte(resp, 2)
|
||||
if func_code == 0x90 then
|
||||
local exc_code = string.byte(resp, 3)
|
||||
log.info("exmodbus_test", "从站返回异常响应,异常码:", exc_code)
|
||||
return
|
||||
elseif func_code ~= 0x10 then
|
||||
log.info("exmodbus_test", "功能码错误,收到:", func_code)
|
||||
return
|
||||
end
|
||||
|
||||
-- 4. 检查起始地址(应为 0x0000)
|
||||
local start_addr = string.unpack(">I2", resp, 3)
|
||||
if start_addr ~= 0x0000 then
|
||||
log.info("exmodbus_test", "起始地址不匹配,收到:", start_addr)
|
||||
return
|
||||
end
|
||||
|
||||
-- 5. 检查寄存器数量(应为 0x0002)
|
||||
local reg_count = string.unpack(">I2", resp, 5)
|
||||
if reg_count ~= 0x0002 then
|
||||
log.info("exmodbus_test", "寄存器数量错误,期望 0x0002,实际:", reg_count)
|
||||
return
|
||||
end
|
||||
|
||||
-- 6. 校验CRC
|
||||
-- 计算前 6 字节的 CRC
|
||||
local crc_calculated = crypto.crc16_modbus(resp:sub(1, 6))
|
||||
-- 提取接收到的 CRC
|
||||
local crc_received = string.unpack("<I2", resp, 7)
|
||||
-- 比较 CRC
|
||||
if crc_calculated ~= crc_received then
|
||||
log.info("exmodbus_test", "CRC 校验错误,计算值:", crc_calculated, ",接收值:", crc_received)
|
||||
return
|
||||
end
|
||||
|
||||
log.info("exmodbus_test", "成功写入从站 2 保持寄存器 0-1")
|
||||
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 rtu_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", "rtu_master 未创建,无法执行 read_slave1_holding_registers()")
|
||||
end
|
||||
sys.wait(2000)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 初始化任务
|
||||
sys.taskInit(task)
|
||||
@@ -0,0 +1,453 @@
|
||||
## 一、演示模块概述
|
||||
|
||||
1、main.lua:主程序入口;
|
||||
|
||||
2、param_field.lua:RTU 主站应用模块(字段参数方式);
|
||||
|
||||
3、raw_frame.lua:RTU 主站应用模块(原始帧方式);
|
||||
|
||||
4、temp_hum_sensor.lua:485温湿度传感器读取模块;
|
||||
|
||||
## 二、演示功能概述
|
||||
|
||||
本 demo 演示的核心功能为:
|
||||
|
||||
1、将设备配置为 modbus RTU 主站模式
|
||||
|
||||
2、与从站 1 和 从站 2 进行通信
|
||||
|
||||
- 对从站 1 进行 2 秒一次的读取保持寄存器 0-1 操作
|
||||
- 对从站 2 进行 4 秒一次的写入保持寄存器 0-1 操作
|
||||
|
||||
3、读取温湿度传感器数据
|
||||
|
||||
- 配置 modbus RTU 主站,读取温湿度传感器数据
|
||||
- 每 2 秒读取一次传感器数据并解析温度和湿度值
|
||||
|
||||
|
||||
|
||||
注意事项:
|
||||
|
||||
1、该示例程序需要搭配 exmodbus 扩展库使用
|
||||
|
||||
2、在 main.lua 中 require "param_field" 模块,可以演示标准 modbus RTU 请求报文格式的使用方式
|
||||
|
||||
3、在 main.lua 中 require "raw_frame" 模块,可以演示非标准 modbus RTU 请求报文格式的使用方式
|
||||
|
||||
4、在 main.lua 中 require "temp_hum_sensor" 模块,可以演示读取485温湿度传感器数值的使用方式
|
||||
|
||||
5、require "param_field"、require "raw_frame" 和 require "temp_hum_sensor",不要同时打开,否则功能会有冲突
|
||||
|
||||
|
||||
|
||||
特别说明:
|
||||
|
||||
关于 RTU 报文,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(
|
||||
0x01, -- 从站地址
|
||||
0x03, -- 功能码:读取保持寄存器
|
||||
0x00, 0x00, -- 寄存器起始地址
|
||||
0x00, 0x02, -- 寄存器数量
|
||||
0xC4, 0x0B -- CRC16校验码
|
||||
)
|
||||
timeout = 1000 -- 超时时间
|
||||
}
|
||||
|
||||
-- 写入请求:
|
||||
local config = {
|
||||
raw_request = string.char(
|
||||
0x02, -- 从站地址
|
||||
0x10, -- 功能码:写入保持寄存器
|
||||
0x00, 0x00, -- 寄存器起始地址
|
||||
0x00, 0x02, -- 寄存器数量
|
||||
0x04, -- 字节数量
|
||||
0x00, 0x12, -- 寄存器 0 的值
|
||||
0x00, 0x34, -- 寄存器 1 的值
|
||||
0x5D, 0x39 -- CRC16校验码
|
||||
)
|
||||
timeout = 1000 -- 超时时间
|
||||
}
|
||||
```
|
||||
|
||||
如果你需要发送的请求报文是符合 modbus RTU 标准格式,可以使用 字段参数 或者 原始帧 方式
|
||||
|
||||
如果你需要发送的请求报文是非标准格式,必须使用 原始帧 方式,使用 字段参数 方式会导致解析的数据不正确
|
||||
|
||||
## 三、演示硬件环境
|
||||
|
||||
### 3.1 硬件环境说明
|
||||
|
||||
本Demo支持两种硬件环境,请根据实际使用的开发板版本选择对应的配置:
|
||||
|
||||
| 硬件环境 | HARDWARE_ENV值 | 控制485接收和发送的使能脚 |
|
||||
|---------|---------------|----------|
|
||||
| V1.3开发板 | "DEV_BOARD_V1.3" | GPIO24(默认) |
|
||||
| V1.2开发板 | "DEV_BOARD_V1.2" | GPIO25 |
|
||||
|
||||
**配置方法:** 在`main.lua`中修改`HARDWARE_ENV`变量的值即可切换硬件环境。
|
||||
|
||||
#### 3.1.1 V1.3开发板(DEV_BOARD_V1.3,默认)
|
||||
|
||||
**配置说明**
|
||||
|
||||
在`main.lua`中设置:
|
||||
```lua
|
||||
HARDWARE_ENV = "DEV_BOARD_V1.3"
|
||||
```
|
||||
|
||||
485_uart驱动会自动完成以下操作:
|
||||
- 使用 **GPIO24** 作为485使能脚
|
||||
|
||||
#### 3.1.2 V1.2开发板(DEV_BOARD_V1.2)
|
||||
|
||||
**配置说明**
|
||||
|
||||
在`main.lua`中设置:
|
||||
```lua
|
||||
HARDWARE_ENV = "DEV_BOARD_V1.2"
|
||||
```
|
||||
|
||||
485_uart驱动会自动完成以下操作:
|
||||
- 使用 **GPIO25** 作为485使能脚
|
||||
|
||||
### 3.2 硬件清单
|
||||
|
||||
下面以Air780EPM V1.3开发板为例进行演示:
|
||||
|
||||
1、Air780EPM V1.3 开发板一块
|
||||
|
||||
2、TYPE-C USB数据线一根
|
||||
|
||||
3、USB-RS485 串口板
|
||||
|
||||
> 此处购买链接仅为推荐,如有问题请直接联系店家
|
||||
|
||||
- 购买链接:[4路RS-485,TYPEC接口 不含USB线](https://item.taobao.com/item.htm?app=chrome&bxsign=scdJP32y4DuiSo5k5D89Yse5PsbQ6EIYXNIPNkzmMi6zdQszLdrZrUy6-Nw96m51cCA5oz67QzXT9StIYyW8C2WWDa9P6CTUC4Imox3bONGK_FgAw8bQ8lYY0rnL7cRpNsG&cpp=1&id=608773837508&price=2.5&shareUniqueId=34323628624&share_crt_v=1&shareurl=true&short_name=h.7YHZDX57ex5G68h&skuId=5692060399621&sourceType=item&sp_tk=QXZFNGZzdXJGeTg%3D&spm=a2159r.13376460.0.0&suid=97525605-306d-4a26-8181-c6a5e5ac1449&tbSocialPopKey=shareItem&tk=AvE4fsurFy8&un=7a6477372f5d73d28011994573a7abfb&un_site=0&ut_sk=1.ZjZmtV%2FpLDkDACz9psyM0ajN_21646297_1765329298678.Copy.1&wxsign=tbwqCWYZys39IqYxa6fkQJ4fSCqNnxJegN9tEYlbjtUm1rAZyDddEbbohCLQnFZUojPMaKxNICvdbp1uAgV6d03jf7_X0hLeyUaHbuddZUTcL_y45R5fClnUqq9IfXXAxNG)
|
||||
|
||||
4、气体浓度变送器(RS485 版)
|
||||
|
||||
> 如果你是小白,建议直接购买同款变送器,由于不同型号的温湿度模块默认的参数也会有所区别
|
||||
|
||||
- 购买链接:[空气质量+温湿度+RS485](https://item.taobao.com/item.htm?app=chrome&bxsign=scdFu-qKg8ZXJ1xjQqlalAwji8TFl1rnWrARQlKI_RJiN6UHRZGAJbKyD-u91Id7rHVdhRGgc1Qz_dP3NaVPGvuOvxnqRK8Ue2_4iWjUqc88zgv0p4m-UtvX8C8Oo6ie3zT&cpp=1&id=845163274010&price=40&shareUniqueId=34323863342&share_crt_v=1&shareurl=true&short_name=h.71oGcXUfrc46J83&skuId=5623949684515&sourceType=item&sp_tk=UkoxV2ZzdUJmdVk%3D&spm=a2159r.13376460.0.0&suid=5bc5312a-35fd-42d4-8a70-01d4f0634912&tbSocialPopKey=shareItem&tk=RJ1WfsuBfuY&un=7a6477372f5d73d28011994573a7abfb&un_site=0&ut_sk=1.ZjZmtV%2FpLDkDACz9psyM0ajN_21646297_1765329298678.Copy.1&wxsign=tbwCFryRbuO3rOk8mETON_dt0pgE9dS7hbxK6Bcm0UJlzwzAbizoHUbY78zKnYm0DkkItxxjmYLA-BAD5i3Efxa1_QbpFAksRNHi4GETTmGVBH4KDIUrzeLBSbPiLKvpHsq)
|
||||
|
||||
Air780EPM 与 USB-RS485 串口板接线图如下:
|
||||
|
||||

|
||||
|
||||
Air780EPM 与气体浓度变送器(RS-485 版)接线图如下:
|
||||
|
||||

|
||||
|
||||
## 演示软件环境
|
||||
|
||||
1、[Luatools下载调试工具](https://docs.openluat.com/air780epm/luatos/common/download/)
|
||||
|
||||
2、[Air780EPM V2018 版本](https://docs.openluat.com/air780epm/luatos/firmware/version/)(理论上最新版本固件也可以,如果使用最新版本的固件不可以,可以烧录 V2018-1 固件对比验证)
|
||||
|
||||
3、[摩尔信使(MThings)官网](https://www.gulink.cn/)(用于模拟 modbus 从站设备)
|
||||
|
||||
## 演示核心步骤
|
||||
|
||||
### RTU 主站应用模块(字段参数方式,对应 param_field.lua)
|
||||
|
||||
1、搭建硬件环境
|
||||
|
||||
- 将 USB-RS485 串口板与 Air780EPM 开发板进行连接
|
||||
- 将 USB-RS485 串口板 与 Air780EPM 开发板的 USB 端同时接在电脑上
|
||||
- 参考图见 演示硬件环境
|
||||
|
||||
2、在摩尔信使上配置模拟 RTU 从站设备环境
|
||||
|
||||
- 点击左上角的 “通道管理”按钮,在 “通道管理” 窗口选择对应的串口(USB-RS485 串口板与 Air780EPM 开发板进行 485 通信时的端口),点击对应串口后面的 “配置” 按钮,在 “串口参数配置” 窗口配置串口参数(要求与代码中调用 exmodbus.create 接口时填入的配置参数一致),操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 点击左上角的 “添加设备”按钮,在 “添加设备” 窗口对通信设备参数进行配置,配置好后点击 “添加” 按钮,左侧栏即为添加后的效果,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 点击左侧的第一个从站(我这里显示为 “COM36-001”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 点击左侧的第二个从站(我这里显示为 “COM36-002”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 此时在摩尔信使上的配置操作已经完成,如果需要在摩尔信使上查看报文,那么操作流程图如下:
|
||||
|
||||

|
||||
|
||||
3、调整软件代码
|
||||
|
||||
- 打开 require "param_field" ,注释掉 require "raw_frame" 和 require "temp_hum_sensor",操作流程图如下:
|
||||
|
||||

|
||||
|
||||
4、打开 Luatools 工具,根据要求烧录本次所需要的内核固件和脚本代码
|
||||
|
||||
5、烧录成功后,自动开机运行
|
||||
|
||||
6、开机运行后 Luatools 工具上记录的日志如下:
|
||||
|
||||
```
|
||||
[2025-12-29 10:08:32.083][000000000.320] I/user.main RTU_MASTER 001.000.000
|
||||
[2025-12-29 10:08:32.089][000000000.347] Uart_ChangeBR 1338:uart1, 115200 115203 26000000 3611
|
||||
[2025-12-29 10:08:32.111][000000000.348] I/user.exmodbus 串口 1 初始化成功,波特率 115200
|
||||
[2025-12-29 10:08:32.120][000000000.348] I/user.exmodbus_test rtu_master 创建成功
|
||||
[2025-12-29 10:08:32.129][000000000.349] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:32.367][000000001.353] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:08:32.532][000000001.354] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
|
||||
[2025-12-29 10:08:33.206][000000002.358] I/user.exmodbus_test 未收到从站 2 的响应(超时)
|
||||
[2025-12-29 10:08:35.210][000000004.359] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:36.199][000000005.362] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:08:38.199][000000007.363] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:39.212][000000008.366] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:08:39.216][000000008.367] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
|
||||
[2025-12-29 10:08:40.210][000000009.370] I/user.exmodbus_test 未收到从站 2 的响应(超时)
|
||||
[2025-12-29 10:08:42.206][000000011.371] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:43.211][000000012.374] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:08:45.212][000000014.375] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:45.228][000000014.390] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:08:45.231][000000014.390] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
|
||||
[2025-12-29 10:08:45.260][000000014.403] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:47.251][000000016.403] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:47.264][000000016.422] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:08:49.270][000000018.423] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:49.276][000000018.436] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:08:49.281][000000018.437] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
|
||||
[2025-12-29 10:08:49.293][000000018.452] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
|
||||
```
|
||||
|
||||
7、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air780EPM 之间的串口通道关闭后,此时 Air780EPM 在发送请求时便会收不到响应,Luatools 工具上显示的日志如下:
|
||||
|
||||

|
||||
|
||||
```
|
||||
[2025-12-29 10:08:32.129][000000000.349] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:32.367][000000001.353] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:08:32.532][000000001.354] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
|
||||
[2025-12-29 10:08:33.206][000000002.358] I/user.exmodbus_test 未收到从站 2 的响应(超时)
|
||||
```
|
||||
|
||||
8、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air780EPM 之间的串口通道打开后,此时 Air780EPM 在发送请求时便会接收到响应,Luatools 工具与摩尔信使上显示的日志如下:
|
||||
|
||||

|
||||
|
||||
```
|
||||
[2025-12-29 10:08:45.212][000000014.375] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:08:45.228][000000014.390] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:08:45.231][000000014.390] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
|
||||
[2025-12-29 10:08:45.260][000000014.403] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
|
||||
```
|
||||
|
||||

|
||||
|
||||
9、关于 Air780EPM 执行读取和写入请求后,摩尔信使上位机的数值变化如下图所示:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### RTU 主站应用模块(原始帧方式,对应 raw_frame.lua)
|
||||
|
||||
1、搭建硬件环境
|
||||
|
||||
- 将 USB-RS485 串口板与 Air780EPM 开发板进行连接
|
||||
- 将 USB-RS485 串口板 与 Air780EPM 开发板的 USB 端同时接在电脑上
|
||||
- 参考图见 演示硬件环境
|
||||
|
||||
2、在摩尔信使上配置模拟 RTU 从站设备环境
|
||||
|
||||
- 点击左上角的 “通道管理”按钮,在 “通道管理” 窗口选择对应的串口(USB-RS485 串口板与 Air780EPM 开发板进行 485 通信时的端口),点击对应串口后面的 “配置” 按钮,在 “串口参数配置” 窗口配置串口参数(要求与代码中调用 exmodbus.create 接口时填入的配置参数一致),操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 点击左上角的 “添加设备”按钮,在 “添加设备” 窗口对通信设备参数进行配置,配置好后点击 “添加” 按钮,左侧栏即为添加后的效果,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 点击左侧的第一个从站(我这里显示为 “COM36-001”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 点击左侧的第二个从站(我这里显示为 “COM36-002”),点击中上部分的 “新增数据” 按钮,在 “新增数据配置” 窗口将 “数据条数” 、“区块” 、“起始数据地址” 按照下图中所示进行配置,最后点击 “确定” 按钮,此时便成功新增保持寄存器 0 和 保持寄存器 1,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
- 此时在摩尔信使上的配置操作已经完成,如果需要在摩尔信使上查看报文,那么操作流程图如下:
|
||||
|
||||

|
||||
|
||||
3、调整软件代码
|
||||
|
||||
- 打开 require "raw_frame" ,注释掉 require "param_field" 和 require "temp_hum_sensor",操作流程图如下:
|
||||
|
||||

|
||||
|
||||
4、打开 Luatools 工具,根据要求烧录本次所需要的内核固件和脚本代码
|
||||
|
||||
5、烧录成功后,自动开机运行
|
||||
|
||||
6、开机运行后 Luatools 工具上记录的日志如下:
|
||||
|
||||
```
|
||||
[2025-12-29 10:23:51.883][000000000.320] I/user.main RTU_MASTER 001.000.000
|
||||
[2025-12-29 10:23:51.884][000000000.349] Uart_ChangeBR 1338:uart1, 115200 115203 26000000 3611
|
||||
[2025-12-29 10:23:51.885][000000000.350] I/user.exmodbus 串口 1 初始化成功,波特率 115200
|
||||
[2025-12-29 10:23:51.886][000000000.350] I/user.exmodbus_test rtu_master 创建成功
|
||||
[2025-12-29 10:23:51.887][000000000.351] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:23:52.540][000000001.354] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:23:52.554][000000001.355] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:23:53.532][000000002.359] I/user.exmodbus_test 未收到从站 2 的响应(超时)
|
||||
[2025-12-29 10:23:55.541][000000004.360] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:23:56.542][000000005.362] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:23:58.546][000000007.363] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:23:59.539][000000008.366] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:23:59.540][000000008.367] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:00.553][000000009.370] I/user.exmodbus_test 未收到从站 2 的响应(超时)
|
||||
[2025-12-29 10:24:02.556][000000011.371] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:03.547][000000012.374] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:24:05.557][000000014.375] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:05.561][000000014.388] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:05.565][000000014.389] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:05.588][000000014.404] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
|
||||
[2025-12-29 10:24:07.576][000000016.405] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:07.609][000000016.422] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:09.607][000000018.422] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:09.611][000000018.436] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:09.614][000000018.437] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:09.639][000000018.452] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
|
||||
[2025-12-29 10:24:11.634][000000020.453] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:11.649][000000020.468] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:13.653][000000022.468] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:13.656][000000022.485] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:13.659][000000022.485] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:13.684][000000022.500] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
|
||||
```
|
||||
|
||||
7、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air780EPM 之间的串口通道关闭后,此时 Air780EPM 在发送请求时便会收不到响应,Luatools 工具上显示的日志如下:
|
||||
|
||||

|
||||
|
||||
```
|
||||
[2025-12-29 10:23:51.887][000000000.351] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:23:52.540][000000001.354] I/user.exmodbus_test 未收到从站 1 的响应(超时)
|
||||
[2025-12-29 10:23:52.554][000000001.355] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:23:53.532][000000002.359] I/user.exmodbus_test 未收到从站 2 的响应(超时)
|
||||
```
|
||||
|
||||
8、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air780EPM 之间的串口通道打开后,此时 Air780EPM 在发送请求时便会接收到响应,Luatools 工具与摩尔信使上显示的日志如下:
|
||||
|
||||
> 程序设计为每隔 2 秒执行一次读取,每隔 4 秒执行一次写入,在日志上呈现出现就是先执行两次读取再执行一次写入
|
||||
|
||||

|
||||
|
||||
```
|
||||
[2025-12-29 10:24:11.634][000000020.453] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:11.649][000000020.468] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:13.653][000000022.468] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:13.656][000000022.485] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
|
||||
[2025-12-29 10:24:13.659][000000022.485] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
|
||||
[2025-12-29 10:24:13.684][000000022.500] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
|
||||
```
|
||||
|
||||

|
||||
|
||||
9、关于 Air780EPM 执行读取和写入请求后,摩尔信使上位机的数值变化如下图所示:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 485 温湿度传感器读取模块(对应 temp_hum_sensor.lua)
|
||||
|
||||
1、搭建硬件环境
|
||||
|
||||
- 将气体浓度变送器(RS-485 版)的 '+' 和 '-' 与供电设备(稳压电源等)进行连接,供电范围 8V ~ 36V DC
|
||||
|
||||
- 将气体浓度变送器(RS-485 版)与 Air780EPM 开发板进行连接
|
||||
- 将 Air780EPM 开发板的 USB 端接在电脑上
|
||||
- 参考图见 演示硬件环境
|
||||
|
||||
2、了解气体浓度变送器(RS-485 版)
|
||||
|
||||
- 该变送器模块上电后默认输出数据,从站地址默认为 1,波特率默认为 9600
|
||||
- 温度传感器数值通过保持寄存器地址 0x001E 输出,输出数据为 16 位有符号整数(-0x7FFF ~ +0x7FFF),
|
||||
- 数据范围为 -40℃ ~ +85℃,分辨率为 0.1℃
|
||||
- 注:寄存器值为 235,实际温度值为 235 * 0.1 = 23.5
|
||||
- 湿度传感器对应保持寄存器地址 0x001F 输出,输出数据为 16 位无符号整数(0 ~ 0xFFFF)
|
||||
- 数据范围为 0%RH ~ 85%RH,分辨率为 0.1%RH
|
||||
- 注:寄存器值为 653,实际湿度值为 653 * 0.1 = 65.3
|
||||
|
||||
3、调整软件代码
|
||||
|
||||
- 打开 require "temp_hum_sensor" ,注释掉 require "raw_frame" 和 require "param_field" ,操作流程图如下:
|
||||
|
||||

|
||||
|
||||
4、打开 Luatools 工具,根据要求烧录本次所需要的内核固件和脚本代码,为气体浓度变送器(RS-485 版)进行供电(提前通电也可以)
|
||||
|
||||
5、烧录成功后,自动开机运行
|
||||
|
||||
6、开机运行后 Luatools 工具上记录的日志如下:
|
||||
|
||||
```
|
||||
[2025-12-29 10:32:29.548][000000000.201] I/user.main RTU_MASTER 001.000.000
|
||||
[2025-12-29 10:32:29.550][000000000.230] Uart_ChangeBR 1338:uart1, 9600 9600 26000000 43333
|
||||
[2025-12-29 10:32:29.552][000000000.231] I/user.exmodbus 串口 1 初始化成功,波特率 9600
|
||||
[2025-12-29 10:32:29.554][000000000.231] I/user.temp_hum_sensor RTU 主站创建成功
|
||||
[2025-12-29 10:32:29.555][000000000.231] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:29.563][000000000.260] I/user.temp_hum_sensor 读取成功,温度为 15.80000 ℃,湿度为 48.90000 %RH
|
||||
[2025-12-29 10:32:31.011][000000002.260] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:31.042][000000002.289] I/user.temp_hum_sensor 读取成功,温度为 15.70000 ℃,湿度为 48.90000 %RH
|
||||
[2025-12-29 10:32:33.049][000000004.289] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:33.080][000000004.317] I/user.temp_hum_sensor 读取成功,温度为 15.60000 ℃,湿度为 49.10000 %RH
|
||||
[2025-12-29 10:32:35.077][000000006.318] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:35.093][000000006.346] I/user.temp_hum_sensor 读取成功,温度为 15.40000 ℃,湿度为 49.30000 %RH
|
||||
[2025-12-29 10:32:37.104][000000008.347] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:37.134][000000008.376] I/user.temp_hum_sensor 读取成功,温度为 15.40000 ℃,湿度为 49.30000 %RH
|
||||
[2025-12-29 10:32:39.121][000000010.376] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:39.151][000000010.405] I/user.temp_hum_sensor 读取成功,温度为 15.50000 ℃,湿度为 49.40000 %RH
|
||||
[2025-12-29 10:32:41.163][000000012.405] I/user.temp_hum_sensor 开始读取温湿度传感器数据
|
||||
[2025-12-29 10:32:41.195][000000012.433] I/user.temp_hum_sensor 读取成功,温度为 15.50000 ℃,湿度为 49.40000 %RH
|
||||
```
|
||||
@@ -0,0 +1,136 @@
|
||||
--[[
|
||||
@module temp_hum_sensor
|
||||
@summary 485温湿度传感器读取模块
|
||||
@version 1.0
|
||||
@date 2025.12.29
|
||||
@author 马梦阳
|
||||
@usage
|
||||
本功能模块演示的内容为:
|
||||
1、将设备配置为 modbus RTU 主站模式
|
||||
2、读取485接口的温湿度传感器数据
|
||||
3、每 2 秒读取一次传感器数据并解析温度和湿度值
|
||||
|
||||
注意事项:
|
||||
1、该示例程序需要搭配 exmodbus 扩展库使用
|
||||
2、参考对应传感器手册,配置从站地址、寄存器地址等参数
|
||||
|
||||
特别说明:
|
||||
1、本示例演示使用的是中盛科技的气体浓度变送器(RS485 版),
|
||||
该变送器模块上电后默认输出数据,从站地址为 1,波特率为 9600
|
||||
2、温度传感器数值通过保持寄存器地址 0x001E 输出,输出数据为 16 位有符号整数(-0x7FFF ~ +0x7FFF),
|
||||
数据范围为 -40℃ ~ +85℃,分辨率为 0.1℃
|
||||
注:寄存器值为 235,实际温度值为 235 * 0.1 = 23.5
|
||||
3、湿度传感器数值通过保持寄存器地址 0x001F 输出,输出数据为 16 位无符号整数(0 ~ 0xFFFF),
|
||||
数据范围为 0%RH ~ 85%RH,分辨率为 0.1%RH
|
||||
注:寄存器值为 653,实际湿度值为 653 * 0.1 = 65.3
|
||||
|
||||
本文件没有对外接口,直接在 main.lua 中 require "temp_hum_sensor" 就可以加载运行;
|
||||
]]
|
||||
|
||||
local exmodbus = require("exmodbus")
|
||||
|
||||
-- 根据硬件环境配置控制485接收和发送的使能脚
|
||||
local rs485_dir_gpio = 24 -- 默认值
|
||||
|
||||
if HARDWARE_ENV == "DEV_BOARD_V1.2" then
|
||||
rs485_dir_gpio = 25 -- V1.2开发板使用GPIO25
|
||||
elseif HARDWARE_ENV == "DEV_BOARD_V1.3" then
|
||||
rs485_dir_gpio = 24 -- V1.3开发板使用GPIO24
|
||||
end
|
||||
|
||||
gpio.setup(1, 1) -- Air780EPM RS485 芯片供电引脚
|
||||
gpio.setup(23, 1) -- Air780EPM vref 脚拉高
|
||||
|
||||
|
||||
-- 创建 RTU 主站配置参数
|
||||
local create_config = {
|
||||
-- 串口配置参数;
|
||||
mode = exmodbus.RTU_MASTER, -- 通信模式:RTU主站
|
||||
uart_id = 1, -- UART 端口号:1
|
||||
baud_rate = 9600, -- 波特率:9600(根据传感器手册调整)
|
||||
data_bits = 8, -- 数据位:8
|
||||
stop_bits = 1, -- 停止位:1
|
||||
parity_bits = uart.None, -- 校验位:无
|
||||
byte_order = uart.LSB, -- 字节顺序:LSB(低位优先)
|
||||
rs485_dir_gpio = rs485_dir_gpio, -- RS485 方向引脚:17
|
||||
rs485_dir_rx_level = 0, -- RS485 接收方向电平:0
|
||||
}
|
||||
|
||||
|
||||
-- 初始化传感器数据结构
|
||||
-- 用于记录传感器的温度和湿度值
|
||||
local sensor_data = {
|
||||
temperature = 0, -- 温度值
|
||||
humidity = 0 -- 湿度值
|
||||
}
|
||||
|
||||
-- 配置读取温湿度传感器的参数
|
||||
local read_config = {
|
||||
slave_id = 1, -- 从站地址:1
|
||||
reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
|
||||
start_addr = 0x001E, -- 起始地址:0x001E(温度寄存器)
|
||||
reg_count = 0x0002, -- 读取 2 个寄存器:温度和湿度
|
||||
timeout = 1000 -- 超时时间 1000 ms
|
||||
}
|
||||
|
||||
|
||||
-- 创建 RTU 主站实例
|
||||
local rtu_master = exmodbus.create(create_config)
|
||||
|
||||
-- 判断主站是否创建成功并记录日志
|
||||
if not rtu_master then
|
||||
log.info("temp_hum_sensor", "RTU 主站创建失败")
|
||||
else
|
||||
log.info("temp_hum_sensor", "RTU 主站创建成功")
|
||||
end
|
||||
|
||||
|
||||
-- 读取温湿度传感器数据的函数
|
||||
local function read_temp_humidity()
|
||||
|
||||
log.info("temp_hum_sensor", "开始读取温湿度传感器数据")
|
||||
|
||||
-- 执行读取操作
|
||||
local read_result = rtu_master:read(read_config)
|
||||
|
||||
-- 根据返回状态处理结果
|
||||
if read_result.status == exmodbus.STATUS_SUCCESS then
|
||||
-- 读取原始寄存器值
|
||||
local temp_raw = read_result.data[read_config.start_addr]
|
||||
local humi_raw = read_result.data[read_config.start_addr + 1]
|
||||
|
||||
-- 处理温度值的符号位
|
||||
if temp_raw > 0x7FFF then
|
||||
temp_raw = temp_raw - 0x10000
|
||||
end
|
||||
|
||||
-- 解析温度和湿度值
|
||||
-- 这里假设温度和湿度都是16位整数,单位分别为0.1℃和0.1%RH
|
||||
sensor_data.temperature = temp_raw / 10.0
|
||||
sensor_data.humidity = humi_raw / 10.0
|
||||
|
||||
log.info("temp_hum_sensor", "读取成功,温度为", sensor_data.temperature, "℃,湿度为", sensor_data.humidity, "%RH")
|
||||
elseif read_result.status == exmodbus.STATUS_DATA_INVALID then
|
||||
log.info("temp_hum_sensor", "收到传感器响应数据但数据损坏/校验失败")
|
||||
elseif read_result.status == exmodbus.STATUS_EXCEPTION then
|
||||
log.info("temp_hum_sensor", "收到传感器异常响应,标准异常码为", read_result.execption_code)
|
||||
elseif read_result.status == exmodbus.STATUS_TIMEOUT then
|
||||
log.info("temp_hum_sensor", "未收到传感器的响应(超时)")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 定时任务函数:每 2 秒读取一次温湿度数据
|
||||
local function task()
|
||||
while true do
|
||||
if rtu_master then
|
||||
read_temp_humidity()
|
||||
else
|
||||
log.info("temp_hum_sensor", "RTU主站未创建,无法读取传感器数据")
|
||||
end
|
||||
sys.wait(2000)
|
||||
end
|
||||
end
|
||||
|
||||
-- 初始化任务
|
||||
sys.taskInit(task)
|
||||
Reference in New Issue
Block a user