简介:这份资源面向工业自动化工程师、PLC编程学习者及需要打通产线数据链路的开发者,聚焦西门子S7-1200与SQL Server数据库的集成方案。内容围绕PLC数据采集、以太网通信配置、数据库表结构设计、通信方式选型以及自定义应用程序编写等关键环节展开,帮助读者理解如何将控制器数据稳定写入关系型数据库,实现集中存储、查询与分析。资源包共19个文件,约8.73MB,以9张png截图、5个xml配置文件为主,另含xsl、plf、pma、idx及ap13等工程与索引文件,可用于还原项目结构、对照配置细节与排查通信问题。目前已有4841人学习下载,适合希望掌握PLC与数据库对接思路、快速搭建实验环境并积累排错经验的中高级读者参考。
1. 从一条产线数据落库需求说起:S7-1200 直连 SQL Server 到底能干什么
去年帮一个做装配检测的朋友看现场,他们的产线控制器用的是一台 S7-1200,工艺参数、扭矩值、检测结果都躺在 PLC 的 DB 块里,质检那边却要每天手工抄表再录进 SQL Server 做追溯。人一累就抄错,追溯链条断在最后一百米。他们最初的想法是加一台工控机跑组态软件中转,后来发现其实 S7-1200 本身就能通过开放式通信把数据直接写进 SQL Server,省掉中间那层。这套方案适合谁?适合手里已经有 S7-1200、又不想为数据采集单独买网关或上位机的现场工程师;也适合做设备联网改造、想把 PLC 数据接进 MES 或质量追溯库的开发者。它不是什么高深架构,核心就是让 PLC 当客户端,主动把数据推给数据库,链路短、故障点少,但坑也集中在这条短链路上。
2. 通信链路拆解:S7-1200 怎么把数据送到 SQL Server
2.1 为什么不能直连,中间必须有个"翻译"
S7-1200 的通信能力集中在 PROFINET、S7 通信、开放式用户通信这几块。它支持 TCP/IP,但只到传输层,上面没有 TDS 协议栈——SQL Server 用的是 TDS(Tabular Data Stream),PLC 根本不认识这套握手和报文格式。所以"直连"这个说法要拆开看:物理上 PLC 能通过网口到达数据库服务器,协议上必须有个中间件把 PLC 的字节流翻译成 SQL 语句。
常见做法有三种。第一种是 PLC 侧用 TCON、TSEND 指令把数据以 TCP 报文发出去,中间件监听端口收报文再转 SQL。第二种是中间件主动用 S7 协议读 PLC 的 DB 块,再写库。第三种是 PLC 通过 OPC UA 或 Modbus TCP 暴露数据,中间件订阅。这个项目走的是第一种,因为改动最小,PLC 侧只需要加几个通信指令,不用动原有逻辑。
提示:选哪种取决于你的中间件部署位置和实时性要求。PLC 主动推适合事件触发(比如一个工件检测完成就发一条),中间件主动读适合周期性采集。
2.2 数据流:从 DB 块到数据库表
整条链路是这样的:PLC 里建一个专门用于通信的 DB 块,把要上传的字段按固定格式拼成一个字节数组;PLC 调用 TSEND 把数组发到中间件监听的 IP 和端口;中间件收到后按约定格式解析,拼成 INSERT 语句写进 SQL Server;数据库返回结果,中间件可选地把成功或失败状态回写给 PLC。
这里的关键是"约定格式"。PLC 发出去的就是一串字节,中间件怎么知道哪几个字节是扭矩、哪几个是工件号?必须提前定好报文结构。我一般会用一个结构体 DB,字段顺序固定,中间件按偏移量取值。下面是一个典型的 DB 块定义思路:
-- SQL Server 侧建表,字段和 PLC 报文一一对应 CREATE TABLE ProductionData ( Id INT IDENTITY(1,1) PRIMARY KEY, WorkpieceNo VARCHAR(32) NOT NULL, -- 工件编号 Torque DECIMAL(10,3) NOT NULL, -- 扭矩值 Result TINYINT NOT NULL, -- 0 不合格 1 合格 StationNo VARCHAR(16) NOT NULL, -- 工位号 CreateTime DATETIME DEFAULT GETDATE() );这张表就是落库目标。WorkpieceNo 对应 PLC 报文里的字符串段,Torque 对应浮点段,Result 对应一个字节,StationNo 对应工位标识。字段类型要和 PLC 侧的数据类型对齐,比如 PLC 的 REAL 是 4 字节浮点,对应 SQL 的 DECIMAL 或 FLOAT 都行,但精度要提前算好,别到后面发现小数位丢了。
2.3 中间件选型:为什么用脚本而不是重型服务
中间件可以是一个常驻的 Python 脚本、一个 .NET 控制台程序,或者一个 Node 服务。这个项目用的是 Python,原因很实际:现场那台工控机已经装了 Python 环境做别的采集,再加一个 socket 监听脚本成本几乎为零,而且改字段、调格式不用重新编译部署。
中间件的职责很单一:监听 TCP 端口、收报文、解析、写库、回状态。它不需要界面,不需要复杂配置,越简单越不容易在无人值守的现场出问题。下面是一个最小可用的监听与写库骨架:
import socket import struct import pyodbc # 数据库连接串,按现场实际改 CONN_STR = ( "DRIVER={ODBC Driver 17 for SQL Server};" "SERVER=192.168.1.100;" "DATABASE=FactoryDB;" "UID=sa;PWD=YourPassword" ) def parse_payload(data: bytes): """按约定偏移解析 PLC 报文""" # 前 32 字节工件号,4 字节扭矩,1 字节结果,16 字节工位号 workpiece = data[0:32].decode('ascii').strip('\x00') torque = struct.unpack('>f', data[32:36])[0] # 大端浮点 result = data[36] station = data[37:53].decode('ascii').strip('\x00') return workpiece, torque, result, station def save_to_db(workpiece, torque, result, station): conn = pyodbc.connect(CONN_STR) cursor = conn.cursor() cursor.execute( "INSERT INTO ProductionData " "(WorkpieceNo, Torque, Result, StationNo) " "VALUES (?, ?, ?, ?)", workpiece, torque, result, station ) conn.commit() conn.close() def main(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('0.0.0.0', 9000)) # 监听所有网卡的 9000 端口 server.listen(5) print("middleware listening on 9000") while True: client, addr = server.accept() data = client.recv(1024) if data: try: workpiece, torque, result, station = parse_payload(data) save_to_db(workpiece, torque, result, station) client.send(b'\x01') # 回写成功标志 except Exception as e: print("handle error:", e) client.send(b'\x00') # 回写失败标志 client.close() if __name__ == '__main__': main()这段代码里几个点值得说清楚。struct.unpack('>f', ...)里的>表示大端字节序,因为 S7-1200 在网络传输时用的是大端,如果你在 PLC 侧没做字节交换,中间件就必须按大端解析,否则扭矩值会变成一个完全看不懂的数。data[0:32]这种硬编码偏移量是最容易翻车的地方,PLC 侧字段顺序一改,这里必须同步改,所以我在现场会要求把报文格式写进交接文档,而不是只留在某个人脑子里。client.send(b'\x01')是给 PLC 的回执,PLC 侧收到后可以决定是否重发,这个机制在丢包时很有用。
2.4 PLC 侧要做什么:TCON、TSEND 和状态处理
PLC 侧的工作分三块:建立连接、发送数据、处理结果。S7-1200 用 TCON 建立 TCP 连接,用 TSEND 发送数据,用 T_RCV 或直接判断 TSEND 的完成位来处理回执。这些指令在 TIA Portal 的"开放式用户通信"指令库里。
连接参数需要配置:中间件的 IP 地址、端口号、连接类型(TCP)。这些参数存在一个连接描述 DB 里,TCON 指令引用它。发送时把要发的数据区指针和长度传给 TSEND。下面是一个典型的调用逻辑描述:
// 伪代码描述 PLC 侧逻辑,实际在 TIA Portal 中用梯形图或 SCL 实现 IF 检测完成 AND NOT 发送中 THEN 发送中 := TRUE; 调用 TSEND( 连接 := 连接DB, 数据 := 发送缓冲区, 长度 := 53, 完成位 => 发送完成, 错误位 => 发送错误 ); END_IF; IF 发送完成 THEN 发送中 := FALSE; // 可选:等待回执或直接进入下一条 END_IF; IF 发送错误 THEN 发送中 := FALSE; // 记录错误码,触发重试或报警 END_IF;这里 53 是报文总长度,和中间件解析的偏移量必须严格对应。发送缓冲区就是那个专门建的通信 DB,里面按顺序放工件号、扭矩、结果、工位号。PLC 侧最容易忽略的是"发送中"这个互锁,如果不加,检测完成信号抖动时会连续调用 TSEND,导致报文粘连或连接异常。另外 TSEND 的错误位要接出来,常见错误有连接断开、对方端口未监听,现场排查时先看这个错误码能省很多时间。
3. 环境搭建与联调:从零把链路跑通
3.1 网络与账号准备
先把网络打通。PLC、中间件所在工控机、SQL Server 服务器三者在同一网段,或者路由可达。用 ping 从工控机测 PLC 的 IP,从工控机测数据库服务器的 IP,两个都通再往下走。SQL Server 要开启 TCP/IP 协议,默认端口 1433,在配置管理器里确认,并且防火墙放行 1433 和中间件监听的 9000。
数据库账号方面,现场常见做法是建一个专用登录名,只给目标表的 INSERT 权限,不要用 sa。连接串里的 UID 和 PWD 按这个账号填。如果 SQL Server 是默认实例,SERVER 写 IP 就行;命名实例要写成IP\实例名。
注意:工控机上的 ODBC 驱动版本要和连接串里的 DRIVER 名称一致。装的是 ODBC Driver 17 就写 17,写错了会报"未找到数据源名称"。
3.2 中间件部署与自启动
中间件脚本放到工控机的一个固定目录,比如C:\Middleware\plc_bridge.py。先手动跑一遍,确认能监听、能写库。手动跑通后再做自启动,Windows 下可以用任务计划程序,触发条件设为"系统启动时",操作设为运行 python 解释器并带上脚本路径。这样工控机重启后中间件自动起来,不用人到现场点一下。
自启动配置完要重启一次工控机验证,别只配了不测。我见过配了任务计划但"起始于"目录没填,脚本里如果有相对路径读配置就会失败,重启后中间件根本没起来,产线数据全丢。
3.3 联调步骤:先通链路再对数据
联调不要一上来就接真实产线。分三步走。第一步,用网络调试助手模拟 PLC,手动发一串符合格式的字节到中间件,看数据库里有没有记录,字段值对不对。这一步验证中间件和数据库。第二步,PLC 侧单独调 TSEND,把中间件换成一个简单的 TCP 接收工具,看 PLC 发出来的字节和预期是否一致,重点看字节序和字符串填充。第三步,两端对接,跑一条真实工件,看全链路。
每一步都要留日志。中间件把收到的原始字节和解析结果都打到日志文件,PLC 侧把发送完成位和错误码记录到 DB 块里。出问题时先看日志,比在现场猜快得多。
3.4 报文格式对照表
联调时最需要的就是一张对照表,把 PLC 侧字段、字节偏移、数据类型、SQL 字段列在一起。下面是一个示例:
| PLC 字段 | 起始偏移 | 长度 | PLC 类型 | SQL 字段 | SQL 类型 |
|---|---|---|---|---|---|
| 工件号 | 0 | 32 | STRING | WorkpieceNo | VARCHAR(32) |
| 扭矩 | 32 | 4 | REAL | Torque | DECIMAL(10,3) |
| 结果 | 36 | 1 | BYTE | Result | TINYINT |
| 工位号 | 37 | 16 | STRING | StationNo | VARCHAR(16) |
这张表要打印出来贴在调试现场,PLC 侧改一个字段,中间件和数据库同步改,三方对齐才不会出错。
4. 避坑与排查:现场最容易翻车的几个点
4.1 扭矩值变成天文数字
现象:数据库里 Torque 字段出现类似 1.2e38 或者完全无意义的数。原因:字节序不对。S7-1200 网络发送是大端,Python 的 struct 默认按本机字节序解析,如果没加>就会把字节顺序读反。解决:确认 struct 格式串带>,或者在 PLC 侧用 SWAP 指令把 REAL 转成小端再发。两种方式选一种,别两边都转。
4.2 字符串后面带一堆乱码
现象:WorkpieceNo 存进去后面跟着一串方框或问号。原因:PLC 的 STRING 类型前两个字节是最大长度和当前长度,实际字符从第三个字节开始,而且不足长度时用 0 填充。中间件如果直接从偏移 0 开始取,会把长度字节也当成字符。解决:要么在 PLC 侧发送前把 STRING 转成纯字符数组,要么在中间件里跳过前两字节并按\x00截断。我一般选前者,PLC 侧多一步转换,中间件逻辑更干净。
4.3 中间件收不到数据
现象:PLC 显示发送错误,中间件日志没有任何记录。原因:常见有三种——中间件没启动或端口没监听;防火墙拦了 9000 端口;PLC 连接描述 DB 里的 IP 或端口填错。解决:先在工控机上用netstat -an | findstr 9000确认端口在监听,再从 PLC 侧 ping 工控机,最后核对连接参数。按这个顺序排,基本能定位。
4.4 数据库连接偶尔超时
现象:大部分数据能写进去,偶尔几条丢失,中间件日志报连接超时。原因:中间件每次写库都新建连接,连接数一多或者数据库负载高时就超时。解决:改成连接池或长连接,用 pyodbc 的话可以复用 connection 对象,配合重试逻辑。另外把 INSERT 放到事务里,失败回滚后重试,避免半条数据。
4.5 PLC 连续发送导致报文粘连
现象:中间件一次 recv 收到两条报文拼在一起,解析错位。原因:PLC 侧没有发送互锁,或者中间件 recv 缓冲区太大把两条读成一条。解决:PLC 侧加"发送中"互锁,确保上一条完成再发下一条;中间件侧按固定长度切分,收满 53 字节才解析一次,而不是收到什么就解析什么。
5. 二次开发与改造:把这套链路改成你自己的
这套东西跑通之后,真正有价值的是怎么按自己的场景改。先说报文格式的扩展。现在固定 53 字节,如果你要加一个温度字段,PLC 侧在发送缓冲区末尾追加 4 字节 REAL,中间件解析函数加一段data[53:57],数据库表加一列,对照表更新。三处同步改,改完按第 3 章的联调步骤重跑一遍。我习惯在中间件里把报文格式写成一个配置列表,每项包含字段名、偏移、长度、类型,解析时循环处理,这样加字段只改配置不改代码。
再说写库策略。现在是来一条写一条,如果产线节拍快,可以考虑批量提交:中间件攒够 50 条或超过 1 秒就批量 INSERT,减少数据库压力。批量写要注意失败时的处理,整批回滚还是逐条重试,取决于你的数据能不能丢。质量追溯数据一般不能丢,那就逐条重试并记录失败队列。
回执机制也值得加强。现在只回一个字节的成功失败标志,PLC 侧收到失败可以重发。更完善的做法是回写一个序列号,PLC 侧比对序列号确认哪条被处理了,避免重复写入。这个在丢包重发场景下很有用。
验证改造是否成功,我一般用三步:先用模拟工具发边界数据(空字符串、最大扭矩、非法结果值),看中间件和数据库怎么处理;再跑真实产线一个班次,对比 PLC 计数和数据库记录数是否一致;最后模拟断网,看恢复后数据能不能补上。这三步走完,基本能放心。
最后说个我自己的教训。早期做这类项目时,我总想着把中间件写得很"聪明",自动识别报文格式、自动重连、自动补数据,结果现场一出问题反而难排查,因为逻辑分支太多。后来我改成"笨"办法:格式固定、失败就记日志、重试次数写死,反而稳定。从那以后我每次做 PLC 和数据库对接,都强制先把报文对照表和失败处理流程写清楚再动手写代码,省下的调试时间远超写文档的时间。希望帮到你。
本文还有配套的精品资源,点击获取