3. Modbus地址模型:四种数据对象的区别与寻址

说实话,Modbus协议里最容易让人懵圈的就是这四种数据对象。我刚入行那会儿,也搞混过线圈和离散输入的区别。后来在项目里吃过亏,才真正搞明白。

这四种数据对象分别是:线圈(Coil)、离散输入(Discrete Input)、保持寄存器(Holding Register)、输入寄存器(Input Register)。它们各自有不同的用途、访问权限和寻址方式。

3.1 四种数据对象的核心区别

我们先从最直观的角度来看。你想想看,PLC或者RTU里的数据,无非就是两类:开关量(位)模拟量(字)。Modbus把这四类分得清清楚楚。

数据对象 数据宽度 访问权限 物理意义 典型应用
线圈(Coil) 1位(0或1) 可读可写 数字量输出 继电器、指示灯、电机启停
离散输入(Discrete Input) 1位(0或1) 只读 数字量输入 按钮、限位开关、传感器状态
保持寄存器(Holding Register) 16位(0~65535) 可读可写 模拟量输出/参数 设定值、PID参数、变频器频率
输入寄存器(Input Register) 16位(0~65535) 只读 模拟量输入 温度、压力、流量采集值

嗯,这里要注意:线圈和离散输入都是位数据,但线圈是输出,离散输入是输入。 说白了,线圈是你主动去控制的,离散输入是外部设备告诉你的状态。

核心记忆法:

  • 带「线圈」的 → 可读可写(你能控制它)
  • 带「输入」的 → 只读(你只能看,不能改)
  • 带「保持」的 → 可读可写(参数可以存着)
  • 带「离散」的 → 位数据(只有0和1)

3.2 寻址方式:从PLC地址到协议地址

这里有个坑,我当年踩过。Modbus的寻址方式,不同厂家、不同软件,定义不一样。最常见的两种:PLC地址协议地址

PLC地址(也叫逻辑地址)是上位机或HMI里看到的地址,比如%MW100、%QX0.0。而协议地址是Modbus报文里实际传输的地址,范围是0~65535。

举个例子:

  • 保持寄存器PLC地址:40001 → 协议地址:0x0000
  • 保持寄存器PLC地址:40002 → 协议地址:0x0001
  • 线圈PLC地址:00001 → 协议地址:0x0000

为什么会这样?因为Modbus协议规定,地址从0开始编号。但PLC厂家为了区分数据类型,加上了前缀。比如40001里的「4」代表保持寄存器,「0001」是序号。实际报文里只传「0000」。

避坑指南: 我曾经在项目里直接用PLC地址去发Modbus报文,结果从站死活没反应。后来才发现,协议地址要减1。比如你要读40001,报文里地址字段要填0x0000,不是0x0001。

3.3 功能码映射关系

每种数据对象对应一组功能码。我习惯把这看成是「操作指令」。你发什么功能码,从站就知道你要操作哪类数据。

数据对象 读功能码 写功能码 功能码说明
线圈 0x01 0x05(写单个)、0x0F(写多个) 读取/写入线圈状态
离散输入 0x02 只读,不能写
保持寄存器 0x03 0x06(写单个)、0x10(写多个) 读取/写入寄存器值
输入寄存器 0x04 只读,不能写

我个人习惯把功能码记成:

  • 0x01:读线圈(像查开关状态)
  • 0x02:读离散输入(像查传感器信号)
  • 0x03:读保持寄存器(像读参数)
  • 0x04:读输入寄存器(像读采集值)
  • 0x05:写单个线圈(像按一个按钮)
  • 0x06:写单个保持寄存器(像设一个参数)
  • 0x0F:写多个线圈(像批量控制)
  • 0x10:写多个保持寄存器(像批量设参数)

小技巧: 如果你记不住功能码,可以这样想:奇数功能码(0x01、0x03、0x05、0x0F)都是操作「可写」对象的,偶数功能码(0x02、0x04)都是操作「只读」对象的。0x06和0x10虽然是偶数,但它们是写操作,属于例外。

3.4 实际项目中的地址规划

我在做大型DCS项目时,遇到过地址冲突的问题。两台设备用了相同的保持寄存器地址,结果数据互相覆盖。后来我总结了一套地址规划原则:

  1. 区分设备类型:每个从站分配独立的地址段。比如从站1用0~99,从站2用100~199。
  2. 区分数据类型:线圈、离散输入、保持寄存器、输入寄存器各自独立编址。它们虽然地址号可能相同,但功能码不同,不会冲突。
  3. 预留扩展空间:每个地址段末尾留10~20个空地址,方便后期增加点位。
  4. 统一命名规范:比如保持寄存器用HR_前缀,线圈用COIL_前缀,方便代码维护。

举个例子,一个温控系统的地址规划:

// 保持寄存器(可读可写)
HR_100: 温度设定值(单位:0.1℃)
HR_101: PID比例系数
HR_102: PID积分时间

// 输入寄存器(只读)
IR_200: 当前温度值(单位:0.1℃)
IR_201: 当前湿度值(单位:0.1%)

// 线圈(可读可写)
COIL_300: 加热器启停(1=启动,0=停止)
COIL_301: 报警复位

// 离散输入(只读)
DI_400: 加热器运行状态(1=运行,0=停止)
DI_401: 超温报警信号(1=报警)

3.5 知识体系结构图

下面这张图,是我自己画的知识结构。你看完应该能一目了然。

Modbus地址模型知识体系 线圈(Coil) 可读可写 | 1位 离散输入(DI) 只读 | 1位 保持寄存器(HR) 可读可写 | 16位 输入寄存器(IR) 只读 | 16位 功能码映射 读:0x01 写:0x05/0x0F 读:0x02 写:无 读:0x03 写:0x06/0x10 读:0x04 写:无 寻址方式 PLC地址:40001 → 协议地址:0x0000 (地址减1原则) PLC地址:00001 → 协议地址:0x0000 (线圈地址同理) 实际应用建议 ① 区分设备类型分配地址段 ② 四种数据独立编址 ③ 预留扩展空间 ④ 统一命名规范 避坑:协议地址从0开始,PLC地址从1开始,两者差1!

3.6 避坑总结

最后,我把自己这些年踩过的坑,总结成几条:

  • 地址偏移问题:协议地址和PLC地址差1,这是最常见的坑。写代码时一定要确认你用的是哪种地址。
  • 数据类型混淆:有人把保持寄存器当输入寄存器用,结果数据写不进去。记住:保持寄存器可读可写,输入寄存器只读。
  • 功能码用错:读离散输入用了0x01,从站会返回异常。功能码必须和数据对象匹配。
  • 批量操作边界:写多个线圈或寄存器时,注意地址范围不能跨段。比如从地址0开始写10个线圈,地址9必须在同一个从站内。

嗯,这些就是Modbus地址模型的核心内容。搞懂了这四种数据对象,你写Modbus程序时就不会再懵了。


专注资料整理