西门子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五次握手进行连接
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)); } }); } }
读取和写入报文格式
数据的读取
/// <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); }
接收数据的响应
/// <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(数据) */ } }); }
数据的写入
/// <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; }
完整代码
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("连接成功"); } }
标签:00,字节,s7,0x00,西门子,0x01,通信协议,byte,0x03 From: https://www.cnblogs.com/funiyi816/p/18400138