C#与西门子PLC通讯——手搓S7通讯协议

本文将尝试从源码角度,使用Tcp/Ip的方式直接与西门子PLC进行交互通讯。

C#与西门子PLC通讯 系列文章目录

往期博客参考 C#与西门子PLC通讯——新手快速入门 C#与西门子PLC通讯——熟手快速入门 建议先看一下这两篇,了解预设背景。

文章目录

前言

知其然,知其所以然。

这篇文章,我们就尝试重复造一个轮子。通过对通讯协议的简要分析,我们能够更好地了解与西门子PLC是如何交互的。最后,我们就运用底层方法,使用Socket通讯将一个数组读取出来,再将数组反转之后写回PLC中。

一、通讯协议

1.1 S7协议位置

首先,参照 ISO-OSI 参考模型,S7 协议位置如下:
https://i-blog.csdnimg.cn/blog_migrate/78f2cb6c673892b7faf38901911e7e37.png
参考西门子官网介绍:S7 协议有哪些属性,优势及特征?

1.2 S7通讯协议

当C#应用程序中与西门子PLC进行通信时,需要经历一系列协议阶段,以确保有效的数据传输和通信。 这些阶段包括TCP/IP协议、TPKT协议、COTP协议和S7连接协议。

  • 通信的第一阶段是建立TCP/IP连接的三次握手。这是通信的基础,它确保了数据能够可靠地在客户端和PLC之间传输。+ 一旦TCP/IP连接建立,下一步是使用TPKT协议。如果直接去尝试发送别的信息,就会被PLC踢出去。+ 接下来是COTP协议。COTP协议用于建立和管理连接,它为应用层提供了一种连接导向的通信方式。在COTP外包一层TPKT,这样就可以发送给PLC,完成协议的确认。+ 最后,我们到达S7连接协议阶段,这是与西门子PLC通信的核心协议。从源码来看,这个是一个固定的内容{ 3, 0, 0, 25, 2, 240, 128, 50, 1, 0, 0, 255, 255, 0, 8, 0, 0, 240, 0, 0, 3, 0, 3, 3, 192};

网络上有很多写得很好的关于S7通讯协议的介绍和分析,这里就不做复读机啦。

完成上述操作之后,就开启了新世界的大门,在PLC的数据海洋里自由荡漾。

1.3 S7 Net Plus源码赏析

1.3.1 声明对象
/// <summary>
/// 创建一个具备连接所需参数的 PLC 对象。
/// 对于 S7-1200 和 S7-1500,默认值为 rack = 0 和 slot = 0。
/// 如果要连接到外部以太网卡 (CP),则需要 slot > 0。
/// 对于 S7-300 和 S7-400,默认值为 rack = 0 和 slot = 2。
/// </summary>
/// <param name="cpu">PLC 的 CpuType(从枚举中选择)</param>
/// <param name="ip">PLC 的 IP 地址</param>
/// <param name="rack">PLC 的机架号,通常为 0,但请在 Step7 或 TIA Portal 的硬件配置中进行检查</param>
/// <param name="slot">PLC 的 CPU 插槽号,对于 S7-1200 和 S7-1500 通常为 0,对于 S7-300 和 S7-400 通常为 2。
/// 如果使用外部以太网卡,必须相应地设置。</param>
public Plc(CpuType cpu, string ip, Int16 rack, Int16 slot)
    : this(cpu, ip, DefaultPort, rack, slot)
{
   
}

/// <summary>
/// 创建一个具备连接所需参数的 PLC 对象。
/// 对于 S7-1200 和 S7-1500,默认值为 rack = 0 和 slot = 0。
/// 如果要连接到外部以太网卡 (CP),则需要 slot > 0。
/// 对于 S7-300 和 S7-400,默认值为 rack = 0 和 slot = 2。
/// </summary>
/// <param name="cpu">PLC 的 CpuType(从枚举中选择)</param>
/// <param name="ip">PLC 的 IP 地址</param>
/// <param name="port">用于连接的端口号,默认为 102。</param>
/// <param name="rack">PLC 的机架号,通常为 0,但请在 Step7 或 TIA Portal 的硬件配置中进行检查</param>
/// <param name="slot">PLC 的 CPU 插槽号,对于 S7-1200 和 S7-1500 通常为 0,对于 S7-300 和 S7-400 通常为 2。
/// 如果使用外部以太网卡,必须相应地设置。</param>
public Plc(CpuType cpu, string ip, int port, Int16 rack, Int16 slot)
    : this(ip, port, TsapPair.GetDefaultTsapPair(cpu, rack, slot))
{
   
    if (!Enum.IsDefined(typeof(CpuType), cpu))
        throw new ArgumentException(
            $"参数 '{nameof(cpu)}' 的值 ({cpu}) 对于枚举类型 '{typeof(CpuType).Name}' 无效。",
            nameof(cpu));

    CPU = cpu;
    Rack = rack;
    Slot = slot;
}


1.3.2 建立连接TcpIp连接

同步方法:

/// <summary>
/// 连接到 PLC 并执行 COTP 连接请求和 S7 通信设置。
/// </summary>
public void Open()
{
   
    try
    {
   
        OpenAsync().GetAwaiter().GetResult();
    }
    catch (Exception exc)
    {
   
        throw new PlcException(ErrorCode.ConnectionError,
            $"无法建立与 {IP} 的连接。\n消息:{exc.Message}", exc);
    }
}

其中,同步方法会调用异步方法。

/// <summary>
/// 连接到 PLC 并执行 COTP 连接请求和 S7 通信设置。
/// </summary>
/// <param name="cancellationToken">用于监视取消请求的令牌。默认值为 None。
/// 请注意,取消不会以任何方式影响打开套接字,只会在成功建立套接字连接后影响用于配置连接的数据传输。
/// 请注意,取消是建议性/协作性的,不会在所有情况下立即导致取消。</param>
/// <returns>表示异步打开操作的任务。</returns>
public async Task OpenAsync(CancellationToken cancellationToken = default)
{
   
    var stream = await ConnectAsync(cancellationToken).ConfigureAwait(false);
    try
    {
   
        await queue.Enqueue(async () =>
        {
   
            cancellationToken.ThrowIfCancellationRequested();
            await EstablishConnection(stream, cancellationToken).ConfigureAwait(false);
            _stream = stream;

            return default(object);
        }).ConfigureAwait(false);
    }
    catch (Exception)
    {
   
        stream.Dispose();
        throw;
    }
}


ConnectAsync对应TcpIp连接方法:

private async Task<NetworkStream> ConnectAsync(CancellationToken cancellationToken)
{
   
    tcpClient = new TcpClient();
    ConfigureConnection();

#if NET5_0_OR_GREATER
    await tcpClient.ConnectAsync(IP, Port, cancellationToken).ConfigureAwait(false);
#else
    await tcpClient.ConnectAsync(IP, Port).ConfigureAwait(false);
#endif
    return tcpClient.GetStream();
}

.Net5 以上会调用await tcpClient.ConnectAsync(IP, Port, cancellationToken).ConfigureAwait(false);。 .Net5 以下会调用await tcpClient.ConnectAsync(IP, Port).ConfigureAwait(false);

1.3.3 通讯协议交互

OpenAsyncEstablishConnection就是建立TPKT协议、COTP协议和S7连接协议三个通讯握手的阶段。 其中,RequestConnection对应TPKT协议、COTP协议,SetupConnection对应S7连接协议。

private async Task EstablishConnection(Stream stream, CancellationToken cancellationToken)
{
   
    // 发起TPKT和COTP连接请求
    await RequestConnection(stream, cancellationToken).ConfigureAwait(false);
    
    // 设置S7连接协议
    await SetupConnection(stream, cancellationToken).ConfigureAwait(false);
}

1.3.3.1 TPKT协议和COTP协议
private async Task RequestConnection(Stream stream, CancellationToken cancellationToken)
{
   
    // 获取COTP连接请求数据
    var requestData = ConnectionRequest.GetCOTPConnectionRequest(TsapPair);
    
    // 发送请求并等待响应
    var response = await NoLockRequestTpduAsync(stream, requestData, cancellationToken).ConfigureAwait(false);

    // 检查响应是否为连接确认类型
    if (response.PDUType != COTP.PduType.ConnectionConfirmed)
    {
   
        throw new InvalidDataException("连接请求被拒绝", response.TPkt.Data, 1, 0x0d);
    }
}

public static byte[] GetCOTPConnectionRequest(TsapPair tsapPair)
{
   
    // 构建COTP连接请求数据
    byte[] bSend1 = {
   
        3, 0, 0, 22,   // TPKT
        17,             // COTP 头部长度
        224,            // 连接请求
        0, 0,           // 目标参考
        0, 46,          // 源参考
        0,              // 标志位
        193,            // 参数代码 (源 TASP)
        2,              // 参数长度
        tsapPair.Local.FirstByte, tsapPair.Local.SecondByte,   // 源 TASP
        194,            // 参数代码 (目标 TASP)
        2,              // 参数长度
        tsapPair.Remote.FirstByte, tsapPair.Remote.SecondByte, // 目标 TASP
        192,            // 参数代码 (TPDU 大小)
        1,              // 参数长度
        10              // TPDU 大小 (2^10 = 1024)
    };

    return bSend1;
}


1.3.3.2 S7连接协议
private async Task SetupConnection(Stream stream, CancellationToken cancellationToken)
{
   
    // 获取S7连接设置数据
    var setupData = GetS7ConnectionSetup();

    // 发送设置数据并等待响应
    var s7data = await NoLockRequestTsduAsync
  • 18
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值