4. Modbus协议详解:Modbus RTU、Modbus TCP、功能码、报文结构

好,咱们今天来啃一块硬骨头——Modbus协议。

说实话,Modbus这玩意儿在工业现场太常见了。我做了十几年SCADA系统集成,几乎每个项目都离不开它。有人觉得它老,1980年代的东西了。但我想说,老不代表不好用。你想想看,一个协议能活40多年,一定有它的道理。

4.1 Modbus的两种“方言”:RTU vs TCP

Modbus其实分两种主流形态:RTU和TCP。很多人搞混,我简单说说区别。

Modbus RTU跑在串口上,RS-232或者RS-485。它的特点是紧凑、高效。每个字节都压缩成16进制,所以同样波特率下,RTU比ASCII模式快一倍。我在一个水处理项目中用过RTU,现场环境恶劣,电磁干扰大,但RTU愣是扛住了,数据没丢过。

Modbus TCP则是跑在以太网上。说白了就是把RTU的报文封装到TCP/IP包里。端口号固定用502。好处是啥?速度快,距离远,还能和办公网共用线路。但注意——别把工业网和办公网直接怼一起,我吃过这个亏,后面讲。

核心区别一句话:RTU是串口上的二进制协议,TCP是以太网上的封装版。RTU有CRC校验,TCP靠TCP/IP协议栈保证可靠性。

4.2 报文结构:拆开看看里面长啥样

Modbus的报文结构其实不复杂。我习惯把它分成三块:

  • 地址域:1个字节,告诉从站“我在叫你”
  • 功能码:1个字节,告诉从站“你要干啥”
  • 数据域:N个字节,具体参数和数值

RTU报文还要加两个字节的CRC校验。TCP报文则加一个MBAP头(7个字节),代替了地址和CRC。

举个例子,读保持寄存器的RTU报文:

从站地址: 0x01
功能码:   0x03
起始地址: 0x00 0x6B
寄存器数: 0x00 0x03
CRC校验:  0x?? 0x??

嗯,这里要注意。CRC计算很多人搞错。我建议直接用查表法,别自己算,容易翻车。

4.3 功能码:Modbus的“指令集”

功能码就是Modbus的指令。常用的就那么几个:

功能码 名称 作用
0x01 读线圈 读取DO(数字量输出)
0x02 读离散输入 读取DI(数字量输入)
0x03 读保持寄存器 读取AO(模拟量输出)或参数
0x04 读输入寄存器 读取AI(模拟量输入)
0x05 写单个线圈 控制单个DO
0x06 写单个寄存器 设置单个AO或参数
0x0F 写多个线圈 批量控制DO
0x10 写多个寄存器 批量设置AO或参数

我个人习惯,项目里80%的通信都用0x03和0x06。读参数、写参数,搞定。但有一次在光伏电站项目中,逆变器非要我用0x10写多个寄存器,否则不响应。嗯,设备厂商的“个性”你得适应。

小技巧:调试时先用0x03读几个寄存器,确认通信正常,再尝试写操作。这样能快速定位问题。

4.4 异常响应:设备说“不”的时候

Modbus从站如果收到非法请求,会返回异常响应。格式是:功能码 + 0x80,再加一个异常码。

常见异常码:

  • 0x01:非法功能码——从站不支持这个指令
  • 0x02:非法数据地址——你读的地址不存在
  • 0x03:非法数据值——参数超出范围
  • 0x04:从站设备故障——设备内部出问题了

我曾经遇到一个情况,PLC一直返回0x02。查了半天,发现是地址偏移搞错了。Modbus地址从0开始,但有些设备手册写的是1开始。你想想看,差一个数,全盘皆输。

避坑指南:我曾经在调试时忽略了异常响应,以为设备没反应就是坏了。其实设备在说“你发的东西我不懂”。所以,一定要在代码里处理异常码,别闷头重试。

4.5 实战中的几个坑

讲几个我踩过的坑,你们记一下:

  1. 字节序问题:Modbus默认大端序(高字节在前),但有些设备用的小端序。我遇到过变频器,高低字节反着来,读出来的数值完全不对。
  2. 寄存器映射:同一个地址,不同厂商可能映射不同的物理量。一定要看设备手册,别想当然。
  3. 通信超时:RTU模式下,报文之间要有3.5个字符的静默时间。太短了,设备会认为报文没结束;太长了,设备会超时。我一般设10ms,稳妥。
  4. TCP连接管理:Modbus TCP是短连接还是长连接?看场景。频繁读写建议长连接,省去握手开销。但记得加心跳,防止连接假死。

嗯,差不多就这些。Modbus协议看着简单,但用好了能解决很多实际问题。下一节咱们讲怎么在SCADA里集成Modbus设备,到时候我会拿一个真实项目案例来拆解。