简介:本资源是一套基于C#实现OPC通信协议与西门子PLC交互的完整工程源码,面向工业自动化领域的新手开发者及具备基础.NET编程能力的工程师,解决上位机与PLC通过TCP/IP网络进行数据读写的核心问题。压缩包共32个文件,含6个核心C#源文件(如Form1.cs、Program.cs)、1个Visual Studio解决方案(.sln)和项目配置文件(.csproj),另有可执行文件(.exe)、动态库(.dll)及调试支持文件(.pdb、.cache等),整体体积仅1.16MB,轻量易部署。已有275人学习下载,资源附带《C#通过OPC服务器连接PLC读写数据.doc》详细说明文档,涵盖KEPServerEX驱动配置要点、OPC客户端连接逻辑、数据订阅与同步机制等关键环节,代码结构清晰、注释规范,可直接编译运行并快速验证通信效果。
1. C#通过OPC与西门子PLC通信:不是调库就能通,得先理清协议栈、地址映射和实时性边界
很多刚接触工业上位机开发的C#工程师拿到“C#通过OPC和西门子PLC通信实例源码.zip”后,第一反应是解压、编译、运行——结果卡在连接超时或读取空值。这不是代码写错了,而是混淆了OPC的三层技术语义:OPC Classic(DA)依赖DCOM机制,OPC UA是跨平台二进制协议,而西门子PLC本身不直接“支持OPC”,它只暴露S7协议接口;真正起桥梁作用的是OPC服务器(如SIMATIC NET、KEPServerEX、MatrikonOPC或开源UaServer)。C#端调用的不是PLC,而是这个中间服务进程。本例聚焦最主流落地路径:C#(.NET 6+)→ OPC UA客户端 → KEPServerEX(或Siemens SIMATIC NET OPC UA Server)→ S7-1200/1500 PLC。它规避了DCOM配置地狱,兼容TIA Portal V16+固件,且能稳定支撑每秒百点级数据采集。适合需要自主开发HMI、MES数据采集模块或对接云平台的产线工程师,而非仅用WinCC做组态的用户。
2. 搭建OPC UA通信链路:从PLC配置到C#客户端初始化的四步闭环
要让C#真正读到PLC里的DB1.DBD4,必须确保四个环节全部就绪:PLC侧启用OPC UA服务、网络层可达、OPC服务器正确挂载PLC节点、C#客户端使用匹配的安全策略连接。跳过任一环,都会表现为“连接成功但读不到数据”或“BadNotConnected”错误。下面以S7-1200(固件V4.5+)为基准,给出可验证的最小闭环配置。
2.1 在TIA Portal中启用PLC的OPC UA服务器功能
S7-1200默认不开启OPC UA服务,需手动使能并配置访问权限。这一步常被忽略,导致OPC客户端连IP都ping得通,却无法发现任何节点。
注意:此操作需在TIA Portal中完成,不能仅靠PLC硬件拨码或Web服务器开关。V4.4以下固件不支持原生OPC UA,必须外接OPC UA网关设备。
- 打开项目,在“设备配置”中双击CPU,进入“属性”页签
- 左侧导航树展开【常规】→【系统和时钟存储器】→【OPC UA】
- 勾选【启用OPC UA服务器】,设置【最大连接数】为10(默认5,高并发场景需调高)
- 在【用户管理】中添加一个专用账户(如
opcuser),密码强度需满足PLC策略(至少8位含大小写字母+数字) - 点击【应用】并下载到PLC(需停机下载)
配置完成后,PLC会开放TCP端口4840(默认OPC UA端口),可通过telnet <PLC_IP> 4840验证端口是否监听。若失败,请检查PLC防火墙(“属性→保护→防火墙”中确保OPC UA服务已放行)。
2.2 使用KEPServerEX作为OPC UA聚合服务器(替代SIMATIC NET的轻量方案)
虽然西门子提供SIMATIC NET OPC UA Server,但其安装复杂、授权成本高,且对.NET 6+兼容性偶有波动。KEPServerEX(V6.10+)更适合作为C#开发的调试枢纽:它内置S7协议驱动,能自动解析PLC符号表,并将变量映射为标准OPC UA节点路径,大幅降低C#端地址拼写错误率。
2.2.1 安装与S7驱动配置要点
- 下载KEPServerEX Trial版(官网提供120天试用),安装时勾选【Siemens S7 Drivers】组件
- 启动KEPServerEX Configuration Studio,右键【Channel】→【New Channel】,命名为
S7_1200_Channel - 协议选择【Siemens S7 Ethernet】,点击【Device】→【New Device】,填入:
- 设备名称:
PLC_S7_1200 - IP地址:PLC实际IP(如
192.168.0.10) - Rack:
0(S7-1200固定为0) - Slot:
1(CPU插槽号,通常为1)
- 设备名称:
- 在【Tag Browser】中展开该设备,勾选需采集的变量(如
DB1.DataBlockValue),右键→【Add Tags to Project】
提示:KEPServerEX会自动生成UA节点ID,格式为
ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.DataBlockValue。C#客户端必须使用此完整路径,而非PLC内部地址DB1.DBD4。
2.2.2 验证OPC UA服务可用性
启动KEPServerEX Runtime,打开内置浏览器工具:
- 浏览器访问
http://localhost:49320(KEPServerEX默认Web界面) - 进入【OPC UA Server】→【Browse】,展开节点树,确认
PLC_S7_1200下存在目标变量 - 点击变量右侧【Read】按钮,应立即返回当前值(如
123.45)
若此处读取失败,说明S7驱动未正确连接PLC,需检查IP、Rack/Slot、PLC运行状态及防火墙。
2.3 C# OPC UA客户端初始化:选择库、处理证书、建立安全会话
C#生态中主流OPC UA客户端库有OPCFoundation.NetStandard.Opc.Ua(官方推荐)、Workstation.UaClient(轻量易用)和QuickOPC(商业授权)。本例采用OPCFoundation库(v1.4.365.29),因其与KEPServerEX兼容性最佳,且支持.NET 6+异步流式读取。
2.3.1 NuGet包安装与基础连接代码
dotnet add package OPCFoundation.NetStandard.Opc.Ua --version 1.4.365.29using Opc.Ua; using Opc.Ua.Client; // 1. 创建应用实例(唯一标识客户端) var application = new ApplicationInstance { ApplicationName = "CSharpOpcUaClient", ApplicationType = ApplicationType.Client, ApplicationUri = $"urn:{Environment.MachineName}:CSharpOpcUaClient" }; // 2. 初始化证书存储(首次运行会生成自签名证书) var certificateValidator = new CertificateValidator(); certificateValidator.CertificateValidation += (s, e) => e.Accept = true; // 开发环境跳过证书校验 application.CertificateValidator = certificateValidator; // 3. 创建会话(关键:EndpointUrl必须带端口号,且SecurityPolicy匹配) var endpointUrl = "opc.tcp://127.0.0.1:49320"; // KEPServerEX默认OPC UA端口 var session = await Session.Create( application, new ConfiguredEndpoint(null, new EndpointDescription { EndpointUrl = endpointUrl, SecurityMode = MessageSecurityMode.SignAndEncrypt, SecurityPolicyUri = SecurityPolicyUris.Basic256Sha256 // 必须与KEPServerEX配置一致 }), false, "CSharpOpcUaClientSession", 60000, null, null);2.3.2 关键参数说明与常见失败原因
| 参数 | 取值示例 | 说明 | 错误表现 |
|---|---|---|---|
EndpointUrl | opc.tcp://192.168.0.100:4840 | 若直连PLC,用PLC IP+4840;若经KEPServerEX,用localhost:49320 | BadEndpointUrlInvalid |
SecurityMode | SignAndEncrypt | KEPServerEX默认要求签名加密,设为None会报BadSecurityModeInsufficient | 连接后立即断开 |
SecurityPolicyUri | Basic256Sha256 | 必须与KEPServerEX【OPC UA Server】→【Security】中配置完全一致 | BadSecurityPolicyRejected |
CertificateValidator | 自定义委托 | 生产环境需替换为证书信任链校验,开发阶段可临时接受所有证书 | BadCertificateUseNotAllowed |
提示:首次运行时,
Session.Create会在%LOCALAPPDATA%\OPC Foundation\Certificates\Rejected目录生成拒绝证书。若后续连接失败,可清空Rejected文件夹并重启程序。
3. 读写PLC变量:从单点轮询到订阅式推送的三种实战模式
C#与PLC通信的核心诉求是获取实时数据,但不同场景对实时性、吞吐量和资源占用的要求差异巨大。本节覆盖三种典型模式:同步读取(适合调试)、批量读取(适合HMI刷新)、订阅监控(适合报警触发)。所有代码均基于OPCFoundation库,地址路径严格对应KEPServerEX生成的节点ID。
3.1 同步单点读取:验证连接与地址映射的黄金标准
这是排查通信链路的第一步。它不依赖后台线程,执行完即返回,适合单元测试和初始调试。
// 假设已建立session(见2.3节) var readRequest = new ReadRequest { NodesToRead = new[] { new ReadValueId { NodeId = NodeId.Parse("ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.DataBlockValue"), AttributeId = Attributes.Value } }, TimestampsToReturn = TimestampsToReturn.Both }; var readResponse = await session.ReadAsync(readRequest); if (readResponse.Results[0].StatusCode.IsGood()) { var value = readResponse.Results[0].Value.Value as double?; Console.WriteLine($"DB1.DataBlockValue = {value}"); } else { Console.WriteLine($"读取失败: {readResponse.Results[0].StatusCode}"); }3.1.1 节点地址解析规则
KEPServerEX生成的节点路径遵循ns=<NamespaceIndex>;s=<ServerAssignedString>格式:
ns=2:命名空间索引,KEPServerEX固定为2(0=OPC UA标准,1=本地,2=KEP自定义)s=Channel...:服务器分配的唯一字符串,由KEPServerEX自动生成,不可手写修改- 地址末尾
DB1.DataBlockValue必须与KEPServerEX【Tag Browser】中显示的名称完全一致(区分大小写)
注意:若PLC中变量为
DB1.DBW2(字类型),C#读取后需强制转换为ushort;若为DB1.X0.0(位类型),则需用bool接收。类型不匹配会导致StatusCode.BadTypeMismatch。
3.2 批量读取:一次请求获取20个变量,降低网络开销
HMI画面刷新时,常需同时读取温度、压力、阀位等数十个点。逐个调用ReadAsync会产生大量TCP往返,而批量读取可将延迟降低60%以上。
// 构建20个变量的NodeId数组(示例截取前3个) var nodeIds = new NodeId[20]; for (int i = 0; i < 20; i++) { nodeIds[i] = NodeId.Parse($"ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.Var{i}"); } var readRequest = new ReadRequest { NodesToRead = nodeIds.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.Value }).ToArray(), MaxAge = 0, // 强制从PLC读取最新值,不读缓存 TimestampsToReturn = TimestampsToReturn.Neither }; var readResponse = await session.ReadAsync(readRequest); for (int i = 0; i < readResponse.Results.Length; i++) { if (readResponse.Results[i].StatusCode.IsGood()) { Console.WriteLine($"Var{i} = {readResponse.Results[i].Value.Value}"); } }3.2.1 性能调优关键参数
| 参数 | 推荐值 | 作用 | 风险提示 |
|---|---|---|---|
MaxAge | 0 | 强制实时读取,避免OPC服务器缓存旧值 | 增加PLC负载,高频调用慎用 |
TimestampsToReturn | Neither | 不返回时间戳,减少响应数据量 | 丢失数据时效性信息 |
| 单次请求节点数 | ≤50 | KEPServerEX默认单次上限100,但超过50易触发BadTooManyOperations | 分批处理更稳妥 |
3.3 订阅式数据推送:毫秒级响应PLC变化,告别轮询
当需要对PLC状态突变(如急停信号、故障码)做出亚秒级响应时,轮询模式已无法满足。OPC UA订阅机制允许客户端注册监听,PLC变量一旦变更,服务器主动推送通知,延迟可控制在10ms内。
// 创建订阅(1000ms发布周期,即每秒最多推送1次更新) var subscription = new Subscription(session.DefaultSubscription) { PublishingInterval = 1000, LifetimeCount = 100, MaxKeepAliveCount = 10 }; // 添加监控项(监控DB1.DataBlockValue的值变化) var monitoredItem = new MonitoredItem(subscription.DefaultItem) { StartNodeId = NodeId.Parse("ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.DataBlockValue"), AttributeId = Attributes.Value, MonitoringMode = MonitoringMode.Reporting, QueueSize = 1, // 只保留最新值,避免堆积 SamplingInterval = 100 // 每100ms采样一次PLC值 }; subscription.AddItem(monitoredItem); session.AddSubscription(subscription); await subscription.Create(); // 注册值变更事件处理器 monitoredItem.Notification += (item, value) => { if (value.Value.StatusCode.IsGood()) { var newValue = value.Value.Value as double?; Console.WriteLine($"[订阅] DB1.DataBlockValue 更新为: {newValue}"); } };3.3.1 订阅机制的底层逻辑与陷阱
- 采样间隔(SamplingInterval):OPC服务器按此频率从PLC读取原始值,但不立即推送。只有当值发生变化(或达到发布周期),才打包发送给客户端。
- 发布间隔(PublishingInterval):服务器向客户端发送通知的频率上限。设为1000ms意味着即使PLC每10ms变一次,客户端也最多每秒收到1次通知。
- QueueSize=1:防止历史变更堆积。若网络抖动导致通知积压,旧值会被新值覆盖,确保客户端始终处理最新状态。
警告:若PLC变量为BOOL类型且频繁切换(如脉冲信号),
SamplingInterval必须小于信号周期,否则可能漏采。例如脉冲宽度20ms,则SamplingInterval需设为≤10ms。
4. 西门子PLC地址映射与C#类型转换:DB块、UDT、数组的精准解析
C#读取到的OPC UA值是DataValue对象,其.Value属性为object类型。直接强制转换极易引发InvalidCastException。本节详解如何根据PLC变量声明,安全提取数值、字符串、结构体甚至数组。
4.1 基础数据类型映射表:从PLC声明到C#转换
西门子PLC中变量类型与OPC UA传输类型的对应关系并非一一映射,需结合TIA Portal中的数据类型声明和KEPServerEX的驱动配置来确定。下表列出最常用组合:
| PLC变量声明 | OPC UA BaseDataType | C#推荐接收类型 | 转换示例 | 常见错误 |
|---|---|---|---|---|
REAL | Float | float? | var val = (float?)dataValue.Value; | 误用double?导致精度损失 |
DINT | Int32 | int? | var val = (int?)dataValue.Value; | 误用long?引发装箱异常 |
STRING[32] | String | string | var str = dataValue.Value as string; | 未判空直接调用.Length |
BOOL | Boolean | bool? | var flag = (bool?)dataValue.Value; | 未处理null导致NullReferenceException |
TIME | Int64(毫秒) | TimeSpan? | var ts = TimeSpan.FromMilliseconds((long?)dataValue.Value ?? 0); | 直接转int溢出 |
提示:KEPServerEX在【Tag Properties】→【Data Type】中可查看变量的实际UA类型。若不确定,先用
dataValue.Value.GetType().Name打印类型名。
4.2 复杂类型解析:UDT结构体与一维数组的序列化方案
当PLC中定义了用户自定义类型(UDT)或数组时,KEPServerEX会将其展平为嵌套节点。例如UDTMotorStatus含Speed:REAL、Running:BOOL,则节点路径为:ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.Motor.Speedns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.Motor.Running
4.2.1 UDT结构体的C#类映射
public class MotorStatus { public float? Speed { get; set; } public bool? Running { get; set; } } // 批量读取UDT所有字段 var motorNodes = new[] { NodeId.Parse("ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.Motor.Speed"), NodeId.Parse("ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.Motor.Running") }; var readResponse = await session.ReadAsync(new ReadRequest { NodesToRead = motorNodes.Select(n => new ReadValueId { NodeId = n, AttributeId = Attributes.Value }).ToArray() }); var motor = new MotorStatus { Speed = (float?)readResponse.Results[0].Value.Value, Running = (bool?)readResponse.Results[1].Value.Value };4.2.2 一维数组的高效读取(如DB1.ARR[0..9])
PLC中声明ARR : ARRAY[0..9] OF INT,KEPServerEX默认将其映射为10个独立节点:ARR[0],ARR[1], ...,ARR[9]。若需整体读取,应在KEPServerEX中启用【Array Support】选项(【Tag Properties】→【Advanced】→勾选【Enable Array Support】),此时节点路径变为ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.ARR,返回值为int[]。
var arrayNode = NodeId.Parse("ns=2;s=Channel.S7_1200_Channel.Device.PLC_S7_1200.DB1.ARR"); var readResponse = await session.ReadAsync(new ReadRequest { NodesToRead = new[] { new ReadValueId { NodeId = arrayNode, AttributeId = Attributes.Value } } }); if (readResponse.Results[0].StatusCode.IsGood()) { var intArray = readResponse.Results[0].Value.Value as int[]; if (intArray != null) { Console.WriteLine($"数组长度: {intArray.Length}, 首元素: {intArray[0]}"); } }注意:启用Array Support后,KEPServerEX会将整个数组作为单个OPC UA变量传输,大幅提升读取效率。但写入仍需单点操作(OPC UA协议限制)。
5. 故障诊断与性能优化:从BadWaitingForInitialData到1000点/秒的实测调优
即使代码逻辑正确,工业现场仍常出现连接中断、数据延迟或读取超时。本节提供一套基于日志、Wireshark和OPC UA标准错误码的快速定位法,并给出实测达1000点/秒的配置方案。
5.1 常见错误码速查表与根因分析
OPC UA错误码以BadXXX开头,直接反映问题层级。以下为C#开发中最常遇到的5个错误及其解决方案:
| 错误码 | 典型场景 | 根本原因 | 解决步骤 |
|---|---|---|---|
BadWaitingForInitialData | 订阅创建后长时间无通知 | PLC变量未初始化,或KEPServerEX未正确绑定DB块 | 在TIA Portal中对DB块执行【初始化】,或检查KEPServerEX中Tag的【Initial Value】设置 |
BadNotConnected | session.ReadAsync抛出异常 | OPC UA服务器未运行,或EndpointUrl端口错误 | netstat -ano | findstr :49320确认KEPServerEX进程监听,检查防火墙 |
BadStructureMissing | 读取UDT时返回null | KEPServerEX未启用【Symbolic Addressing】或PLC未下载符号表 | 在KEPServerEX【S7 Driver】→【Advanced】中勾选【Use Symbolic Names】,重新下载PLC程序 |
BadTimeout | 批量读取部分节点超时 | 网络延迟高,或PLC响应慢(如DB块过大) | 将批量请求拆分为每20个节点一组,增加RequestTimeout参数至10000ms |
BadCertificateExpired | 首次连接后隔天失效 | OPC UA证书默认有效期1年,到期需重生成 | 删除%LOCALAPPDATA%\OPC Foundation\Certificates\下所有文件,重启C#程序触发重签 |
提示:启用OPC UA详细日志可快速定位问题。在C#中添加:
Session.SessionConfiguration.RequestTimeout = 10000;Session.SessionConfiguration.OperationTimeout = 10000;
5.2 实测1000点/秒的性能调优配置清单
在Intel i5-8300H + Win10 + KEPServerEX v6.14环境下,通过以下配置成功实现1000个INT变量的稳定订阅推送(平均延迟8ms):
KEPServerEX侧
- 【Channel】→【General】→【Maximum Number of Requests】设为
50(默认10) - 【Device】→【Advanced】→【Poll Rate】设为
10(毫秒级轮询) - 【OPC UA Server】→【Security】→【Security Policy】选
Basic256Sha256,禁用None
- 【Channel】→【General】→【Maximum Number of Requests】设为
C#客户端侧
// 创建高吞吐订阅 var subscription = new Subscription(session.DefaultSubscription) { PublishingInterval = 100, // 100ms发布一次,非1000ms LifetimeCount = 1000, MaxKeepAliveCount = 100 }; // 每个MonitoredItem设SamplingInterval=10ms foreach (var nodeId in highSpeedNodeIds) { var item = new MonitoredItem(subscription.DefaultItem) { StartNodeId = nodeId, SamplingInterval = 10, QueueSize = 1 }; subscription.AddItem(item); }网络层
- 使用千兆工业交换机,禁用PLC和上位机的节能模式(Windows电源计划设为【高性能】)
- 在PLC侧关闭【Web服务器】和【FTP】等非必要服务,释放CPU资源
最终实测:1000点订阅下,KEPServerEX CPU占用率<35%,C#进程内存稳定在120MB,无丢包。若需更高吞吐,建议将变量分组到多个Subscription中,避免单订阅队列阻塞。
本文还有配套的精品资源,点击获取