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用0~99,从站2用100~199。
- 区分数据类型:线圈、离散输入、保持寄存器、输入寄存器各自独立编址。它们虽然地址号可能相同,但功能码不同,不会冲突。
- 预留扩展空间:每个地址段末尾留10~20个空地址,方便后期增加点位。
- 统一命名规范:比如保持寄存器用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 知识体系结构图
下面这张图,是我自己画的知识结构。你看完应该能一目了然。
3.6 避坑总结
最后,我把自己这些年踩过的坑,总结成几条:
- 地址偏移问题:协议地址和PLC地址差1,这是最常见的坑。写代码时一定要确认你用的是哪种地址。
- 数据类型混淆:有人把保持寄存器当输入寄存器用,结果数据写不进去。记住:保持寄存器可读可写,输入寄存器只读。
- 功能码用错:读离散输入用了0x01,从站会返回异常。功能码必须和数据对象匹配。
- 批量操作边界:写多个线圈或寄存器时,注意地址范围不能跨段。比如从地址0开始写10个线圈,地址9必须在同一个从站内。
嗯,这些就是Modbus地址模型的核心内容。搞懂了这四种数据对象,你写Modbus程序时就不会再懵了。