3、寄存器地址映射:线圈、离散输入、保持寄存器、输入寄存器的区别与实战应用

好,咱们今天聊点实在的。

Modbus 协议里,最绕不开的就是那四种寄存器。线圈、离散输入、保持寄存器、输入寄存器。名字听着挺唬人,其实说白了,就是四种不同的“数据箱子”。

我刚入行那会儿,也经常搞混。尤其是现场调试时,地址写错一位,整个设备就不动了。那种尴尬,经历过的人都懂。所以今天,我带你把这四个箱子彻底理清楚。

3.1 四种寄存器的本质区别

先看一张表,这是我个人习惯的总结方式,一目了然。

类型 功能码(读/写) 数据宽度 读写属性 典型应用
线圈(Coil) 01 / 05、15 1 bit 可读可写 启停信号、阀门开关
离散输入(Discrete Input) 02 1 bit 只读 限位开关、急停状态
保持寄存器(Holding Register) 03 / 06、16 16 bit 可读可写 速度设定、PID参数
输入寄存器(Input Register) 04 16 bit 只读 温度、压力传感器值

你想想看,为什么要有只读和可读可写的区分?

原因很简单:安全。离散输入和输入寄存器,通常来自传感器或开关。这些数据是现场的真实状态,你只能看,不能改。要是谁都能写,那现场设备就乱套了。

核心记忆法:
“线圈”和“保持寄存器”是双向的——你能控制它。
“离散输入”和“输入寄存器”是单向的——你只能读它。

3.2 地址映射的坑与实战

嗯,这里要注意。Modbus 的地址映射,不同厂家有不同玩法。我见过最坑的一次,是某国产 PLC 的线圈地址从 0 开始,而触摸屏的地址从 1 开始。结果就是,你写 0,它读 1,永远对不上。

我个人习惯,在项目一开始就定好地址映射表。比如这样:

// 机器人远程监控项目地址映射表(示例)
// 线圈(0x 区)
0x0001: 电机启动命令 (写)
0x0002: 电机停止命令 (写)
0x0003: 急停复位 (写)

// 离散输入(1x 区)
1x0001: 电机运行状态 (读)
1x0002: 急停按下状态 (读)
1x0003: 限位开关触发 (读)

// 保持寄存器(4x 区)
4x0001: 目标速度 (读/写)  单位: rpm
4x0002: 目标加速度 (读/写) 单位: mm/s²
4x0010: PID 比例系数 (读/写)

// 输入寄存器(3x 区)
3x0001: 当前速度 (读) 单位: rpm
3x0002: 电机温度 (读) 单位: 0.1℃
3x0003: 累计运行时间 (读) 单位: 秒

你看,这样一列,谁都不会搞错。而且我建议,注释里一定要写单位。不然半年后你自己回来维护,看着“4x0001”这个地址,你根本想不起来它代表什么。

3.3 实战:用 Python 读取四种寄存器

光说不练假把式。咱们直接上代码。我用的是 pymodbus 库,这是目前最流行的 Modbus 库之一。

from pymodbus.client import ModbusTcpClient

# 连接机器人控制器
client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()

# 1. 读取线圈(功能码01)
# 读取地址0x0001开始的3个线圈
coil_status = client.read_coils(0x0001, 3)
print(f"线圈状态: {coil_status.bits}")

# 2. 读取离散输入(功能码02)
# 读取地址1x0001开始的2个离散输入
discrete_inputs = client.read_discrete_inputs(0x0001, 2)
print(f"离散输入: {discrete_inputs.bits}")

# 3. 读取保持寄存器(功能码03)
# 读取地址4x0001开始的2个保持寄存器
holding_regs = client.read_holding_registers(0x0001, 2)
print(f"保持寄存器: {holding_regs.registers}")

# 4. 读取输入寄存器(功能码04)
# 读取地址3x0001开始的3个输入寄存器
input_regs = client.read_input_registers(0x0001, 3)
print(f"输入寄存器: {input_regs.registers}")

# 写线圈(功能码05)
client.write_coil(0x0001, True)  # 启动电机

# 写保持寄存器(功能码06)
client.write_register(0x0001, 1500)  # 设定目标速度1500rpm

client.close()
小提示:
注意看,读线圈和读离散输入返回的都是 bits,而读保持寄存器和输入寄存器返回的是 registers。这是库的设计,但也是很多新手容易忽略的地方。

3.4 避坑指南:我曾经踩过的雷

我曾经在一个汽车焊装线项目里,遇到过一个问题。机器人报“通信超时”,但网络明明是通的。查了半天,发现是保持寄存器的地址写错了。

怎么回事呢?

原来,那个机器人控制器的保持寄存器地址,不是从 0 开始的,而是从 40001 开始。这是 Modbus 的“PLC 地址”表示法。比如 40001 对应地址 0,40002 对应地址 1,以此类推。

所以,当你用 read_holding_registers(0, 1) 时,实际上读的是 40001。如果你直接写 read_holding_registers(40001, 1),那就错了,因为库会把 40001 当成地址偏移量,结果读到了天上去了。

警告:
一定要搞清楚你的设备使用的是“协议地址”还是“PLC 地址”。
协议地址:从 0 开始。
PLC 地址:从 1 开始,且前面带类型标识(如 40001)。
两者差 1,差之毫厘,谬以千里。

3.5 总结一下

四种寄存器,其实就两个维度:

  • 位 vs 字:线圈和离散输入是位(1 bit),保持寄存器和输入寄存器是字(16 bit)。
  • 读 vs 写:线圈和保持寄存器可读可写,离散输入和输入寄存器只读。

你想想看,是不是很简单?

在实际项目中,我建议你先把地址映射表画出来,再写代码。这样能省掉 80% 的调试时间。嗯,今天就聊到这儿,下一章咱们聊聊 Modbus 的异常处理,那才是真正考验工程师功底的地方。