西门子 PLC 与 C# 通信本教程基于项目中的SiemensPlcConnector模块从零讲解如何用 C# 连接西门子 PLC 并读写数据。目录先了解几个概念环境准备项目结构第一步连接 PLC第二步读取数据第三步写入数据S7 地址格式速查核心代码逐段讲解完整示例程序常见问题排查1. 先了解几个概念什么是 PLCPLC可编程逻辑控制器是工业现场常用的控制设备负责采集传感器信号、控制电机阀门等。什么是 S7 协议西门子 S7 系列 PLC如 S7-1200、S7-1500通过S7 通信协议与上位机你的 C# 程序交换数据底层走TCP/IP网络。整体通信流程┌─────────────┐ TCP/IP (端口 102) ┌─────────────┐ │ C# 程序 │ ◄──────────────────────► │ 西门子 PLC │ │ (上位机) │ S7 协议读写 DB/M/I/Q │ (控制器) │ └─────────────┘ └─────────────┘本模块做了什么层级说明S7.Net 库开源第三方库负责底层 S7 协议报文SiemensPlcConnector我们封装的通用类提供连接、读写、重试、释放PlcAddressHelper地址拼接工具减少手写地址出错Program.cs你的业务代码调用上述类完成具体功能2. 环境准备2.1 软件要求.NET 8 SDK或更高版本Visual Studio 2022 / VS Code / Rider 任一 IDE可选TIA Portal —— 用于查看 PLC 程序与 DB 块地址2.2 PLC 侧设置TIA Portal在 PLC 设备属性中必须勾选保护与安全 → 连接机制 → 允许来自远程对象的 PUT/GET 通信访问否则 C# 程序无法读写数据。2.3 网络要求PC 与 PLC 在同一网段例如 PC192.168.0.10PLC192.168.0.1能ping通 PLC 的 IP 地址防火墙允许 TCP 102 端口2.4 安装依赖项目已通过 NuGet 引用S7netplus首次编译时自动下载cd SiemensPlcConnector dotnet restore dotnet build3. 项目结构SiemensPlcConnector/ ├── SiemensPlcConnector.csproj # 项目配置与 NuGet 依赖 ├── SiemensPlcConnector.cs # 核心连接类重点学习 ├── PlcAddressHelper.cs # 地址拼接辅助类 ├── Program.cs # 运行示例 └── README.md # 本教程4. 第一步连接 PLC4.1 最简连接代码usingS7.Net;usingSiemens.S7Connector;// using var程序结束时自动断开连接并释放资源usingvarplcnewSiemensPlcConnector(ipAddress:192.168.0.1,// PLC 的 IP 地址改成你的实际 IPcpuType:CpuType.S71200,// CPU 型号必须与 PLC 一致rack:0,// 机架号S7-1200/1500 固定为 0slot:1);// 槽号S7-1200/1500 固定为 1// 发起连接if(plc.Connect()){Console.WriteLine(连接成功);}else{Console.WriteLine(连接失败请检查 IP 和网络。);}4.2 逐行说明代码含义using S7.Net引用第三方 S7 协议库using Siemens.S7Connector引用本项目封装的连接器using var plc ...创建连接器离开作用域时自动调用Dispose()断开连接ipAddressPLC 在局域网中的 IPCpuType.S71200告诉库这是 S7-1200 CPU不同型号握手参数不同rack: 0, slot: 1S7-1200/1500 的标准参数S7-300/400 通常slot: 2plc.Connect()建立 TCP 连接并完成 S7 握手成功返回true4.3 不同 CPU 的参数对照PLC 型号CpuTypeRackSlotS7-1200CpuType.S7120001S7-1500CpuType.S7150001S7-300CpuType.S730002S7-400CpuType.S7400025. 第二步读取数据连接成功后用地址字符串指定要读的位置。5.1 常用读取方法// 读取 BOOL开关量地址格式DB块.字节.位boolisRunningplc.ReadBool(DB1.DBX0.0);// 读取 INT16 位整数占 2 字节shortspeedplc.ReadInt(DB1.DBW2);// 读取 REAL32 位浮点数占 4 字节floattemperatureplc.ReadReal(DB1.DBD4);// 读取 DINT32 位整数intcounterplc.ReadDInt(DB1.DBD8);// 通用泛型读取上面方法的底层实现intvalueplc.Readint(DB1.DBD8);拓展——代码示例5.2 地址与数据类型对应关系假设 PLC 中 DB1 的布局如下DB1 偏移量 变量名 类型 C# 读取方法 ───────────────────────────────────────────────── 0.0 Running BOOL ReadBool(DB1.DBX0.0) 0.1 Alarm BOOL ReadBool(DB1.DBX0.1) 2 Speed INT ReadInt(DB1.DBW2) 4 Temperature REAL ReadReal(DB1.DBD4) 8 Counter DINT ReadDInt(DB1.DBD8)重要地址必须与 TIA Portal 中 DB 块的实际偏移一致否则读到的是错误数据。5.3 用辅助类拼接地址推荐手写地址容易出错可以用PlcAddressHelper// 等价于 DB1.DBX0.0stringaddrPlcAddressHelper.DbBool(dbNumber:1,byteOffset:0,bitOffset:0);boolrunningplc.ReadBool(addr);// 等价于 DB1.DBW2stringspeedAddrPlcAddressHelper.DbInt(1,2);shortspeedplc.ReadInt(speedAddr);6. 第三步写入数据写入语法与读取类似第二个参数是要写入的值。// 写入 BOOL把 DB1 第 0 字节的第 1 位设为 trueplc.Write(DB1.DBX0.1,true);// 写入 INT注意 C# 中 INT 对应 short 类型需要强制转换plc.Write(DB1.DBW2,(short)1500);// 写入 REALfloatplc.Write(DB1.DBD4,36.5f);// 写入 DINTintplc.Write(DB1.DBD8,99999);写入时的类型注意PLC 类型C# 写入类型示例BOOLbooltrue/falseINTshort(short)1500DINTint99999REALfloat36.5f7. S7 地址格式速查7.1 地址组成规则DB1.DBX0.0 │ │ │ │ │ │ │ └── 位号0~7仅 BOOL 使用 │ │ └──── 字节偏移 │ └─────── 寻址方式X位, B字节, W字(2字节), D双字(4字节) └─────────── DB 块编号7.2 常用寻址前缀前缀含义示例数据大小DBX位BoolDB1.DBX0.01 bitDBB字节DB1.DBB101 byteDBW字IntDB1.DBW02 bytesDBD双字DInt/RealDB1.DBD44 bytesM标志位M0.01 bitI输入I0.01 bitQ输出Q0.01 bit7.3 字节对齐提醒偏移 0: BOOL (占 1 字节内的若干位) 偏移 2: INT (必须从偶数偏移开始占 2 字节) → 偏移 0~1 是 BOOLINT 从 2 开始 偏移 4: REAL (占 4 字节)BOOL 虽然只占 1 个 bit但在 DB 块中通常按字节规划后面的 INT/REAL 仍需遵守对齐规则。8. 核心代码逐段讲解以下摘自SiemensPlcConnector.cs按功能块讲解帮助理解封装思路。8.1 类的基本结构publicsealedclassSiemensPlcConnector:IDisposable{privatePlc?_plc;// S7.Net 底层连接对象privatebool_isConnected;// 连接状态标志privatebool_disposed;// 是否已释放资源publicstringIpAddress{get;}// 只读属性IP 地址publicshortRack{get;}// 只读属性机架号publicshortSlot{get;}// 只读属性槽号publicCpuTypeCpuType{get;}// 只读属性CPU 型号}要点IDisposable实现用完即释放模式using var语法依赖它private字段外部不能直接操作底层_plc必须通过公开方法{ get; }只读属性连接参数创建后不允许修改8.2 构造函数 —— 保存连接参数publicSiemensPlcConnector(stringipAddress,CpuTypecpuTypeCpuType.S71200,shortrack0,shortslot1){// 防御性编程拒绝空 IPif(string.IsNullOrWhiteSpace(ipAddress))thrownewArgumentException(IP 地址不能为空。,nameof(ipAddress));IpAddressipAddress.Trim();// Trim() 去掉首尾空格CpuTypecpuType;Rackrack;Slotslot;}要点构造函数只保存参数不立即连接延迟连接用时再连cpuType CpuType.S71200是默认参数不传时默认 S7-1200throw抛出异常让调用者知道参数不合法8.3 Connect() —— 建立连接publicboolConnect(){if(IsConnected)returntrue;// 已连接则跳过try{_plcnewPlc(CpuType,IpAddress,Rack,Slot);// 创建底层对象_plc.Open();// 发起 TCP S7 握手_isConnected_plc.IsConnected;return_isConnected;}catch(Exception){_isConnectedfalse;// 失败时清理状态returnfalse;// 返回 false 而不是抛异常便于调用者判断}}初学者要点返回bool而不是抛异常连接失败是常见情况调用者可以用if判断try/catch网络问题不会导致程序崩溃8.4 ReadT() —— 泛型读取publicTReadT(stringaddress){EnsureConnected();// 读之前先确保已连接returnExecuteWithRetry(()// 包一层重试逻辑{varvalue_plc!.Read(address);// S7.Net 读取原始值if(valueisTtypedValue)// 类型已匹配则直接返回returntypedValue;return(T)Convert.ChangeType(value,typeof(T))!;// 否则强制转换});}初学者要点T是泛型同一个方法可以返回bool、int、float等不同类型EnsureConnected()若未连接会自动尝试Connect()ExecuteWithRetry网络抖动时自动重连并重试提高稳定性_plc!中的!是 null forgiving 运算符告诉编译器此处不为 null8.5 ExecuteWithRetry —— 失败重试机制privateTExecuteWithRetryT(FuncTaction){Exception?lastExceptionnull;for(intattempt0;attemptMaxRetryCount;attempt){try{returnaction();// 执行传入的读写操作}catch(Exceptionex){lastExceptionex;_isConnectedfalse;if(attemptMaxRetryCount)break;Disconnect();// 断开旧连接Connect();// 重新连接}}throwlastException??newInvalidOperationException(PLC 操作失败。);}初学者要点FuncT传入一个无参、返回 T的委托lambda 表达式默认最多重试 3 次MaxRetryCount 3每次失败后先断开再重连避免 stale 连接8.6 Dispose() —— 释放资源publicvoidDispose(){if(_disposed)return;// 防止重复释放Disconnect();// 断开 PLC 连接_plcnull;// 释放引用_disposedtrue;GC.SuppressFinalize(this);}初学者要点配合using var plc ...使用代码块结束时自动调用Dispose()工业程序必须正确释放连接否则 PLC 连接数耗尽9. 完整示例程序以下代码可直接替换Program.cs后运行修改 IP 和地址usingS7.Net;usingSiemens.S7Connector;// ── 1. 创建连接器 ──usingvarplcnewSiemensPlcConnector(ipAddress:192.168.0.1,// ← 改成你的 PLC IPcpuType:CpuType.S71200,rack:0,slot:1);// ── 2. 连接 ──if(!plc.Connect()){Console.WriteLine(❌ 连接失败请检查);Console.WriteLine( 1. IP 地址是否正确);Console.WriteLine( 2. PC 与 PLC 是否同网段);Console.WriteLine( 3. PLC 是否开启 PUT/GET 访问);return;}Console.WriteLine(✅ PLC 连接成功);try{// ── 3. 读取 ──boolrunningplc.ReadBool(DB1.DBX0.0);shortspeedplc.ReadInt(DB1.DBW2);floattempplc.ReadReal(DB1.DBD4);Console.WriteLine($运行状态:{running});Console.WriteLine($速度:{speed});Console.WriteLine($温度:{temp:F1}℃);// ── 4. 写入 ──plc.Write(DB1.DBX0.1,true);// 置位报警确认plc.Write(DB1.DBW6,(short)1200);// 写入目标速度Console.WriteLine(✅ 写入完成);}catch(Exceptionex){Console.WriteLine($❌ 读写异常:{ex.Message});}// ── 5. using 结束自动 Disconnect Dispose ──运行命令dotnet run--project SiemensPlcConnector10. 常见问题排查Q1Connect() 返回 false检查项操作IP 是否正确在 TIA Portal 或 PLC 面板查看实际 IP网络是否通ping 192.168.0.1CPU 型号CpuType必须与 PLC 一致PUT/GET 权限TIA Portal → 设备属性 → 连接机制 → 勾选允许防火墙临时关闭防火墙测试Q2连接成功但读取报错检查项操作DB 块是否存在TIA Portal 中确认 DB1 已创建且未优化或知道优化后的偏移地址偏移与 TIA Portal 中变量偏移完全一致数据类型DBW读 INTDBD读 REAL/DINT不要混用DB 块权限DB 块属性中允许 HMI/上位机访问Q3读到的数值不对检查字节序S7 为大端序S7.Net 已自动处理一般无需关心检查BOOL 位号DBX0.1是第 0 字节第 1 位不是第 1 字节检查DB 优化TIA Portal V13 默认优化的块访问地址不再从 0 连续排列需在 DB 块属性中关闭优化或查符号表偏移Q4程序退出后 PLC 连接未释放确保使用using var或手动调用plc.Dispose()// ✅ 推荐自动释放usingvarplcnewSiemensPlcConnector(192.168.0.1);plc.Connect();// ... 业务代码 ...// 离开作用域时自动 Disconnect// ✅ 也可以手动释放varplcnewSiemensPlcConnector(192.168.0.1);try{plc.Connect();// ... 业务代码 ...}finally{plc.Dispose();}学习路线建议第 1 步跑通 Program.cs确认能连接 PLC ↓ 第 2 步在 TIA Portal 建一个 DB1添加 BOOL/INT/REAL 变量 ↓ 第 3 步用 ReadBool / ReadInt / ReadReal 逐个读取核对数值 ↓ 第 4 步用 Write 写入在 TIA Portal 在线监控中观察变化 ↓ 第 5 步阅读 SiemensPlcConnector.cs 源码理解封装思路 ↓ 第 6 步在自己的 WinForms/WPF 项目中引用本模块参考资源S7netplus GitHub —— 底层 S7 协议库文档西门子 S7 地址寻址手册 —— 官方地址格式说明