3. 协议帧结构:Modbus RTU 报文格式详解
好,咱们今天来聊聊Modbus RTU的报文格式。说实话,这是整个Modbus协议里最核心的部分。你搞懂了报文结构,就等于拿到了串口通信的钥匙。我当年刚入行时,就是靠死磕这个格式,才把一堆乱七八糟的设备给调通的。
Modbus RTU的报文,说白了就是一串字节。它遵循一个固定的套路:地址码、功能码、数据区、CRC校验。这四个部分缺一不可。咱们一个一个来看。
3.1 地址码:谁在说话?
地址码占1个字节。取值范围是0到247。其中0是广播地址,所有从机都得响应。1到247是每个从机的唯一身份。
我遇到过一个小坑:有些设备出厂默认地址是1,结果现场两台设备都设成1,总线直接乱套。所以我的习惯是,项目一开始就规划好地址表,从1开始顺序分配,留几个备用地址。
- 主机发送的请求帧,地址码指向目标从机
- 从机回复的响应帧,地址码就是自己的地址
- 广播地址0,所有从机都要处理,但不需要回复
3.2 功能码:要干什么?
功能码也是1个字节。它告诉从机:你要执行什么操作。常用的就那么几个,我列个表给你看。
| 功能码 | 含义 | 操作对象 |
|---|---|---|
| 0x01 | 读取线圈状态 | DO(数字量输出) |
| 0x02 | 读取离散输入 | DI(数字量输入) |
| 0x03 | 读取保持寄存器 | AO(模拟量输出/参数) |
| 0x04 | 读取输入寄存器 | AI(模拟量输入) |
| 0x05 | 写单个线圈 | 单个DO |
| 0x06 | 写单个寄存器 | 单个AO |
| 0x0F | 写多个线圈 | 多个DO |
| 0x10 | 写多个寄存器 | 多个AO |
你看,功能码的设计其实很清晰。读操作是0x01到0x04,写操作是0x05、0x06、0x0F、0x10。我建议你先把0x03和0x06这两个搞熟,因为90%的现场应用都离不开它们。
3.3 数据区:具体参数
数据区是变长的。它跟在功能码后面,具体内容取决于功能码。比如读操作,数据区里放的是起始地址和读取数量。写操作,数据区里放的是地址、数据值,还有字节数。
举个例子,读取从机地址1的保持寄存器,从地址0开始读2个寄存器:
请求帧:01 03 00 00 00 02 C4 0B
地址码:01
功能码:03
起始地址高字节:00
起始地址低字节:00
寄存器数量高字节:00
寄存器数量低字节:02
CRC校验:C4 0B
你想想看,这个报文是不是很直观?地址1的从机,执行功能码03,从地址0开始读2个寄存器。数据区就是起始地址和数量。
响应帧呢?从机会这样回复:
响应帧:01 03 04 00 0A 00 14 7A 5E
地址码:01
功能码:03
字节数:04(表示后面有4个字节的数据)
数据1高字节:00
数据1低字节:0A(十进制10)
数据2高字节:00
数据2低字节:14(十进制20)
CRC校验:7A 5E
这里要注意,Modbus的数据是大端模式。高位字节在前,低位字节在后。我刚开始做项目时,因为大小端搞反,读出来的温度值直接翻了好几倍。后来养成了习惯,每次解析数据前先确认字节序。
3.4 CRC校验:数据可靠吗?
CRC校验占2个字节,放在报文末尾。它是对地址码、功能码、数据区这三个部分做的循环冗余校验。算法是固定的,叫CRC-16-Modbus。
说白了,CRC就是用来检查报文在传输过程中有没有被干扰。现场环境复杂,电机启停、变频器干扰,都可能让数据出错。没有CRC,你根本不知道收到的数据对不对。
CRC的计算过程,我简单说一下:
- 初始化CRC寄存器为0xFFFF
- 对每个字节,与CRC寄存器低8位异或
- 右移1位,如果移出的位是1,则与0xA001异或
- 重复8次,处理完一个字节
- 所有字节处理完后,CRC寄存器取反
你不需要手写这个算法,网上有很多现成的代码。但我建议你至少理解它的原理,这样出问题时能快速定位。
3.5 报文格式总结
咱们用一张图来总结一下Modbus RTU的报文结构。我画了个SVG图,把四个部分的关系展示清楚。
你看这张图,报文从左到右依次排列。地址码告诉谁干活,功能码告诉干什么活,数据区是干活的具体参数,CRC保证活干得没出错。这个逻辑,你想想看,是不是很清晰?
最后说一句,实际调试时,我习惯用串口助手抓报文。先看地址码对不对,再看功能码,然后解析数据区,最后验证CRC。一步一步来,问题很快就能定位。
- 地址码:1字节,0为广播,1-247为从机地址
- 功能码:1字节,定义操作类型
- 数据区:变长,大端模式,内容取决于功能码
- CRC校验:2字节,CRC-16-Modbus算法,初始值0xFFFF
嗯,报文格式就讲到这里。你把这个结构刻在脑子里,后面讲异常处理、多机通信时,你会觉得特别顺。