☰
Python 通过 python-snap7 读写西门子 S7-300/400 PLC 实战指南
2026/10/15 12:21:46 网站建设 项目流程

简介:这份资源面向需要用 Python 与西门子 S7-300、S7-400 系列 PLC 通信的工程师与学习者,通过 TCP/IP 直连方式实现数据读写,省去依赖专用组态软件的麻烦。压缩包共 12 个文件,约 910KB,包含 3 个 py 脚本与 2 个 pyc 编译文件承载通信与业务逻辑,5 个 xls 用于 PLC 点位标签与校验数据管理,另有 1 个 ui 界面文件和 1 个 png 示意图,整体结构紧凑。作者已亲测跑通,并附上自制的 DEMO 工程,其中演示了 Python 对 Excel 文件的读写处理,界面用 QT 简单搭建,方便对照理解通信流程与数据落盘方式。目前已有 3616 人学习下载,适合希望快速上手西门子 PLC 数据采集、点位表管理与上位机交互的读者参考借鉴。

1. 用 Python 打通西门子 S7-300/400:为什么我最后选了 python-snap7

车间里那台跑了七八年的 S7-300 还在稳定干活,但上位机那边要加个数据看板,或者把产量、报警记录导进 Excel 做日报,找原厂改一次程序报价够买台新电脑。这种场景下,用 Python 直接读写 PLC 的 DB 块、M 区、I/Q 区,是最省事的路径。我试过 OPC 服务器转一道、也试过组态软件脚本,最后稳定跑下来的还是 python-snap7 这条路——它直接封装了西门子 S7 通信协议(基于 ISO-TCP 的 RFC1006),不依赖西门子官方驱动,Windows、Linux、树莓派都能装。

这篇笔记面向的是手上有 S7-300 或 S7-400、想用 Python 做数据采集或轻量控制的工程师。S7-1200/1500 也能连,但需要额外开启访问权限,本文重点放在 300/400 这两个老系列上,因为它们的坑更集中、资料更零散。读完你能自己搭起连接、读写 DB 块、把数据落进 Excel,并且知道连不上时该查哪几个地方。

2. 环境搭建与连接参数:从装包到第一次握手

2.1 python-snap7 的安装与那个绕不开的 DLL

python-snap7 本身是纯 Python 封装,但它底层调用的是 snap7 的 C 库(snap7.dll / libsnap7.so)。很多人pip install python-snap7之后一运行就报RuntimeError: can't find snap7 library,就是这一步没处理。

Windows 下的常见做法是去 snap7 官方源下载对应版本的压缩包,把snap7.dll放到C:\Windows\System32或者 Python 安装目录的DLLs文件夹里。Linux 下更省事,直接装系统包:

# Ubuntu/Debian 系 sudo apt-get install snap7 # 或者用 pip 装完 python 包后,手动把 libsnap7.so 放到 /usr/lib/ pip install python-snap7

装完先验证库能不能加载,这一步能省掉后面一半的玄学问题:

import snap7 from snap7.util import * # 打印 snap7 底层库版本,能打印出来说明 DLL/SO 加载成功 print(snap7.__version__) client = snap7.client.Client() print("Client 对象创建成功")

逻辑说明:snap7.client.Client()是所有操作的入口对象,创建它本身不建立网络连接,只是初始化。如果这一行就报错,问题一定在库文件路径,不在网络。参数上没什么可调的,重点是把库文件放对位置。

2.2 连接 S7-300/400 的四个关键参数

连接 PLC 用的是client.connect(address, rack, slot, tcpport),四个参数里前三个最容易填错。

import snap7 client = snap7.client.Client() # 典型 S7-300/400 连接参数 # address: PLC 的 IP # rack: 机架号,S7-300/400 通常是 0 # slot: 槽号,S7-300 的 CPU 一般在 2,S7-400 视电源和接口模块位置而定 # tcpport: ISO-TCP 端口,默认 102 client.connect('192.168.0.10', 0, 2, 102) # 确认连接状态 print(client.get_connected()) # True 表示握手成功 # 读一下 CPU 信息,验证通信正常 cpu_info = client.get_cpu_info() print(cpu_info.ModuleTypeName)

逻辑说明:rack和slot是 S7 协议里定位 CPU 的坐标。S7-300 一般是 rack=0、slot=2;S7-400 如果机架上先插了电源模块和接口模块,CPU 的 slot 可能是 2 或 3,得看硬件组态。tcpport默认 102,除非网络管理员改过,否则不用动。

参数怎么改:连不上时先把slot在 2 和 3 之间试一遍,这是最常见的翻车点。另外确认 PLC 的 IP 和电脑在同一网段,用ping先通一下。S7-300 老 CPU 有的不支持 ISO-TCP,需要确认 CPU 型号带 "PN" 或已配 CP343-1 以太网模块。

提示:连接前先在 STEP7 或博途的硬件组态里确认 CPU 的以太网接口已启用,并且没有勾选"禁止来自远端的通信"之类的保护选项。

3. 读写 DB 块与 M 区:地址换算和数据类型处理

3.1 DB 块读取:从字节流到真实数值

PLC 里的数据是一段连续的字节,Python 拿到的是bytearray,需要按数据类型解析。这是整个流程里最需要细心的地方,偏移量算错一位,读出来的数就是乱的。

import snap7 from snap7.util import get_real, get_int, get_bool client = snap7.client.Client() client.connect('192.168.0.10', 0, 2, 102) # 读取 DB1,从偏移 0 开始读 12 个字节 # db_read(db_number, start, size) data = client.db_read(1, 0, 12) # 按偏移解析:假设 DB1.DBD0 是 Real,DB1.DBW4 是 Int,DB1.DBX6.0 是 Bool temperature = get_real(data, 0) # 偏移 0,4 字节 Real count = get_int(data, 4) # 偏移 4,2 字节 Int running = get_bool(data, 6, 0) # 偏移 6,第 0 位 Bool print(f"温度={temperature}, 计数={count}, 运行={running}")

逻辑说明:db_read的start是字节偏移,size是要读的字节数。get_real、get_int、get_bool这些工具函数负责把字节流按西门子的字节序(大端)解析成 Python 值。注意get_bool多一个 bit 参数,因为布尔量是按位寻址的。

参数怎么改:偏移量必须和 PLC 程序里的变量地址严格对应。DB1.DBD0 对应偏移 0,DB1.DBD4 对应偏移 4,以此类推。如果一次读多个不连续的变量,要么分多次读,要么一次读一大段再按偏移切。读的 size 别超过 DB 块实际长度,否则会报错。

3.2 写入与 M 区、I/Q 区操作

写操作和读对称,用db_write。M 区(标志位)、I 区(输入)、Q 区(输出)用另外的接口。

from snap7.util import set_real, set_int # 写 DB1.DBD8 一个 Real 值 write_buf = bytearray(4) set_real(write_buf, 0, 25.6) client.db_write(1, 8, write_buf) # 读 M 区:从 MB0 开始读 4 个字节 m_data = client.read_area(snap7.types.Areas.MK, 0, 0, 4) # 读 Q 区:从 QB0 开始读 2 个字节 q_data = client.read_area(snap7.types.Areas.PA, 0, 0, 2) # 读 I 区:从 IB0 开始读 2 个字节 i_data = client.read_area(snap7.types.Areas.PE, 0, 0, 2)

逻辑说明:read_area的第一个参数是区域代码,Areas.MK是 M 区,Areas.PA是输出区(Q),Areas.PE是输入区(I),Areas.DB是数据块。第二个参数是 DB 号(非 DB 区填 0),第三个是起始字节,第四个是长度。

参数怎么改:写 DB 时db_write的start同样是字节偏移,写入的bytearray长度要和数据类型匹配——Real 是 4 字节,Int 是 2 字节。写 M 区用write_area,区域代码用Areas.MK。注意写 Q 区会直接影响现场输出,调试阶段务必先确认没有接实际执行机构,或者用仿真环境。

注意:对运行中的产线 PLC 做写操作前,一定确认目标地址不是安全联锁或急停相关的变量。我一般先在离线环境把地址表核对三遍再上现场。

3.3 把采集数据落进 Excel

采集回来的数据最终要给人看,Excel 是最通用的载体。用 openpyxl 或 pandas 都行,我一般用 pandas 直接写,代码短。

import pandas as pd from datetime import datetime records = [] for i in range(10): data = client.db_read(1, 0, 12) records.append({ '时间': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '温度': get_real(data, 0), '计数': get_int(data, 4), '运行': get_bool(data, 6, 0), }) time.sleep(1) df = pd.DataFrame(records) df.to_excel('plc_data.xlsx', index=False)

逻辑说明:循环采集时每次重新db_read,把解析后的值存进字典列表,最后一次性写 Excel。to_excel的index=False去掉行号列,表格更干净。

参数怎么改:采集周期用time.sleep控制,但注意别设太短,S7-300 的通信资源有限,100ms 以下的高频采集容易丢包。数据量大时建议先写 CSV,最后再转 Excel,避免内存里堆太多 DataFrame。

4. 避坑与排查:连不上、读出错、写不进的五条血泪经验

4.1 现象:connect 超时,ping 得通但连不上

原因:最常见的是 rack/slot 填错,其次是 PLC 的 ISO-TCP 端口被防火墙挡了,或者 CPU 本身不支持以太网通信(老款 S7-300 只有 MPI 口,必须加 CP343-1 模块)。

解决:先用 STEP7 在线看硬件组态确认 rack/slot;用telnet 192.168.0.10 102测端口通不通;确认 CPU 型号带 PN 或已装以太网 CP 模块。三条都过了还连不上,换根网线试试,我遇到过网线水晶头氧化导致间歇性断连的。

4.2 现象:db_read 报 "Address out of range"

原因:读的起始偏移加长度超过了 DB 块的实际定义长度。比如 DB1 只定义了 10 个字节,你从偏移 8 读 4 个字节,就越界了。

解决:在 STEP7 里打开 DB 块看实际长度,或者用client.db_get(1)拿到 DB 信息里的 Size 字段。读之前先判断start + size <= db_size。

4.3 现象:读出来的 Real 值是个天文数字或极小值

原因:字节序搞反了,或者偏移量差了一位。西门子是大端序,python-snap7 的get_real已经处理了大端,但如果偏移量错了,解析出来的就是相邻变量的字节。

解决:对照 PLC 变量表逐个核对偏移。DB1.DBD0 是偏移 0,DB1.DBD4 是偏移 4,别把 DBD 的编号当成偏移量。用get_real读出来不对时,先print(data.hex())看原始字节,手动算一下。

4.4 现象:db_write 返回成功但 PLC 里值没变

原因:写到了错误的 DB 号或偏移,或者 PLC 程序在下一个扫描周期把该地址覆盖了。还有一种情况是 DB 块被设置为"只读"或处于优化访问模式(S7-1200/1500 常见)。

解决:确认 DB 号和偏移无误;检查 PLC 程序里是否有对该地址的写操作;S7-300/400 的 DB 块默认是非优化访问,一般不存在这个问题,但如果是从 1200/1500 移植过来的程序要注意。

4.5 现象:长时间运行后连接断开,报 "ISO : An error occurred during recv"

原因:网络抖动、PLC 侧通信资源耗尽、或者电脑休眠导致 TCP 连接被回收。S7-300 的通信连接数有限,多个客户端同时连容易挤掉。

解决:加心跳和重连机制,捕获异常后client.disconnect()再client.connect()。采集程序里加个try/except包住读写操作,断线后等几秒重连。下面这段是我常用的重连模板:

def safe_read(client, db, start, size, retry=3): for i in range(retry): try: if not client.get_connected(): client.connect('192.168.0.10', 0, 2, 102) return client.db_read(db, start, size) except Exception as e: print(f"读取失败,第 {i+1} 次重试: {e}") time.sleep(2) return None

逻辑说明:每次读之前先检查连接状态,断了就重连,最多重试retry次。参数retry按现场网络质量调,一般 3 次够用,网络差的可以加到 5 次。

5. 进阶技巧:批量采集、地址表驱动与一个验证习惯

5.1 用地址表驱动采集,别把偏移写死在代码里

项目一多,DB 块地址经常变,把偏移量硬编码在 Python 里,改一次就要动代码。我现在的做法是维护一张 Excel 或 CSV 地址表,程序启动时读进来,按表采集。

变量名区域DB号偏移类型说明
温度DB10Real反应釜温度
计数DB14Int当日产量
运行DB16.0Bool设备运行状态
报警M010Bool综合报警位
import pandas as pd from snap7.util import get_real, get_int, get_bool # 读取地址表 tags = pd.read_excel('tags.xlsx') def read_tag(client, row): if row['区域'] == 'DB': data = client.db_read(int(row['DB号']), int(row['偏移']), 4) else: data = client.read_area(snap7.types.Areas.MK, 0, int(row['偏移']), 4) if row['类型'] == 'Real': return get_real(data, 0) elif row['类型'] == 'Int': return get_int(data, 0) elif row['类型'] == 'Bool': return get_bool(data, 0, 0) for _, row in tags.iterrows(): print(row['变量名'], read_tag(client, row))

逻辑说明:地址表把"变量名-区域-DB号-偏移-类型"解耦出来,程序只负责按表解析。参数上,偏移列对 Bool 类型要写成6.0这种带位号的格式,解析时拆开用。

5.2 一个验证习惯:先读已知值再上生产

每次新项目接线完成,我不会直接跑采集程序,而是先手动在 PLC 里给几个测试变量赋已知值——比如温度写 25.0、计数写 100、运行位置 1——然后用 Python 读一遍,确认读出来的和写进去的完全一致。这一步能同时验证 rack/slot、偏移量、数据类型三件事,比逐个排查快得多。

# 验证脚本:读三个已知值,和预期对比 data = client.db_read(1, 0, 12) assert abs(get_real(data, 0) - 25.0) < 0.01, "温度读取异常" assert get_int(data, 4) == 100, "计数读取异常" assert get_bool(data, 6, 0) == True, "运行状态读取异常" print("验证通过,可以上生产采集")

逻辑说明:用assert做硬校验,任何一项不匹配直接抛异常,避免带着错误配置上生产。Real 值比较用容差,因为浮点传输可能有微小误差。

从那以后我每次新接一个 S7-300/400 的采集项目,都强制先跑一遍这个已知值验证,再开正式采集。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询