西门子 PLC 与 C# 通信
西门子 PLC 与 C# 通信
本教程基于项目中的
SiemensPlcConnector模块,从零讲解如何用 C# 连接西门子 PLC 并读写数据。
目录
- 先了解几个概念
- 环境准备
- 项目结构
- 第一步:连接 PLC
- 第二步:读取数据
- 第三步:写入数据
- S7 地址格式速查
- 核心代码逐段讲解
- 完整示例程序
- 常见问题排查
1. 先了解几个概念
什么是 PLC?
PLC(可编程逻辑控制器)是工业现场常用的控制设备,负责采集传感器信号、控制电机阀门等。
什么是 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 在同一网段(例如 PC
192.168.0.10,PLC192.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. 第一步:连接 PLC
4.1 最简连接代码
usingS7.Net;usingSiemens.S7Connector;// using var:程序结束时自动断开连接并释放资源usingvarplc=newSiemensPlcConnector(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()断开连接 |
ipAddress | PLC 在局域网中的 IP |
CpuType.S71200 | 告诉库这是 S7-1200 CPU,不同型号握手参数不同 |
rack: 0, slot: 1 | S7-1200/1500 的标准参数;S7-300/400 通常slot: 2 |
plc.Connect() | 建立 TCP 连接并完成 S7 握手,成功返回true |
4.3 不同 CPU 的参数对照
| PLC 型号 | CpuType | Rack | Slot |
|---|---|---|---|
| S7-1200 | CpuType.S71200 | 0 | 1 |
| S7-1500 | CpuType.S71500 | 0 | 1 |
| S7-300 | CpuType.S7300 | 0 | 2 |
| S7-400 | CpuType.S7400 | 0 | 2 |
5. 第二步:读取数据
连接成功后,用地址字符串指定要读的位置。
5.1 常用读取方法
// 读取 BOOL(开关量),地址格式:DB块.字节.位boolisRunning=plc.ReadBool("DB1.DBX0.0");// 读取 INT(16 位整数),占 2 字节shortspeed=plc.ReadInt("DB1.DBW2");// 读取 REAL(32 位浮点数),占 4 字节floattemperature=plc.ReadReal("DB1.DBD4");// 读取 DINT(32 位整数)intcounter=plc.ReadDInt("DB1.DBD8");// 通用泛型读取(上面方法的底层实现)intvalue=plc.Read<int>("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.0"stringaddr=PlcAddressHelper.DbBool(dbNumber:1,byteOffset:0,bitOffset:0);boolrunning=plc.ReadBool(addr);// 等价于 "DB1.DBW2"stringspeedAddr=PlcAddressHelper.DbInt(1,2);shortspeed=plc.ReadInt(speedAddr);6. 第三步:写入数据
写入语法与读取类似,第二个参数是要写入的值。
// 写入 BOOL:把 DB1 第 0 字节的第 1 位设为 trueplc.Write("DB1.DBX0.1",true);// 写入 INT:注意 C# 中 INT 对应 short 类型,需要强制转换plc.Write("DB1.DBW2",(short)1500);// 写入 REAL(float)plc.Write("DB1.DBD4",36.5f);// 写入 DINT(int)plc.Write("DB1.DBD8",99999);写入时的类型注意
| PLC 类型 | C# 写入类型 | 示例 |
|---|---|---|
| BOOL | bool | true/false |
| INT | short | (short)1500 |
| DINT | int | 99999 |
| REAL | float | 36.5f |
7. S7 地址格式速查
7.1 地址组成规则
DB1.DBX0.0 │ │ │ │ │ │ │ └── 位号(0~7),仅 BOOL 使用 │ │ └──── 字节偏移 │ └─────── 寻址方式:X=位, B=字节, W=字(2字节), D=双字(4字节) └─────────── DB 块编号7.2 常用寻址前缀
| 前缀 | 含义 | 示例 | 数据大小 |
|---|---|---|---|
DBX | 位(Bool) | DB1.DBX0.0 | 1 bit |
DBB | 字节 | DB1.DBB10 | 1 byte |
DBW | 字(Int) | DB1.DBW0 | 2 bytes |
DBD | 双字(DInt/Real) | DB1.DBD4 | 4 bytes |
M | 标志位 | M0.0 | 1 bit |
I | 输入 | I0.0 | 1 bit |
Q | 输出 | Q0.0 | 1 bit |
7.3 字节对齐提醒
偏移 0: BOOL (占 1 字节内的若干位) 偏移 2: INT (必须从偶数偏移开始,占 2 字节) → 偏移 0~1 是 BOOL,INT 从 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,CpuTypecpuType=CpuType.S71200,shortrack=0,shortslot=1){// 防御性编程:拒绝空 IPif(string.IsNullOrWhiteSpace(ipAddress))thrownewArgumentException("IP 地址不能为空。",nameof(ipAddress));IpAddress=ipAddress.Trim();// Trim() 去掉首尾空格CpuType=cpuType;Rack=rack;Slot=slot;}要点:
- 构造函数只保存参数,不立即连接(延迟连接,用时再连)
cpuType = CpuType.S71200是默认参数,不传时默认 S7-1200throw抛出异常,让调用者知道参数不合法
8.3 Connect() —— 建立连接
publicboolConnect(){if(IsConnected)returntrue;// 已连接则跳过try{_plc=newPlc(CpuType,IpAddress,Rack,Slot);// 创建底层对象_plc.Open();// 发起 TCP + S7 握手_isConnected=_plc.IsConnected;return_isConnected;}catch(Exception){_isConnected=false;// 失败时清理状态returnfalse;// 返回 false 而不是抛异常,便于调用者判断}}初学者要点:
- 返回
bool而不是抛异常:连接失败是常见情况,调用者可以用if判断 try/catch:网络问题不会导致程序崩溃
8.4 Read<T>() —— 泛型读取
publicTRead<T>(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 运算符,告诉编译器"此处不为 null"
8.5 ExecuteWithRetry —— 失败重试机制
privateTExecuteWithRetry<T>(Func<T>action){Exception?lastException=null;for(intattempt=0;attempt<=MaxRetryCount;attempt++){try{returnaction();// 执行传入的读写操作}catch(Exceptionex){lastException=ex;_isConnected=false;if(attempt>=MaxRetryCount)break;Disconnect();// 断开旧连接Connect();// 重新连接}}throwlastException??newInvalidOperationException("PLC 操作失败。");}初学者要点:
Func<T>:传入一个"无参、返回 T"的委托(lambda 表达式)- 默认最多重试 3 次(
MaxRetryCount = 3) - 每次失败后先断开再重连,避免 stale 连接
8.6 Dispose() —— 释放资源
publicvoidDispose(){if(_disposed)return;// 防止重复释放Disconnect();// 断开 PLC 连接_plc=null;// 释放引用_disposed=true;GC.SuppressFinalize(this);}初学者要点:
- 配合
using var plc = ...使用:代码块结束时自动调用Dispose() - 工业程序必须正确释放连接,否则 PLC 连接数耗尽
9. 完整示例程序
以下代码可直接替换Program.cs后运行(修改 IP 和地址):
usingS7.Net;usingSiemens.S7Connector;// ── 1. 创建连接器 ──usingvarplc=newSiemensPlcConnector(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. 读取 ──boolrunning=plc.ReadBool("DB1.DBX0.0");shortspeed=plc.ReadInt("DB1.DBW2");floattemp=plc.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. 常见问题排查
Q1:Connect() 返回 false
| 检查项 | 操作 |
|---|---|
| IP 是否正确 | 在 TIA Portal 或 PLC 面板查看实际 IP |
| 网络是否通 | ping 192.168.0.1 |
| CPU 型号 | CpuType必须与 PLC 一致 |
| PUT/GET 权限 | TIA Portal → 设备属性 → 连接机制 → 勾选允许 |
| 防火墙 | 临时关闭防火墙测试 |
Q2:连接成功但读取报错
| 检查项 | 操作 |
|---|---|
| DB 块是否存在 | TIA Portal 中确认 DB1 已创建且未优化(或知道优化后的偏移) |
| 地址偏移 | 与 TIA Portal 中变量偏移完全一致 |
| 数据类型 | DBW读 INT,DBD读 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():
// ✅ 推荐:自动释放usingvarplc=newSiemensPlcConnector("192.168.0.1");plc.Connect();// ... 业务代码 ...// 离开作用域时自动 Disconnect// ✅ 也可以手动释放varplc=newSiemensPlcConnector("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 地址寻址手册 —— 官方地址格式说明