4、Modbus协议详解:Modbus RTU/ASCII/TCP帧结构、功能码、地址映射、典型应用场景
好,咱们今天聊聊Modbus。说实话,这可能是工业现场最“老”但又最“活”的协议了。我入行那会儿,师傅就跟我说:“你搞懂Modbus,就搞懂了一半的工业通信。”这么多年下来,我觉得这话一点不夸张。
Modbus是施耐德电气(当时还叫Modicon)在1979年搞出来的。你想想看,都快半个世纪了,现在新出的设备、PLC、DCS、仪表,哪个不支持Modbus?说白了,它就是工业界的“普通话”。
4.1 Modbus的三种“方言”:RTU、ASCII、TCP
Modbus其实有三种变体,我习惯叫它们“方言”。虽然核心逻辑一样,但长相和用法差别挺大。
4.1.1 Modbus RTU
这是最常用的,没有之一。我在现场调试,十个项目里有八个用的是RTU。
帧结构长这样:
| 地址码(1字节) | 功能码(1字节) | 数据区(N字节) | CRC校验(2字节) |
- 地址码:从站地址,范围1-247。0是广播地址。
- 功能码:告诉从站要干嘛,比如读线圈、读寄存器。
- 数据区:具体参数,比如起始地址、数据个数。
- CRC校验:16位循环冗余校验,保证数据不出错。
关键点:RTU是二进制传输,效率高。但有个坑——字节之间不能有超过1.5个字符时间的间隔,否则从站会认为帧结束了。我遇到过好几次,就是因为串口中断优先级没调好,导致数据包被拆开,从站死活不响应。
4.1.2 Modbus ASCII
这个用得少了,但有些老设备或者无线传输场景还会见到。
帧结构:
| 起始符":"(1字节) | 地址码(2字符) | 功能码(2字符) | 数据区(N字符) | LRC校验(2字符) | 结束符CR/LF |
你看,它把每个字节拆成两个ASCII字符来发。比如0x1A就发成'1'和'A'。好处是肉眼可读,调试方便。坏处嘛——传输效率直接砍半。
我记得有一次,一个老外的设备只支持ASCII模式,我这边RTU死活对不上。折腾了半天,最后发现是校验方式不一样:RTU用CRC,ASCII用LRC(纵向冗余校验)。嗯,这个坑我替你们踩过了。
4.1.3 Modbus TCP
这个现在越来越流行。说白了就是把Modbus包塞进TCP/IP里,去掉了CRC校验(因为TCP自己会校验),加了个MBAP头。
帧结构:
| MBAP头(7字节) | 功能码(1字节) | 数据区(N字节) |
MBAP头包含:事务处理标识符(2字节)、协议标识符(2字节,固定为0)、长度(2字节)、单元标识符(1字节)。
我的经验:Modbus TCP默认端口是502。很多IT部门会封这个端口,你得提前跟网络管理员打招呼。另外,TCP虽然可靠,但延迟比串口大,做高速采集时要小心。
4.2 功能码——Modbus的“指令集”
功能码就是Modbus的指令。主站发一个功能码,从站就知道要干什么。常用的就那么几个,我列个表:
| 功能码 | 名称 | 作用 | 我常用的场景 |
|---|---|---|---|
| 01 (0x01) | 读线圈 | 读取DO状态 | 读取继电器输出 |
| 02 (0x02) | 读离散输入 | 读取DI状态 | 读取按钮、限位开关 |
| 03 (0x03) | 读保持寄存器 | 读取AO或参数 | 读取温度、压力值 |
| 04 (0x04) | 读输入寄存器 | 读取AI值 | 读取模拟量输入 |
| 05 (0x05) | 写单线圈 | 控制单个DO | 启动/停止电机 |
| 06 (0x06) | 写单寄存器 | 设置单个AO或参数 | 设定PID目标值 |
| 15 (0x0F) | 写多线圈 | 批量控制DO | 批量复位报警 |
| 16 (0x10) | 写多寄存器 | 批量设置参数 | 下载配方参数 |
注意:不是所有从站都支持全部功能码。有些便宜的小仪表,可能只支持03和06。采购前一定要确认清楚,不然到了现场才发现不支持,那就尴尬了。
4.3 地址映射——把数据“对上号”
Modbus的地址映射,说白了就是一张“数据地图”。你得知道哪个地址对应哪个物理量。
Modbus定义了四种数据对象:
- 线圈(Coil):DO,可读可写,地址从00001开始
- 离散输入(Discrete Input):DI,只读,地址从10001开始
- 输入寄存器(Input Register):AI,只读,地址从30001开始
- 保持寄存器(Holding Register):AO或参数,可读可写,地址从40001开始
举个例子:你读地址40001,就是读第一个保持寄存器。但这里有个坑——有些设备用0-based地址(0对应40001),有些用1-based地址(1对应40001)。我当年就被这个搞晕过,读出来的数据全是乱的。
避坑指南:拿到一个新设备,先读一个已知值的寄存器,确认地址偏移。我曾经在项目里因为地址偏移搞错,把变频器的转速读成了电流,差点把电机烧了。
4.4 典型应用场景
说了这么多理论,咱们看看实际怎么用。
场景一:PLC与变频器通信
这是最常见的。PLC通过Modbus RTU控制变频器的启停和频率。
实现步骤:
- PLC作为主站,变频器作为从站,地址设为1
- 用功能码06写寄存器40001(控制字)启动变频器
- 用功能码06写寄存器40002(频率设定值)设置50Hz
- 用功能码03读寄存器40003(当前频率)做反馈
我建议你先把变频器的通信参数(波特率、数据位、校验位)设好,再用串口助手调试一下,确认能读写成功,再连PLC。不然两边一起调,出了问题都不知道是谁的锅。
场景二:DCS采集现场仪表数据
DCS系统通常通过Modbus TCP采集现场仪表的数据。比如采集温度、压力、流量。
典型配置:
- DCS作为Modbus TCP主站
- 现场仪表作为从站,每个仪表一个IP地址
- 用功能码04读输入寄存器,获取模拟量值
- 用功能码03读保持寄存器,获取仪表参数
我的经验:现场仪表的数据格式要注意。有些仪表用整数,有些用浮点数,还有些用BCD码。你得搞清楚数据在寄存器里是怎么存的。比如一个32位浮点数,是占两个寄存器,高16位在前还是低16位在前?这个叫“字节序”,搞错了数据就是错的。
场景三:SCADA系统与RTU通信
在SCADA系统里,RTU(远程终端单元)通过Modbus与中心站通信。RTU采集现场信号,打包成Modbus帧,通过无线或卫星传到中心站。
关键点:
- RTU作为从站,中心站作为主站
- 用功能码01和02读取DI/DO状态
- 用功能码03和04读取AI/AO值
- 用功能码05和06控制输出
我记得有个项目在新疆,RTU和中心站距离上百公里,用的无线电台。Modbus RTU帧在无线链路上经常出错,后来我改成Modbus ASCII模式,虽然慢了点,但LRC校验比CRC更适合无线环境,通信成功率从70%提到了98%。
4.5 调试技巧
最后分享几个调试Modbus的实用技巧:
- 先用串口助手抓包:看看主站发了什么,从站回了什么。很多问题一看报文就明白了。
- 注意超时时间:串口通信一般设100-500ms超时。太短容易误判,太长影响效率。
- 重试机制:Modbus没有自动重发,你得自己在应用层实现。我一般设3次重试,每次间隔加倍。
- 广播地址0:可以同时控制所有从站,但广播后从站不会回复,别傻等着。
最后提醒:Modbus是个“主从”协议,从站不会主动发数据。如果你需要从站主动上报事件,Modbus做不到。这时候就得考虑其他协议了,比如CANopen或者Profinet。
好了,Modbus这块就聊到这儿。说白了,它就是个简单可靠的“老黄牛”,虽然功能不多,但胜在稳定、通用。你把它搞透了,以后遇到各种设备都能快速上手。下一章咱们聊聊Modbus在DCS里的具体集成方案,到时候我会拿一个实际项目案例来讲。