当前位置: 首页 > news >正文

西门子 PLC 与 C# 通信

西门子 PLC 与 C# 通信

本教程基于项目中的SiemensPlcConnector模块,从零讲解如何用 C# 连接西门子 PLC 并读写数据。


目录

  1. 先了解几个概念
  2. 环境准备
  3. 项目结构
  4. 第一步:连接 PLC
  5. 第二步:读取数据
  6. 第三步:写入数据
  7. S7 地址格式速查
  8. 核心代码逐段讲解
  9. 完整示例程序
  10. 常见问题排查

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 在同一网段(例如 PC192.168.0.10,PLC192.168.0.1
  • ping通 PLC 的 IP 地址
  • 防火墙允许 TCP 102 端口

2.4 安装依赖

项目已通过 NuGet 引用S7netplus,首次编译时自动下载:

cd SiemensPlcConnector dotnet restore dotnet build

3. 项目结构

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()断开连接
ipAddressPLC 在局域网中的 IP
CpuType.S71200告诉库这是 S7-1200 CPU,不同型号握手参数不同
rack: 0, slot: 1S7-1200/1500 的标准参数;S7-300/400 通常slot: 2
plc.Connect()建立 TCP 连接并完成 S7 握手,成功返回true

4.3 不同 CPU 的参数对照

PLC 型号CpuTypeRackSlot
S7-1200CpuType.S7120001
S7-1500CpuType.S7150001
S7-300CpuType.S730002
S7-400CpuType.S740002

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# 写入类型示例
BOOLbooltrue/false
INTshort(short)1500
DINTint99999
REALfloat36.5f

7. S7 地址格式速查

7.1 地址组成规则

DB1.DBX0.0 │ │ │ │ │ │ │ └── 位号(0~7),仅 BOOL 使用 │ │ └──── 字节偏移 │ └─────── 寻址方式:X=位, B=字节, W=字(2字节), D=双字(4字节) └─────────── DB 块编号

7.2 常用寻址前缀

前缀含义示例数据大小
DBX位(Bool)DB1.DBX0.01 bit
DBB字节DB1.DBB101 byte
DBW字(Int)DB1.DBW02 bytes
DBD双字(DInt/Real)DB1.DBD44 bytes
M标志位M0.01 bit
I输入I0.01 bit
Q输出Q0.01 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-1200
  • throw抛出异常,让调用者知道参数不合法

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>是泛型:同一个方法可以返回boolintfloat等不同类型
  • 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 SiemensPlcConnector

10. 常见问题排查

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 地址寻址手册 —— 官方地址格式说明
http://www.jsqmd.com/news/1147617/

相关文章:

  • Java 多维数组详解
  • 接口参数总是对不上?我用这个在线 URL 解析器省了半小时调试时间
  • Wholehog 控台前世今生,又是一个车库传奇!
  • AI时代,数据安全的新威胁:身份安全正在失效?
  • PIC32MX795F512L与TS2007FC构建高性能音频系统
  • 装修公司为何适合做GEO?—— 地理围栏营销的精准掘金术
  • 市场上餐厨油回收源头厂家
  • IIM-20670与PIC18F97J60的SPI通信与运动传感应用
  • BMI160与TM4C129在运动监测中的硬件配置与算法实现
  • 家庭影音直链播放系统搭建:alist+Emby+Docker实战
  • IIM-20670与PIC18F4680构建工业级运动跟踪系统
  • PCA 置信椭圆可视化:从 sklearn 到 matplotlib 的 3 种实现方案与性能对比
  • QMK Toolbox终极指南:如何快速免费刷写机械键盘固件
  • 现代C++学习: 自己动手,实现C++的智能指针
  • BMI160与PIC18F2620的运动监测系统开发指南
  • DVWA SQL注入防御对比:Low/Medium/High/Impossible 4级安全代码解析
  • 儿童预防接种预约微信小程序-springboot
  • IIM-20670与MK64FN1M0VDC12的运动跟踪系统开发指南
  • 架构需求:用户需要什么样的架构
  • 基于ICM-42605和TM4C129XKCZAD的6DOF运动追踪系统设计
  • Smartbi 权限绕过漏洞实战:3种利用场景与自动化检测脚本编写
  • IIM-20670运动传感器与PIC32MX微控制器的集成应用
  • IIM-20670运动传感器与PIC18F86J15的工业级应用解析
  • IIM-20670运动传感器与TM4C123GH6PZ微控制器应用指南
  • IIM-20670与PIC18F86K90运动跟踪系统开发指南
  • WebLogic CVE-2018-2628 漏洞修复:5种防护策略与T3协议访问控制实战
  • WebSocket 即时通讯源码协议版本治理与历史检索架构设计
  • 穿透物理遮挡,告别穿戴设备:动态三维重构与无感定位重塑营区管理
  • 数据结构绪论
  • IIM-20670与PIC18F87J11的六轴运动传感器硬件设计与优化