4、Modbus 协议详解:Modbus RTU、Modbus ASCII、Modbus TCP的区别、报文结构解析
好,咱们进入正题。Modbus 这玩意儿,说实话,是我入行以来打交道最多的协议。没有之一。你想想看,从PLC到变频器,从温控表到智能电表,几乎是个工业设备就支持Modbus。为什么?因为它简单、开放、够用。
今天这一章,我就把Modbus的三个“变种”——RTU、ASCII、TCP——掰开揉碎了讲清楚。尤其是报文结构,我会带着你一行一行地拆解。嗯,这里要注意,很多工程师干了三五年,报文结构还搞混,调试时抓瞎。咱们今天就把这个坑填上。
4.1 三种Modbus的“出身”与定位
Modbus 是 Modicon 公司在 1979 年搞出来的。那时候还没有以太网,串口通信是主流。所以最早的 Modbus 就是跑在 RS-232/RS-485 上的。后来以太网普及了,大家觉得串口太慢,于是有了 Modbus TCP。
我个人习惯把这三个版本分成两类:
- 串行链路版:Modbus RTU 和 Modbus ASCII。它们跑在 RS-232 或 RS-485 上。
- 以太网版:Modbus TCP。跑在 TCP/IP 协议栈上。
说白了,RTU 和 ASCII 是“亲兄弟”,只是编码方式不同。而 TCP 是“表兄弟”,连报文结构都变了。
4.2 Modbus RTU —— 工业现场最常用的“老黄牛”
Modbus RTU,我估计你项目中 80% 的串口设备都是这个模式。它的特点是:二进制传输,紧凑高效。
我在项目中遇到过一件事:一个老工程师坚持用 ASCII,说“看得懂”。结果波特率 9600 下,一个轮询周期慢了将近一倍。后来我给他改成 RTU,问题立刻解决。所以我的建议是:只要不是特殊要求,一律用 RTU。
4.2.1 RTU 报文结构
RTU 的报文结构非常简洁,就四部分:
| 地址域 | 功能码 | 数据域 | CRC校验 |
|---|---|---|---|
| 1 字节 | 1 字节 | N 字节 | 2 字节 |
举个例子,读取从站地址 01 的保持寄存器,起始地址 0x0000,读取 2 个寄存器:
请求报文:01 03 00 00 00 02 C4 0B
响应报文:01 03 04 00 0A 00 14 3A 9B
咱们拆开看:
- 01:从站地址。范围 1-247,0 是广播地址。
- 03:功能码。03 表示读取保持寄存器。
- 00 00:起始地址。高字节在前(大端模式)。
- 00 02:寄存器数量。这里读 2 个。
- C4 0B:CRC 校验。低字节在前(小端模式)。
响应报文里,04 表示数据字节数(2个寄存器 × 2字节 = 4字节),后面 00 0A(十进制的10)和 00 14(十进制的20)就是读回来的数据。
关键点:RTU 报文没有起始位和停止位,靠的是“帧间隔”来区分。3.5 个字符时间的静默,表示一帧结束。这个时间跟波特率有关,很多新手在这里栽跟头。
4.3 Modbus ASCII —— 人类可读的“老古董”
Modbus ASCII,说白了就是把 RTU 的二进制数据转成 ASCII 字符。每个字节拆成两个十六进制字符发送。比如 0x1A 就变成 '1' 和 'A' 两个字符(0x31, 0x41)。
为什么要这么干?因为早期有些通信链路不支持二进制传输,或者调试时方便人眼看。但代价是:传输效率降低一倍。
4.3.1 ASCII 报文结构
ASCII 报文比 RTU 多了起始和结束标记:
| 起始 | 地址域 | 功能码 | 数据域 | LRC校验 | 结束 |
|---|---|---|---|---|---|
| ':' (0x3A) | 2 字符 | 2 字符 | N 字符 | 2 字符 | CR+LF |
还是刚才那个例子,ASCII 格式下是这样的:
请求报文::010300000002F1\r\n
响应报文::010304000A0014E4\r\n
注意看,CRC 换成了 LRC(纵向冗余校验)。LRC 的计算很简单:把所有字节相加,取补码。我刚开始做的时候,总觉得 LRC 不如 CRC 可靠。确实,LRC 的检错能力弱一些,但 ASCII 模式本身就不适合高速或高可靠性场景。
避坑指南:我曾经在一个项目中,设备手册写的是 ASCII 模式,结果实际用的是 RTU。折腾了两天才发现是文档错了。所以拿到新设备,第一件事就是抓报文确认模式,别信手册。
4.4 Modbus TCP —— 以太网时代的“新贵”
Modbus TCP 跟前面两个完全不一样。它去掉了 CRC 校验(因为 TCP/IP 协议栈已经保证了可靠性),增加了 MBAP 报文头。
你想想看,串口通信需要自己管校验,但以太网有 TCP 的确认重传机制,所以 Modbus TCP 就“偷懒”了。这不是坏事,反而让报文更简洁。
4.4.1 TCP 报文结构
| MBAP头 | 功能码 | 数据域 |
|---|---|---|
| 7 字节 | 1 字节 | N 字节 |
MBAP 头包含四个字段:
- 事务处理标识符(2 字节):用于匹配请求和响应。我习惯用递增计数器。
- 协议标识符(2 字节):Modbus 协议固定为 0x0000。
- 长度(2 字节):后续字节数(包括功能码和数据域)。
- 单元标识符(1 字节):相当于 RTU 的从站地址。但在 TCP 中,它更多用于网关路由。
同样的例子,TCP 报文:
请求报文:00 01 00 00 00 06 01 03 00 00 00 02
响应报文:00 01 00 00 00 07 01 03 04 00 0A 00 14
拆解一下:
- 00 01:事务 ID,第 1 次请求。
- 00 00:协议 ID,Modbus。
- 00 06:长度,后面还有 6 个字节(单元标识符 1 + 功能码 1 + 数据 4)。
- 01:单元标识符,指向从站 1。
- 后面就跟 RTU 的数据部分一样了。
个人经验:调试 Modbus TCP 时,我建议先用 Wireshark 抓包。看事务 ID 是否匹配,长度字段是否正确。很多网关设备在单元标识符的处理上会有“小动作”,抓包一看就明白。
4.5 三种协议的对比总结
好了,咱们把三个版本放在一起对比一下:
| 特性 | Modbus RTU | Modbus ASCII | Modbus TCP |
|---|---|---|---|
| 编码方式 | 二进制 | ASCII 字符 | 二进制 |
| 校验方式 | CRC-16 | LRC | 无(依赖TCP) |
| 帧分隔 | 3.5字符静默 | 起始':' 结束CR+LF | TCP 流 |
| 传输效率 | 高 | 低(约RTU一半) | 高 |
| 典型场景 | RS-485 总线 | 无线/噪声环境 | 以太网 |
| 最大节点数 | 247 | 247 | 无限制 |
我个人建议的选型原则很简单:
- 串口通信,首选 RTU。除非你的通信链路对 ASCII 字符有特殊要求(比如某些无线模块)。
- 以太网环境,用 TCP。别想着把 RTU 封装到 TCP 里,那是画蛇添足。
- ASCII 模式,说实话,我只有在调试阶段偶尔用用。正式项目里几乎不用。
4.6 一个容易忽略的细节:字节序
嗯,这里要特别提一下字节序的问题。Modbus 协议规定:多字节数据采用大端模式(高字节在前)。但 CRC 校验是个例外,它是小端模式(低字节在前)。
我曾经在一个项目中,自己写 CRC 计算函数,结果把高低字节搞反了。调试了整整一个下午,最后发现是字节序的问题。所以,如果你自己实现 Modbus 协议栈,一定要记住这个“例外”。
另外,不同厂家的设备在数据域内部的字节序也可能不同。比如一个 32 位浮点数,有的厂家按 ABCD 顺序,有的按 CDAB。这不是 Modbus 协议的问题,是设备厂商的实现差异。遇到这种情况,只能看手册或者抓报文分析。
好了,这一章的内容就到这里。Modbus 协议看似简单,但细节很多。下一章咱们会深入讲功能码和数据模型,到时候再结合具体案例来分析。记住,搞工业通信,报文结构是基本功,一定要烂熟于心。