Python使用pyModbus库读写PLC数据完整教程
在工业物联网与产线监控场景中,使用Python采集PLC(可编程逻辑控制器)的运行状态或下发控制参数是一项常见需求。要实现这一目标,核心路径是通过工业标准通讯协议Modbus,借助Python生态中维护活跃的pymodbus库建立客户端连接,并对PLC暴露的线圈与寄存器执行读写操作。本文将以工业现场最普及的Modbus TCP模式为例,完整演示从环境搭建、网络握手、基础点位读写,到32位浮点数高低字节解析与通讯故障排查的全过程。
一、Modbus与PLC数据模型对应关系
在编写代码前,必须理清PLC内部变量与Modbus功能码之间的映射规则。PLC通常将对外交互的数据划分为四种基础存储区:用于开关量状态的线圈(Coil)与离散输入(Discrete Input),以及用于数值模拟量的保持寄存器(Holding Register)与输入寄存器(Input Register)。
在Modbus协议标准中,这些存储区分别对应不同的功能码。Python客户端发出请求时,底层协议会根据请求类型调用相应功能码并传入起始地址与长度。下表总结了常见的四类存储区及对应操作:
| 存储区名称 | PLC典型用途 | 读写属性 | 单点数据长度 | pymodbus常用API |
|---|---|---|---|---|
| 线圈 (Coils) | 启停控制、继电器输出 | 可读可写 | 1 位 (Bit) | read_coils / write_coil |
| 离散输入 (Discrete Inputs) | 光电传感器、行程开关 | 只读 | 1 位 (Bit) | read_discrete_inputs |
| 保持寄存器 (Holding Registers) | 工艺设定值、温度限值、累计量 | 可读可写 | 16 位 (Word) | read_holding_registers / write_register |
| 输入寄存器 (Input Registers) | 模拟量输入模块采集的原生数值 | 只读 | 16 位 (Word) | read_input_registers |
address=0,PLC的40002对应address=1。若读写数据发现错位,首先应检查是否相差了偏移量1。
二、安装与开发环境准备
推荐使用Python 3.8及以上版本。在终端中执行以下命令安装pymodbus库。为保证数据解析模块完整,建议安装基础依赖:
pip install pymodbus
在PLC侧,需要确保以下三项配置已经完成:
- 启用Modbus TCP服务器功能: 在PLC编程软件中开启Modbus TCP服务块(如西门子S7-1200的
MB_SERVER指令),并绑定本地端口(默认为502)。 - 同网段IP规划: 上位机网卡IP必须与PLC位于同一局域网网段,且能通过
ping <PLC_IP>稳定连通。 - 分配单元标识符(Slave ID / Unit ID): 确定PLC的站号(通常为1),Modbus TCP在直连单机时常默认为1或255。
三、完整读写操作流程
使用pymodbus与PLC交互的标准生命周期由四个主要阶段构成:建立连接、执行读写交互、校验返回报文状态、显式断开套接字。
1. 读写开关量(线圈 Coil)
线圈对应PLC中的单个布尔状态(True/False,高电平/低电平)。例如远程启停电机或阀门开关。以下脚本展示如何连接PLC并对线圈执行写入与回读验证:
from pymodbus.client import ModbusTcpClient
PLC_IP = "192.168.1.100"
PLC_PORT = 502
SLAVE_ID = 1
# 初始化同步客户端
client = ModbusTcpClient(PLC_IP, port=PLC_PORT, timeout=3.0)
if not client.connect():
print(f"无法建立与PLC {PLC_IP}:{PLC_PORT} 的网络连接")
exit(1)
try:
# 写入单个线圈:地址 0(对应PLC地址 00001),设置为 True
write_res = client.write_coil(address=0, value=True, slave=SLAVE_ID)
if write_res.isError():
print(f"写入线圈失败: {write_res}")
else:
print("线圈写入成功!")
# 读取线圈状态:起始地址 0,读取数量 1
read_res = client.read_coils(address=0, count=1, slave=SLAVE_ID)
if read_res.isError():
print(f"读取线圈失败: {read_res}")
else:
coil_status = read_res.bits[0]
print(f"地址 0 的线圈当前状态: {coil_status}")
finally:
# 必须显式释放连接
client.close()
示例输出:
线圈写入成功!
地址 0 的线圈当前状态: True
2. 读写16位整数(保持寄存器 Holding Register)
工业产线中的设定速度、报警阈值通常以16位无符号整数(0~65535)或有符号整数(-32768~32767)存放在保持寄存器中。单个寄存器占用2个字节。
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("192.168.1.100", port=502)
if client.connect():
try:
# 写入单个保持寄存器:地址 10,数值 1500(例如设定转速 1500 RPM)
client.write_register(address=10, value=1500, slave=1)
# 批量写入连续寄存器:从地址 20 开始写入 [100, 200, 300]
client.write_registers(address=20, values=[100, 200, 300], slave=1)
# 读取保持寄存器:从地址 20 开始连续读取 3 个字
res = client.read_holding_registers(address=20, count=3, slave=1)
if not res.isError():
print(f"寄存器读取结果列表: {res.registers}")
else:
print("读取寄存器发生异常")
finally:
client.close()
示例输出:
寄存器读取结果列表: [100, 200, 300]
四、32位浮点数与大端/小端字节序解析
PLC中采集的温度、压力、流量等物理量绝大多数采用IEEE 754标准的32位单精度浮点数(Float)。由于Modbus单个保持寄存器只能承载16位(2字节),因此一个32位浮点数必须跨两个连续的保持寄存器存放。
不同PLC厂商(如西门子、施耐德、欧姆龙、三菱)在组合这两个寄存器时,字顺序(Word Order)和字节顺序(Byte Order)存在差异。常见组合为:
- Big-Endian(大端,ABCD): 高位字在低地址,西门子S7系列默认格式。
- Little-Endian Byte Swap(CDAB): 低位字在低地址,国内部分控制器及三菱常用。
在最新版pymodbus中,推荐使用BinaryPayloadDecoder和BinaryPayloadBuilder进行多字节数据类型转换。示例如下:
from pymodbus.client import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadBuilder, BinaryPayloadDecoder
client = ModbusTcpClient("192.168.1.100", port=502)
if client.connect():
try:
# 1. 写入浮点数:将 45.78 写入寄存器 100 和 101
builder = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.BIG)
builder.add_32bit_float(45.78)
payload = builder.to_registers()
client.write_registers(address=100, values=payload, slave=1)
print(f"已写入寄存器原始数据字: {[hex(x) for x in payload]}")
# 2. 读取浮点数:从寄存器 100 连续读出 2 个寄存器
result = client.read_holding_registers(address=100, count=2, slave=1)
if not result.isError():
decoder = BinaryPayloadDecoder.fromRegisters(
result.registers,
byteorder=Endian.BIG,
wordorder=Endian.BIG
)
real_val = decoder.decode_32bit_float()
# 保留两位小数
print(f"成功解析出PLC浮点数值: {round(real_val, 2)}")
finally:
client.close()
示例输出:
已写入寄存器原始数据字: ['0x4237', '0x1eb8']
成功解析出PLC浮点数值: 45.78
wordorder=Endian.BIG 改为 wordorder=Endian.LITTLE 进行测试。
五、常见通讯故障与排查清单
在工程现场调试时,若读写未达到预期,请按以下检查项逐一排查:
总结来说,使用Python的pymodbus库与PLC交互并不复杂。只要在开发之初明确地址换算基准(0-based与1-based)、配置正确的从站号,并在解析32位数据时匹配PLC的字节序与字序,就能稳定可靠地实现工业自动化数据的采集与闭环控制。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









