西门子 PLC S7 协议和 Snap7 代码示例

本文通过将图片和代码结合的方式,讲解西门子 PLC S7 协议和开发。

本文部分内容参考 Snap7 官网(【参考1】), 博客文章 The Siemens S7 Communication - Part 1 General Structure(【参考2】)、Part 2 Job Requests and Ack Data

关键要点:

  1. S7 协议的网络包
  2. S7 协议的字节序
  3. Snap7 代码示例

本文内容

  1. 引言
  2. S7 协议
    1. 数据包
    2. 报文头
  3. Snap7 简介
    1. 安装
    2. Snap7 Server C++ 代码示例
    3. Snap7 C++ 客户端代码示例
    4. 格式转换

引言

首先展示一下西门子 S7 系列 PLC 硬件。

西门子并没有发布 S7 协议的官方文档,如下几个开源网站可以帮助理解 S7 协议:

  1. Snap7:Davide Nardella开源的 S7 通信库,实现了基本通信,还提供了S7协议的大量文档。
  2. S7 Wireshark dissector 插件:能抓取协议的大部分内容,源代码里有很多协议常量。
  3. Apache PLC4X:支持多种工业协议(包括S7)和开发语言,详见 支持列表

S7 协议

本文所说的 S7 协议(S7 protocol)特指“S7 以太网”通信(基于 RFC1006 使用 TCP/IP 传输),主要用于西门子 PLC 连接到 PC 工作站。从下图可知,西门子 S7 协议也可以通过其他协议(如图中的 FDL)传输数据。

图 1:从 ISO 七层参考模型视角看 S7 协议,图片来源于 S7 协议有哪些属性,优势及特征?

数据包

图 2:协议封装,图片来源于 Snap7 Siemens communications overview

如图2,S7协议数据单元(PDU)主要包含三个部分:

  1. 报头(Header):包含长度信息、PDU引用以及消息类型常量。
  2. 参数(Parameters):其内容和结构会根据PDU的消息类型和功能类型而有很大差异。
  3. 数据(Data):这是一个可选字段,用于携带可能存在的数据,例如内存值、代码块、固件数据等。
报文头

报头长度为10至12字节,其中确认消息包含两个额外的错误代码字节。除此之外,所有协议数据单元(PDU)的报头格式都是一致的。

图 2:S7 协议报文头,图片来源于【参考 2】

字段:

  1. 协议 ID:[1 字节] 协议常量,始终设置为 0x32
  2. 消息类型:[1 字节] 消息的一般类型(有时称为ROSCTR类型)
    1. 0x01 - 作业请求:由主站发送的请求(例如,读写内存、读写代码块、启动/停止设备、设置通信)
    2. 0x02 - 确认(Ack):由从站发送的简单确认消息,不包含数据字段
    3. 0x03 - 带数据确认(Ack-Data):包含可选数据字段的确认消息,包含对作业请求的回复
    4. 0x07 - 用户数据(Userdata):原始协议的扩展,参数字段包含请求/响应ID(用于编程/调试、SZL读取、安全功能、时间设置、循环读取等)
  3. 保留字段:[2 字节] 始终设置为0x0000(但可能被忽略)
  4. PDU引用:[2 字节] 由主站生成,每次新传输时递增,用于将响应与请求相关联
  5. 参数长度:[2 字节] 参数字段的长度,采用大端序
  6. 数据长度:[2 字节] 数据字段的长度,采用大端序
  7. 错误类:[1 字节] 仅存在于带数据确认(Ack-Data)消息中,参考 constants.txt
  8. 错误代码:[1 字节] 仅存在于带数据确认(Ack-Data)消息中

Snap7 简介

安装

snap7-full-1.4.2 中拷贝文件到对应目录即可。

例如,Windows 开发环境下,

  1. snap7.dll(来自 release\Windows\Win64\snap7.dll)
  2. lib/snap7.h(来自 release\Wrappers\c-cpp\snap7.h)
  3. lib/snap7.cpp(来自 release\Wrappers\c-cpp\snap7.cpp)
  4. lib/snap7.lib(来自 release\Windows\Win64\snap7.lib)
Snap7 Server C++ 代码示例

日常开发中可以 Snap7 Server 当模拟器用。

#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include "lib/snap7.h"

TS7Server *Server;
unsigned char DB1[512];  // Our DB1
unsigned char DB2[128];  // Our DB2
unsigned char DB3[1024]; // Our DB3
unsigned char MB[2048];  // 2048 = CPU 315 Merkers amount

int main(int argc, char* argv[])
{
    int Error;
    Server = new TS7Server;
    Server->RegisterArea(srvAreaDB,     // We are registering a DB
                         1,             // Its number is 1 (DB1)
                         &DB1,          // Its address
                         sizeof(DB1));  // Its size
    Server->RegisterArea(srvAreaDB, 2, &DB2, sizeof(DB2));
    Server->RegisterArea(srvAreaDB, 3, &DB3, sizeof(DB3));
    // Let’s share all Merkers from M0.0 to M2047.7
    Server->RegisterArea(srvAreaMK, 0, &MB, sizeof(MB));
    // Start the server onto the default adapter “0.0.0.0”
    Error=Server->Start();
    if (Error==0)
    {
       // Now the server is running ... wait a key to terminate
        getchar();
    }
    else
        printf("%s\n",SrvErrorText(Error).c_str());
    Server->Stop(); // not strictly needed
    delete Server;
}
Snap7 C++ 客户端代码示例
#include <iostream>  
#include "lib/snap7.h"

// 连接参数
const char* plc_address = "127.0.0.1";
const int rack = 0; 
const int slot = 0;
// 数据位置
static int db_number = 1; 
const int data_offset = 66;
// 数据存储变量
static bool mydata = false; 

int main(){
    TS7Client* MyClient = new TS7Client();
    MyClient->ConnectTo(plc_address, rack, slot);
    if (!MyClient->Connected() && MyClient->Connect()>0) {
        return -1;
    }

    bool newVal = true;
    MyClient->DBWrite(db_number, data_offset, 1, &newVal);

    MyClient->DBRead(db_number, data_offset, 1, &mydata);
    std::cout << "mydata:" << mydata << std::endl; 
    //输出:mydata:1

    delete MyClient;
}
格式转换

格式转换可以参考库:S7-cpp-for-Snap7 stars:Mapping data for Snap7 library in C/C++

发布于:2024-10-27 18:57:00 描述有误?我来纠错