如何通过pymodbus的ModbusSerialClient批量读取多个保持寄存器?
解决Pymodbus批量读取保持寄存器的Illegal Address错误
你的问题我很熟悉——用Pymodbus的ModbusSerialClient逐个读寄存器没问题,但批量读就触发Illegal Address错误,核心问题出在两个地方:对read_holding_registers参数的误解,以及尝试访问设备无效的寄存器地址。
先分析你之前代码的问题
- 参数混淆:
read_holding_registers的第二个参数是要读取的寄存器总数,不是地址跨度。你用add_max - add_min计算的是地址差,不是实际需要的寄存器数量。比如从3860到3926,地址差是66,但如果要覆盖到3926开始的2个寄存器(到3927),实际需要读68个寄存器(3927-3860+1)。 - 无效地址访问:你的寄存器列表是分散的(3900,3902,3904...),直接读取从最小到最大地址的连续范围,会包含很多设备未定义的寄存器地址(比如3862到3899、3901、3903等),设备检测到无效地址就会返回Illegal Address错误。
- 可能的单次读取超限:很多Modbus设备限制单次读取的寄存器数量(比如最多125个,有些设备更少),如果你的请求超出这个上限也会报错。
解决方案:分块读取连续有效寄存器
既然你的目标是提升性能,最合理的做法是把需要的寄存器整理成连续的有效块,每个块单独读取——这样既能减少通信次数(比逐个读快),又不会访问无效地址。
比如你的需求是读取这些起始地址的2个寄存器:3860、3900、3902、3904、3906、3926。可以把它们合并成3个连续块:
- 3860-3861(起始3860,读2个)
- 3900-3907(起始3900,读8个,覆盖3900、3902、3904、3906各自的2个寄存器)
- 3926-3927(起始3926,读2个)
对应的代码实现:
from pymodbus.client import ModbusSerialClient # 定义要读取的连续寄存器块(起始地址,读取数量) register_blocks = [ (3860, 2), (3900, 8), (3926, 2) ] # 初始化客户端 client = ModbusSerialClient( method='rtu', port='/dev/ttyUSB0', timeout=1, parity='N', baudrate=19200, unit=1 ) client.connect() # 存储所有寄存器结果,键为寄存器地址,值为对应值 all_registers = {} for start_addr, count in register_blocks: response = client.read_holding_registers(start_addr, count, unit=0x01) if not response.isError(): # 遍历读取到的寄存器,存入字典 for offset in range(count): reg_addr = start_addr + offset all_registers[reg_addr] = response.registers[offset] else: print(f"读取块 [{start_addr}, {start_addr+count-1}] 失败: {response}") client.close() # 提取你需要的起始地址对应的寄存器值 target_starts = [3900, 3902, 3904, 3906, 3926, 3860] for start in target_starts: val1 = all_registers.get(start) val2 = all_registers.get(start + 1) print(f"起始地址 {start} 的两个寄存器值: {val1}, {val2}")
如果设备允许读取全范围的替代方案
如果你确认设备支持读取从3860到3927的所有寄存器,也可以尝试一次性读取,但要计算正确的寄存器数量:
client = ModbusSerialClient(method='rtu', port='/dev/ttyUSB0', timeout=1, parity='N', baudrate=19200, unit=1) client.connect() min_start = 3860 max_end = 3926 + 1 # 最后一个起始地址读2个,结束地址是3927 total_regs = max_end - min_start + 1 # 计算总寄存器数量 response = client.read_holding_registers(min_start, total_regs, unit=0x01) if not response.isError(): target_starts = [3900, 3902, 3904, 3906, 3926, 3860] for start in target_starts: # 计算当前起始地址在结果中的索引 idx = start - min_start print(f"起始地址 {start} 的两个寄存器值: {response.registers[idx]}, {response.registers[idx+1]}") else: print(f"全范围读取失败: {response}")
注意:这个方案只有在设备所有中间地址都是有效寄存器时才会成功,否则还是会触发Illegal Address错误。
额外建议
- 一定要查看设备的Modbus手册,确认:
- 有效的保持寄存器地址范围
- 设备允许的单次最大读取寄存器数量
- 尽量优先选择分块读取的方式,既安全又能保证性能提升。
内容的提问来源于stack exchange,提问作者Hardik Sondagar
相关产品推荐
相关产品推荐

