西门子s7通信协议

西门子s7通信协议

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

S7协议帧结构
1 TPKT 会话层 主要设置版本号 预留号 报文总长度
2 COPT 表示层 设置PDU类型
3 s7协议 应用层 设置协议头和协议参数等

s7协议的使用
使用tcp连接时需要进行五次握手,其中有三次是tcp客户端与服务器的基有链接,然后需要再通过s7协议发送两次请求连接,共为五次握手。

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

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

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

 

 

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

 

 

使用tcp五次握手进行连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/// <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);
}

  

接收数据的响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/// <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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/// <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;
}

  

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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("连接成功");
    }
}

  

 

posted @   funiyi816  阅读(365)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示