简介:这份资源面向工业自动化工程师、PLC编程人员及希望打通控制系统与数据库链路的技术学习者,聚焦西门子S7-1200与SQL Server之间的数据集成问题。内容围绕PLC数据采集、通信接口配置、数据库表结构设计、通信方式选型以及自定义应用程序编写等环节展开,帮助读者理解如何将现场控制数据稳定写入关系型数据库,并实现查询与指令回传。资源包共19个文件,以9个png截图、5个xml配置文件为主,另含xsl、plf、pma、idx及ap13等工程与索引文件,整体约8.73MB,结构紧凑,便于对照配置与查阅。目前已有4839人学习下载,适合需要参考实际工程配置、梳理通信与数据映射思路的读者,可作为搭建S7-1200与SQL Server集成方案时的辅助资料。
1. S7-1200 连 SQL Server:不是加条指令,是补一整条数据链
车间里最常见的诉求是这样的:一台 S7-1200 跑着产线逻辑,班组长想看每小时的产量、报警记录、工艺参数曲线,但这些数据现在只躺在 PLC 的 DB 块里,掉电就没了,追溯全靠人抄。于是有人问,能不能让 S7-1200 直接连 SQL Server,把数据写进数据库表?
结论先给:能,但不是"PLC 里加一条 SQL 指令"这么简单。S7-1200 本身没有原生数据库客户端,它对外能说清楚的话只有几种——S7 通信、Modbus TCP、开放式用户通信(TCON/TSEND/TRCV)、以及 HTTP/FTP 这类应用层协议。SQL Server 走的是 TDS 协议,PLC 不会说。所以真正落地的方案,一定是在 PLC 和数据库之间放一个中间层,由它把 PLC 的数据取出来,再翻译成 SQL 语句写库。这篇文章就按这条链路拆:中间层怎么选、PLC 侧怎么把数据吐出来、中间层怎么写库、参数怎么调、坑在哪。适合正在做产线数据采集、设备联网上报、MES 轻量对接的电气和上位机工程师。
2. 中间层选型:为什么没人让 PLC 直连数据库
2.1 三种主流链路的取舍
把 S7-1200 的数据送进 SQL Server,工程上常见三条路,各有明确的适用边界。
第一条是PLC → OPC UA/OPC DA 服务器 → 上位机程序 → SQL Server。S7-1200 固件较新的型号自带 OPC UA 服务器功能,或者用第三方 OPC 服务器通过 S7 协议读 PLC。上位机用 Python、C# 订阅变量变化,再写库。优点是标准化、变量管理清晰、支持订阅(数据变了才推),适合变量多、实时性要求中等的场景。缺点是链路长,OPC 服务器本身要授权,调试时多一层黑匣子。
第二条是PLC → Modbus TCP → 网关/边缘盒子 → SQL Server。PLC 侧做 Modbus TCP 服务端,边缘设备轮询寄存器,再写库。优点是协议简单、网关便宜、隔离性好,PLC 不用改太多逻辑。缺点是 Modbus 没有数据类型概念,全是 16 位寄存器,浮点数、字符串要自己拼,地址映射容易错位。
第三条是PLC → 开放式用户通信(TSEND/TRCV)→ 上位机 Socket → SQL Server。PLC 主动把数据打包成字节流发给上位机,上位机解析后写库。优点是 PLC 侧可控、延迟低、不依赖额外授权。缺点是要自己定协议、处理粘包和重连,PLC 侧要写通信块,对电气工程师来说门槛偏高。
我一般会这样选:变量少于 200 个、要求秒级、预算有限,走 Modbus TCP 加边缘盒子;变量多、要订阅、要和 MES 共用一套地址空间,走 OPC UA;只有几组关键数据、要求毫秒级、不想买授权,走开放式用户通信自己定协议。三条路最终都汇到同一个动作——中间层执行 INSERT。
2.2 为什么不让 PLC 直接发 SQL
有人会想,能不能在 PLC 里拼一个字符串INSERT INTO ...,通过 TCP 直接发给 SQL Server 的 1433 端口。技术上 PLC 能发字节,但 SQL Server 不接受裸 SQL 文本,它走的是 TDS 协议,有握手、登录包、令牌流。PLC 要实现 TDS 客户端,等于在梯形图里写一个数据库驱动,不现实,也没必要。
更现实的问题是:数据库连接需要账号密码、需要处理断线重连、需要事务和错误回滚,这些逻辑放在 PLC 里会挤占扫描周期,还会让程序变得无法维护。所以行业里的共识是——PLC 只负责把数据准备好,翻译和写库交给中间层。这不是偷懒,是分工。
2.3 中间层跑在哪
中间层可以是一台工控机、一个边缘网关、甚至一台常开的服务器。判断标准有三条:能不能稳定访问 PLC 的网段、能不能稳定访问 SQL Server 的网段、断电后能不能自恢复。很多现场翻车不是因为代码错,是因为中间层跑在一台被人随手关机的办公电脑上。
如果 PLC 和数据库不在同一网段,中间层要双网卡或者做路由。这一点在项目初期就要确认,别等调试时才发现 ping 不通。下面这张表是我做方案时常用的对照,用来快速定链路。
| 链路 | PLC 侧改动 | 中间层复杂度 | 实时性 | 典型适用 |
|---|---|---|---|---|
| OPC UA | 开启服务器或加 OPC 块 | 中 | 秒级 | 变量多、要订阅 |
| Modbus TCP | 建映射区 | 低 | 百毫秒到秒级 | 变量少、预算低 |
| 开放式用户通信 | 写 TSEND/TRCV | 高 | 毫秒级 | 关键数据、低延迟 |
3. PLC 侧准备:把数据整理成中间层能读的样子
3.1 建一个专用的数据 DB 块
不管走哪条链路,第一步都一样:在 S7-1200 里建一个专门给外部读的全局 DB 块,不要把内部逻辑用的 DB 直接暴露出去。原因是内部 DB 的偏移会随程序修改而变,中间层按偏移读就会错位。
在 TIA Portal 里新建一个 DB,比如命名为DB_DB_Upload,取消"优化的块访问"(也就是勾选"允许绝对寻址"),这样每个变量才有固定的字节偏移。下面是一个典型结构。
DB_DB_Upload (非优化访问) 偏移 0.0 Real Temperature 温度 偏移 4.0 Real Pressure 压力 偏移 8.0 DInt ProductCount 产量计数 偏移 12.0 Int StatusWord 状态字 偏移 14.0 Bool AlarmFlag 报警标志 偏移 16.0 String[20] BatchNo 批次号逻辑说明:非优化访问是让中间层能按绝对地址读的前提,优化访问的 DB 变量没有固定偏移,第三方工具读不到。Real 占 4 字节,DInt 占 4 字节,Int 占 2 字节,Bool 在字节内按位排,String 前面有 2 字节头部(最大长度和当前长度),这些偏移必须记清楚,中间层解析时按这个表来。
参数说明:Temperature、Pressure用 Real 对应浮点;ProductCount用 DInt 是因为产量可能超过 32767;BatchNo用 String 时要注意,S7 String 的前两个字节不是字符,解析时要跳过。
3.2 用 MOVE 把逻辑数据同步到上传区
DB 建好后,在 OB1 或循环中断里把实际数据搬进去。不要在每个逻辑点直接写上传 DB,那样耦合太紧。用一个集中同步段,每个扫描周期或每 100ms 搬一次。
// OB1 末尾,集中同步 MOVE "DB_Process".Temp_Actual -> "DB_DB_Upload".Temperature MOVE "DB_Process".Press_Actual -> "DB_DB_Upload".Pressure MOVE "DB_Process".Count_Total -> "DB_DB_Upload".ProductCount MOVE "DB_Process".Status -> "DB_DB_Upload".StatusWord MOVE "DB_Process".Alarm -> "DB_DB_Upload".AlarmFlag MOVE "DB_Process".Batch -> "DB_DB_Upload".BatchNo逻辑说明:集中同步的好处是,中间层读到的永远是一个相对一致的快照,不会出现温度是新的、压力是旧的这种撕裂。如果数据量大,可以放到一个 100ms 的循环中断 OB 里,减轻主循环负担。
参数说明:同步周期要和中间层的采集周期匹配。中间层 1 秒采一次,PLC 侧 100ms 同步一次就够;中间层要 100ms 采一次,PLC 侧就要放到更快的循环里,但要注意别让通信块拖慢扫描周期。
3.3 走 Modbus TCP 时的映射配置
如果选 Modbus TCP,PLC 侧要启用 Modbus TCP 服务器。在 TIA Portal 里添加MB_SERVER指令,指定一个保持寄存器区(比如从DB_DB_Upload映射)。注意 Modbus 寄存器是 16 位的,一个 Real 要占两个寄存器,高低字顺序要和中间层约定一致。
MB_SERVER 参数(示意) MB_HOLD_REG -> 指向映射 DB 的指针 CONNECT -> 连接参数(端口 502) DISCONNECT -> 断开控制逻辑说明:MB_SERVER把 PLC 变成 Modbus 从站,中间层作为主站来读。映射区里的数据按 16 位寄存器排列,Real 需要两个寄存器拼。高低字顺序(大端/小端)是最容易翻车的地方,中间层解析出来是乱码,八成是字序反了。
参数说明:端口默认 502,如果现场有多个 Modbus 设备,注意端口和单元标识不要冲突。MB_SERVER的连接数有限,中间层不要开太多并发连接。
4. 中间层写库:从读到 INSERT 的完整代码
4.1 Python 读 PLC 并写 SQL Server
中间层用什么语言都行,Python 上手快、库全,我用得最多。下面这段是走 Modbus TCP 的完整示例,读 PLC 寄存器,解析成工程量,写进 SQL Server。
# 依赖:pip install pymodbus pyodbc from pymodbus.client import ModbusTcpClient import pyodbc import struct import time # PLC 连接参数 PLC_IP = "192.168.0.10" PLC_PORT = 502 UNIT_ID = 1 # SQL Server 连接参数 SQL_CONN = ( "DRIVER={ODBC Driver 17 for SQL Server};" "SERVER=192.168.0.20,1433;" "DATABASE=LineData;" "UID=sa;PWD=YourStrongPwd;" "TrustServerCertificate=yes;" ) def read_plc(client): # 从保持寄存器 0 开始读 20 个寄存器 rr = client.read_holding_registers(address=0, count=20, slave=UNIT_ID) if rr.isError(): raise RuntimeError("Modbus read failed") regs = rr.registers # Real 占两个寄存器,按大端拼接 temperature = struct.unpack(">f", struct.pack(">HH", regs[0], regs[1]))[0] pressure = struct.unpack(">f", struct.pack(">HH", regs[2], regs[3]))[0] # DInt 占两个寄存器 product_count = (regs[4] << 16) | regs[5] status_word = regs[6] alarm_flag = regs[7] & 0x01 return temperature, pressure, product_count, status_word, alarm_flag def write_sql(conn, row): sql = ( "INSERT INTO dbo.ProductionLog " "(LogTime, Temperature, Pressure, ProductCount, StatusWord, AlarmFlag) " "VALUES (?, ?, ?, ?, ?, ?)" ) cur = conn.cursor() cur.execute(sql, row) conn.commit() def main(): client = ModbusTcpClient(PLC_IP, port=PLC_PORT) client.connect() conn = pyodbc.connect(SQL_CONN, autocommit=False) while True: try: data = read_plc(client) write_sql(conn, (time.strftime("%Y-%m-%d %H:%M:%S"), *data)) except Exception as e: print("cycle error:", e) # 断线重连 try: client.close(); client.connect() except Exception: pass try: conn.close(); conn = pyodbc.connect(SQL_CONN) except Exception: pass time.sleep(1) if __name__ == "__main__": main()逻辑说明:read_holding_registers一次读 20 个寄存器,减少往返次数。Real 用struct按大端解析,如果现场字序相反,把>HH改成<HH或交换两个寄存器顺序。写库用参数化 SQL,避免拼接字符串。整个循环包在 try 里,任何一步失败都触发重连,保证长时间运行不中断。
参数说明:address=0对应映射区起始;count=20要覆盖所有变量;slave=UNIT_ID要和 PLC 侧一致;autocommit=False配合显式commit,方便批量提交;time.sleep(1)是采集周期,按需调整。
4.2 建表语句和字段类型对应
中间层写库前,SQL Server 里要先有表。字段类型要和 PLC 数据类型对应,别用错。
CREATE TABLE dbo.ProductionLog ( Id BIGINT IDENTITY(1,1) PRIMARY KEY, LogTime DATETIME2(0) NOT NULL, Temperature FLOAT NULL, Pressure FLOAT NULL, ProductCount INT NULL, StatusWord SMALLINT NULL, AlarmFlag BIT NULL, BatchNo NVARCHAR(20) NULL ); CREATE INDEX IX_ProductionLog_LogTime ON dbo.ProductionLog(LogTime);逻辑说明:LogTime建索引是因为查询几乎都按时间范围来。Temperature用 FLOAT 对应 Real,ProductCount用 INT 对应 DInt,StatusWord用 SMALLINT 对应 Int,AlarmFlag用 BIT 对应 Bool。批次号用 NVARCHAR 对应 String。
参数说明:DATETIME2(0)精确到秒,够用且省空间;如果要多点写入,把Id设成自增主键,避免并发冲突。
4.3 批量写入降低数据库压力
一秒一条,一天 86400 条,单条 INSERT 会让数据库频繁提交。改成攒一批再写,能明显降低压力。
buffer = [] BATCH_SIZE = 50 def flush(conn, buffer): if not buffer: return sql = ( "INSERT INTO dbo.ProductionLog " "(LogTime, Temperature, Pressure, ProductCount, StatusWord, AlarmFlag) " "VALUES (?, ?, ?, ?, ?, ?)" ) cur = conn.cursor() cur.executemany(sql, buffer) conn.commit() buffer.clear()逻辑说明:executemany一次提交多条,减少网络往返和事务开销。攒够 50 条或超过一定时间就 flush,避免数据长时间不落库。
参数说明:BATCH_SIZE按数据量和实时性要求调,50 到 500 都常见;如果要求每条都能立刻查到,就退回单条写。
5. 避坑与排查:现场最容易翻车的五件事
5.1 读出来全是乱码或数量级不对
现象:中间层读到的温度是1.2e-38或者一个巨大的整数。原因:Real 的高低字顺序和中间层解析顺序不一致,或者把两个寄存器拼反了。解决:先确认 PLC 侧 Real 在寄存器里的排列,再在中间层用struct试>HH和<HH两种,看哪个对。也可以先写一个已知值(比如 25.5)到 PLC,再读出来比对。
5.2 连接几分钟就断一次
现象:中间层跑一会儿就报连接错误,重连后又能跑。原因:PLC 的通信连接资源有限,或者防火墙/交换机对空闲连接做了回收。解决:中间层加心跳,定期读一个固定寄存器;PLC 侧确认MB_SERVER或 OPC 连接数没超;网络侧确认没有会话超时策略。重连逻辑一定要有,别指望连接永远不断。
5.3 数据库写入越来越慢
现象:刚开始很快,跑几天后 INSERT 变慢。原因:表没有索引或索引过多、日志文件增长、单条提交太频繁。解决:按查询条件建必要索引,别乱建;改成批量提交;定期检查数据库日志和磁盘空间。如果只是记录,考虑按天分表或加分区。
5.4 时间对不上
现象:数据库里的时间和现场对不上,差几小时。原因:中间层所在机器的时区或时间不对,或者 PLC 和中间层时间没同步。解决:中间层统一用一台机器的时间,开启时间同步;如果要多地部署,统一用 UTC 存储,展示时再转本地。
5.5 断电后数据丢失或重复
现象:中间层重启后,断电期间的数据没了,或者重启后重复写了一段。原因:中间层没有断点续传机制,或者重连后从固定位置重新读。解决:如果数据允许丢,就接受;如果不允许,中间层要记录最后成功写入的时间或序号,重启后从断点继续。PLC 侧可以加一个递增的序号,中间层按序号去重。
6. 进阶:把这条链路做稳的三个习惯
第一个习惯是先定数据契约再写代码。PLC 侧的 DB 偏移、数据类型、中间层的解析方式、数据库的字段类型,这四样要在动手前写成一张表,三方对齐。我见过太多项目,PLC 改了 DB 顺序没通知上位机,结果温度写进了压力字段,查了一天才发现。数据契约一旦定下,改任何一方都要同步更新。
第二个习惯是给中间层加可观测性。不要只 print,要记录每次采集的成功/失败、耗时、写入条数。最简单的做法是写本地日志文件,按天滚动。出问题时,日志能告诉你到底是 PLC 没读到,还是数据库没写进去。没有日志的中间层,排查全靠猜。
第三个习惯是在 PLC 侧留一个测试值。比如在 DB 里放一个每次加一的计数器,中间层读到的值应该连续递增。如果中间层读到的计数器不连续,说明采集有丢包或周期不稳。这个计数器不参与业务,只用来验证链路,成本极低,但能省很多事。
下面这张表是我做验收时常用的检查项,照着过一遍,基本能覆盖八成问题。
| 检查项 | 正常表现 | 异常时先看 |
|---|---|---|
| PLC 侧 DB 偏移 | 与契约一致 | 是否被改成优化访问 |
| 中间层读取 | 值稳定、量级合理 | 字序、寄存器地址 |
| 数据库写入 | 条数随时间增长 | 连接串、权限、磁盘 |
| 断线重连 | 断开后自动恢复 | 重连逻辑、心跳 |
| 时间戳 | 与现场一致 | 时区、时间同步 |
最后说一个我自己的教训。早期做这类项目,我总想一步到位,把采集、解析、写库、报警全塞进一个脚本,结果一出问题就整条链路停摆。后来改成模块化:读 PLC 一个函数、解析一个函数、写库一个函数、重连一个函数,每个函数单独能测。链路长了不可怕,可怕的是你不知道哪一段断了。把每一段都做成能单独验证的,问题就变成填空题,而不是玄学。希望帮到你。
本文还有配套的精品资源,点击获取