第3章 Modbus协议实战:Modbus RTU/ASCII/TCP协议帧结构,主从站通信机制
各位同学,今天我们来啃一块硬骨头——Modbus协议。说实话,这玩意儿在工业现场太常见了,你随便进一个工厂,十有八九能碰到它。我最早接触Modbus是在一个水处理项目中,当时被RTU和ASCII两种模式搞得晕头转向,后来踩了不少坑才摸清楚门道。今天我把这些经验都掏出来,咱们一起把它吃透。
3.1 Modbus协议的前世今生
Modbus诞生于1979年,由Modicon公司(现在叫施耐德)发明。你想想看,一个40多年前的协议,到现在还在广泛使用,说明它确实有两把刷子。它的核心思想很简单:主站问,从站答。主站通常是PLC、DCS或者上位机,从站是各种传感器、执行器、智能仪表。
核心要点:Modbus是主从架构,同一时间只能有一个主站,最多支持247个从站(地址1-247)。从站不能主动发数据,只能响应主站的请求。
我个人习惯把Modbus比作一个班级:老师(主站)点名提问,学生(从站)被点到才能回答。如果两个学生同时说话,那就乱套了。嗯,这里要注意,总线冲突在工业现场可是大忌。
3.2 三种协议帧结构详解
Modbus有三种变体:RTU、ASCII和TCP。它们的数据模型完全一样,只是打包方式不同。我建议你先把RTU搞明白,因为它是用得最多的。
3.2.1 Modbus RTU帧结构
RTU模式采用二进制编码,效率高,是工业现场的首选。它的帧结构如下:
| 地址码 | 功能码 | 数据区 | CRC校验 |
|---|---|---|---|
| 1字节 | 1字节 | N字节 | 2字节 |
举个例子,读取从站1的保持寄存器(功能码03),起始地址0x0000,读取2个寄存器:
请求帧:01 03 00 00 00 02 C4 0B
响应帧:01 03 04 00 0A 00 14 3A 0C
我来拆解一下:
- 01:从站地址,表示我们要找1号从站
- 03:功能码,读取保持寄存器
- 00 00:起始地址,从0x0000开始读
- 00 02:读取数量,读2个寄存器
- C4 0B:CRC校验,低字节在前
响应帧中,04表示数据长度是4个字节(2个寄存器×2字节),后面跟着具体数值0x000A和0x0014。
避坑指南:我曾经在CRC校验上吃过亏。RTU的CRC是循环冗余校验,计算时多项式是0x8005,初始值0xFFFF。很多新手容易搞错字节序——CRC是低字节在前,高字节在后。如果你用串口调试工具看到校验不对,先检查这个。
3.2.2 Modbus ASCII帧结构
ASCII模式用可打印字符传输,每个字节拆成两个ASCII字符。说白了,就是把二进制转成十六进制字符串再发。它的帧结构多了起始和结束标记:
| 起始符 | 地址码 | 功能码 | 数据区 | LRC校验 | 结束符 |
|---|---|---|---|---|---|
| ':' (0x3A) | 2字符 | 2字符 | 2N字符 | 2字符 | CR+LF |
同样的读取操作,ASCII帧长这样:
:010300000002F8\r\n
注意看,每个字节都变成了两个字符。比如地址01变成了"01",功能码03变成了"03"。LRC校验是纵向冗余校验,比CRC简单,但可靠性也差一些。
我个人很少用ASCII模式,除非是调试阶段或者通信链路质量特别差的时候。为什么?因为ASCII效率低,同样的数据量,它比RTU多一倍的传输时间。你想想看,在工业现场,时间就是金钱啊。
3.2.3 Modbus TCP帧结构
TCP模式去掉了CRC校验,加了一个MBAP报文头。因为TCP/IP协议栈自己会做校验,Modbus就不重复劳动了。帧结构如下:
| MBAP头 | 功能码 | 数据区 |
|---|---|---|
| 7字节 | 1字节 | N字节 |
MBAP头包含:
- 事务处理标识符(2字节):用于匹配请求和响应
- 协议标识符(2字节):固定为0x0000
- 长度(2字节):后续字节数
- 单元标识符(1字节):相当于RTU的从站地址
请求帧: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,00 00是协议ID,00 06表示后面还有6个字节。嗯,要注意TCP模式下从站地址变成了单元标识符,而且可以跨网段通信,这是RTU做不到的。
3.3 主从站通信机制
Modbus的通信机制可以用一句话概括:主站发起请求,从站响应。但这里面有几个关键点,我当年可是花了不少时间才搞明白。
3.3.1 主站的工作流程
- 构建请求帧:根据功能码和参数组装报文
- 发送请求:通过串口或网络发出
- 等待响应:设置超时时间(我一般设500ms)
- 解析响应:检查地址、功能码、校验码
- 处理异常:如果超时或收到异常码,重试或报错
经验之谈:我在一个项目中遇到过从站响应特别慢的情况,后来发现是总线上的设备太多,导致信号反射。解决办法是在总线两端加120欧姆终端电阻。如果你遇到通信时好时坏,先查这个。
3.3.2 从站的响应逻辑
从站收到请求后,先检查地址是否匹配。如果匹配,就执行操作并返回正常响应;如果不匹配,直接忽略。如果操作失败(比如读取不存在的寄存器),就返回异常响应。
异常响应帧的格式是:地址码 + (功能码+0x80) + 异常码 + 校验。异常码有几种:
| 异常码 | 含义 | 常见原因 |
|---|---|---|
| 01 | 非法功能码 | 从站不支持该功能 |
| 02 | 非法数据地址 | 寄存器地址超出范围 |
| 03 | 非法数据值 | 读取数量过多 |
| 04 | 从站设备故障 | 硬件问题或正在忙 |
我记得有一次调试,从站一直返回异常码02。查了半天,发现是地址计算错了——有些设备的寄存器地址是从1开始的,而Modbus协议里是从0开始的。这个坑,你一定要注意。
3.4 核心知识体系
下面这张图是我自己整理的Modbus知识体系,涵盖了本章的核心内容。你可以把它当作一个快速参考。
3.5 实战中的避坑指南
最后,我把自己这些年踩过的坑总结一下,希望能帮你少走弯路:
坑1:字节序问题——Modbus RTU中,16位数据是大端模式(高字节在前),但有些设备厂商会搞成小端。我建议你在调试时先用功能码03读一个已知数值,确认字节序。
坑2:超时设置——串口通信的超时时间不能太短。我曾经设了100ms,结果从站偶尔响应慢一点就超时了。后来改成500ms,问题解决。记住,工业现场有干扰,重传是常态。
坑3:地址冲突——同一个总线上不能有两个相同地址的从站。我在一个项目中遇到过,两个传感器出厂默认地址都是1,结果通信全乱套了。解决办法是上电前先单独配置地址。
调试小技巧:用串口助手抓包时,注意RTU帧之间的间隔要大于3.5个字符时间。如果间隔太短,从站会把两帧当成一帧处理。波特率9600时,3.5个字符大约是3.6ms。
好了,Modbus协议的核心内容就这些。你把这些搞明白,工业现场80%的通信问题都能应付。剩下的20%,靠经验和耐心去磨。
公众号:蓝海资料掘金营,微信deep3321