3、Modbus地址映射与数据模型:线圈、离散输入、保持寄存器、输入寄存器的区别与地址范围

好,咱们今天来聊聊Modbus里最基础、也最容易搞混的一块——地址映射和数据模型。

说实话,我刚开始接触Modbus那会儿,也被这四个东西绕得晕头转向。线圈、离散输入、保持寄存器、输入寄存器……名字长得像,功能却完全不同。你想想看,要是地址映射搞错了,设备压根不理你,或者更糟——你写了个数据,结果把不该改的参数给改了。嗯,我在现场就见过这种事故,一台变频器直接被写成了出厂设置,生产线停了两个小时。

所以,咱们今天把这四个东西彻底讲透。我保证,看完这一章,你脑子里会非常清晰。

3.1 四种数据对象的本质区别

Modbus协议定义了四种基本的数据对象。说白了,就是四种不同类型的“内存区域”。它们分别是:

  • 线圈(Coil):可读可写,1位(bit),对应物理上的开关量输出。比如控制继电器、指示灯、电机启停。
  • 离散输入(Discrete Input):只读,1位(bit),对应物理上的开关量输入。比如读取按钮状态、限位开关、传感器信号。
  • 保持寄存器(Holding Register):可读可写,16位(word),用于存储参数、设定值、累计值等。比如设定PID目标值、读取当前温度。
  • 输入寄存器(Input Register):只读,16位(word),用于读取模拟量输入或传感器数据。比如读取4-20mA信号、电压值。

我个人的习惯是,把线圈和离散输入想象成“开关”,把保持寄存器和输入寄存器想象成“仪表盘上的数字”。这样记起来特别方便。

核心记忆口诀:

  • 线圈 = 输出开关(可读可写)
  • 离散输入 = 输入开关(只读)
  • 保持寄存器 = 可读写的数值
  • 输入寄存器 = 只读的数值

3.2 地址范围与功能码对应关系

每种数据对象都有自己独立的地址空间。Modbus协议规定,地址从0开始编号,但实际使用时,我们通常用“前缀+偏移量”的方式来表示。比如线圈的地址范围是00001~09999,但实际报文里发送的是0~9998。

这里我列个表,你一看就明白:

数据对象 位/字 读写属性 PLC地址范围(常用) 协议地址范围(0-based) 对应功能码
线圈(Coil) 1位 可读可写 00001 ~ 09999 0 ~ 9998 01(读)、05(写单个)、15(写多个)
离散输入(Discrete Input) 1位 只读 10001 ~ 19999 0 ~ 9998 02(读)
保持寄存器(Holding Register) 16位 可读可写 40001 ~ 49999 0 ~ 9998 03(读)、06(写单个)、16(写多个)
输入寄存器(Input Register) 16位 只读 30001 ~ 39999 0 ~ 9998 04(读)

⚠️ 注意: 不同厂家对地址范围的实现可能略有差异。有些PLC的地址范围会扩展到5位甚至6位(比如400001~465535)。我曾经遇到过一台老款温控器,它的保持寄存器地址从40001开始,但实际报文里地址是0。如果你按40001去发报文,它根本不响应。所以,一定要看设备手册里的“协议地址”说明,而不是只看PLC标签上的地址。

3.3 地址映射的底层逻辑

你可能会问:为什么地址要分这么多种?直接统一用寄存器不行吗?

嗯,这个问题问得好。其实,Modbus协议设计之初,就是为了适应不同种类的工业设备。有些设备只有开关量(比如继电器),有些设备只有模拟量(比如温度变送器)。如果统一用一种数据模型,反而会造成浪费和混乱。

举个例子:

  • 一个按钮开关,只需要1位数据。如果用16位的寄存器来存,就浪费了15位。
  • 一个温度值,需要16位精度。如果用线圈来存,你得用16个线圈拼成一个数值,读写起来非常麻烦。

所以,Modbus用四种数据模型,各司其职,效率最高。

我个人在项目中,一般这样分配:

  • 所有开关量输出(继电器、电磁阀)→ 映射到线圈区
  • 所有开关量输入(按钮、限位)→ 映射到离散输入区
  • 所有模拟量输出(变频器频率、阀门开度)→ 映射到保持寄存器
  • 所有模拟量输入(温度、压力、流量)→ 映射到输入寄存器

这样做的好处是,程序结构清晰,别人一看你的地址分配就知道这个点是干什么的。

3.4 避坑指南:地址偏移的陷阱

这里我要重点讲一个坑,我当年刚入行时踩过,后来带徒弟时也发现很多人会犯——地址偏移量

比如,你想读取保持寄存器地址40001。在Modbus报文里,你发送的功能码是03,地址是0。因为协议地址是从0开始的,而PLC地址是从1开始的。所以:

PLC地址 = 协议地址 + 1
协议地址 = PLC地址 - 1

举个例子:

  • 你要读40001 → 报文里地址填0
  • 你要读40002 → 报文里地址填1
  • 你要读40100 → 报文里地址填99

我曾经在调试一个项目时,用上位机软件读一个温控器的数据。手册上写温度参数在地址40010。我直接在软件里填40010,结果读出来全是0。折腾了半小时,后来才发现,软件里填的是协议地址,应该填9(因为40010 - 40001 = 9)。

💡 我的建议: 在写程序或配置组态软件时,先搞清楚你的工具用的是“PLC地址”还是“协议地址”。如果是PLC地址,直接填40001这种;如果是协议地址,记得减1。不确定的话,先读一个已知值的地址验证一下。

3.5 数据模型与地址映射的SVG结构图

下面这张图,我把四种数据模型和它们的地址映射关系画出来了。你可以保存下来,以后写程序时对照着看。

Modbus 四种数据模型与地址映射 线圈 (Coil) 可读可写 | 1位 PLC地址: 00001~09999 协议地址: 0~9998 功能码: 01, 05, 15 例: 继电器、指示灯 离散输入 只读 | 1位 PLC地址: 10001~19999 协议地址: 0~9998 功能码: 02 例: 按钮、限位开关 保持寄存器 可读可写 | 16位 PLC地址: 40001~49999 协议地址: 0~9998 功能码: 03, 06, 16 例: 设定值、参数 输入寄存器 只读 | 16位 PLC地址: 30001~39999 协议地址: 0~9998 功能码: 04 例: 温度、压力值 地址映射规则:PLC地址 = 协议地址 + 1 例如:PLC地址40001 → 协议地址0 ⚠️ 常见错误:地址偏移搞错 直接使用PLC地址作为协议地址发送 → 设备无响应 正确做法:协议地址 = PLC地址 - 起始地址(如40001-1=0)

3.6 实战中的地址规划建议

最后,我分享一点实战经验。在做项目时,地址规划一定要提前做好,不然后期改起来非常痛苦。

我一般会这样做:

  1. 先列设备清单:把现场所有需要通信的设备列出来,包括每个设备的输入输出点数、模拟量通道数。
  2. 分配地址段:比如线圈从00001开始,给第一台设备分配10个线圈(00001~00010),第二台设备分配20个线圈(00011~00030),以此类推。保持寄存器也一样,从40001开始分段。
  3. 留出余量:每个设备至少多留20%的地址空间,方便后期扩展。我曾经因为没留余量,后来加了一个传感器,结果地址不够用,只能重新分配,所有程序都得改,那叫一个崩溃。
  4. 做好文档:把地址分配表写在项目文档里,或者直接写在PLC程序注释里。这样别人接手你的项目,一看就懂。

💡 小技巧: 如果你用的是西门子S7-1200/1500或者三菱FX系列PLC,它们内部有专门的Modbus指令库,地址映射已经帮你做好了。你只需要在指令里填PLC地址就行,不用手动计算偏移量。但如果你是用C#、Python自己写Modbus库,那就得小心处理地址偏移了。

好了,关于Modbus地址映射和数据模型,我就讲到这里。这四个东西其实不难,关键是理解它们的本质区别,以及记住地址偏移的规则。你只要记住:线圈和离散输入是开关量,保持寄存器和输入寄存器是数值量;PLC地址比协议地址大1。嗯,这样就够了。


专注资料整理