2、Modbus寻址与数据模型:线圈、离散输入、保持寄存器、输入寄存器的地址映射规则
好,咱们接着聊。上一章我们把Modbus的物理层和协议帧结构理清了,这一章我重点讲讲Modbus里最容易让人犯迷糊的东西——寻址与数据模型。
说实话,我刚开始接触Modbus时,也被这四种对象搞得有点晕。线圈、离散输入、保持寄存器、输入寄存器……名字听着都差不多,地址还容易记混。后来在产线上调试一台老式PLC时,因为地址映射搞反了,折腾了整整一个下午。嗯,从那以后我就把这套规则刻在脑子里了。
2.1 四种基本数据对象
Modbus协议定义了四种基本的数据对象。你想想看,这其实就是在模拟一个PLC或者RTU内部的数据存储区。它们分别是:
| 对象类型 | 对象名称 | 数据宽度 | 访问权限 | 物理意义 |
|---|---|---|---|---|
| 离散量输入 | Discrete Input | 1 bit | 只读 | 现场开关、按钮、限位信号 |
| 线圈 | Coil | 1 bit | 读写 | 继电器、指示灯、电磁阀 |
| 输入寄存器 | Input Register | 16 bit | 只读 | 模拟量输入、传感器数值 |
| 保持寄存器 | Holding Register | 16 bit | 读写 | 设定值、PID参数、累计值 |
我个人习惯把这四种对象分成两组来记:位对象和字对象。位对象就是线圈和离散输入,一个字节能塞8个;字对象就是保持寄存器和输入寄存器,一个寄存器占两个字节。
2.2 地址映射规则——别搞混了!
这里有个关键点,也是很多新手栽跟头的地方:Modbus协议中的地址是0-based的,但很多设备手册和组态软件里用的是1-based的地址。
什么意思呢?
在Modbus协议报文里,数据地址是从0开始的。比如你要读第一个线圈,协议里填的地址是0x0000。但你去翻某个变频器的说明书,它告诉你“运行状态”的地址是00001。这个00001就是1-based的地址,对应协议里的地址0。
标准的地址映射规则如下:
| 对象类型 | PLC地址范围(1-based) | 协议地址范围(0-based) | 功能码 |
|---|---|---|---|
| 线圈 | 00001 ~ 09999 | 0x0000 ~ 0x270E | 01(读)、05(写单)、15(写多) |
| 离散输入 | 10001 ~ 19999 | 0x0000 ~ 0x270E | 02(读) |
| 输入寄存器 | 30001 ~ 39999 | 0x0000 ~ 0x270E | 04(读) |
| 保持寄存器 | 40001 ~ 49999 | 0x0000 ~ 0x270E | 03(读)、06(写单)、16(写多) |
你看,协议地址范围其实是一样的,都是0x0000到0x270E(也就是0到9999)。区别在于PLC地址的前缀数字:0代表线圈,1代表离散输入,3代表输入寄存器,4代表保持寄存器。
2.3 实际项目中的地址换算
我在智能工厂项目里,经常要跟不同品牌的设备打交道。西门子的S7-1200、三菱的FX系列、还有各种国产温控器,它们的地址表示方式五花八门。但底层都是Modbus,所以换算方法万变不离其宗。
举个例子:
你拿到一个设备手册,上面写着“当前温度”的地址是30012。那么:
- 先看前缀:3开头,说明是输入寄存器(只读)。
- 去掉前缀:剩下0012,这是1-based地址。
- 换算成协议地址:12 - 1 = 11,也就是0x000B。
- 功能码用04(读输入寄存器)。
再比如,你要控制一个继电器,手册上写线圈地址是00005:
- 前缀0,说明是线圈(可读写)。
- 去掉前缀:剩下0005。
- 协议地址:5 - 1 = 4,即0x0004。
- 功能码用05(写单个线圈)或15(写多个线圈)。
2.4 数据模型在内存中的布局
你可能会问:这四种对象在设备内部是怎么存的?
其实,Modbus协议并没有规定具体的物理存储方式。它只定义了逻辑上的数据模型。也就是说,设备厂商可以自由安排这些数据在内存里的位置,只要保证通过Modbus能正确读写就行。
但实际项目中,大部分设备都是这样做的:
- 线圈和离散输入:通常放在一个连续的bit区域。比如地址0到9999对应线圈,地址10000到19999对应离散输入。但注意,这两个区域是独立的,地址0的线圈和地址0的离散输入不是同一个东西。
- 保持寄存器和输入寄存器:也是独立的16位字区域。地址0的保持寄存器和地址0的输入寄存器,物理上可能是两个不同的内存单元。
我记得有一次,一个同事问我:“为什么我读保持寄存器地址0能读到数据,但读输入寄存器地址0却是0?”
我告诉他:“这很正常。保持寄存器和输入寄存器是两个不同的存储区。保持寄存器0存的是设定值,输入寄存器0存的是实际值。你读的是两个不同的变量。”
2.5 地址映射的常见坑
做Modbus集成,地址映射这块有几个坑,我帮你列出来:
很多组态软件(比如组态王、WinCC)在配置变量时,会让你填“寄存器地址”。它们默认用的是1-based地址。但如果你在代码里直接写Modbus协议,一定要记得减1。
🔴 坑2:功能码用错
读线圈用01,读离散输入用02,读保持寄存器用03,读输入寄存器用04。这四个功能码千万别搞混。我见过有人用03功能码去读离散输入,结果返回的数据全是乱的。
🔴 坑3:数据类型不匹配
保持寄存器是16位的,但很多设备会把32位浮点数或32位整数放在两个连续的寄存器里。比如地址40001和40002组合成一个float。这时候你要注意字节序(Big Endian还是Little Endian)。
🔴 坑4:地址范围超限
标准Modbus的地址范围是0到65535(0x0000到0xFFFF)。但很多设备只实现了0到9999。如果你访问超出范围的地址,设备会返回异常码02(非法数据地址)。
2.6 一个完整的地址映射示例
最后,我拿一个实际项目中的例子来收尾。假设我们要读取一个Modbus温控器的数据,它的手册上给出了这样的地址表:
| 参数名称 | PLC地址 | 数据类型 | 读写属性 |
|---|---|---|---|
| 当前温度 | 30001 | 16位无符号整数 | 只读 |
| 设定温度 | 40001 | 16位无符号整数 | 读写 |
| 运行状态 | 00001 | 1位布尔 | 只读 |
| 启动/停止 | 00002 | 1位布尔 | 读写 |
那么,在Modbus协议层面,我们应该这样操作:
- 读当前温度:功能码04,地址0x0000(30001 - 30001 = 0),读1个寄存器。
- 读设定温度:功能码03,地址0x0000(40001 - 40001 = 0),读1个寄存器。
- 写设定温度:功能码06,地址0x0000,写入1个寄存器。
- 读运行状态:功能码02,地址0x0000(00001 - 00001 = 0),读1个位。
- 写启动/停止:功能码05,地址0x0001(00002 - 00001 = 1),写入1个位。
你看,只要掌握了这个换算规则,不管设备手册怎么写,你都能准确找到对应的数据。
好了,这一章就到这里。下一章我们聊聊Modbus的通信模式——RTU和ASCII,以及它们在实际应用中的选择。到时候我会分享一个我在污水处理厂项目中踩过的坑,保证让你印象深刻。