西门子s7协议

目录

西门子s7协议

西门子PLC数据类型

PLC中类型与C#对应类型

特殊说明:

S7协议帧结构

示例代码(C#访问PLC数据):

上位机和西门子PLC的通讯

西门子PLC的存储区

S7协议通讯网络模型

S7协议栈基于ISO/OSI模型,但主要实现以下4层:

S7协议的主要通信方式

s7协议通讯流程

COTP连接(第一次握手)报文

完整报文示例(Hex格式)

S7连接(第二次握手)报文

读取格式

关键差异说明

写入格式

关键差异说明

完整代码


西门子S7通信协议(S7 Communication Protocol)是西门子为其SIMATIC S7系列PLC (如S7-200、S7-300、S7-400、S7-1200、S7-1500)开发的一套工业自动化通信协议,主要用于PLC与HMI(人机界面)、SCADA系统、上位机(如PC)、其他PLC或工业设备之间的数据交换。

西门子s7协议

S7Comm(S7 Communication)是西门子专有的协议,是西门子S7通讯协议簇里的一种。

S7通信协议是西门子S7系列PLC内部集成的一种通信协议,是S7系列PLC的精髓所在。它是一种运行在传输层之上的(会话层/表示层/应用层)、经过特殊优化的通信协议,其信息传输可以基于MPI网络、PROFIBUS网络或者以太网S7在TCP连接上后还需要进行两次握手,S7协议的TCP/IP实现依赖于面向块的ISO传输服务。S7协议被封装在TPKT和ISO-COTP协议中,这使得PDU(协议数据单元) 能够通过TCP传送。

西门子PLC数据类型

PLC 数据类型

基本数据类型:包括位、位字符串、整数、浮点数、定时器、日期&时间、字符、数组和结构

PLC中类型与C#对应类型

| PLC数据类型 (西门子) | C# 对应类型 | 存储大小 | 值范围 | 示例 |
| BOOL (位) | bool | 1 bit | true/false | I0.0, Q1.1 |
| BYTE (字节) | byte | 8 bits | 0-255 | IB0 |
| WORD (字) | ushort | 16 bits | 0-65535 | MW10 |
| DWORD (双字) | uint | 32 bits | 0-4,294,967,295 | MD20 |
| INT (短整数) | short | 16 bits | -32,768~32,767 | DB1.DBW0 |
| DINT (双整数) | int | 32 bits | -2,147,483,648~2,147,483,647 | DB2.DBD4 |
| USINT (无符号短整数) | byte | 8 bits | 0-255 | - |
| UINT (无符号整数) | ushort | 16 bits | 0-65,535 | - |
| UDINT (无符号双整数) | uint | 32 bits | 0-4,294,967,295 | - |
| REAL (浮点数) | float | 32 bits | ±1.5×10⁻⁴⁵~±3.4×10³⁸ | DB3.DBD8 |
| LREAL (长浮点数) | double | 64 bits | ±5.0×10⁻³²⁴~±1.7×10³⁰⁸ | DB4.DBD12 |
| CHAR (字符) | char | 8 bits | ASCII字符 | 'A' |
| STRING (字符串) | string | 可变长度 | 最大254字符 | "Hello" |
| TIME (时间间隔) | TimeSpan | 32 bits | 0~2,147,483,647 ms | T#1D2H3M4S |

DATE_AND_TIME (日期时间) DateTime 64 bits 1970-01-01~2106-02-07 DT#2023-10-01-14:30:00
[PLC数据类型]

特殊说明:

  1. PLC数组 → C#数组(如 REAL[10]float[]

  2. PLC结构体(STRUCT) → C# classstruct

  3. TIME 类型在西门子PLC中以毫秒存储,C#用 TimeSpan 表示时间间隔

  4. DATE_AND_TIME 在C#中对应完整的 DateTime 结构

  5. 字符串处理需注意:西门子STRING前2字节存储长度信息(如 [长度][最大长度][字符数据]

S7协议帧结构

1 TPKT 会话层 主要设置版本号 预留号 报文总长度

2 COPT 表示层 设置PDU类型

3 s7协议 应用层 设置协议头和协议参数等

示例代码(C#访问PLC数据):

cs 复制代码
// 使用S7.Net库读取不同类型数据
var plc = new Plc(CpuType.S71500, "192.168.1.10", 0, 1);
plc.Open();

// 读取不同类型
bool boolVal = plc.Read("I0.0");          // BOOL
byte byteVal = plc.Read("MB0");           // BYTE
ushort wordVal = plc.Read("MW2");         // WORD
int intVal = plc.Read("DB1.DBW4");        // INT
float realVal = plc.Read("DB1.DBD8");     // REAL
DateTime dtVal = plc.Read("DB1.DBD12");   // DATE_AND_TIME

plc.Close();

上位机和西门子PLC的通讯

西门子PLC的存储区

  • I:输入

  • Q:输出

  • AI:模拟量输入

  • V/DB:变量存储区

可以使用modbus协议和s7协议和plc进行通讯,但是modbus协议只能请求上面四个区,西门子plc还有M:位区、T:定时器...​,使用S7协议可以操作所有的区

S7协议通讯网络模型

S7协议栈基于ISO/OSI模型,但主要实现以下4层:

  1. 物理层(第1层)

    • 支持多种物理介质:RS485、工业以太网(PROFINET)、MPI等

    • 典型传输速率:9.6Kbps-100Mbps(取决于具体实现)

  2. 数据链路层(第2层)

    • 对于MPI/PROFIBUS网络使用西门子专有的第2层协议

    • 对于PROFINET/工业以太网使用标准的IEEE 802.3协议

  3. 网络层(第3层)

    • 使用西门子专有的网络层协议

    • 处理设备寻址和路由

  4. 传输层(第4层)

    • 使用西门子专有的传输协议

    • 提供可靠的端到端数据传输

  5. 应用层(第7层)

    • S7通信协议本身

    • 包含多种服务功能如读写变量、控制PLC等

S7协议的主要通信方式

  1. S7基本通信

    • 用于简单的数据交换

    • 基于OSI模型的第1-3层

  2. S7通信(扩展通信)

    • 更复杂的数据交换

    • 基于OSI模型的第1-4层

    • 提供更多服务功能

  3. S7函数通信

    • 用于远程函数调用

    • 基于OSI模型的完整7层

s7协议通讯流程

TCP三次握手(TCP连接时进行) => COTP连接(第一次握手连接) => S7连接(第二次握手) => 数据的读写

COTP连接(第一次握手)报文

字段分类 字段名称 字节位置 长度(字节) 示例值(Hex) 说明
TPKT头部 版本号 0 1 0x03 TPKT协议版本,固定为 0x03
TPKT头部 保留位 1 1 0x00 保留字段,默认为0
TPKT头部 报文总长度 2-3 2 0x0016 整个报文长度(22字节)
COTP会话层 COTP数据长度 4 1 0x11 后续COTP数据长度(17字 节)
COTP会话层 PDU类型 5 1 0xE0 0xE0表示连接请求(CR)
COTP会话层 目标引用 6-7 2 0x0000 初始为0,由服务器分配
COTP会话层 源引用 8-9 2 0x0001 客户端生成的标识
COTP会话层 扩展格式 10 1 0x00 流控制标志(默认0)
COTP参数部分 源TSAP标识 11 1 0xC1 0xC1表示源TSAP(上位机)
COTP参数部分 源TSAP长度 12 1 0x02 源TSAP参数长度(2字节)
COTP参数部分 源TSAP值 13-14 2 0x1000 连接模式:0x10(S7双边通信)
COTP参数部分 目标TSAP标识 15 1 0xC2 0xC2表示目标TSAP(PLC)
COTP参数部分 目标TSAP长度 16 1 0x02 目标TSAP参数长度(2字节)
COTP参数部分 目标TSAP值 17-18 2 0x0301 机架号0x03(0),槽 号0x01(1)
COTP参数部分 TPDU大小标识 19 1 0xC0 0xC0表示TPDU大小参数
COTP参数部分 TPDU大小长度 20 1 0x01 TPDU大小参数长度(1字节)
COTP参数部分 TPDU大小值 21 1 0x0A 2^10=1024字节(最大传 输单元)
[第一次连接握手]

完整报文示例(Hex格式)

cs 复制代码
03 00 00 16 11 E0 00 00 00 01 00 C1 02 10 00 C2 02 03 01 C0 01 0A

S7连接(第二次握手)报文

使用tcp五次握手链接

cs 复制代码
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
 
    /// <summary>
    /// 五次握手
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button1_Click(object sender, EventArgs e)
    {
        // s7协议
        // 1 需要通过socket三次握手,不用写握手过程
        // 目前提供设备型号s71200  cpu:1212c    电压是24vDC
        TcpClient client = new TcpClient();
        client.Connect("192.168.107.202",102); // 连接服务器
 
        receiveData(client);
 
        // 2 第一请求连接  发送请求帧为
        // 总共22个字节
        byte[] bs1 = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x16, // 2 字节 报文的总长度
 
            0x11, // 1字节从该字节往后字节个数 十进制是17
            0xE0, // PDU 类型
            0x00,0x00, // DST引用 默认值
            0x00,0x01, // src引用
            0x00, // 采用默认值
            0xc1, // 上位机擦书
            0x02, // 上位机长度
            0x10,0x00, // 0x01代表双边通信 0x00机架号和插槽号
            
            0xC2, // plc参数
            0x02, // 长度
 
            0x03,0x01, // 0x01和0x00 共同控制机架号和插槽
            0xC0,0x01,
            0x0a
        };
        client.GetStream().Write(bs1,0,bs1.Length); // 发送第一次请求帧 
 
 
        // 3 第二次请求连接 发送请求帧为
        bs1 = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x19, // 2 字节 报文的总长度
 
            0x02, // 当前字节后的字节数
            0xF0, // PUD类型 数据传输
            0x80, // 最高是十进制128
 
            0x32, // 协议ID,固定值
            0x01, // 工作类型 0x01 主站发送请求
            0x00,0x00,
            0x00,0x00,
            0x00,0x08, // 参数长度
            0x00,0x00, // 数据长度
 
            0xF0, // 功能码
            0x00, // Reserved保留值
            0x00,0x03, // 允许操作最大工作队列
            0x00,0x03, 
            0x03,0xc0, // 允许处理最大字节数组
        };
        client.GetStream().Write(bs1,0,bs1.Length);
        MessageBox.Show("连接成功");
 
    }
 
    /// <summary>
    /// 接收响应数据集
    /// </summary>
    /// <param name="tcpClient"></param>
    public void receiveData(TcpClient tcpClient)
    {
        Task.Run(() =>
        {
            byte[] bytes = new byte[1024];
            while (tcpClient.Connected)
            {
                // ONE
                int count = tcpClient.GetStream().Read(bytes,0,bytes.Length);
                if (count == 0) return;
                Console.WriteLine(BitConverter.ToString(bytes, 0, count) + "\r\n");
 
                // TWO
                byte[] s = new byte[count];
                Array.Copy(bytes,s,count);
                Console.WriteLine(string.Join(",",s));
            }
        });
    }
}

读取格式

读取报文

关键差异说明
  1. 报文长度

    • 请求报文总长度为31字节(0x001F),响应报文为29字节(0x001D)。
  2. ROSCTR字段

    • 请求报文为作业请求(0x01),响应报文为确认响应(0x03)。
  3. 数据长度

    • 请求报文数据长度为0(0x0000),响应报文为8字节(0x0008)。
  4. 返回数据

    • 响应报文包含实际读取的数据(0x000000-N),长度为32字节(0x0020)。

cs 复制代码
/// <summary>
/// 读取M区
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
    // 发送请求帧 请求M区地址从00开始 读取一个数据
    // 读取数据时的请求帧
    byte[] data = new byte[] 
    {
        // TPKT: 版本号 预留号 总字节长度
        0x03, // 版本号 
        0x00, // 预留号
        0x00,0x01F, // 总字节长度
 
        // COTP: 
        0x02, // 往下的长度
        0xF0, // PDU类型
        0x80, // 目标引用
 
        // s7-header s7头
        0x32, // 协议ID 默认
        0x01, // 主站开始发请求
        0x00,0x00, // 预留位置
        0x03,0x7b, // 随机生成的数字 每次在基础之上递增
        0x00,0x0e, // 参数长度
        0x00,0x00, // 数据长度
 
        // s7-参数部分
        0x04, // 功能码 读取功能                 重点
        0x01, // 如果涉及多读时候 设置为1,
        0x12, // 结构表示 一般默认12
        0x0a, // 往后的字节长度
        0x10, // 寻址模式
        0x02, // 读取的数据类型 02是字节类型
        0x00,0x01, // 读取长度                   重点
        0x00,0x00, // 读取不是DB区               重点
        0x83, // 0x83 M存储区,0x84DB块          重点
        0x00,0x00,0x70, // 开始数据起始地址      重点
 
        // 列如M30000, 实际地址是30000*8=24 00000,把转成山歌字节,转成16进制3a980 对应三个地址,0x03,0xA9 0x80
        // 列如数据DB块的数据,DB21234,4000,其中DB号是21234 转成16进制0x52F2,DB区改为0x52,0xF2
        // 40000*8=,2000,转成16进制7d00 转成三个字节变成 0x00,0x7d,0x00
    };
    socket.Send(data);
}

收取数据响应

cs 复制代码
/// <summary>
/// 接收响应数据
/// </summary>
void startReceive()
{
    Task.Run(() =>
    {
        byte[] bytes = new byte[1024];
        while (true)
        {
            int count = socket.Receive(bytes);
            if (count == 0) break;
 
            // 转为16进制的字符串
            Console.WriteLine("十六进制打印:"+BitConverter.ToString(bytes,0,count));
 
            // 转为十进制打印
            byte[] datas = new byte[count];
            Array.Copy(bytes,datas,count);
            Console.WriteLine("十进制打印:" + string.Join(",",datas));
 
            Invoke(new Action(() =>
            {
                try
                {
                    this.label1.Text = datas[25].ToString();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
            }));
 
 
            /* 响应的数据
            * 连接的响应
            * 03-00-00-16-11-D0-00-01-00-08-00-C0-01-0A-C1-02-10-00-C2-02-03-01
            * 03-00-00-1B-02-F0-80-32-03-00-00-00-00-00-08-00-00-00-00-F0-00-00-03-00-03-00-F0
            * 
            * 读取数据的响应
            * 03-00-00-1A-02-F0-80-32-03-00-00-03-7B-00-02-00-05-00-00-04-01-FF(读取成功的标志)-04(读取的数据类型)-00-08(数据的长度)-0C(数据)
            */
 
        }
    });
}

写入格式

写入报文

关键差异说明
  1. 报文长度

    • 请求报文总长度为36字节(0x0024),响应报文为29字节(0x001D)。
  2. 功能码

    • 请求报文为写入操作(0x05),响应报文可能复用读取功能码(0x04)或应为写入响应(需确认协议规范)。
  3. 数据内容

    • 请求报文包含写入地址(0x000000)和数据(0xFF),响应报文返回操作状态和可能的回读数据。

数据写入

cs 复制代码
/// <summary>
/// 写入M14
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
    byte[] value = BitConverter.GetBytes(uint.Parse(textBox1.Text));
    // 生成写的报文
    byte[] bs = new byte[]
    {
        // TPKT部分
        0x03, // 版本号
        0x00, // 预留号
        // 0x00,0x24, // 报文总长度36
        0x00,0x27, // 报文总长度39
 
        // TOPT
        0x02, // 长度
        0xF0, // PDU类型
        0xB0, // 目标引用
 
        // s7 header
        0x32, // 协议id
        0x01, // 主站开始请求
 
        0x00,0x00, // 预留部分
 
        0x03,0x7d, // 随机生成
        0x00,0x0E, // 参数长度
        0x00,0x08, // 参数数据长度
 
        // s7 参数
        0x05, // 05代表写入,04代表读取
        0x01, // 通信项数 可以支持多写
        0x12, // 变量指定
        0x0A, // 后面的长度
        0x10,
        0x02, // 传输数据类型 字节
 
        // 0x00,0x01, // 操作数据的长度
        0x00,0x04, // 操作数据的长度
 
        0x00,0x00, // M区 不是DB区
        0x83, // M区
 
        // 0x00,0x00,0x70, // 开始写入的地址M14
        0x00,0x3e,0x80, // 开始写入的地址M2000
 
        0x00,
        0x04, // 字节类型
 
        // 0x00,0x80, // 写入的长度  8位=1字节
        0x00,0x20, // 写入的长度  8位=1字节 (写入4个数据 4*8=32转16进制为20)
 
        //byte.Parse(textBox1.Text)
        value[3],value[2],value[1],value[0]
    };
    tcp.Send(bs);
    Type = RequestType.Write;
}

完整代码

cs 复制代码
public partial class Form1 : Form
{
    TcpClientHelper tcp;
    public Form1()
    {
        InitializeComponent();
    }
 
    private void Tcp_OnClose(TcpClientHelper obj)
    {
        MessageBox.Show("客户端关闭");
    }
 
    enum RequestType
    {
        Write,  // 写入请求
        Read,   // 读取请求
        Connect // 连接的请求
    }
    RequestType Type;
 
 
    private void Tcp_OnMessage(byte[] arg1, TcpClientHelper arg2)
    {
        // 获取数据即可 自动触发
        BeginInvoke(new Action(() =>
        {
            switch (Type)
            {
                case RequestType.Write:
                    // 写入数据的响应
                    Console.WriteLine("写入数据的响应"+BitConverter.ToString(arg1) );
                    break;
                case RequestType.Read:
                    // 读取数据的响应
                    Console.WriteLine("读取数据的响应" + BitConverter.ToString(arg1));
                    this.label1.Text = arg1[arg1.Length-1].ToString();
                    break;
                case RequestType.Connect:
                    Console.WriteLine("连接时的响应" + BitConverter.ToString(arg1));
                    break;
                    
            }
        }));
    }
 
    /// <summary>
    /// 写入M14
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button1_Click(object sender, EventArgs e)
    {
        byte[] value = BitConverter.GetBytes(uint.Parse(textBox1.Text));
        // 生成写的报文
        byte[] bs = new byte[]
        {
            // TPKT部分
            0x03, // 版本号
            0x00, // 预留号
            // 0x00,0x24, // 报文总长度36
            0x00,0x27, // 报文总长度39
 
            // TOPT
            0x02, // 长度
            0xF0, // PDU类型
            0xB0, // 目标引用
 
            // s7 header
            0x32, // 协议id
            0x01, // 主站开始请求
 
            0x00,0x00, // 预留部分
 
            0x03,0x7d, // 随机生成
            0x00,0x0E, // 参数长度
            0x00,0x08, // 参数数据长度
 
            // s7 参数
            0x05, // 05代表写入,04代表读取
            0x01, // 通信项数 可以支持多写
            0x12, // 变量指定
            0x0A, // 后面的长度
            0x10,
            0x02, // 传输数据类型 字节
 
            // 0x00,0x01, // 操作数据的长度
            0x00,0x04, // 操作数据的长度
 
            0x00,0x00, // M区 不是DB区
            0x83, // M区
 
            // 0x00,0x00,0x70, // 开始写入的地址M14
            0x00,0x3e,0x80, // 开始写入的地址M2000
 
            0x00,
            0x04, // 字节类型
 
            // 0x00,0x80, // 写入的长度  8位=1字节
            0x00,0x20, // 写入的长度  8位=1字节 (写入4个数据 4*8=32转16进制为20)
 
            //byte.Parse(textBox1.Text)
            value[3],value[2],value[1],value[0]
        };
        tcp.Send(bs);
        Type = RequestType.Write;
    }
 
    /// <summary>
    /// 读取M14
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void button2_Click(object sender, EventArgs e)
    {
        // 读取的报文31字节
        byte[] bs = new byte[]
        {
            0x03,
            0x00,
            0x00,0x1f,
 
            0x02,
            0xf0,
            0xb0,
 
            // 协议参数
            0x32,
            0x01,
            0x00,0x00,
            0x03,0x7d,
            0x00,0x0e,
            0x00,0x00, // 读取操作 为0
 
            0x04,0x01,
            0x12,0x0a,
            0x10,
            0x02,
 
            //0x00,0x01, // 读取长度
            0x00,0x04, // M2000 读取四个字节
 
            0x00,0x00,
            0x83,
 
            // 0x00, 0x00,0x70
            0x00,0x3e,0x80,
        };
        tcp.Send(bs);
        Type = RequestType.Read;
    }
 
    /// <summary>
    /// 连接
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Form1_Load(object sender, EventArgs e)
    {
        // 1 创建客户端对象
        tcp = new TcpClientHelper();
        tcp.Connect("192.168.107.202", 102); // 连接服务器
 
        // 2 获取数据
        tcp.OnMessage += Tcp_OnMessage;
 
        // 3 关闭连接
        tcp.OnClose += Tcp_OnClose;
 
        // 第一次连接请求
        byte[] bs = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x16, // 2 字节 报文的总长度
 
            0x11, // 1字节从该字节往后字节个数 十进制是17
            0xE0, // PDU 类型
            0x00,0x00, // DST引用 默认值
            0x00,0x01, // src引用
            0x00, // 采用默认值
            0xc1, // 上位机擦书
            0x02, // 上位机长度
            0x10,0x00, // 0x01代表双边通信 0x00机架号和插槽号
            
            0xC2, // plc参数
            0x02, // 长度
 
            0x03,0x01, // 0x01和0x00 共同控制机架号和插槽
            0xC0,0x01,
            0x0a
        };
        tcp.Send(bs);
 
        // 第二次请求连接
        bs = new byte[]
        {
            0x03, // 1字节版本号 默认是03
            0x00, // 1字节 保留值 默认0
            0x00, 0x19, // 2 字节 报文的总长度
 
            0x02, // 当前字节后的字节数
            0xF0, // PUD类型 数据传输
            0x80, // 最高是十进制128
 
            0x32, // 协议ID,固定值
            0x01, // 工作类型 0x01 主站发送请求
            0x00,0x00,
            0x00,0x00,
            0x00,0x08, // 参数长度
            0x00,0x00, // 数据长度
 
            0xF0, // 功能码
            0x00, // Reserved保留值
            0x00,0x03, // 允许操作最大工作队列
            0x00,0x03,
            0x03,0xc0, // 允许处理最大字节数组
        };
        tcp.Send(bs);
        Type = RequestType.Connect;
        MessageBox.Show("连接成功");
    }
}

本文部分借鉴于网络,如有侵权请联系删除!!!

相关推荐
yuzhangfeng2 小时前
【云计算物理网络】从传统网络到SDN:云计算的网络演进之路
网络·云计算
TDengine (老段)3 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
zhu12893035563 小时前
网络安全的现状与防护措施
网络·安全·web安全
wirepuller_king5 小时前
创建Linux虚拟环境并远程连接,finalshell自定义壁纸
linux·运维·服务器
zhu12893035565 小时前
网络安全与防护策略
网络·安全·web安全
Yan-英杰5 小时前
【百日精通JAVA | SQL篇 | 第二篇】数据库操作
服务器·数据库·sql
沫夕残雪5 小时前
HTTP,请求响应报头,以及抓包工具的讨论
网络·vscode·网络协议·http
π2705 小时前
爬虫:网络请求(通信)步骤,http和https协议
网络·爬虫
风123456789~6 小时前
【Linux运维】查询指定日期的上月
linux·运维·服务器
CC.cc.7 小时前
Linux系统之systemctl管理服务及编译安装配置文件安装实现systemctl管理服务
linux·运维·服务器