news 2026/7/18 17:04:46

西门子 PLC 与 C# 通信_项目实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
西门子 PLC 与 C# 通信_项目实战

一、项目概述

该项目是一个基于 WPF 和 .NET Framework 4.7.2 开发的西门子 S7-1200 PLC 数据采集应用程序,用于某周期性数据采集场景。

核心功能

  • 单路实时采集:每秒读取 PLC 指定地址的数值,实时刷新界面
  • 自动周期存盘:按配置间隔(默认 10 分钟)将瞬时值和周期最低值写入 CSV
  • 手动单次采集:用户可随时触发一次采集并独立存盘
  • 秒级高精度采集:可选的秒级 CSV 异步写入,不影响主采集路径
  • 长期稳定运行:支持 8 小时以上连续运行,异常不导致进程退出

技术栈

组件版本说明
框架.NET Framework 4.7.2WPF 桌面应用
PLC 通信S7netplus 0.20.0西门子 S7 协议库
UI 框架WPF现代化界面设计

二、项目结构

S7DataCollector/ ├── Config/ │ └── AppSettings.cs # 集中配置读取(App.config + 默认值) ├── Models/ │ └── DataRecord.cs # 采集记录模型 ├── Services/ │ ├── CsvLogger.cs # 自动/手动 CSV 写入器 │ ├── ErrorLogger.cs # 错误日志写入器 │ ├── PlcService.cs # PLC 通信服务封装 │ └── SecondCsvWriter.cs # 秒级 CSV 异步写入器 ├── App.xaml / App.xaml.cs # 应用入口与全局异常处理 ├── MainWindow.xaml / MainWindow.xaml.cs # 主窗口与业务逻辑 ├── App.config # 运行时配置 └── S7DataCollector.csproj # 项目配置

三、核心架构

3.1 单路采集设计

程序采用单路采集架构,即只有一个定时器负责读取 PLC,读取结果同时供给三个消费端:

UiTimer (1秒) ──→ Read PLC ──→ ① UI 实时显示 ──→ ② 周期最低值累加器 ──→ ③ 秒级 CSV 入队

这种设计确保了:

  • 每秒只读取一次 PLC,降低通信负荷
  • 所有消费端使用同一数据源,避免数据不一致
  • 读超时被压缩到刷新周期内,避免一次慢读阻塞后续采集

3.2 双定时器模型

定时器间隔职责
_uiTimerUiRefreshSeconds(默认 1 秒)唯一读 PLC 路径,更新界面和周期最低值
_logTimerIntervalMinutes(默认 10 分钟)触发自动存盘,写入 CSV 和历史表

3.3 三路 CSV 输出

程序生成三类独立的 CSV 文件:

文件类型文件名触发方式特点
自动采集DataLog_Auto_yyyyMMdd.csv定时器周期触发含瞬时值 + 周期最低值
手动采集DataLog_Manual_yyyyMMdd.csv用户点击触发独立序号,不含周期最低值
秒级采集DataLog_Second_yyyyMMdd.csv每秒入队,后台批量写异步写入,不阻塞主流程

四、配置说明

配置文件位于App.config(开发时)或S7DataCollector.exe.config(运行时)。

<?xml version="1.0" encoding="utf-8"?><!-- 运行时配置:PLC 连接与采集周期。 修改后需重新部署/覆盖 exe 同目录的 S7DataCollector.exe.config 才会生效。 业务代码通过 AppSettings 读取;键缺失时使用 AppSettings 中的默认常量。 --><configuration><startup><supportedRuntimeversion="v4.0"sku=".NETFramework,Version=v4.7.2"/></startup><appSettings><!-- ========== PLC 连接参数 ========== --><!-- PLC IP 地址 --><addkey="PlcIp"value="192.168.0.1"/><!-- 机架号(S7-1200 通常为 0) --><addkey="PlcRack"value="0"/><!-- 插槽号(S7-1200 通常为 1) --><addkey="PlcSlot"value="1"/><!-- ========== 读数地址与类型 ========== --><!-- 读取地址:MD10 = M区双字,字节偏移10;也可改为 DB1.DBD0 等 --><addkey="PlcAddress"value="MD10"/><!-- Float/Real:按 IEEE754 浮点解析(读到 DInt 位型时会自动转 Float) 其它可选:DInt / DWord / Int / Word --><addkey="PlcValueType"value="Float"/><!-- ========== 采集周期 ========== --><!-- 自动写入 CSV 的间隔(分钟);仅影响存盘,不影响界面刷新 --><addkey="IntervalMinutes"value="10"/><!-- 界面「当前 PLC 数值」刷新间隔(秒)= 单路读 PLC 周期,与 CSV 存盘无关 --><addkey="UiRefreshSeconds"value="1"/><!-- PLC 通信超时上限(毫秒);单路采集实际超时还会被压到「刷新周期-100ms」以内 --><addkey="ReadTimeoutMs"value="5000"/><!-- ========== 秒级 CSV(与 10 分钟 Auto 文件分离;采集线程只入队,后台批量写) ========== --><!-- true/false:是否启用 DataLog_Second_yyyyMMdd.csv --><addkey="EnableSecondLog"value="true"/><!-- 有界队列容量(约等于可缓冲秒数);写不过来时丢最旧并计数 --><addkey="SecondLogQueueCapacity"value="600"/><!-- 单次最多刷多少行 --><addkey="SecondLogBatchSize"value="50"/><!-- 最大刷盘间隔(毫秒);与 BatchSize 任一触发即写 --><addkey="SecondLogFlushIntervalMs"value="1000"/></appSettings></configuration>

4.1 PLC 连接参数

配置项默认值说明
PlcIp192.168.0.1PLC IP 地址
PlcRack0机架号(S7-1200 通常为 0)
PlcSlot1插槽号(S7-1200 通常为 1)
usingSystem;usingSystem.Globalization;usingS7.Net;usingS7DataCollector.Config;namespaceS7DataCollector.Services{/// <summary>/// PLC 通信服务:封装 S7netplus,负责与西门子 S7-1200 的连接、读数、断开。/// <para>/// 默认读取 M 区双字地址 MD10;数值类型由 <see cref="AppSettings.PlcValueType"/> 决定///(Float/Real 时按 IEEE754 解释 32 位原始数据)。/// </para>/// <para>本类方法均为线程安全(内部 <c>_sync</c> 锁);调用方仍建议串行化对同一实例的访问。</para>/// </summary>publicsealedclassPlcService:IDisposable{/// <summary>保护 _plc 实例的互斥锁</summary>privatereadonlyobject_sync=newobject();/// <summary>S7netplus 底层 PLC 对象;未连接或已断开时为 null</summary>privatePlc_plc;privatebool_disposed;/// <summary>PLC IP 地址</summary>publicstringIpAddress{get;}/// <summary>机架号(S7-1200 通常为 0)</summary>publicshortRack{get;}/// <summary>插槽号(S7-1200 通常为 1)</summary>publicshortSlot{get;}/// <summary>读取地址,如 MD10、MW10、DB1.DBW0</summary>publicstringAddress{get;}/// <summary>数值类型名:Float / Real / DInt / DWord / Int 等</summary>publicstringValueTypeName{get;}/// <summary>读写超时(毫秒)</summary>publicintReadTimeoutMs{get;}/// <summary>当前是否已与 PLC 建立连接</summary>publicboolIsConnected{get{lock(_sync){return_plc!=null&&_plc.IsConnected;}}}/// <summary>/// 创建 PLC 服务。参数为 null 时从 <see cref="AppSettings"/> 读取默认配置。/// </summary>publicPlcService(stringip=null,short?rack=null,short?slot=null,stringaddress=null,stringvalueType=null,int?readTimeoutMs=null){IpAddress=string.IsNullOrWhiteSpace(ip)?AppSettings.PlcIp:ip.Trim();Rack=rack??AppSettings.PlcRack;Slot=slot??AppSettings.PlcSlot;Address=string.IsNullOrWhiteSpace(address)?AppSettings.PlcAddress:address.Trim();ValueTypeName=string.IsNullOrWhiteSpace(valueType)?AppSettings.PlcValueType:valueType.Trim();ReadTimeoutMs=readTimeoutMs??AppSettings.ReadTimeoutMs;}/// <summary>/// 连接到 PLC。若已有连接会先断开再重建。/// </summary>/// <exception cref="InvalidOperationException">连接失败时抛出</exception>publicvoidConnect(){ThrowIfDisposed();lock(_sync){DisconnectInternal();// CpuType.S71200:适配本项目目标 PLC(S7-1200)_plc=newPlc(CpuType.S71200,IpAddress,Rack,Slot){ReadTimeout=ReadTimeoutMs,WriteTimeout=ReadTimeoutMs};_plc.Open();if(!_plc.IsConnected){thrownewInvalidOperationException($"无法连接到 PLC:{IpAddress}(Rack={Rack}, Slot={Slot})");}}}/// <summary>/// 按配置地址读取数值(MD10 默认按 Float)。/// 若底层读到 DInt/DWord 位型,会按 IEEE754 转成 Float。/// </summary>/// <returns>转换后的 double 数值</returns>/// <exception cref="InvalidOperationException">未连接或读空值时抛出</exception>publicdoubleReadValue(){ThrowIfDisposed();lock(_sync){if(_plc==null||!_plc.IsConnected){thrownewInvalidOperationException("PLC 未连接,无法读取数据。");}varresult=_plc.Read(Address);if(result==null){thrownewInvalidOperationException($"读取{Address}返回空值。");}returnConvertByValueType(result);}}/// <summary>/// 按 <see cref="ValueTypeName"/> 将底层读结果转换为 double。/// </summary>privatedoubleConvertByValueType(objectresult){vartypeName=(ValueTypeName??"Float").Trim();// Float / Real:真空等模拟量常用,按 IEEE754 解释 32 位if(typeName.Equals("Real",StringComparison.OrdinalIgnoreCase)||typeName.Equals("Float",StringComparison.OrdinalIgnoreCase)){returnConvertDIntBitsToFloat(result);}if(typeName.Equals("DWord",StringComparison.OrdinalIgnoreCase)||typeName.Equals("UDInt",StringComparison.OrdinalIgnoreCase)){returnConvert.ToUInt32(result,CultureInfo.InvariantCulture);}if(typeName.Equals("Int",StringComparison.OrdinalIgnoreCase)||typeName.Equals("Word",StringComparison.OrdinalIgnoreCase)){returnConvert.ToInt16(result,CultureInfo.InvariantCulture);}if(typeName.Equals("DInt",StringComparison.OrdinalIgnoreCase)){returnConvert.ToInt32(result,CultureInfo.InvariantCulture);}// 未识别类型时:按 Float(DInt 位型转浮点)处理returnConvertDIntBitsToFloat(result);}/// <summary>/// 将 DInt/DWord 的 32 位原始数据按 IEEE754 解释为 Float。/// S7netplus 读 MD 区时有时返回 int/uint,需用位型重解释而非数值强转。/// </summary>privatestaticdoubleConvertDIntBitsToFloat(objectresult){if(resultisfloatf)returnf;if(resultisdoubled)returnd;if(resultisuintu)returnBitConverter.ToSingle(BitConverter.GetBytes(u),0);if(resultisinti)returnBitConverter.ToSingle(BitConverter.GetBytes(i),0);if(resultislongl)returnBitConverter.ToSingle(BitConverter.GetBytes(unchecked((int)l)),0);// 其它数值:先落到 32 位整型位型,再转 Floattry{varbits=Convert.ToInt32(result,CultureInfo.InvariantCulture);returnBitConverter.ToSingle(BitConverter.GetBytes(bits),0);}catch{returnConvert.ToSingle(result,CultureInfo.InvariantCulture);}}/// <summary>断开与 PLC 的连接(可重复调用)</summary>publicvoidDisconnect(){lock(_sync){DisconnectInternal();}}/// <summary>内部断开逻辑(调用方须已持有 _sync)</summary>privatevoidDisconnectInternal(){if(_plc==null)return;try{if(_plc.IsConnected){_plc.Close();}}catch{// 断开时忽略异常,避免掩盖上层重连逻辑}finally{_plc=null;}}privatevoidThrowIfDisposed(){if(_disposed)thrownewObjectDisposedException(nameof(PlcService));}/// <summary>释放资源:断开连接并标记已释放</summary>publicvoidDispose(){if(_disposed)return;Disconnect();_disposed=true;}}}

4.2 读数参数

配置项默认值说明
PlcAddressMD10读取地址(如 MD10, DB1.DBD0)
PlcValueTypeFloat值类型(Float/Real/DInt/DWord/Int)

4.3 采集周期

配置项默认值说明
IntervalMinutes10自动 CSV 存盘间隔(分钟)
UiRefreshSeconds1界面刷新间隔,即单路采集周期(秒)
ReadTimeoutMs5000PLC 通信超时上限(毫秒)

4.4 秒级 CSV 参数

配置项默认值说明
EnableSecondLogtrue是否启用秒级 CSV
SecondLogQueueCapacity600有界队列容量(约缓冲秒数)
SecondLogBatchSize50单次最多刷盘行数
SecondLogFlushIntervalMs1000最大刷盘间隔(毫秒)

五、数据流程

5.1 启动流程

App.OnStartup └── StartApplication ├── RegisterGlobalExceptionHandlers() # 注册三类异常钩子 ├── EnsureLogsDirectory() # 确保 Logs 目录存在 ├── WriteSessionStart() # 写入会话启动日志 └── new MainWindow().Show() # 显示主窗口

5.2 自动采集流程

BtnStart_Click ├── EnsurePlcConnected(forceReconnect: true) ├── StartTimers() # 启动双定时器 ├── ResetIntervalMin() # 重置周期最低值累加器 ├── RefreshRealtimeAsync() # 立即刷新界面(写入缓存) └── SaveAutoSampleAsync() # 立即记一条自动 CSV ┌── UiTimer_Elapsed (每秒触发) │ └── RefreshRealtimeAsync() │ ├── Read PLC │ ├── PublishSample() │ │ ├── UpdateLastSample() # 更新缓存 │ │ ├── UpdateIntervalMin() # 更新周期最低值 │ │ └── SecondCsvWriter.TryEnqueue() # 秒级入队 │ └── 更新 UI └── LogTimer_Elapsed (每10分钟触发) └── SaveAutoSampleAsync() ├── TryGetFreshInPeriodSample() # 取新鲜缓存瞬时值 ├── TakeAndClearIntervalMin() # 结算周期最低值 ├── CsvLogger.Append() # 写入 CSV ├── AddHistoryBounded() # 追加历史表 └── BeginNewPeriod() # 推进周期起点

5.3 周期最低值机制

程序在每个自动采集周期内(默认 10 分钟)维护一个最低值:

  1. 采样阶段:每秒读取的数值与当前周期最低值比较,保留较小值
  2. 结算阶段:自动存盘时取出周期最低值,清空累加器
  3. 回滚机制:若写盘失败,已取出的最低值会合并回当前周期
  4. 周期边界:拒绝上周期遗留的陈旧采样,避免边界低压点污染

六、并发设计

6.1 同步原语

锁对象用途说明
_plcLockPLC 读写互斥所有 PlcService 调用须在此锁内
_logGate存盘互斥使用SemaphoreSlim,支持 async/await
_intervalMinLock周期最低值保护保护_intervalMinValue,_intervalMinTime,_periodStart
_lastSampleLock采样缓存保护保护_lastValue,_lastStamp
_uiRefreshBusy界面刷新防重入使用Interlocked.CompareExchange

6.2 秒级 CSV 异步写入

SecondCsvWriter采用生产者-消费者模式

  • 采集线程:只调用TryEnqueue()非阻塞入队
  • 后台工作线程:按批(BatchSize)或按间隔(FlushIntervalMs)批量刷盘
  • 有界队列:队列满时丢弃最旧数据,不反压采集线程

七、错误处理与容错

7.1 全局异常防护

程序注册了三类未处理异常钩子,全部记录日志并尽量阻止进程退出:

异常类型处理方式
DispatcherUnhandledException记日志,标记Handled = true
AppDomain.UnhandledException记日志,尽力阻止终止
TaskScheduler.UnobservedTaskException记日志,调用SetObserved()

7.2 自动存盘失败处理

  • 存盘冲突:自动采集标记延后补写(_pendingAutoSave),不丢周期
  • 写盘失败:回滚已取出的周期最低值,预约重试(_autoSaveRetryAfterTicks
  • 重试触发:由下次秒级刷新路径顺带触发补写

7.3 PLC 通信异常处理

  • 连接失败:进入周期重试模式,由秒级定时器持续尝试重连
  • 连续失败计数:记录连续失败次数,用于状态提示
  • 安全断开:断开时忽略异常,避免掩盖上层重连逻辑

八、文件输出格式

8.1 自动采集 CSV

Id,Data,Time,Source,MinData,MinTime 1,1.23456,2026-07-16 10:00:00,Auto,1.12345,2026-07-16 10:05:30 2,1.34567,2026-07-16 10:10:00,Auto,1.23456,2026-07-16 10:12:15

8.2 手动采集 CSV

Id,Data,Time,Source 1,1.45678,2026-07-16 10:03:00,Manual 2,1.56789,2026-07-16 10:08:00,Manual

8.3 秒级采集 CSV

Id,Data,Time 1,1.23456,2026-07-16 10:00:00 2,1.23457,2026-07-16 10:00:01 3,1.23455,2026-07-16 10:00:02

九、界面布局

主窗口自上而下分为五个区域:

区域内容功能
通信状态区指示灯 + 状态文字 + 配置摘要显示连接状态和当前配置
实时数据区大字号数值 + 采样时间实时显示 PLC 读数
历史表格区DataGrid 绑定ObservableCollection<DataRecord>显示采集历史记录
控制按钮区启动采集 / 停止采集 / 手动采集一次控制采集流程
路径+状态栏文件路径提示 + 运行状态消息显示输出路径和运行状态

十、运行说明

10.1 启动方式

方式一:直接运行(最快)

双击:S7DataCollector\bin\Release\net472\S7DataCollector.exe

方式二:Visual Studio

打开 S7DataCollector.sln → F5(调试)或 Ctrl+F5(直接运行)

方式三:命令行

cd"S7DataCollector"dotnet run-cRelease

10.2 配置修改注意事项

  1. 修改S7DataCollector.exe.config(运行时)而非源码中的App.config
  2. 修改后必须关闭程序再重新打开,配置才会生效
  3. PLC 需允许 PUT/GET 访问,电脑防火墙需放行
  4. 连不上时查看界面红色状态灯和Logs\ErrorLog.txt

10.3 部署要求

  • 目标机器需安装 .NET Framework 4.7.2 或更高版本
  • 电脑与 PLC 需在同一网段
  • 建议先 ping 通 PLC IP 再启动程序

十一、设计要点总结

  1. 单路采集:一个定时器读 PLC,多路消费,降低通信负荷
  2. 缓存策略:使用缓存瞬时值避免边界再读 PLC,新鲜度阈值 2 秒
  3. 周期最低值:半开区间累加,写盘失败回滚,边界不污染
  4. 异步秒级写入:有界队列 + 后台批量写,不阻塞主流程
  5. 异常防护:全局异常钩子 + 自动重试 + 状态恢复,支持长期无人值守
  6. 资源管理:完整的IDisposable实现,窗口关闭时释放所有资源
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 16:59:33

2026年口碑好的白钢雕塑,哪家才是专业之选?

在白钢雕塑市场繁荣发展的2026年&#xff0c;客户在选择时往往会面临众多困惑&#xff0c;不知道哪家才是真正专业的选择。下面&#xff0c;我们就为大家深入剖析一些值得考虑的白钢雕塑厂家&#xff0c;并重点推荐奕锦不锈钢雕塑。奕锦不锈钢雕塑&#xff1a;品质与诚信的典范…

作者头像 李华
网站建设 2026/7/18 16:51:17

2026小红书去水印下载工具哪个好?APP网页工具对比与合规指南

日常整理个人小红书素材、备份自有作品、处理已授权学习素材时&#xff0c;去水印下载是高频刚需。2026年各类小红书去水印工具层出不穷&#xff0c;涵盖独立APP、网页在线工具、轻量小程序等多种形态&#xff0c;但不同工具的安全性、稳定性、画质保留效果差异极大&#xff0c…

作者头像 李华
网站建设 2026/7/18 16:50:15

openai模型性能对比

模型性能对比&#xff08;以 GPT-5.1 为基准&#xff09; 先说明一点:这些模型分属 OpenAI 不同的发布节奏——通用对话线(GPT-5.1→5.2→5.4→5.5→5.6)、编程专用线(Codex 5.1→5.2→5.3)、以及轻量线(mini/nano)。命名体系在 GPT-5.6 之后也变了(从"数字版本"变成…

作者头像 李华
网站建设 2026/7/18 16:48:11

JESD204B时钟架构设计与FPGA实现关键技巧

1. JESD204B时钟架构的核心挑战 在高速数据转换系统中&#xff0c;JESD204B协议已经成为ADC/DAC与FPGA间数据传输的事实标准。这个协议最显著的特点就是通过串行链路实现多通道同步&#xff0c;而这一切的基础就是精确的时钟分配方案。我经历过多个项目从最初时钟设计不合理导致…

作者头像 李华
网站建设 2026/7/18 16:47:16

2026年最新AI agent面试(08)_大模型训练评测

大家好&#xff0c;我是浩哥&#xff0c;这是我输出AI agent面试专题第八章&#xff0c;后面还有二期。建议加入粉丝&#xff0c;后面粉丝可见。 大模型训练 / 评测 / 推理参数 面试题型整理与标准答案 导语&#xff1a;本题型考察训练范式&#xff08;预训练 / SFT / DPO / …

作者头像 李华