Modbus应用笔记
在之前碰到一个这样的项目:大概就是 ESP32S3 需要和一个/多个摄像头模组(嵌入式Linux系统)进行通讯,且某些情况需要获取摄像头模组拍摄得到的照片。由于各种原因,最后选择 RS485 Modbus RTU 的方式实现一主(ESP32)多从(摄像头模组)。
而标准的Modbus RTU报文结构限制了一包最大只能为256字节。
Modbus RTU采样大端字节序。
| 字段 | 地址域 | 功能码 | 数据域 | CRC 校验域 |
|---|---|---|---|---|
| 长度(字节) | 1 | 1 | 可变长度(N) | 2 |
| 描述 | 从站设备地址(1~247),0 为广播地址(所有从站接收,但不响应) | 指定操作类型(如读/写寄存器) | 请求/响应的参数或数据,长度取决于功能码(最多 252 字节) | 循环冗余校验(从地址域到数据域计算),低字节在前(如 CRC=0xABCD → CD AB) |
那么对于传一张图(大小为180-400KB)来说,频繁的交互会浪费非常多的时间。因此自然想到了能不能修改一下 RTU 协议格式,将数据域的长度变大呢。
由于 Modbus 严格遵循主机请求,从机回复的策略。当时采用了 功能码 23 作为收发应用层数据。
Modbus RTU 功能码 23 (0x17) 叫做 读/写多个寄存器。这是一个组合功能,允许客户端在一个通信事务中同时执行读取和写入寄存器操作。其设计目的是为了提高效率,特别是在需要原子性地读取一组寄存器状态并基于该状态更新另一组寄存器的场景下(避免中间状态被其他操作改变)。
请求报文格式 (主机 -> 从机):
| 字段 | 从机地址 | 功能码 | 读起始地址 | 读寄存器数量 | 写起始地址 | 写寄存器数量 | 写入字节数 | 写入寄存器数据 | CRC 校验 |
|---|---|---|---|---|---|---|---|---|---|
| 长度(字节) | 1 | 1 | 2 | 2 | 2 | 2 | 1 | M*2 | 2 |
| 描述 | 目标从站设备的地址 (1-247)。 | 0x17 (23) | 要读取的第一个寄存器的地址 (高位字节在前)。 | 要读取的寄存器数量 N (高位字节在前,1-125)。 | 要写入的第一个保持寄存器的地址 (高位字节在前)。 | 要写入的保持寄存器数量 M (高位字节在前,1-121)。 | 后续写入数据的字节数 (M * 2)。 | 要写入 M 个寄存器的数据 (每个寄存器 2 字节,高位字节在前)。 | 循环冗余校验 (从从机地址到写入数据的最后一个字节)。 |
其中 PDU = 功能码 + 读起始地址 + 读寄存器数量 + 写起始地址 + 写寄存器数量 + 写入字节数 + 写入寄存器数据。
响应报文格式 (从机 -> 主机)
| 字段 | 从机地址 | 功能码 | 返回字节数 | 读取寄存器数据 | CRC 校验 |
|---|---|---|---|---|---|
| 长度 (字节) | 1 | 1 | 1 | N * 2 | 2 |
| 描述 | 响应从站设备的地址 (与请求一致)。 | 0x17 (23) | 后续读取数据的字节数 (N * 2)。 | 读取到的 N 个寄存器的数据 (每个寄存器 2 字节,高位字节在前)。 | 循环冗余校验 (从从机地址到读取数据的最后一个字节)。 |
其中 PDU = 功能码 + 返回字节数 + 读取寄存器数据。
执行顺序:先写后读。写入操作完成后立即执行读取操作。
Modbus 是一种请求/应答协议,其网络中只有一个 Modbus 客户端,但可能存在多个 Modbus 服务器。服务器只能控制自身的内部状态,而客户端可以读取和写入服务器中的数据。
每个Modbus服务器(从机)内部维护了四个独立的数据存储区域(即“Tables”),用于存储不同类型的数据。
| 名称 | 数据类型 | 客户端(主机)权限 | 典型应用 |
|---|---|---|---|
| 离散输入/开关量输入(Discrete Inputs) | 二进制 0-1 | 读 | 传感器状态、开关信号 |
| 线圈/输出线圈(Coils) | 二进制 0-1 | 读/写 | 控制继电器、指示灯 |
| 输入寄存器(Input Registers) | 16位整数 0-65535(0xFFFF) | 读 | 模拟量输入(温度、压力) |
| 保持寄存器(Holding Registers) | 16位整数 0-65535(0xFFFF) | 读/写 | 设备参数配置、数据存储 |
在 MODBUS Application Protocol 1 1 b 这份文档有对其标准规范进行阐述。
ESP32
esp-modbus 实现了 Modbus 的协议,不过目前测试发现 mbc_master_send_request() 的实现对于 Function code 0x17 支持不太完善。
mb_commands_t
MB_FUNC_READWRITE_MULTIPLE_REGISTERS
libmodbus
include(FetchContent)
set(LIBMODBUS_GIT_TAG "v3.1.11")
FetchContent_Declare(
libmodbus
GIT_REPOSITORY "https://github.com/stephane/libmodbus.git"
GIT_TAG ${LIBMODBUS_GIT_TAG}
)
string(REGEX REPLACE "^v" "" LIBMODBUS_VERSION_STRING ${LIBMODBUS_GIT_TAG})
string(REPLACE "." ";" VERSION_LIST ${LIBMODBUS_VERSION_STRING})
list(GET VERSION_LIST 0 LIBMODBUS_VERSION_MAJOR)
list(GET VERSION_LIST 1 LIBMODBUS_VERSION_MINOR)
list(GET VERSION_LIST 2 LIBMODBUS_VERSION_MICRO)
FetchContent_MakeAvailable(libmodbus)
add_library(libmodbus STATIC
${libmodbus_SOURCE_DIR}/src/modbus.c
${libmodbus_SOURCE_DIR}/src/modbus-data.c
${libmodbus_SOURCE_DIR}/src/modbus-rtu.c
)
configure_file(${libmodbus_SOURCE_DIR}/src/win32/config.h.win32 ${libmodbus_SOURCE_DIR}/src/config.h)
configure_file(${libmodbus_SOURCE_DIR}/src/modbus-version.h.in ${libmodbus_SOURCE_DIR}/src/modbus-version.h)
target_include_directories(libmodbus PUBLIC
${libmodbus_SOURCE_DIR}/src
)
target_compile_definitions(libmodbus
PUBLIC DLLBUILD
)
target_link_libraries(libmodbus
PRIVATE ws2_32
)