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主站)。
配置步骤:
- 确定数据位置:温度值在PLC的VD100(双字,浮点数),对应Modbus保持寄存器地址40051~40052(因为VD100是第51个双字,占用2个寄存器)。
- 配置网关串口:波特率9600,8数据位,无校验,1停止位。从站地址设为1。
- 配置网关网口:IP地址192.168.1.100,端口502。
- 建立映射表:将Modbus RTU的地址40051(协议地址0x0032)映射到Modbus TCP的地址40051。
- 数据格式处理:因为PLC是浮点数,网关需要做字节序转换(大端转小端)。
嗯,这里要注意——字节序问题经常被忽略。西门子PLC默认是大端模式,而很多上位机是小端模式。如果不做转换,读出来的浮点数会变成天文数字。
经验总结:配置多协议网关时,记住三个关键点:地址映射要准确、数据格式要匹配、通信参数要一致。只要这三点不出错,Modbus通信基本稳了。
好了,Modbus协议的核心内容就这些。下一章咱们讲Modbus TCP的具体实现,包括多主站通信和防火墙穿透。有什么问题欢迎交流。