跳转至

Modbus RTU 主站应用

作者:马梦阳 | 最后修改:2026-04-13

一、Modbus 协议概述

1.1 Modbus 协议介绍

Modbus 最初由 Modicon(现为施耐德电气旗下品牌)于 1979 年开发,是一种用于可编程逻辑控制器(PLC)之间通信的工业通信协议。由于其简单、开放、免费且易于实现,Modbus 已成为工业自动化领域最广泛使用的通信协议之一,被广泛应用于工业控制、楼宇自动化、能源管理、智能仪表等场景。

该协议采用主从架构,最初基于串行通信(如 Modbus RTU 和 Modbus ASCII),后扩展支持以太网传输(Modbus TCP)。Modbus 定义了四种数据对象:线圈、离散输入、输入寄存器和保持寄存器,并通过功能码实现设备间的数据交换。

1.2 Modbus 三种常见通信协议模式

1.2.1 Modbus RTU

传输介质:RS485/RS232 串行通信

编码格式:二进制编码

完整报文:[从站地址][功能码][数据][CRC16 校验]

  • 向单个保持寄存器写入数据:
  • 请求报文格式:[从站地址][功能码][寄存器起始地址 寄存器值][CRC16 校验]
  • 举例:01 06 00 00 00 01 48 0A
    • 01 :从站地址
    • 06 :向单个保持寄存器写入数据功能码
    • 00 00 :寄存器起始地址
    • 00 01 :写入的单个寄存器数据
    • 48 0A :CRC16 校验值
  • 响应报文格式:[从站地址][功能码][寄存器起始地址 寄存器值][CRC16 校验]
  • 举例:01 06 00 00 00 01 48 0A
    • 01 :从站地址
    • 06 :向单个保持寄存器写入数据功能码
    • 00 00 :寄存器起始地址
    • 00 01 :写入的单个寄存器数据
    • 48 0A :CRC16 校验值
  • 异常响应报文格式:[从站地址][功能码][异常码][CRC16 校验]
  • 举例:01 86 02 83 A0
    • 01 :从站地址
    • 86 :原功能码为 06,最高位为 1 表示异常响应
    • 02 :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的)
    • 83 A0 :CRC16 校验值
  • 向多个保持寄存器写入数据:
  • 请求报文格式:[从站地址][功能码][寄存器起始地址 寄存器数量 寄存器值字节数 寄存器值][CRC16 校验]
  • 举例:01 10 00 00 00 02 04 00 01 00 05 62 6C
    • 01 :从站地址
    • 10 :写多个寄存器功能码
    • 00 00 :寄存器起始地址
    • 00 02 :要写入的寄存器数量
    • 04 :寄存器值的字节数
    • 00 01 :写入的第一个寄存器值
    • 00 05 :写入的第二个寄存器值
    • 62 6C :CRC16 校验值
  • 响应报文格式:[从站地址][功能码][寄存器起始地址 寄存器数量][CRC16 校验]
  • 举例:01 10 00 00 00 02 41 C8
    • 01 :从站地址
    • 10 :写多个寄存器功能码
    • 00 00 :寄存器起始地址
    • 00 02 :要写入的寄存器数量
    • 41 C8 :CRC16 校验值
  • 异常响应报文格式:[从站地址][功能码][异常码][CRC16 校验]
  • 举例:01 90 02 CD C1
    • 01 :从站地址
    • 90 :原功能码为 10,最高位为 1 表示异常响应
    • 02 :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的)
    • CD C1 :CRC16 校验值
  • 读取保持寄存器数据:
  • 请求报文格式:[从站地址][功能码][寄存器起始地址 寄存器数量][CRC16 校验]
  • 举例:01 03 00 00 00 02 C4 0B
    • 01 :从站地址
    • 03 :读单/多个保持寄存器功能码
    • 00 00 :寄存器起始地址
    • 00 02 :要读取的寄存器数量
    • C4 0B :CRC16 校验值
  • 响应报文格式:[从站地址][功能码][寄存器值字节数 寄存器值][CRC16 校验]
  • 举例:01 03 04 00 01 00 05 6B F0
    • 01 :从站地址
    • 03 :读单/多个保持寄存器功能码
    • 04 :寄存器值字节数
    • 00 01 :读取的第一个寄存器值
    • 00 05 :读取的第二个寄存器值
    • 6B F0 :CRC16 校验值
  • 异常响应报文格式:[从站地址][功能码][异常码][CRC16 校验]
  • 举例:01 83 02 C0 F1
    • 01 :从站地址
    • 83 :原功能码为 03,最高位为 1 表示异常响应
    • 02 :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的)
    • C0 F1 :CRC16 校验值

1.2.2 Modbus ASCII

传输介质:RS485/RS232 串行通信

编码格式:ASCII 字符编码

完整报文:[:][从站地址][功能码][数据][LRC 校验][\r\n]

  • 向单个保持寄存器写入数据:
  • 报文格式:[:][从站地址][功能码][寄存器起始地址 寄存器值][LRC 校验][\r\n]
  • 举例::010600000001B8\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘0’ ‘6’ :向单个保持寄存器写入数据功能码,HEX 值为 30 36
    • ‘0’ ‘0’ ‘0’ ‘0’ :寄存器起始地址,HEX 值为 30 30 30 30
    • ‘0’ ‘0’ ‘0’ ‘1’ :写入的单个寄存器数据,HEX 值为 30 30 30 31
    • ‘B’ ‘8’ :LRC 校验值,HEX 值为 42 38
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 响应报文格式:[:][从站地址][功能码][寄存器起始地址 寄存器值][LRC 校验][\r\n]
  • 举例::010600000001B8\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘0’ ‘6’ :向单个保持寄存器写入数据功能码,HEX 值为 30 36
    • ‘0’ ‘0’ ‘0’ ‘0’ :寄存器起始地址,HEX 值为 30 30 30 30
    • ‘0’ ‘0’ ‘0’ ‘1’ :写入的单个寄存器数据,HEX 值为 30 30 30 31
    • ‘B’ ‘8’ :LRC 校验值,HEX 值为 42 38
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 异常响应报文格式:[:][从站地址][功能码][异常码][LRC 校验][\r\n]
  • 举例::018602CF\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘8’ ‘6’ :原功能码为 06,最高位为 1 表示异常响应,HEX 值为 38 36
    • ‘0’ ‘2’ :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的),HEX 值为 30 36
    • ‘C’ ‘F’ :LRC 校验值,HEX 值为 43 46
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 向多个保持寄存器写入数据:
  • 请求报文格式:[:][从站地址][功能码][寄存器起始地址 寄存器数量 寄存器值字节数 寄存器值][LRC 校验][\r\n]
  • 举例::0110000000020400010005D2\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘1’ ‘0’ :写多个寄存器功能码,HEX 值为 31 30
    • ‘0’ ‘0’ ‘0’ ‘0’ :寄存器起始地址,HEX 值为 30 30 30 30
    • ‘0’ ‘0’ ‘0’ ‘2’ :要写入的寄存器数量,HEX 值为 30 30 30 32
    • ‘0’ ‘4’ :寄存器值的字节数,HEX 值为 30 34
    • ‘0’ ‘0’ ‘0’ ‘1’ :写入的第一个寄存器值,HEX 值为 30 30 30 31
    • ‘0’ ‘0’ ‘0’ ‘5’ :写入的第二个寄存器值,HEX 值为 30 30 30 35
    • ‘D’ ‘2’ :LRC 校验值,HEX 值为 44 32
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 响应报文格式:[:][从站地址][功能码][寄存器起始地址 寄存器数量][LRC 校验][\r\n]
  • 举例::011000000002BC\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘1’ ‘0’ :写多个寄存器功能码,HEX 值为 31 30
    • ‘0’ ‘0’ ‘0’ ‘0’ :寄存器起始地址,HEX 值为 30 30 30 30
    • ‘0’ ‘0’ ‘0’ ‘2’ :要写入的寄存器数量,HEX 值为 30 30 30 32
    • ‘B’ ‘C’ :LRC 校验值,HEX 值为 42 43
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 异常响应报文格式:[:][从站地址][功能码][异常码][LRC 校验][\r\n]
  • 举例::019002D4\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘9’ ‘0’ :原功能码为 10,最高位为 1 表示异常响应,HEX 值为 39 30
    • ‘0’ ‘2’ :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的),HEX 值为 30 36
    • ‘D’ ‘4’ :LRC 校验值,HEX 值为 44 34
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 读取保持寄存器数据:
  • 请求报文格式:[:][从站地址][功能码][寄存器起始地址 寄存器数量][LRC 校验][\r\n]
  • 举例::010300000002BA\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘0’ ‘3’ :读单/多个保持寄存器功能码,HEX 值为 30 33
    • ‘0’ ‘0’ ‘0’ ‘0’ :寄存器起始地址,HEX 值为 30 30 30 30
    • ‘0’ ‘0’ ‘0’ ‘2’ :要读取的寄存器数量,HEX 值为 30 30 30 32
    • ‘B’ ‘A’ :LRC 校验值,HEX 值为 42 41
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 响应报文格式:[:][从站地址][功能码][寄存器值字节数 寄存器值][LRC 校验][\r\n]
  • 举例::0103040001000552\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘0’ ‘3’ :读单/多个保持寄存器功能码,HEX 值为 30 33
    • ‘0’ ‘4’ :寄存器值字节数,HEX 值为 30 34
    • ‘0’ ‘0’ ‘0’ ‘1’ :读取的第一个寄存器值,HEX 值为 30 30 30 31
    • ‘0’ ‘0’ ‘0’ ‘5’ :读取的第二个寄存器值,HEX 值为 30 30 30 35
    • ‘5’ ‘2’ :LRC 校验值,HEX 值为 35 32
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A
  • 异常响应报文格式:[:][从站地址][功能码][异常码][LRC 校验][\r\n]
  • 举例::018302D2\r\n
    • ‘:’ :起始符,HEX 值为 3A
    • ‘0’ ‘1’ :从站地址,HEX 值为 30 31
    • ‘8’ ‘3’ :原功能码为 03,最高位为 1 表示异常响应,HEX 值为 38 33
    • ‘0’ ‘2’ :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的),HEX 值为 30 36
    • ‘D’ ‘2’ :LRC 校验值,HEX 值为 44 32
    • ‘\r’ ‘\n’ :结束符,HEX 值为 0D 0A

1.2.3 Modbus TCP

传输介质:以太网等

编码格式:二进制编码格式

完整报文:[MBAP 头][功能码][数据]

  • 向单个保持寄存器写入数据:
  • 请求报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][寄存器起始地址 寄存器值]
  • 举例:00 01 00 00 00 06 01 06 00 00 00 01
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 06 :后续字节数
    • 01 :从站地址
    • 06 :向单个保持寄存器写入数据功能码
    • 00 00 :寄存器起始地址
    • 00 01 :写入的单个寄存器数据
  • 响应报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][寄存器起始地址 寄存器值]
  • 举例:00 01 00 00 00 06 01 06 00 00 00 01
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 06 :后续字节数
    • 01 :从站地址
    • 06 :向单个保持寄存器写入数据功能码
    • 00 00 :寄存器起始地址
    • 00 01 :写入的单个寄存器数据
  • 异常响应报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][异常码]
  • 举例:00 01 00 00 00 03 01 86 02
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 03 :后续字节数
    • 01 :从站地址
    • 86 :原功能码为 06,最高位为 1 表示异常响应
    • 02 :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的)
  • 向多个保持寄存器写入数据:
  • 请求报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][寄存器起始地址 寄存器数量 寄存器值字节数 寄存器值]
  • 举例:00 01 00 00 00 0B 01 10 00 00 00 02 04 00 01 00 05
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 0B :后续字节数
    • 01 :从站地址
    • 10 :写多个寄存器功能码
    • 00 00 :寄存器起始地址
    • 00 02 :要写入的寄存器数量
    • 04 :寄存器值的字节数
    • 00 01 :写入的第一个寄存器值
    • 00 05 :写入的第二个寄存器值
  • 响应报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][寄存器起始地址 寄存器数量]
  • 举例:00 01 00 00 00 06 01 10 00 00 00 02
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 06 :后续字节数
    • 01 :从站地址
    • 10 :写多个寄存器功能码
    • 00 00 :寄存器起始地址
    • 00 02 :要写入的寄存器数量
  • 异常响应报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][异常码]
  • 举例:00 01 00 00 00 03 01 90 02
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 03 :后续字节数
    • 01 :从站地址
    • 90 :原功能码为 10,最高位为 1 表示异常响应
    • 02 :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的)
  • 读取保持寄存器数据:
  • 请求报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][寄存器起始地址 寄存器数量]
  • 举例:00 01 00 00 00 06 01 03 00 00 00 02
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 06 :后续字节数
    • 01 :从站地址
    • 03 :读单/多个保持寄存器功能码
    • 00 00 :寄存器起始地址
    • 00 02 :要读取的寄存器数量
  • 响应报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][寄存器值字节数 寄存器值]
  • 举例:00 01 00 00 00 07 01 03 04 00 01 00 05
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 07 :后续字节数
    • 01 :从站地址
    • 03 :读单/多个保持寄存器功能码
    • 04 :寄存器值的字节数
    • 00 01 :读取的第一个寄存器值
    • 00 05 :读取的第二个寄存器值
  • 异常响应报文格式:[事务标识符 协议标识符 后续字节数 从站地址][功能码][异常码]
  • 举例:00 01 00 00 00 03 01 83 02
    • 00 01 :事务标识符
    • 00 00 :协议标识符(modbus 固定为 00 00)
    • 00 03 :后续字节数
    • 01 :从站地址
    • 83 :原功能码为 03,最高位为 1 表示异常响应
    • 02 :非法数据地址(请求中指定的数据地址是从站设备不允许的或不存在的)

1.3 Modbus 四种基本数据类型

注:Modbus 协议中通常将这四类称为“数据对象”(Data Objects),但在工程实践中常简称为“数据类型”,本文沿用此习惯以增强可读性。

Modbus 协议定义了四种基本类型(线圈、离散输入、输入寄存器、保持寄存器)。采取这样的划分是基于工业控制系统中常见的硬件接口特性、数据访问需求以及通信效率的综合考量。通过将数据按照功能、读写属性和物理意义进行分类,Modbus 在保持协议间接性的同时,可以有效映射真实设备的输入输出行为,为不同厂商设备之间的互操作性提供了清晰、一致的数据模型基础。

1.3.1 线圈(Coils)

  • 中文别名:数字输出、开关输出、DO(Digital Output)
  • 存储单元:单比特(1 bit)
  • 数值范围:两种状态:0(OFF)或 1(ON)
  • 访问权限:可读可写
  • 功能码:
  • 01(0x01):读单个或多个线圈
  • 05(0x05):写单个线圈
  • 15(0x15):写多个线圈
  • 典型用途:控制继电器、开关、指示灯等数字输出设备
  • 实际应用举例:
  • 控制继电器的吸合或断开
  • 控制电机的启动或停止
  • 控制阀门的开启或关闭
  • 控制指示灯的点亮或熄灭

1.3.2 离散输入(Discrete Inputs)

  • 中文别名:数字输入、开关输入、DI(Digital Input)
  • 存储单元:单比特(1 bit)
  • 数值范围:两种状态:0(OFF)或 1(ON)
  • 访问权限:只读
  • 功能码:
  • 02(0x02):读单个或多个离散输入
  • 典型用途:读取限位开关、按钮、传感器等数字输入状态
  • 实际应用举例:
  • 读取一个按钮是否被按下
  • 检测一个限位开关是否触发
  • 判断门磁传感器是否报警(门 开/关)
  • 查看故障报警信号的状态

1.3.3 输入寄存器(Input Registers)

  • 中文别名:模拟量输入、只读寄存器、AI(Analog Input)
  • 存储单元:16 位(2 bytes)
  • 数值范围:0 ~ 65535(无符号)或者 -32768 ~ 32767(有符号,非协议标准,需要主/从站设备配合实现)
  • 访问权限:只读
  • 功能码:
  • 04(0x04):读单个或多个输入寄存器
  • 典型用途:读取传感器温度、压力、电压等模拟量输入值
  • 实际应用举例:
  • 读取温度传感器的测量值(如 25.4℃)
  • 读取压力传感器的实际压力
  • 读取流量计的当前流量
  • 读取设备运行的累计时间(通常由设备内部维护,只供读取)

1.3.4 保持寄存器(Holding Registers)

  • 中文别名:模拟量输出、读写寄存器、AO(Analog Output)
  • 存储单元:16 位(2 bytes)
  • 数值范围:0 ~ 65535(无符号)或者 -32768 ~ 32767(有符号,非协议标准,需要主/从站设备配合实现)
  • 访问权限:可读可写
  • 功能码:
  • 03(0x03):读单个或多个保持寄存器
  • 06(0x06):写单个保持寄存器
  • 16(0x16):写多个保持寄存器
  • 典型用途:存储配置参数、设定值、设备状态等可被主站设备修改的数据
  • 实际应用举例:
  • 写入方面:设置目标温度、设定电机转速、修改报警阈值、发送控制命令代码
  • 读取方面:读取设备内部计算的参数、获取系统状态信息、读取预置的配方数据

1.3.5 数据类型一览表

数据类型(中文)
数据类型(英文)
存储单元
访问权限
功能码
典型用途
线圈
Coils
1 bit
可读可写
01(0x01):读单个或多个线圈
05(0x05):写单个线圈
15(0x15):写多个线圈
控制继电器、开关、指示灯等数字输出设备
离散输入
Discrete Inputs
1 bit
只读
02(0x02):读单个或多个离散输入
读取限位开关、按钮、传感器等数字输入状态
输入寄存器
Input Registers
16 bit
只读
04(0x04):读单个或多个输入寄存器
读取传感器温度、压力、电压等模拟量输入值
保持寄存器
Holding Registers
16 bit
可读可写
03(0x03):读单个或多个保持寄存器
06(0x06):写单个保持寄存器
16(0x16):写多个保持寄存器
存储配置参数、设定值、设备状态等可被主站设备修改的数据

1.4 常用基础功能码

部分功能码并非标配,具体需要看设备厂商是否支持。

功能名称
功能码 (十进制)
功能码 (十六进制)
描述
读取离散输入
02
02
读取只读的物理开关量输入点
读取线圈
01
01
读取可读可写的开关量输出点
写单个线圈
05
05
写入单个开关量输出点
写多个线圈
15
0F
写入多个连续的开关量输出点
读取输入寄存器
04
04
读取只读的模拟量输入或数据
读取保持寄存器
03
03
读取可读可写的模拟量输出或数据存储区
写单个寄存器
06
06
写入单个寄存器值
写多个寄存器
16
10
写入多个连续的寄存器值
读/写多个寄存器
23
17
在一个请求中同时执行读和写寄存器操作
屏蔽写寄存器
22
16
对寄存器进行“与”/“或”掩码操作,用于修改特定位

1.5 异常响应

异常响应时从站返回:响应码 + 异常码

报文格式:[响应码][异常码]

1.5.1 响应码

格式:0x80 + 功能码

举例:0x83(读保持寄存器异常响应码)

1.5.2 异常码

异常码 (十六进制)
名称
产生原因举例
01
非法功能
(ILLEGAL FUNCTION)

从站设备不支持请求报文中的功能码。例如,向一个只支持读保持寄存器(03)的传感器发送读线圈(01)命令。
02
非法数据地址
(ILLEGAL DATA ADDRESS)
请求中指定的数据地址是从站设备不允许的或不存在的。例如,试图读取一个起始地址为999的保持寄存器,但该设备只有0-99的寄存器。
03
非法数据值
(ILLEGAL DATA VALUE)
请求数据字段中的值对于从站设备来说是无效的。例如,在写多个寄存器(16)时,请求的寄存器数量为0,或者字节计数字节与后续数据不匹配。
04
服务器(从站)设备故障
(SERVER DEVICE FAILURE)
从站设备在处理请求的过程中发生了不可恢复的错误。这是一个通用错误,通常表示设备内部故障,例如存储器故障、软件异常等。
05
确认
(ACKNOWLEDGE)
从站设备已接受请求并正在处理中,但需要很长的处理时间。主站设备应稍后重新查询,而不是再次发送相同请求。
06
服务器(从站)设备忙
(SERVER DEVICE BUSY)
从站设备正忙于处理长时间命令。主站设备应当稍后重试相同的请求。
08
存储器奇偶校验错误
(MEMORY PARITY ERROR)
从站设备尝试读取记录文件,但检测到存储器中存在奇偶校验错误。主站设备可以重试请求,但从站设备可能需要进行检修。
0A
网关路径不可用
(GATEWAY PATH UNAVAILABLE)
通常与网关设备相关,表示网关配置错误或无法处理请求。
0B
网关目标设备响应失败
(GATEWAY TARGET DEVICE FAILED TO RESPOND)
网关无法从目标设备(即最终的被访问从站)获得响应。这通常意味着目标设备离线或存在通信问题。

二、演示功能概述

2.1 演示功能介绍

本篇文章主要演示 Modbus RTU 主站应用,演示功能如下:

1、将 Air8000 配置为 Modbus RTU 主站模式

2、与从站地址为 1 和 2 的从设备进行通信

  • 对从站地址为 1 的从设备进行每 2 秒一次的读取保持寄存器 0-1 操作
  • 对从站地址为 2 的从设备进行每 4 秒一次的写入保持寄存器 0-1 操作

3、读取温湿度传感器数据

  • 每 2 秒读取一次传感器数据并解析温度和湿度数值

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",不要同时打开,否则功能会有冲突

2.3 特别说明

关于 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 标准格式的请求报文时,可以使用 字段参数 或者 原始帧 方式

如果你需要发送非标准格式的请求报文时,必须使用 原始帧 方式,使用 字段参数 方式会导致解析的数据不正确

三、演示硬件环境

1、Air8000 开发板一块

2、TYPE-C USB 数据线一根

3、USB-RS485 串口板

此处购买链接仅为推荐,如有问题请直接联系店家

4、气体浓度变送器(RS485 版)

如果你是小白,建议直接购买同款变送器,由于不同型号的温湿度模块默认的参数也会有所区别

Air8000 与 USB-RS485 串口板接线图如下:

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

四、演示软件环境

1. 烧录工具:Luatools 下载调试工具

2. 内核固件:本demo开发测试时使用的固件为Air8000 V2018 1号固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

3. 脚本文件:Air8000 脚本文件

4. 模拟工具:摩尔信使(MThings)官网(用于模拟 modbus 从站设备)

5. LuatOS 运行所需要的 lib 文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件。

准备好软件环境之后,接下来查看 Air8000 开发板使用说明,将本篇文章中演示使用的项目文件烧录到 Air8000 开发板中。

五、API 接口说明

exmodbus 扩展库说明:https://docs.openluat.com/osapi/ext/exmodbus/

六、代码示例介绍

6.1 各模块代码解析说明

6.1.1 RTU 主站应用模块(字段参数方式,对应 param_field.lua)

1. 调用 exmodbus 扩展库

说明:搭配扩展库使用时需要 require 对应的库文件名,否则在运行时会出现错误

local exmodbus = require("exmodbus")

2. 配置创建主站所需要的参数

本文章演示使用 Modbus RTU 主站,通信模式应固定为 exmodbus.RTU_MASTER; 其余串口相关参数应根据实际情况进行填写,并且要与从站设备串口参数保持一致; RS485 芯片供电引脚、RS485 方向引脚等管脚需要根据自己所使用的开发板进行调整,演示使用的是 Air8000 整机开发板。

gpio.setup(16, 1)         -- Air8000 开发板 RS485 芯片供电引脚
local rs485_dir_gpio = 17 -- Air8000 开发板 RS485 方向引脚

-- 创建 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 接收方向电平
}

3. 配置主站向从站 1 进行读取请求的请求参数

定义 slave1_data 用于读取成功后记录读取的数值; 在配置读命令的字段参数时要符合 Modbus 协议规范,不可超出范围、不可请求无效地址等等,若参数配置不正确时扩展库也会输出错误日志进行提醒; 超时时间参数是可选项,不填时默认为 1000 毫秒(1 秒)。

-- 初始化从站 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
}

4. 配置主站向从站 2 进行写入请求的请求参数

定义 slave2_data 用于模拟向从站 2 写入的数值; 在配置写命令的字段参数时要符合 Modbus 协议规范,不可超出范围、不可请求无效地址等等,若参数配置不正确时扩展库也会输出错误日志进行提醒; data 字段参数是一个数组类型,其内部通过键值对的方式进行存储,每一个键值对代表一个数据类型的数值,每一个键表示一个数据类型的地址,例如写入保持寄存器 0-1,则 data 中有两个键值对,这两个键值对的键分别为 0 和 1; force_multiple 字段参数可用于控制向从站 2 发送写入请求时报文中的功能码,如果从站设备不支持 05(写单个线圈)或 06(写单个保持寄存器)功能码时,可以将 force_multiple 设置为 true,此时 exmodbus 扩展库在组包时便会使用 0F(写单个/多个线圈)或 10(写单个/多个保持寄存器)功能码; 超时时间参数是可选项,不填时默认为 1000 毫秒(1 秒)。

-- 初始化从站 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
}

5. 创建 Modbus RTU 主站

调用 create 接口时应判断创建结果,防止后续调用无效实例对象。

-- 创建 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

6. 配置读取函数

读取成功后的数值会存在 read_result.data 字段参数内,该字段参数内也是通过键值对的方式存储数值,其中键为对应数据类型的地址,例如读取保持寄存器 0,则键为 0,值为该数据类型的数值。

-- 读取从站 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

7. 配置写入函数

-- 写入从站 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

8. 配置定时任务函数并进行初始化

示例代码中设计为每 2 秒执行一次读取操作,每 4 秒执行一次写入操作。

-- 定时任务函数:每 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)

6.1.2 RTU 主站应用模块(原始帧方式,对应 raw_frame.lua)

1. 调用 exmodbus 扩展库

说明:搭配扩展库使用时需要 require 对应的库文件名,否则在运行时会出现错误

local exmodbus = require("exmodbus")

2. 配置创建主站所需要的参数

本文章演示使用 Modbus RTU 主站,通信模式应固定为 exmodbus.RTU_MASTER; 其余串口相关参数应根据实际情况进行填写,并且要与从站设备串口参数保持一致; RS485 芯片供电引脚、RS485 方向引脚等管脚需要根据自己所使用的开发板进行调整,演示使用的是 Air8000 整机开发板。

gpio.setup(16, 1)         -- Air8000 开发板 RS485 芯片供电引脚
local rs485_dir_gpio = 17 -- Air8000 开发板 RS485 方向引脚

-- 创建 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 接收方向电平
}

3. 配置主站向从站 1 进行读取请求的请求参数

定义 slave1_data 用于读取成功后记录读取的数值; raw_request 字段参数用于填写读取请求时的原始报文帧,在填写时要符合 Modbus RTU 报文协议规范,exmodbus 扩展库只负责执行请求操作,并将请求到的原始报文帧进行返回,用户需要自己在脚本代码中进行解析判断; 超时时间参数是可选项,不填时默认为 1000 毫秒(1 秒)。

-- 初始化从站 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
}

4. 配置主站向从站 2 进行写入请求的请求参数

raw_request 字段参数用于填写写入请求时的原始报文帧,在填写时要符合 Modbus RTU 报文协议规范,exmodbus 扩展库只负责执行请求操作,并将请求到的原始报文帧进行返回,用户需要自己在脚本代码中进行解析判断; 超时时间参数是可选项,不填时默认为 1000 毫秒(1 秒)。

-- 配置写入从站 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
}

5. 创建 Modbus RTU 主站

调用 create 接口时应判断创建结果,防止后续调用无效实例对象。

-- 创建 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

6. 配置读取函数

读取成功后的原始报文帧会存在 read_result.raw_response 字段参数内,用户需要自己在脚本代码中做解析。

-- 读取从站 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

7. 配置写入函数

写入成功后的原始报文帧会存在 read_result.raw_response 字段参数内,用户需要自己在脚本代码中做解析。

-- 写入从站 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

8. 配置定时任务函数并进行初始化

示例代码中设计为每 2 秒执行一次读取操作,每 4 秒执行一次写入操作。

-- 定时任务函数:每 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)

6.1.3 485 温湿度传感器读取模块(对应 temp_hum_sensor.lua)

特别说明:

1、本示例演示使用的是中盛科技的气体浓度变送器(RS485 版),
    该变送器模块上电后默认输出数据,从站地址为 1,波特率为 9600,数据位为 8,停止位为 1,奇偶校验无
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

1. 调用 exmodbus 扩展库

说明:搭配扩展库使用时需要 require 对应的库文件名,否则在运行时会出现错误

local exmodbus = require("exmodbus")

2. 配置创建主站所需要的参数

本文章演示使用 Modbus RTU 主站,通信模式应固定为 exmodbus.RTU_MASTER; 本文章演示时使用串口 1 进行通信,因此 uart_id 需要填 1; 波特率、数据位、校验位、字节序需要查看对应从站设备的设备手册后进行填写; RS485 芯片供电引脚、RS485 方向引脚等管脚需要根据自己所使用的开发板进行调整,演示使用的是 Air8000 整机开发板。

gpio.setup(16, 1)         -- Air8000 开发板 RS485 芯片供电引脚
local rs485_dir_gpio = 17 -- Air8000 开发板 RS485 方向引脚

-- 创建 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
}

3. 配置主站向从站设备进行读取温湿度请求的请求参数

定义 slave1_data 用于读取成功后记录温度和湿度值; 在配置读命令的字段参数时要根据从站设备的设备手册说明进行填写,例如温湿度传感器的温度通过保持寄存器地址 0x001E 输出,则只读取温度数值时,则需要将寄存器起始地址填为 0x001E,寄存器数量填为 0x0001; 并且填写时需要符合 Modbus 协议规范,不可超出范围、不可请求无效地址等等,若参数配置不正确时扩展库也会输出错误日志进行提醒; 超时时间参数是可选项,不填时默认为 1000 毫秒(1 秒)。

-- 初始化传感器数据结构
-- 用于记录传感器的温度和湿度值
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
}

4. 创建 Modbus RTU 主站

调用 create 接口时应判断创建结果,防止后续调用无效实例对象。

-- 创建 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

5. 配置读取温湿度函数

读取成功后的温度、湿度数值会存在 read_result.data 字段参数内,该字段参数内通过键值对的方式存储数值,其中键为对应数据类型的地址,例如读取保持寄存器 0,则键为 0,值为该数据类型的数值; 根据前面的说明,该温湿度传感器的温度、湿度数值分辨率为 0.1,则读取到数值后还需要进行解析处理。

-- 读取温湿度传感器数据的函数
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

6. 配置定时任务函数并进行初始化

示例代码中设计为每 2 秒执行一次读取操作。

-- 定时任务函数:每 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)

6.2 完整示例代码展示

6.2.1 RTU 主站应用模块(字段参数方式,对应 param_field.lua)

--[[
@module  param_field
@summary RTU 主站应用模块(字段参数方式)
@version 1.0
@date    2025.12.12
@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")


gpio.setup(16, 1)         -- Air8000 开发板 RS485 芯片供电引脚
local rs485_dir_gpio = 17 -- Air8000 开发板 RS485 方向引脚


-- 创建 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)

6.2.2 RTU 主站应用模块(原始帧方式,对应 raw_frame.lua)

--[[
@module  raw_frame
@summary RTU 主站应用模块(原始帧方式)
@version 1.0
@date    2025.12.12
@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")


gpio.setup(16, 1)         -- Air8000 开发板 RS485 芯片供电引脚
local rs485_dir_gpio = 17 -- Air8000 开发板 RS485 方向引脚


-- 创建 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)

6.2.3 485 温湿度传感器读取模块(对应 temp_hum_sensor.lua)

--[[
@module  temp_hum_sensor
@summary 485温湿度传感器读取模块
@version 1.0
@date    2025.12.12
@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")


-- Air8000 开发板硬件配置
gpio.setup(16, 1)         -- RS485 芯片供电引脚
local rs485_dir_gpio = 17 -- RS485 方向引脚


-- 创建 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)

七、操作步骤演示

7.1 RTU 主站应用模块(字段参数方式,对应 param_field.lua)

1、搭建硬件环境

  • 将 USB-RS485 串口板与 Air8000 开发板进行连接
  • 将 USB-RS485 串口板 与 Air8000 开发板的 USB 端同时接在电脑上
  • 参考图见 演示硬件环境

2、在摩尔信使上配置模拟 RTU 从站设备环境

  • 点击左上角的 “通道管理”按钮,在 “通道管理” 窗口界面选择对应的串口(USB-RS485 串口板与 Air8000 开发板进行 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-08 14:14:56.464][000000000.394] I/user.main RTU_MASTER 001.000.000
[2025-12-08 14:14:56.468][000000000.425] Uart_ChangeBR 1338:uart1, 115200 115203 26000000 3611
[2025-12-08 14:14:56.472][000000000.426] I/user.exmodbus 串口 1 初始化成功,波特率 115200
[2025-12-08 14:14:56.477][000000000.426] I/user.exmodbus_test rtu_master 创建成功
[2025-12-08 14:14:56.481][000000000.427] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:14:57.124][000000001.433] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:14:57.128][000000001.433] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:14:58.135][000000002.437] I/user.exmodbus_test 未收到从站 2 的响应(超时)
[2025-12-08 14:15:00.139][000000004.438] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:01.139][000000005.441] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:15:03.144][000000007.442] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:04.140][000000008.445] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:15:04.146][000000008.446] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:05.149][000000009.449] I/user.exmodbus_test 未收到从站 2 的响应(超时)
[2025-12-08 14:15:07.139][000000011.450] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:08.146][000000012.453] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:15:10.148][000000014.454] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:11.154][000000015.457] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:15:11.165][000000015.458] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:12.160][000000016.461] I/user.exmodbus_test 未收到从站 2 的响应(超时)
[2025-12-08 14:15:14.150][000000018.462] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:15.158][000000019.465] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:15:17.166][000000021.466] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:18.160][000000022.469] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:15:18.163][000000022.470] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:18.177][000000022.486] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
[2025-12-08 14:15:20.178][000000024.487] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:20.207][000000024.503] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 14:15:22.197][000000026.504] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:22.228][000000026.519] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 14:15:22.231][000000026.520] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:22.235][000000026.534] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
[2025-12-08 14:15:24.224][000000028.534] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:24.255][000000028.550] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 14:15:26.246][000000030.551] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:26.257][000000030.569] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 14:15:26.265][000000030.570] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:26.275][000000030.583] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
[2025-12-08 14:15:28.284][000000032.584] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:28.289][000000032.598] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 14:15:30.292][000000034.599] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:30.309][000000034.614] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 14:15:30.322][000000034.615] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:30.334][000000034.634] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值

7、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的串口通道关闭后,此时 Air8000 在发送请求时便会收不到响应,Luatools 工具上显示的日志如下:

[2025-12-08 14:14:56.481][000000000.427] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:14:57.124][000000001.433] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 14:14:57.128][000000001.433] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:14:58.135][000000002.437] I/user.exmodbus_test 未收到从站 2 的响应(超时)

8、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的串口通道打开后,此时 Air8000 在发送请求时便会接收到响应,Luatools 工具与摩尔信使上显示的日志如下:

[2025-12-08 14:15:18.163][000000022.470] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值,寄存器 0 数值为 123 ,寄存器 1 数值为 456
[2025-12-08 14:15:18.177][000000022.486] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1 的值
[2025-12-08 14:15:20.178][000000024.487] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 14:15:20.207][000000024.503] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0

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

7.2 RTU 主站应用模块(原始帧方式,对应 raw_frame.lua)

1、搭建硬件环境

  • 将 USB-RS485 串口板与 Air8000 开发板进行连接
  • 将 USB-RS485 串口板 与 Air8000 开发板的 USB 端同时接在电脑上
  • 参考图见 演示硬件环境

2、在摩尔信使上配置模拟 RTU 从站设备环境

  • 点击左上角的 “通道管理”按钮,在 “通道管理” 窗口界面选择对应的串口(USB-RS485 串口板与 Air8000 开发板进行 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-08 15:14:13.691][000000000.703] I/user.main RTU_MASTER 001.000.000
[2025-12-08 15:14:13.695][000000000.736] Uart_ChangeBR 1338:uart1, 115200 115203 26000000 3611
[2025-12-08 15:14:13.699][000000000.737] I/user.exmodbus 串口 1 初始化成功,波特率 115200
[2025-12-08 15:14:13.702][000000000.737] I/user.exmodbus_test rtu_master 创建成功
[2025-12-08 15:14:13.706][000000000.738] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:14.595][000000001.743] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 15:14:14.600][000000001.743] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:15.602][000000002.747] I/user.exmodbus_test 未收到从站 2 的响应(超时)
[2025-12-08 15:14:17.591][000000004.747] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:18.591][000000005.750] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 15:14:20.591][000000007.750] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:21.602][000000008.754] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 15:14:21.611][000000008.754] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:22.607][000000009.758] I/user.exmodbus_test 未收到从站 2 的响应(超时)
[2025-12-08 15:14:24.603][000000011.758] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:25.612][000000012.762] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 15:14:27.618][000000014.762] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:28.616][000000015.766] I/user.exmodbus_test 未收到从站 1 的响应(超时)
[2025-12-08 15:14:28.620][000000015.766] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:29.623][000000016.770] I/user.exmodbus_test 未收到从站 2 的响应(超时)
[2025-12-08 15:14:31.615][000000018.770] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:31.646][000000018.790] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:33.633][000000020.790] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:33.651][000000020.808] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:33.656][000000020.808] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:33.662][000000020.821] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
[2025-12-08 15:14:35.670][000000022.821] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:35.682][000000022.839] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:37.692][000000024.839] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:37.699][000000024.854] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:37.705][000000024.854] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:37.716][000000024.870] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
[2025-12-08 15:14:39.717][000000026.870] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:39.748][000000026.887] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:41.737][000000028.887] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:41.753][000000028.902] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:41.755][000000028.902] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:41.758][000000028.916] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1
[2025-12-08 15:14:43.765][000000030.916] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:43.795][000000030.934] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:45.788][000000032.934] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:45.795][000000032.949] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:45.801][000000032.949] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:45.812][000000032.964] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1

7、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的串口通道关闭后,此时 Air8000 在发送请求时便会收不到响应,Luatools 工具上显示的日志如下:

8、如下图所示,鼠标右键点击 “通道” 下方的按钮,当我们把摩尔信使上由上位机与 Air8000 之间的串口通道打开后,此时 Air8000 在发送请求时便会接收到响应,Luatools 工具与摩尔信使上显示的日志如下:

程序设计为每隔 2 秒执行一次读取,每隔 4 秒执行一次写入,在日志上呈现出现就是先执行两次读取再执行一次写入

[2025-12-08 15:14:31.615][000000018.770] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:31.646][000000018.790] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:33.633][000000020.790] I/user.exmodbus_test 开始读取从站 1 保持寄存器 0-1 的值
[2025-12-08 15:14:33.651][000000020.808] I/user.exmodbus_test 成功读取到从站 1 保持寄存器 0-1 的值,寄存器 0 数值为 0 ,寄存器 1 数值为 0
[2025-12-08 15:14:33.656][000000020.808] I/user.exmodbus_test 开始写入从站 2 保持寄存器 0-1 的值
[2025-12-08 15:14:33.662][000000020.821] I/user.exmodbus_test 成功写入从站 2 保持寄存器 0-1

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

7.3 485 温湿度传感器读取模块(对应 temp_hum_sensor.lua)

1、搭建硬件环境

  • 将气体浓度变送器(RS-485 版)的 '+' 和 '-' 与供电设备(稳压电源等)进行连接,供电范围 8V ~ 36V DC
  • 将气体浓度变送器(RS-485 版)与 Air8000 开发板进行连接
  • 将 Air8000 开发板的 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-09 22:11:43.077][000000000.588] I/user.main RTU_MASTER 001.000.000
[2025-12-09 22:11:43.092][000000000.620] Uart_ChangeBR 1338:uart1, 9600 9600 26000000 43333
[2025-12-09 22:11:43.101][000000000.621] I/user.exmodbus 串口 1 初始化成功,波特率 9600
[2025-12-09 22:11:43.112][000000000.621] I/user.temp_hum_sensor RTU 主站创建成功
[2025-12-09 22:11:43.123][000000000.622] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:43.138][000000000.724] I/user.temp_hum_sensor 读取成功,温度为 16.70000 ℃,湿度为 83.20000 %RH
[2025-12-09 22:11:43.154][000000002.724] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:43.166][000000002.761] I/user.temp_hum_sensor 读取成功,温度为 16.80000 ℃,湿度为 82.90000 %RH
[2025-12-09 22:11:43.172][000000004.761] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:43.178][000000004.793] I/user.temp_hum_sensor 读取成功,温度为 16.60000 ℃,湿度为 83.50000 %RH
[2025-12-09 22:11:43.388][000000006.794] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:43.399][000000006.828] I/user.temp_hum_sensor 读取成功,温度为 16.90000 ℃,湿度为 82.70000 %RH
[2025-12-09 22:11:45.096][000000008.828] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:45.128][000000008.858] I/user.temp_hum_sensor 读取成功,温度为 17.00000 ℃,湿度为 82.30000 %RH
[2025-12-09 22:11:47.116][000000010.858] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:47.148][000000010.890] I/user.temp_hum_sensor 读取成功,温度为 16.80000 ℃,湿度为 83.00000 %RH
[2025-12-09 22:11:49.156][000000012.890] I/user.temp_hum_sensor 开始读取温湿度传感器数据
[2025-12-09 22:11:49.188][000000012.920] I/user.temp_hum_sensor 读取成功,温度为 16.70000 ℃,湿度为 83.40000 %RH

八、总结

本文章演示 Air8000 作为 Modbus RTU 主站如何与从站设备进行 Modbus 通信。

九、硬件电路说明

合宙 Air8000 系列模组管脚详细说明