3. Modbus协议详解(上):Modbus历史、RTU与ASCII模式、报文结构、功能码分类
各位同学,今天我们来啃Modbus协议这块硬骨头。说实话,在我二十多年的工业通信生涯里,Modbus是我打交道最多的协议,没有之一。它就像工业界的"普通话"——虽然不够华丽,但谁都能听懂。
3.1 Modbus的前世今生
Modbus诞生于1979年,由Modicon公司(现在的施耐德电气)提出。那时候我还没入行,但听老工程师讲,当时PLC之间通信简直是噩梦——每家都有自己的私有协议,互不兼容。Modbus的出现,说白了就是给工业设备定了个"说话规矩"。
为什么它能活到现在?我总结三点:
- 开放免费:Modbus协议完全公开,不收一分钱许可费
- 简单可靠:报文结构极其简洁,单片机都能轻松实现
- 硬件门槛低:RS-232/RS-485串口就能跑,成本几乎为零
我记得2015年做一个水处理项目,甲方要求用Profinet,但现场几十台老旧仪表只支持Modbus。最后怎么办?加个协议转换器,照样跑得稳稳的。这就是Modbus的生命力——你永远不知道它会在什么犄角旮旯里出现。
3.2 RTU模式 vs ASCII模式
Modbus有两种传输模式,我习惯叫它们"二进制模式"和"文本模式"。
| 特性 | RTU模式 | ASCII模式 |
|---|---|---|
| 数据表示 | 二进制(0x00-0xFF) | ASCII字符('0'-'9', 'A'-'F') |
| 字节效率 | 1字节=1个数据 | 1字节=2个ASCII字符 |
| 帧间隔 | 3.5字符时间 | 1秒 |
| 校验方式 | CRC-16 | LRC(纵向冗余校验) |
| 典型速率 | 9600-115200 bps | 9600 bps以下 |
我的建议:除非你有特殊需求,否则一律用RTU。为什么?效率差太多了。同样读取10个寄存器,RTU只需要约15毫秒,ASCII要30毫秒以上。你想想看,在100个从站的系统里,这差距就大了去了。
3.3 报文结构深度解析
Modbus报文结构其实就四部分,我管它叫"四件套":
| 地址码 | 功能码 | 数据区 | 校验码 |
| 1字节 | 1字节 | N字节 | 2字节 |
3.3.1 地址码(1字节)
取值范围1-247,0是广播地址。这里有个坑:有些设备地址从1开始,有些从0开始。我遇到过一台国产温控器,地址设成0就死机,折腾了我半天。
3.3.2 功能码(1字节)
这是Modbus的灵魂。1-127是标准功能码,128-255是异常响应。后面我会详细讲。
3.3.3 数据区(N字节)
根据功能码不同,数据区格式也不同。比如读线圈时,数据区包含起始地址和数量;写寄存器时,包含地址和要写入的值。
3.3.4 CRC校验(2字节)
RTU模式用CRC-16,多项式是0x8005。我手算过几次,太痛苦了,后来都用查表法。给你个参考代码:
// CRC-16查表法实现
uint16_t crc16(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
3.4 功能码分类详解
Modbus功能码很多,但实际项目中常用的就这几个。我按使用频率排序:
3.4.1 位操作类
- 01 (0x01) - 读线圈:读取DO状态,返回1位/线圈
- 02 (0x02) - 读离散输入:读取DI状态,只读不写
- 05 (0x05) - 写单个线圈:控制单个DO,0xFF00=ON,0x0000=OFF
- 15 (0x0F) - 写多个线圈:批量控制DO,效率高
3.4.2 寄存器操作类
- 03 (0x03) - 读保持寄存器:最常用的功能码,读模拟量、参数等
- 04 (0x04) - 读输入寄存器:读AI值,只读
- 06 (0x06) - 写单个寄存器:写单个16位数据
- 16 (0x10) - 写多个寄存器:批量写,常用于参数下载
为什么03和06最常用?因为大部分工业参数都是16位的,比如温度、压力、速度。我做过一个项目,用03功能码每秒轮询200个寄存器,CPU占用率才5%,稳得很。
实战案例:用03功能码读取变频器频率
请求:01 03 00 00 00 01 84 0A
| | | | | |
| | 起始地址 CRC
| | 寄存器数量=1
| 功能码=03
地址=01
响应:01 03 02 13 88 B5 F6
| | | | |
| | 数据=0x1388=5000
| | 字节数=2
| 功能码=03
地址=01
0x1388换算成十进制是5000,如果量程是0-100Hz,那实际频率就是50.00Hz。嗯,这里要注意字节序——Modbus默认是大端模式,高位在前。
3.5 功能码使用避坑指南
我踩过的坑,你们就别再踩了:
- 功能码15和16的批量操作:一次最多写2000个线圈或125个寄存器。别问我怎么知道的——我曾经试图一次写300个寄存器,结果从站直接罢工了。
- 功能码05和06的"写后即读":写完一定要读回来确认。有些设备写操作不报错,但实际没写进去。我习惯写完等50ms再读。
- 功能码01和02的区别:01读的是可读可写的线圈(DO),02读的是只读的离散输入(DI)。别搞混了,否则读回来的数据全是0xFF。
最后说一句:Modbus协议虽然简单,但越简单的东西越容易出幺蛾子。我见过太多"Modbus通信不稳定"的问题,最后发现都是报文格式写错了。所以,写代码前先把报文结构画清楚,能省你三天调试时间。
下一章我们讲Modbus的异常处理、多主站通信和实际项目中的性能优化。到时候我会分享一个我处理过的"Modbus总线崩溃"的案例,保证让你大开眼界。