在工业自动化和设备通信领域,Modbus 协议几乎是无法绕开的基础协议——它发展时间长、应用范围广、生态完善、实现门槛低,至今仍然是 PLC、传感器、变频器、驱动器等设备之间通信的“通用语言”。而 PyModbus 作为 Python 生态中非常常用的 Modbus 通讯库,支持 RTU(串行通信)、ASCII 以及 TCP/IP 三种主流模式,基本能够覆盖绝大多数工业现场的 Modbus 设备对接需求。下面就从最基础的连接建立开始,系统梳理一遍使用 pymodbus 模块实现 Modbus 通讯的完整流程。

1. 建立通讯
要让程序与 Modbus 设备正常交互,第一步就是先建立连接。PyModbus 提供的客户端接口比较直观,下面以 Modbus TCP 通讯为例:
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('localhost', port=502)
client.connect()
如果目标设备是串口通信设备,例如 RS232 或 RS485,则可以改用ModbusSerialClient,并配置串口号、波特率、校验位、停止位等参数。连接建立成功后,就可以继续执行寄存器读取、写入等 Modbus 数据交互操作。
2. 读取数据
2.1 读取寄存器
在 Modbus 协议中,常见的数据区域主要包括保持寄存器(Holding Register)和输入寄存器(Input Register)。其中保持寄存器支持读写,输入寄存器通常为只读。下面先看一个读取保持寄存器的示例:
response = client.read_holding_registers(address=1, count=10, unit=1)
if not response.isError():
print("Register Values: ", response.registers)
else:
print("Failed to read registers")
这里address=1表示起始寄存器地址(需要特别注意:很多 Modbus 设备的地址映射从 0 开始,也有部分设备手册会按 1 开始标注,是否存在地址偏移一定要以设备说明书为准),count=10表示连续读取 10 个寄存器,unit=1表示设备 ID,也就是常说的从站地址。
另外还有一个版本兼容细节需要注意:在某些 PyModbus 版本中,unit参数已经调整为sla ve。如果你当前使用的是较新的版本,写unit通常依然可以正常运行,因为库本身做了兼容处理;不过为了避免后续升级带来问题,最好还是先查看当前版本文档,确认实际参数名称,减少踩坑概率。
3. 写入数据
3.1 写入单个寄存器
当需要向设备写入单个寄存器的值时,可以使用write_register方法:
write_response = client.write_register(address=1, value=25, unit=1)
if not write_response.isError():
print("Written successfully")
else:
print("Failed to write register")
这里的参数含义与读取寄存器时一致,分别是寄存器地址、写入值和设备 ID。是否写入成功,同样可以通过isError()来判断返回结果。
3.2 写入多个寄存器
如果业务场景中需要一次性写入多个连续寄存器,那么可以使用write_registers方法(注意函数名末尾多了一个 s):
values = [20, 40, 60, 80, 100]
write_response = client.write_registers(address=1, values=values, unit=1)
if not write_response.isError():
print("Multiple registers written successfully")
else:
print("Failed to write multiple registers")
这里传入的是一个列表,程序会从address指定的起始地址开始,按顺序把列表中的值依次写入对应的连续寄存器中。这种方式在批量下发参数、配置设备时非常常见。
4. 处理异常
工业现场的通信环境通常没有想象中那么稳定——线路干扰、设备掉线、地址配置错误、帧校验异常等情况都可能随时出现。因此,更稳妥的做法是使用try-except来捕获 Modbus 通讯异常:
from pymodbus.exceptions import ModbusException
try:
# 尝试执行Modbus操作
response = client.read_holding_registers(address=1, count=10, unit=1)
except ModbusException as ex:
print("An error occurred:", str(ex))
这样即使读取失败或写入异常,程序也不会直接中断退出,至少可以先记录错误日志,或者进一步做重试、告警、断线重连等处理,从而提高整个 Modbus 通讯程序的稳定性和可靠性。
5. 数据类型转换
Modbus 寄存器本质上通常以 16 位无符号整数(UInt16)的形式存储数据,但在真实项目中,经常还需要解析浮点数、32 位整数、有符号整数等业务数据。这时候就需要借助 Python 的struct模块来完成寄存器数据转换。下面的代码示例展示了一组常见的转换方法,实际开发中通常建议封装成工具类或公共函数,便于复用:
def _convert_to_float(self, registers):
if len(registers) != 2:
print("Invalid register length for float")
return None
combined = (registers[0] << 16) + registers[1]
return struct.unpack(">f", combined.to_bytes(4, byteorder="big"))[0]
def _convert_to_int32(self, registers):
if len(registers) != 2:
print("Invalid register length for int32")
return None
combined = (registers[0] << 16) + registers[1]
return struct.unpack(">i", combined.to_bytes(4, byteorder="big"))[0]
def _convert_to_uint32(self, registers):
if len(registers) != 2:
print("Invalid register length for uint32")
return None
combined = (registers[0] << 16) + registers[1]
return struct.unpack(">I", combined.to_bytes(4, byteorder="big"))[0]
def _convert_to_int16(self, registers):
if len(registers) != 1:
print("Invalid register length for int16")
return None
return struct.unpack(">h", struct.pack(">H", registers[0]))[0]
def _convert_to_uint16(self, registers):
if len(registers) != 1:
print("Invalid register length for uint16")
return None
return struct.unpack(">H", struct.pack(">H", registers[0]))[0]
除了读取后的解析,写入寄存器前的数据反向转换同样很关键:
def _int32_to_registers(self, value):
packed = struct.pack(">i", value)
high_register, low_register = struct.unpack(">HH", packed)
return [high_register, low_register]
def _uint32_to_registers(self, value):
packed = struct.pack(">I", value)
high_register, low_register = struct.unpack(">HH", packed)
return [high_register, low_register]
def _uint16_to_register(self, value):
packed = struct.pack(">H", value)
register = struct.unpack(">H", packed)[0]
return [register]
def _int16_to_register(self, value):
packed = struct.pack(">h", value)
register = struct.unpack(">h", packed)[0]
return [register]
这里使用的是大端序(>),因为很多 Modbus 设备默认采用大端字节序进行数据传输。如果你的 PLC、仪表或其他从站设备采用的是小端序,那么记得把格式前缀改成<,否则解析出来的数据很可能不正确。
总结
总体来看,PyModbus 已经把 Modbus 通讯涉及的很多底层细节封装得比较完善,无论是建立连接、读取保持寄存器、写入寄存器、异常处理,还是数据类型转换,都可以通过较少的 Python 代码快速实现。掌握这些核心操作之后,就能更高效地把 Python 与 PLC、传感器、工业设备连接起来,应用到设备控制、数据采集、远程监控、自动化测试等场景中。需要注意的是,实际项目中的难点往往不在 pymodbus 库本身,而更多集中在设备文档细节,例如寄存器地址偏移、字节序定义、功能码限制、读写权限以及厂商私有协议扩展等。想写出真正稳定可靠的 Modbus 通讯程序,最有效的方法仍然是认真阅读设备手册,并结合抓包和现场测试进行验证。
