3、Modbus地址模型:线圈、离散输入、保持寄存器、输入寄存器的定义与寻址范围

好,咱们今天聊聊Modbus的地址模型。说实话,这玩意儿是Modbus通讯的根基。你如果搞不清楚线圈和寄存器到底是怎么回事,那后面写程序、排查故障,基本就是两眼一抹黑。

我刚开始接触Modbus时,也被这四种对象搞得有点晕。什么线圈、离散输入、保持寄存器、输入寄存器……名字听着都差不多。后来在项目里吃过亏,才真正搞明白它们的区别。嗯,今天我就把这块掰开了揉碎了讲给你听。

3.1 四种基本对象:它们到底是什么?

Modbus协议定义了四种基本数据对象。说白了,就是四种不同类型的“内存区域”。设备里的数据就放在这些区域里,主站通过读写这些区域来获取信息或控制设备。

我个人习惯把这四种对象分成两类:位对象字对象

  • 位对象:一个bit,只有0或1。适合表示开关状态、报警信号。
  • 字对象:16个bit,也就是两个字节。适合表示数值、参数、计数器等。

咱们先看个表格,一目了然:

对象类型 数据宽度 读写属性 典型用途
线圈 (Coil) 1 bit 可读可写 控制继电器、启动/停止信号
离散输入 (Discrete Input) 1 bit 只读 读取按钮状态、限位开关
保持寄存器 (Holding Register) 16 bit 可读可写 设定温度、PID参数、运行频率
输入寄存器 (Input Register) 16 bit 只读 读取电压、电流、温度测量值

你看,表格里写得清清楚楚。但光看表格还不够,咱们得结合实战经验来理解。

3.2 线圈 (Coil) —— 可读可写的“开关”

线圈,功能码是01(读)和05(写单个)/15(写多个)。它代表一个可读可写的二进制输出点。

寻址范围:传统Modbus协议中,线圈的地址范围是 00001 - 09999。但注意,这是PLC时代的“协议地址”。在报文中,实际传输的是 0 - 9998 的偏移地址。

重要提醒:很多新手会混淆“协议地址”和“报文地址”。比如你看到图纸上写“线圈地址00001”,在Modbus报文里,你发送的地址其实是0。这个坑,我当年就踩过。

举个例子。你有一个继电器,想通过Modbus控制它吸合。你可能会这样操作:

# 假设线圈地址是 00001(报文地址为0)
# 功能码 05:写单个线圈
# 数据:0xFF00 表示吸合,0x0000 表示断开

报文:01 05 00 00 FF00 8C 3A
# 01 - 从站地址
# 05 - 功能码
# 00 00 - 线圈地址(偏移地址0)
# FF00 - 吸合命令
# 8C 3A - CRC校验

我在项目中遇到过一件事。有一次调试一个水泵控制系统,PLC那边一直报线圈写入失败。我查了半天,发现是地址搞错了。图纸上写的是“线圈地址00001”,但设备手册里要求报文地址从1开始。你想想看,这种不一致的情况,在工业现场太常见了。所以,拿到设备第一件事,就是确认它的地址映射规则。

3.3 离散输入 (Discrete Input) —— 只读的“开关”

离散输入,功能码是02(读)。它代表一个只读的二进制输入点。说白了,就是只能看,不能改。

寻址范围:协议地址范围是 10001 - 19999。报文偏移地址对应 0 - 9998

这种对象通常用来读取按钮、限位开关、光电传感器等状态。你只能读取它的当前值,不能通过Modbus去改变它。

举个例子:

# 读取离散输入地址 10001(报文地址0)
# 功能码 02

报文:01 02 00 00 00 01 79 CB
# 01 - 从站地址
# 02 - 功能码
# 00 00 - 起始地址
# 00 01 - 读取1个位
# 79 CB - CRC

嗯,这里要注意。离散输入和线圈虽然都是位对象,但它们的本质区别在于:线圈是输出,离散输入是输入。你想想看,一个控制继电器,一个读取传感器,用途完全不同。

3.4 保持寄存器 (Holding Register) —— 最常用的“参数区”

保持寄存器,功能码是03(读)和06(写单个)/16(写多个)。这是Modbus里最常用、最灵活的对象。它可读可写,16位宽,可以存放各种参数。

寻址范围:协议地址范围是 40001 - 49999。报文偏移地址对应 0 - 9998

我个人习惯把保持寄存器看作是设备的“参数配置区”。比如变频器的运行频率、PID控制器的设定值、温控仪的目标温度,都放在这里。

实战技巧:很多设备支持32位数据(比如浮点数、长整型)。这时需要占用两个连续的保持寄存器。比如地址40001和40002组合成一个32位值。我曾经调试过一个流量计,它的累积流量就是32位浮点数,分布在两个寄存器里。如果你只读一个寄存器,那数据肯定是错的。

看个例子:

# 读取保持寄存器 40001(报文地址0)
# 功能码 03

报文:01 03 00 00 00 01 84 0A
# 01 - 从站地址
# 03 - 功能码
# 00 00 - 起始地址
# 00 01 - 读取1个寄存器
# 84 0A - CRC

3.5 输入寄存器 (Input Register) —— 只读的“测量值区”

输入寄存器,功能码是04(读)。它和保持寄存器一样是16位宽,但它是只读的。通常用来存放传感器采集到的实时数据。

寻址范围:协议地址范围是 30001 - 39999。报文偏移地址对应 0 - 9998

比如温度传感器测到的当前温度、压力变送器测到的压力值、电能表测到的电压电流,都放在输入寄存器里。你只能读,不能写。

# 读取输入寄存器 30001(报文地址0)
# 功能码 04

报文:01 04 00 00 00 01 31 CA
# 01 - 从站地址
# 04 - 功能码
# 00 00 - 起始地址
# 00 01 - 读取1个寄存器
# 31 CA - CRC

3.6 寻址范围总结与避坑指南

咱们把寻址范围再总结一下,方便你记忆:

对象类型 协议地址范围 报文偏移地址 功能码
线圈 00001 - 09999 0 - 9998 01, 05, 15
离散输入 10001 - 19999 0 - 9998 02
输入寄存器 30001 - 39999 0 - 9998 04
保持寄存器 40001 - 49999 0 - 9998 03, 06, 16

避坑指南:我曾经在调试一个进口温控仪时,发现它的保持寄存器地址居然从40000开始,而不是标准的40001。当时我按照标准地址去读,死活读不到数据。后来看了手册才发现,这家厂商的地址映射是“0-based”的。所以,永远不要假设所有设备都遵循同一套地址规则。拿到设备,先看手册,再动手。

另外,还有一个常见的坑:地址重叠。有些设备会把同一个物理数据映射到不同的地址区域。比如一个温度值,既可以通过输入寄存器30001读取,也可以通过保持寄存器40001读取。但两者的读写属性不同。你想想看,如果你需要修改这个温度值,那只能通过保持寄存器去写,输入寄存器是只读的。

好了,关于Modbus的地址模型,今天就讲到这里。这四种对象,说白了就是四种不同的数据容器。搞清楚了它们的定义和寻址范围,你写通讯程序时就不会再犯低级错误了。下一节,咱们聊聊功能码的具体用法,到时候我会结合代码实例,带你一步步调试。