目录
西门子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中类型与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数据类型] |
特殊说明:
-
PLC数组 → C#数组(如
REAL[10]
→float[]
) -
PLC结构体(STRUCT) → C#
class
或struct
-
TIME 类型在西门子PLC中以毫秒存储,C#用
TimeSpan
表示时间间隔 -
DATE_AND_TIME 在C#中对应完整的
DateTime
结构 -
字符串处理需注意:西门子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层):
-
支持多种物理介质:RS485、工业以太网(PROFINET)、MPI等
-
典型传输速率:9.6Kbps-100Mbps(取决于具体实现)
-
-
数据链路层(第2层):
-
对于MPI/PROFIBUS网络使用西门子专有的第2层协议
-
对于PROFINET/工业以太网使用标准的IEEE 802.3协议
-
-
网络层(第3层):
-
使用西门子专有的网络层协议
-
处理设备寻址和路由
-
-
传输层(第4层):
-
使用西门子专有的传输协议
-
提供可靠的端到端数据传输
-
-
应用层(第7层):
-
S7通信协议本身
-
包含多种服务功能如读写变量、控制PLC等
-
S7协议的主要通信方式
-
S7基本通信:
-
用于简单的数据交换
-
基于OSI模型的第1-3层
-
-
S7通信(扩展通信):
-
更复杂的数据交换
-
基于OSI模型的第1-4层
-
提供更多服务功能
-
-
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));
}
});
}
}
读取格式
读取报文
关键差异说明
-
报文长度:
- 请求报文总长度为31字节(0x001F),响应报文为29字节(0x001D)。
-
ROSCTR字段:
- 请求报文为作业请求(0x01),响应报文为确认响应(0x03)。
-
数据长度:
- 请求报文数据长度为0(0x0000),响应报文为8字节(0x0008)。
-
返回数据:
-
响应报文包含实际读取的数据(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(数据)
*/
}
});
}
写入格式
写入报文
关键差异说明
-
报文长度:
- 请求报文总长度为36字节(0x0024),响应报文为29字节(0x001D)。
-
功能码:
- 请求报文为写入操作(0x05),响应报文可能复用读取功能码(0x04)或应为写入响应(需确认协议规范)。
-
数据内容:
-
请求报文包含写入地址(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("连接成功");
}
}
本文部分借鉴于网络,如有侵权请联系删除!!!