3、Modbus协议详解:Modbus RTU/ASCII/TCP协议帧结构、功能码、地址映射

好,咱们今天来啃一块硬骨头——Modbus协议。说实话,这玩意儿在工业现场太常见了。我做了十几年SCADA系统,几乎每个项目都绕不开它。你想想看,从PLC到变频器,从温控表到智能电表,Modbus就像工业界的“普通话”。

但很多人学Modbus有个误区——光看协议文档,越看越晕。我个人的习惯是:先搞懂它为什么这么设计,再看帧结构,最后才是功能码。咱们今天就按这个路子来。

3.1 Modbus的三种“方言”

Modbus其实有三种实现方式:RTU、ASCII和TCP。说白了,它们底层的数据模型一模一样,就是打包方式不同。

特性 Modbus RTU Modbus ASCII Modbus TCP
物理层 RS-232/485 RS-232/485 以太网
编码方式 二进制(8位) ASCII字符(16进制) 二进制(8位)
帧间隔 3.5字符时间 1秒超时 无(TCP流)
校验方式 CRC-16 LRC 无(依赖TCP)
典型速率 9600~115200 bps 9600 bps(效率低) 100Mbps

我在项目中遇到过最头疼的情况:现场用了RTU模式,但线缆太长导致帧间隔不准。嗯,这里要注意——RTU对时序要求很苛刻,3.5字符时间的帧间隔是硬指标。ASCII虽然慢,但容错性好,适合无线数传电台这种不稳定链路。

3.2 协议帧结构拆解

咱们先看RTU的帧结构,这是最常用的。一个完整的RTU报文长这样:

| 地址域(1B) | 功能码(1B) | 数据域(NB) | CRC校验(2B) |
| 0x01       | 0x03       | 0x00 0x00 0x00 0x0A | 0xC5 0xCD |

举个例子:上面这条报文的意思是——向地址为1的设备,发送读保持寄存器命令,从地址0开始读10个寄存器。

地址域:0x01~0xF7(247个有效地址),0x00是广播地址。我建议你现场设备地址别用默认的1,容易冲突。

功能码:这是协议的核心,后面细讲。

数据域:根据功能码不同,结构也不同。读操作是起始地址+数量,写操作是地址+数据值。

CRC校验:低字节在前,高字节在后。很多新手在这里栽跟头——计算CRC时别忘了包含地址和功能码。

ASCII模式呢?其实就是把RTU的每个字节拆成两个ASCII字符,再加个冒号开头和回车结尾。举个例子,RTU的0x1A在ASCII里就变成'1'和'A'两个字符。效率低了一半,但肉眼可读。

TCP模式最简单——去掉CRC,加了个MBAP头(7字节)。MBAP头包含事务标识符、协议标识符、长度和单元标识符。说白了,TCP把地址域挪到了MBAP里,校验交给TCP/IP协议栈自己处理。

核心要点:三种模式的报文结构虽然不同,但数据模型完全一致。你在配置网关时,只需要关心功能码和地址映射,底层通信方式由网关自动处理。

3.3 功能码详解

功能码是Modbus的灵魂。我把它分成三类:位操作、字操作和诊断操作。咱们重点看前两类。

功能码 名称 操作对象 说明
0x01 读线圈 DO(数字量输出) 读取开关量输出状态
0x02 读离散输入 DI(数字量输入) 读取开关量输入状态
0x03 读保持寄存器 AO(模拟量输出) 最常用,读写参数
0x04 读输入寄存器 AI(模拟量输入) 只读,采集传感器数据
0x05 写单个线圈 DO 控制单个开关
0x06 写单个寄存器 AO 设置单个参数
0x0F 写多个线圈 DO 批量控制
0x10 写多个寄存器 AO 批量设置参数

我个人最常用的是0x03和0x06。为什么?因为保持寄存器既能读又能写,适合做参数配置。输入寄存器只能读,适合做数据采集。

这里有个坑——我曾经遇到一个温控表,厂家手册说用0x04读温度值,结果死活读不到。后来发现它内部把温度值放在保持寄存器里,得用0x03。所以拿到新设备,先看手册,别想当然。

实战技巧:调试时先用ModScan这类工具发0x03读几个寄存器,看看返回数据是否合理。如果返回异常码0x02(非法数据地址),说明地址范围不对。如果返回0x01(非法功能码),说明设备不支持这个功能码。

3.4 地址映射——最容易被忽视的部分

地址映射,说白了就是搞清楚“PLC里的这个数据,对应Modbus报文里的哪个地址”。很多工程师在这里翻车。

Modbus的地址空间分四类:

  • 线圈(0x区):地址范围00001~09999,对应功能码0x01/0x05/0x0F
  • 离散输入(1x区):地址范围10001~19999,对应功能码0x02
  • 输入寄存器(3x区):地址范围30001~39999,对应功能码0x04
  • 保持寄存器(4x区):地址范围40001~49999,对应功能码0x03/0x06/0x10

注意!这里的地址是PLC的“逻辑地址”,不是Modbus报文里的“协议地址”。报文里是从0开始的偏移量。举个例子:PLC的40001对应报文地址0x0000,40002对应0x0001,以此类推。

我建议你在做网关配置时,统一用“协议地址”(即偏移量)来配置,避免混淆。很多网关配置软件支持两种地址格式,选偏移量模式更不容易出错。

避坑指南:我曾经遇到一个项目,现场工程师把PLC的40001直接填到网关的地址栏里,结果读到的数据全是错的。因为网关期望的是偏移量0,他填了40001,相当于从地址40001开始读,早就超出范围了。记住:逻辑地址=协议地址+起始偏移量。

3.5 实战:多协议网关的Modbus配置

咱们来看一个真实场景。现场有一台西门子S7-200 PLC(Modbus RTU从站),需要把它的温度数据转发到上位机(Modbus TCP主站)。

配置步骤:

  1. 确定数据位置:温度值在PLC的VD100(双字,浮点数),对应Modbus保持寄存器地址40051~40052(因为VD100是第51个双字,占用2个寄存器)。
  2. 配置网关串口:波特率9600,8数据位,无校验,1停止位。从站地址设为1。
  3. 配置网关网口:IP地址192.168.1.100,端口502。
  4. 建立映射表:将Modbus RTU的地址40051(协议地址0x0032)映射到Modbus TCP的地址40051。
  5. 数据格式处理:因为PLC是浮点数,网关需要做字节序转换(大端转小端)。

嗯,这里要注意——字节序问题经常被忽略。西门子PLC默认是大端模式,而很多上位机是小端模式。如果不做转换,读出来的浮点数会变成天文数字。

经验总结:配置多协议网关时,记住三个关键点:地址映射要准确、数据格式要匹配、通信参数要一致。只要这三点不出错,Modbus通信基本稳了。

好了,Modbus协议的核心内容就这些。下一章咱们讲Modbus TCP的具体实现,包括多主站通信和防火墙穿透。有什么问题欢迎交流。