3. 串行通信协议:Modbus 家族详解
各位同学,今天咱们来聊聊Modbus。说实话,这玩意儿在工控圈子里,就跟咱们吃饭用筷子一样普遍。我做了十几年变电站自动化,几乎每个项目都离不开它。Modbus之所以能活这么久,说白了就是——简单、可靠、够用。
Modbus其实是个大家族,主要有三个兄弟:RTU、ASCII和TCP。咱们一个一个来盘。
3.1 Modbus RTU 协议帧结构
RTU是我用得最多的,也是现场最主流的。它的帧结构长这样:
| 地址码(1字节) | 功能码(1字节) | 数据区(N字节) | CRC校验(2字节) |
嗯,就这么简单。我给大家拆开讲讲:
- 地址码:从站地址,范围1-247。0是广播地址,所有设备都得听。
- 功能码:告诉从站你要干啥。比如03是读寄存器,06是写单个寄存器。
- 数据区:具体的数据内容,长度可变。
- CRC校验:16位循环冗余校验,保证数据没被干扰。
RTU的传输是二进制格式,效率高。但有个硬性要求:报文之间必须有3.5个字符时间的间隔。为什么?这是为了区分两条不同的报文。你想想看,如果连续发数据,从站怎么知道哪一段是一条完整的报文?
3.2 Modbus ASCII 协议
ASCII版本,说白了就是把RTU的二进制数据转成ASCII字符来传。每个字节拆成两个十六进制字符,比如0x1A就变成'1'和'A'两个字符。
它的帧结构多了个开头和结尾:
| ':' (0x3A) | 地址码(2字符) | 功能码(2字符) | 数据区(2N字符) | LRC校验(2字符) | CR/LF |
你可能会问:明明RTU效率更高,为什么还要用ASCII?
我遇到过这种情况:有些老旧设备或者通信链路质量很差,RTU的二进制数据容易出错。ASCII虽然慢,但每个字符都是可见的,调试起来方便。而且它的校验方式是LRC(纵向冗余校验),比CRC简单。
3.3 Modbus TCP 协议
TCP版本,就是把Modbus封装在TCP/IP协议里。它的帧结构变了:
| MBAP头(7字节) | 功能码(1字节) | 数据区(N字节) |
MBAP头包含:事务处理标识符(2字节)、协议标识符(2字节)、长度(2字节)、单元标识符(1字节)。
这里有个关键点:TCP版本没有CRC校验了。为什么?因为TCP/IP协议栈自己会做校验,你再做一遍就是画蛇添足。嗯,设计者考虑得很周到。
我在做变电站远程监控时,经常用Modbus TCP。只要网络通,就能读到数据。但要注意一点:TCP是面向连接的,如果网络断了,你得自己处理重连逻辑。我曾经有个项目,就因为没处理好重连,导致后台画面卡死——从那以后,我写的每个TCP客户端都加了心跳和自动重连。
3.4 功能码详解
功能码是Modbus的核心。我整理了一张表,大家收藏好:
| 功能码 | 名称 | 作用 | 我常用的场景 |
|---|---|---|---|
| 01 | 读线圈状态 | 读取DO输出 | 读取断路器分合位 |
| 02 | 读离散输入 | 读取DI输入 | 读取刀闸位置信号 |
| 03 | 读保持寄存器 | 读取模拟量/参数 | 读取电压、电流、功率 |
| 04 | 读输入寄存器 | 读取只读模拟量 | 读取温度、湿度传感器 |
| 05 | 写单个线圈 | 控制单个DO | 遥控分合闸 |
| 06 | 写单个寄存器 | 设置单个参数 | 修改定值、设置阈值 |
| 15 | 写多个线圈 | 批量控制DO | 批量投切电容器组 |
| 16 | 写多个寄存器 | 批量设置参数 | 批量下装保护定值 |
功能码03和06是我用得最多的。03用来读遥测数据,06用来写遥调指令。举个例子:
// 读从站地址01,从寄存器地址0x0000开始,读2个寄存器
请求:01 03 00 00 00 02 C4 0B
响应:01 03 04 00 01 00 02 79 CB
// 响应数据:寄存器0x0000=0x0001,寄存器0x0001=0x0002
3.5 知识体系总览
下面这张图,是我自己画的Modbus知识体系。你看一眼,心里就有数了:
你看这张图就明白了:三种协议虽然传输方式不同,但功能码是通用的。你学会了RTU,ASCII和TCP基本就是换个壳子。
好了,Modbus这块就讲到这里。记住一句话:Modbus不复杂,复杂的是现场的各种坑。多动手、多调试,慢慢就有感觉了。
公众号:蓝海资料掘金营,微信deep3321