S7协议通信
在工业自动化领域,上位机与PLC的通信是绕不开的核心环节,西门子S7系列PLC在工控自动化中很常见(如S7-1200/1500),需要使用西门子S7协议进行数据交互,S7协议相对modbus协议更复杂,而且没有开源的资料,不过可以通过专门的S7协议库进行通信。
一、 为什么选择原生S7协议?
在开始写代码之前,我们需要确定通信方案。常见的上位机与PLC通信方式主要有三种,它们各有优劣:
- 原生S7协议:西门子专用协议,基于TCP/IP(端口102)。优势在于效率极高、无需额外授权费用、直接读写DB块/I/Q区;劣势是协议细节复杂(但已有成熟开源库封装)。
- OPC UA:跨平台标准协议。优势是通用性强、数据结构丰富;劣势是配置繁琐、实时性略逊于S7协议、商业服务器需要授权。
- Modbus TCP:通用工业协议。优势是简单;劣势是需要在PLC中做地址映射,不如S7协议直接。
对于追求高性能和低成本的csharp开发项目,原生S7协议 通常是首选。我们将基于开源库S7netplus来实现,它屏蔽了底层的报文构造,让开发变得像调用普通函数一样简单。
二、 开发环境搭建
工欲善其事,必先利其器。我们需要准备以下环境:
- IDE:Visual Studio 2022。
- 框架:.NET 6.0 或 .NET Framework 4.8。
- 通信库 :通过NuGet安装
S7.Net。
在Visual Studio中打开"NuGet包管理器",搜索S7.Net并安装,或者在包管理器控制台运行以下命令:
powershell
Install-Package S7.Net
三、 csharp核心代码实现
以下是一个封装良好的通信类示例,涵盖了连接、读取、写入和异常处理。
csharp
using System;
using S7.Net;
namespace SiemensS7Helper
{
/// <summary>
/// 西门子PLC通信助手
/// </summary>
public class PlcHelper : IDisposable
{
private readonly Plc _plc;
private bool _isConnected = false;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ip">PLC IP地址</param>
/// <param name="cpuType">CPU型号</param>
/// <param name="rack">机架号,S7-1200/1500通常为0</param>
/// <param name="slot">槽号,S7-1200通常为1,1500通常为0或1</param>
public PlcHelper(string ip, CpuType cpuType, int rack = 0, int slot = 1)
{
_plc = new Plc(cpuType, ip, rack, slot);
// 设置超时时间(可选)
_plc.ReadTimeout = 1000;
}
/// <summary>
/// 建立连接
/// </summary>
public bool Connect()
{
try
{
if (!_plc.IsConnected)
{
_plc.Open();
}
_isConnected = _plc.IsConnected;
return _isConnected;
}
catch (Exception ex)
{
Console.WriteLine($"连接失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 读取浮点数 (Real)
/// </summary>
/// <param name="dbNumber">DB块号</param>
/// <param name="offset">偏移量</param>
public float? ReadFloat(int dbNumber, int offset)
{
if (!_isConnected) return null;
try
{
// 读取4个字节(Real类型占4字节)
var data = _plc.ReadBytes(DataType.DataBlock, dbNumber, offset, 4);
return S7.Net.Types.Real.FromByteArray(data);
}
catch (Exception ex)
{
Console.WriteLine($"读取浮点数失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 写入整型数 (Int)
/// </summary>
/// <param name="dbNumber">DB块号</param>
/// <param name="offset">偏移量</param>
/// <param name="value">值</param>
public bool WriteInt(int dbNumber, int offset, int value)
{
if (!_isConnected) return false;
try
{
var bytes = S7.Net.Types.Int.ToByteArray((short)value); // Int对应short
_plc.WriteBytes(DataType.DataBlock, dbNumber, offset, bytes);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"写入整型数失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
if (_plc.IsConnected)
{
_plc.Close();
_isConnected = false;
}
}
public void Dispose()
{
Disconnect();
}
}
}
四、 调用示例与测试
在Main函数或按钮点击事件中调用上述类:
csharp
static void Main(string[] args)
{
// 假设PLC IP为192.168.0.10,型号为S71200
using (var plc = new PlcHelper("192.168.0.10", CpuType.S71200))
{
if (plc.Connect())
{
Console.WriteLine("连接成功!开始读写测试...");
// 1. 写入数据:向DB1的偏移量0处写入100
bool writeSuccess = plc.WriteInt(1, 0, 100);
if (writeSuccess) Console.WriteLine("写入成功");
// 2. 读取数据:读取DB1偏移量4处的浮点数
float? temp = plc.ReadFloat(1, 4);
if (temp.HasValue)
{
Console.WriteLine($"读取到的温度值: {temp.Value}");
}
}
else
{
Console.WriteLine("连接失败,请检查网线或IP配置。");
}
}
Console.ReadKey();
}