3. Modbus数据模型:离散量输入、线圈、输入寄存器、保持寄存器的定义与地址映射
好,咱们今天聊点实在的。Modbus协议里最核心、也最容易把人绕晕的,就是它的数据模型。
说白了,Modbus把设备里的数据分成了四个“抽屉”。每个抽屉放什么类型的数据,地址怎么编,都有讲究。我刚开始接触那会儿,也搞混过几次,后来踩了坑才彻底明白。
3.1 四个基本数据对象
Modbus协议定义了四种基本数据对象。你想想看,一个传感器或者PLC,它里面无非就这几类数据:
- 离散量输入(Discrete Input):只读的开关量。比如一个按钮按没按,一个限位开关有没有触发。
- 线圈(Coil):可读可写的开关量。比如控制一个继电器的通断,或者一个指示灯亮不亮。
- 输入寄存器(Input Register):只读的16位数据。比如温度传感器读回来的数值,或者压力变送器的当前值。
- 保持寄存器(Holding Register):可读可写的16位数据。比如设定一个PID的目标值,或者修改一个报警阈值。
我个人习惯,把前两个叫“位数据”,后两个叫“字数据”。这样好记。
核心要点:
- 离散量输入和输入寄存器,设备只能告诉你“是多少”,你不能改。
- 线圈和保持寄存器,你可以读,也可以写。上位机可以控制设备。
3.2 地址映射规则
地址映射这块,是新手最容易出问题的地方。Modbus的地址是5位十进制数,从00001到65535。但不同的数据对象,地址范围不一样。
我记得有一次,一个同事死活读不到一个传感器的数据。他用的地址是30001,但那个传感器其实是保持寄存器,地址应该是40001。嗯,这里要注意,地址前缀其实就代表了数据类型。
| 数据对象 | 地址范围(PLC习惯) | 功能码 | 读写属性 |
|---|---|---|---|
| 离散量输入 | 10001 ~ 19999 | 02 | 只读 |
| 线圈 | 00001 ~ 09999 | 01, 05, 15 | 读写 |
| 输入寄存器 | 30001 ~ 39999 | 04 | 只读 |
| 保持寄存器 | 40001 ~ 49999 | 03, 06, 16 | 读写 |
你可能会问,为什么地址是5位?其实这是PLC厂商的习惯。Modbus协议本身只传一个16位的地址(0~65535),但为了方便记忆,PLC厂家给加了个前缀。比如40001,实际协议里传的是地址0。
我的小技巧:
写代码时,直接用协议地址(0~65535)就好。别被PLC的5位地址搞晕。比如保持寄存器40001,在代码里就是地址0。
3.3 实际项目中的地址规划
我在项目中遇到过这样一个情况:一个设备有20个温度点,10个压力点,还有一堆开关量。怎么规划地址?
我的做法是:
- 保持寄存器从40001开始,连续分配。温度用40001~40020,压力用40021~40030。
- 线圈从00001开始,分配开关量控制。
- 离散量输入从10001开始,分配状态反馈。
这样规划,上位机读数据时,可以用一次功能码03连续读20个寄存器,效率很高。
避坑指南:
我曾经遇到过一台设备,它的保持寄存器地址从40001开始,但实际协议里传的是地址1。也就是说,40001对应协议地址1,而不是0。这种偏移量问题,一定要看设备手册确认。否则你读回来的数据全是错的。
3.4 代码示例:用Python读取不同数据对象
下面我用Python的pymodbus库,演示一下怎么读取这四种数据。
from pymodbus.client import ModbusTcpClient
# 连接设备
client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()
# 1. 读取线圈(地址0,对应PLC地址00001)
result = client.read_coils(0, 10) # 读10个线圈
print("线圈状态:", result.bits)
# 2. 读取离散量输入(地址0,对应PLC地址10001)
result = client.read_discrete_inputs(0, 8) # 读8个离散量
print("离散量输入:", result.bits)
# 3. 读取保持寄存器(地址0,对应PLC地址40001)
result = client.read_holding_registers(0, 5) # 读5个寄存器
print("保持寄存器值:", result.registers)
# 4. 读取输入寄存器(地址0,对应PLC地址30001)
result = client.read_input_registers(0, 3) # 读3个寄存器
print("输入寄存器值:", result.registers)
client.close()
你看,代码里用的地址都是0开始的。这就是协议地址。至于它对应PLC的40001还是30001,那是设备手册的事。
3.5 地址映射的常见误区
最后,我总结几个常见坑:
- 地址偏移搞错:有些设备地址从0开始,有些从1开始。一定要看手册。
- 数据类型搞混:把保持寄存器当输入寄存器读,功能码用错了,设备会报错。
- 连续读取超出范围:比如设备只有10个保持寄存器,你一次读20个,会返回异常。
- 位和字节顺序:16位寄存器里,高低字节的顺序,不同厂家可能不同。我遇到过Modbus和Modbus Plus顺序相反的坑。
嗯,今天就聊这么多。记住这四个抽屉,地址映射别搞错,Modbus数据模型这块你就拿下了。