1. 课程导入

Modbus 是工业自动化领域应用最广泛的通信协议之一。它诞生于 1979 年,最初用于施耐德(Modicon)PLC 之间的串行通信,如今已扩展到串口、以太网、光纤甚至无线链路。对嵌入式与工控开发者而言,掌握 Modbus 驱动开发,意味着能够把传感器、仪表、变频器、PLC、网关等设备接入统一的数据模型,实现采集、控制与远程运维。本课程以“能写、能调、能排障”为目标,从协议原理出发,逐步构建一套可复用的 Modbus RTU/TCP 驱动。

一、Modbus 协议概述:主从模型与两种承载

Modbus 的核心是请求—响应的主从(Client/Server,传统称 Master/Slave)模型。主站发起请求,从站按地址识别自己并返回响应;从站之间不直接通信。协议定义了一个与物理层无关的协议数据单元 PDU,再由不同承载方式加上头部,形成应用数据单元 ADU。

  • Modbus RTU:运行在 RS-232/RS-485 串行链路上,以二进制编码,靠 3.5 个字符时间的静默间隔划分帧,常用波特率 9600、19200、115200,配合 CRC16 校验。
  • Modbus TCP:运行在以太网 TCP/IP 之上,默认端口 502,在 PDU 前加 7 字节 MBAP 报文头,用事务标识符匹配请求与响应,不再需要 CRC(由 TCP 保证可靠性)。
  • Modbus ASCII:以可读字符传输,效率低,现代工程中较少使用,但理解其 LRC 校验有助于建立完整概念。

无论哪种承载,PDU 都由功能码 + 数据组成。常用功能码包括:01 读线圈、02 读离散输入、03 读保持寄存器、04 读输入寄存器、05 写单个线圈、06 写单个寄存器、15 写多个线圈、16 写多个寄存器。理解“四类数据区 + 功能码”的映射,是驱动开发的第一块基石。

Modbus 主从通信与报文封装关系 主站 Client 发起请求 / 轮询 解析响应 / 超时重试 从站 Server 地址匹配 / 执行 返回数据或异常码 请求:功能码 + 起始地址 + 数量 响应:字节数 + 数据 / 异常码 PDU(协议数据单元) 功能码 Function Code + 数据 Data Modbus RTU ADU(串行) 地址 PDU CRC16 帧间隔 ≥ 3.5 字符时间,二进制编码 Modbus TCP ADU(以太网) MBAP 头(7B) PDU 无 CRC 端口 502,事务 ID 匹配请求响应

二、学习目标:从协议理解到可交付驱动

完成本课程后,你应当能够独立完成以下任务:

  1. 准确描述 Modbus RTU/TCP 的帧结构、地址模型与异常响应机制。
  2. 在 MCU 或 Linux 平台上实现串口/以太网收发、帧同步、CRC 校验与超时重试。
  3. 设计分层驱动:物理层、链路层、协议层、应用接口层,并给出可测试的 API。
  4. 使用调试工具定位常见故障:无响应、CRC 错误、地址越界、粘包拆包、并发冲突。
  5. 把驱动接入实际场景,如采集电表、控制变频器、对接 SCADA 或网关。

三、驱动开发整体框架

一个可维护的 Modbus 驱动不应把所有逻辑塞进一个函数。推荐按四层组织:

层次职责典型实现
物理层串口/网口打开、参数配置、字节收发UART + RS485 方向控制、socket
链路层帧定界、CRC 校验、MBAP 解析、超时重传RTU 状态机、TCP 事务表
协议层功能码编解码、地址映射、异常码处理PDU 构造与解析
应用接口层面向业务的读写 API、数据缓存、回调read_holding_regs()、write_coil()

核心设计原则:协议层不关心串口还是网口;链路层不关心寄存器含义;应用层不关心字节序与 CRC。层与层之间用统一的数据结构传递 PDU,才能让 RTU 与 TCP 共用同一套上层代码。

四、典型场景与最小可用示例

假设要读取一台电表的保持寄存器,从站地址 1,起始地址 0x0000,读取 2 个寄存器。RTU 请求帧如下:

请求: 01 03 00 00 00 02 C4 0B
      |  |  |     |     └── CRC16
      |  |  |     └──────── 寄存器数量 = 2
      |  |  └────────────── 起始地址 = 0x0000
      |  └───────────────── 功能码 03(读保持寄存器)
      └──────────────────── 从站地址 1

响应: 01 03 04 00 64 00 C8 3A 5E
      |  |  |  └── 数据:0x0064=100,0x00C8=200
      |  |  └───── 字节数 = 4
      |  └──────── 功能码 03
      └─────────── 从站地址 1

若从站返回异常,功能码最高位置 1,例如 01 83 02 ... 表示功能码 03 出错,异常码 02 代表“非法数据地址”。驱动必须把异常码翻译成可读错误,而不是简单丢弃。

五、常见误区与复盘

误区一:认为 TCP 不需要处理粘包。TCP 是字节流,一次 recv 可能收到半个 MBAP 或两个完整帧,必须按长度字段循环解析。

误区二:RTU 用固定延时替代 3.5 字符间隔。高波特率下固定延时会导致帧粘连,应基于波特率计算字符时间。

误区三:忽略 RS485 收发方向切换。发送完成后未及时切回接收,会丢失从站响应。

误区四:把寄存器地址与协议地址混为一谈。设备手册常以 40001 表示保持寄存器,而报文中使用偏移 0x0000,需要做映射。