目录
西门子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协议的使用
使用tcp连接时需要进行五次握手,其中有三次是tcp客户端与服务器的基有链接,然后需要再通过s7协议发送两次请求连接,共为五次握手。
S7协议帧结构
1 TPKT 会话层 主要设置版本号 预留号 报文总长度
2 COPT 表示层 设置PDU类型
3 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));
}
});
}
}
数据的读取
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(数据)
*/
}
});
}
数据的写入
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("连接成功");
}
}