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控制变频器的启停和频率。

实现步骤:

  1. PLC作为主站,变频器作为从站,地址设为1
  2. 用功能码06写寄存器40001(控制字)启动变频器
  3. 用功能码06写寄存器40002(频率设定值)设置50Hz
  4. 用功能码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的实用技巧:

  1. 先用串口助手抓包:看看主站发了什么,从站回了什么。很多问题一看报文就明白了。
  2. 注意超时时间:串口通信一般设100-500ms超时。太短容易误判,太长影响效率。
  3. 重试机制:Modbus没有自动重发,你得自己在应用层实现。我一般设3次重试,每次间隔加倍。
  4. 广播地址0:可以同时控制所有从站,但广播后从站不会回复,别傻等着。

最后提醒:Modbus是个“主从”协议,从站不会主动发数据。如果你需要从站主动上报事件,Modbus做不到。这时候就得考虑其他协议了,比如CANopen或者Profinet。

好了,Modbus这块就聊到这儿。说白了,它就是个简单可靠的“老黄牛”,虽然功能不多,但胜在稳定、通用。你把它搞透了,以后遇到各种设备都能快速上手。下一章咱们聊聊Modbus在DCS里的具体集成方案,到时候我会拿一个实际项目案例来讲。