Python使用pyModbus库读写PLC数据完整教程

Nora
Nora
发布时间:2026-10-10| 3人浏览过
原创

在工业物联网与产线监控场景中,使用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
Python使用pyModbus库读写PLC数据完整教程
图1:Python上位机与PLC设备之间的网络通讯架构与Modbus主从角色示意图
地址偏移特别提醒: PLC组态软件(如西门子TIA Portal、三菱GX Works、汇川InoProShop)中常标注“40001”为第一个保持寄存器。这是经典的“1-based”地址记法。而在Modbus底层协议报文中,寄存器是从0开始编址的(0-based)。因此PLC的40001对应pymodbus代码中的address=0,PLC的40002对应address=1。若读写数据发现错位,首先应检查是否相差了偏移量1。

二、安装与开发环境准备

推荐使用Python 3.8及以上版本。在终端中执行以下命令安装pymodbus库。为保证数据解析模块完整,建议安装基础依赖:

pip install pymodbus

在PLC侧,需要确保以下三项配置已经完成:

  1. 启用Modbus TCP服务器功能: 在PLC编程软件中开启Modbus TCP服务块(如西门子S7-1200的MB_SERVER指令),并绑定本地端口(默认为502)。
  2. 同网段IP规划: 上位机网卡IP必须与PLC位于同一局域网网段,且能通过ping <PLC_IP>稳定连通。
  3. 分配单元标识符(Slave ID / Unit ID): 确定PLC的站号(通常为1),Modbus TCP在直连单机时常默认为1或255。

三、完整读写操作流程

使用pymodbus与PLC交互的标准生命周期由四个主要阶段构成:建立连接、执行读写交互、校验返回报文状态、显式断开套接字。

Python使用pyModbus库读写PLC数据完整教程
图2:执行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): 低位字在低地址,国内部分控制器及三菱常用。
Python使用pyModbus库读写PLC数据完整教程
图3:32位单精度浮点数在Modbus双寄存器中的存放与解码重构过程

在最新版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
如果解析出来的数值极其离奇(例如变成 1.35e-38 或 NaN): 这表明你的字序(wordorder)或字节序(byteorder)与PLC内部配置不匹配。只需将 wordorder=Endian.BIG 改为 wordorder=Endian.LITTLE 进行测试。

五、常见通讯故障与排查清单

在工程现场调试时,若读写未达到预期,请按以下检查项逐一排查:

Python使用pyModbus库读写PLC数据完整教程
图4:常见Modbus通讯故障分类与解决路径总结图

总结来说,使用Python的pymodbus库与PLC交互并不复杂。只要在开发之初明确地址换算基准(0-based与1-based)、配置正确的从站号,并在解析32位数据时匹配PLC的字节序与字序,就能稳定可靠地实现工业自动化数据的采集与闭环控制。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。