目录
- [1. 通讯协议概述](#1. 通讯协议概述)
- [2. 协议栈结构](#2. 协议栈结构)
- [3. 报文格式详解](#3. 报文格式详解)
- [4. 通讯原理](#4. 通讯原理)
- [5. 调用方法与API](#5. 调用方法与API)
- [6. 实际应用示例](#6. 实际应用示例)
- [7. 故障排除](#7. 故障排除)
1. 通讯协议概述
1.1 西门子S7通讯简介
西门子S7通讯协议是用于与西门子S7系列PLC(包括S7-200/300/400/1200/1500)进行数据交换的专有通讯协议。该协议基于TCP/IP网络,实现了可靠的数据传输和高效的PLC数据访问。
1.2 主要特点
- 基于TCP/IP:利用标准以太网进行通讯
- 客户端/服务器模式:PC作为客户端主动连接PLC
- 支持多种数据类型:位、字节、字、双字、浮点数等
- 高效的数据访问:支持批量读写操作
- 多CPU类型支持:兼容不同型号的西门子PLC
2. 协议栈结构
2.1 分层架构
┌─────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ S7 Protocol (读/写请求与响应) │
├─────────────────────────────────────┤
│ 表示层 (Presentation Layer) │
│ COTP (Connection Oriented Transport) │
├─────────────────────────────────────┤
│ 会话层 (Session Layer) │
│ TPKT (Transport Packet) │
├─────────────────────────────────────┤
│ 传输层 (Transport Layer) │
│ TCP (Transmission Control Protocol)│
├─────────────────────────────────────┤
│ 网络层 (Network Layer) │
│ IP (Internet Protocol) │
└─────────────────────────────────────┘
2.2 各层功能说明
2.2.1 TPKT层 (Transport Packet)
- 功能:提供数据包长度信息和版本标识
- 格式:固定4字节头部 + 数据部分
- 作用:确保数据完整传输
2.2.2 COTP层 (Connection Oriented Transport)
- 功能:建立和管理面向连接的通讯
- 协议:ISO 8073 (COTP)
- 作用:处理连接建立、数据传输和连接释放
2.2.3 S7协议层
- 功能:定义具体的PLC操作指令
- 操作类型:读、写、控制等
- 作用:实现PLC数据访问
3. 报文格式详解
3.1 完整报文结构
┌──────────────┬──────────────┬───────────────────┬──────────────┐
│ TPKT Header │ COTP Header │ S7 Protocol Data │ Data │
│ (4 bytes) │ (变长) │ (变长) │ (变长) │
└──────────────┴──────────────┴───────────────────┴──────────────┘
3.2 TPKT报文头格式
cpp
struct TPKT_Header {
uint8_t version; // 版本号,通常为0x03
uint8_t reserved; // 保留字段,通常为0x00
uint16_t length; // 整个报文长度(包括头本身)
};
示例:
03 00 00 21 // 版本=3,长度=33字节
3.3 COTP报文头格式
3.3.1 连接请求 (CR - Connection Request)
cpp
struct COTP_CR {
uint8_t length; // COTP参数长度
uint8_t pdu_type; // PDU类型:0x0E (连接请求)
uint8_t dst_ref; // 目标引用(通常0x00)
uint8_t src_ref; // 源引用
uint8_t class_option; // 类选项
uint8_t ext_format; // 扩展格式
};
典型连接请求:
11 0E 00 00 00 01 00 C0 01 0A C1 02 01 00 C2 01 02
3.3.2 连接确认 (CC - Connection Confirm)
cpp
struct COTP_CC {
uint8_t length; // COTP参数长度
uint8_t pdu_type; // PDU类型:0x0D (连接确认)
uint8_t dst_ref; // 目标引用
uint8_t src_ref; // 源引用
// ... 其他参数
};
3.3.3 数据传输 (DT - Data Transfer)
cpp
struct COTP_DT {
uint8_t length; // 长度:0x02
uint8_t pdu_type; // PDU类型:0x03 (数据传输)
uint8_t eot; // 传输结束标志:0x80
};
3.4 S7协议报文格式
3.4.1 S7报文头
cpp
struct S7_Header {
uint8_t pdu_id; // PDU标识:0x32 (S7协议)
uint8_t reserved1; // 保留字段
uint16_t length; // S7数据长度
uint8_t reserved2; // 保留字段:0x00
uint8_t function; // 功能码
};
功能码定义:
0x04- 读取变量 (Read Variable)0x05- 写入变量 (Write Variable)0x1A- 请求下载 (Request Download)
3.4.2 读取请求报文
cpp
struct S7_Read_Request {
S7_Header header; // S7头
uint8_t item_count; // 读取项数量
S7_Item items[]; // 读取项列表
};
struct S7_Item {
uint8_t spec; // 规范标识:0x12
uint8_t length; // 参数长度
uint8_t syntax_id; // 语法ID
uint16_t area; // 内存区域
uint16_t db_number; // DB编号(如使用DB)
uint16_t start; // 起始地址
uint16_t count; // 读取数量
uint16_t data_size; // 数据大小
};
实际读取报文示例(读取DB1.DBB0开始的10字节):
// TPKT Header
03 00 00 1F
// COTP DT Header
02 F0 80
// S7 Read Request
32 // PDU ID: 0x32
01 00 00 // Length: 0x0001
00 // Reserved: 0x00
04 01 // Function: 0x04 (Read), Items: 1
// S7 Item (读取DB1.DBW0开始的5个字)
12 0A 10 02 00 01 // Spec=0x12, Length=10, SyntaxID=0x10
00 01 00 00 // Area=0x00(DataBlock), DB=1, Start=0
00 03 00 04 // Word access, 5 items, 2 bytes each
3.4.3 读取响应报文
cpp
struct S7_Read_Response {
S7_Header header; // S7头
uint8_t item_count; // 响应项数量
uint8_t data_len[]; // 数据长度
uint8_t data[]; // 实际数据
};
实际读取响应示例:
// TPKT Header
03 00 00 23
// COTP DT Header
02 F0 80
// S7 Read Response
32 03 00 00 // PDU ID, Length, Reserved
01 00 00 01 // Items: 1, Return code: 0x00 (Success)
0A 00 // Data length: 10 bytes
41 42 43 44 45 46 47 48 49 4A // Actual data (ASCII: "ABCDEFGHIJ")
4. 通讯原理
4.1 连接建立过程
Client (PC) Server (PLC)
│ │
│─────── TCP SYN ──────────────>│
│<────── TCP SYN+ACK ───────────│
│─────── TCP ACK ──────────────>│
│ │
│───── COTP CR (Connect Request)│
│ │
│<─── COTP CC (Connect Confirm)│
│ │
│ 连接已建立 │
4.2 数据读取流程
Application │ SiemensS7 Class │ Network │ PLC
│ │ │ │
│ ReadByteArray │ │ │
│──────────────>│ │ │
│ │ Build S7 Request │ │
│ │───────────────────>│ │
│ │ │ TCP/IP │
│ │ │─────────────>│
│ │ │ │ Process
│ │ │<─────────────│ Response
│ │<───────────────────│ │
│ │ Parse Response │ │
│<──────────────│ │ │
│ Data │ │ │
4.3 地址解析机制
4.3.1 S7地址格式
[DB编号.][区域代码][地址][.位号]
示例:
DB1.DBW0 - DB1的字地址0
M10.0 - 位存储器M10.0
I0.0 - 输入位I0.0
QW0 - 输出字QW0
4.3.2 内存区域编码
csharp
public enum StoreType
{
DataBlock = 0x00, // DB块
Timer = 0x01, // 定时器
Counter = 0x02, // 计数器
Memory = 0x03, // 位存储器(M)
Input = 0x04, // 输入(I)
Output = 0x05 // 输出(Q)
}
4.3.3 地址解析算法
csharp
// 伪代码示例
function ParseAddress(addressString):
// 解析 "DB1.DBW0" 格式
if addressString contains "DB":
dbNumber = extract number before first "."
area = DataBlock
offset = extract number after "."
// 解析 "M10.0" 格式
if addressString matches "[A-Z]\d+\.\d+":
area = determineArea(letter)
wordOffset = number before "."
bitOffset = number after "."
return { dbNumber, area, offset, bitOffset }
5. 调用方法与API
5.1 SiemensS7类主要属性
csharp
public class SiemensS7
{
// 连接参数
public string IPAddress { get; set; } // PLC IP地址
public int Port { get; set; } // 通讯端口,默认102
public CpuType CpuType { get; set; } // CPU类型
public short Rack { get; set; } // 机架号
public short Slot { get; set; } // 插槽号
// 超时设置
public int ConnectTimeOut { get; set; } // 连接超时
public int ReceiveTimeOut { get; set; } // 接收超时
}
5.2 连接管理API
5.2.1 建立连接
csharp
// 创建实例
SiemensS7 plc = new SiemensS7();
// 设置参数
plc.IPAddress = "192.168.1.100";
plc.Port = 102;
plc.CpuType = CpuType.S71200;
plc.Rack = 0;
plc.Slot = 0;
// 建立连接
OperateResult result = plc.Connect();
if (result.IsSuccess)
{
// 连接成功
}
else
{
// 连接失败,检查result.Message
}
5.2.2 断开连接
csharp
plc.DisConnect();
5.3 数据读取API
5.3.1 按地址读取字节数组
csharp
// 读取DB1.DBW0开始的10字节
OperateResult<byte[]> result = plc.ReadByteArray(
StoreType.DataBlock, // 存储区域
1, // DB编号
0, // 起始地址
10 // 读取长度
);
if (result.IsSuccess)
{
byte[] data = result.Content;
// 处理数据
}
5.3.2 按变量地址读取
csharp
// 读取变量 "DB1.DBW0"
OperateResult<object> result = plc.ReadVariable("DB1.DBW0");
if (result.IsSuccess)
{
object value = result.Content;
// value 可能是 int, short, float, bool 等类型
}
5.4 数据写入API
5.4.1 按变量地址写入
csharp
// 写入整数到 "DB1.DBW0"
int value = 12345;
OperateResult result = plc.WriteVariable("DB1.DBW0", value);
if (result.IsSuccess)
{
// 写入成功
}
// 写入浮点数
float floatValue = 123.45f;
result = plc.WriteVariable("DB1.DBD4", floatValue);
6. 实际应用示例
6.1 基础读写操作
csharp
using NET8_CommModbusMcS7Lib;
class S7CommunicationExample
{
SiemensS7 plc = new SiemensS7();
// 初始化连接
public bool Initialize()
{
plc.IPAddress = "192.168.1.100";
plc.Port = 102;
plc.CpuType = CpuType.S71200;
plc.Rack = 0;
plc.Slot = 0;
var result = plc.Connect();
return result.IsSuccess;
}
// 批量读取数据
public byte[] ReadProductionData()
{
// 读取DB100.DBB0开始的100字节
var result = plc.ReadByteArray(StoreType.DataBlock, 100, 0, 100);
if (result.IsSuccess)
{
return result.Content;
}
else
{
// 错误处理
Console.WriteLine($"读取失败: {result.Message}");
return null;
}
}
// 读取生产参数
public int ReadParameter()
{
// 读取DB1.DBD10的整数
var result = plc.ReadVariable("DB1.DBD10");
if (result.IsSuccess && result.Content is int value)
{
return value;
}
return 0;
}
// 写入控制指令
public bool WriteControlCommand(bool startCommand)
{
// 写入位信号
var result = plc.WriteVariable("M0.0", startCommand);
return result.IsSuccess;
}
}
6.2 实际应用:数据采集系统
csharp
public class DataAcquisitionSystem
{
private SiemensS7 plc;
private bool isRunning;
public bool StartAcquisition()
{
plc = new SiemensS7();
plc.IPAddress = "192.168.1.100";
plc.CpuType = CpuType.S71200;
if (!plc.Connect().IsSuccess)
{
return false;
}
isRunning = true;
Task.Run(() => AcquisitionLoop());
return true;
}
private void AcquisitionLoop()
{
while (isRunning)
{
try
{
// 读取生产数据
var productionData = ReadProductionData();
// 读取设备状态
var deviceStatus = ReadDeviceStatus();
// 处理数据
ProcessData(productionData, deviceStatus);
Thread.Sleep(1000); // 1秒采集周期
}
catch (Exception ex)
{
Console.WriteLine($"采集异常: {ex.Message}");
}
}
}
private ProductionData ReadProductionData()
{
// 读取DB100的数据块
var rawData = plc.ReadByteArray(StoreType.DataBlock, 100, 0, 50);
if (rawData.IsSuccess)
{
byte[] data = rawData.Content;
return new ProductionData
{
Temperature = BitConverter.ToInt16(data, 0) / 10.0f,
Pressure = BitConverter.ToInt16(data, 2) / 100.0f,
FlowRate = BitConverter.ToInt32(data, 4),
ProductCount = BitConverter.ToUInt32(data, 8)
};
}
return null;
}
private DeviceStatus ReadDeviceStatus()
{
// 读取状态位
var statusResult = plc.ReadVariable("M0.0");
var alarmResult = plc.ReadVariable("M0.1");
return new DeviceStatus
{
IsRunning = (bool)statusResult.Content,
HasAlarm = (bool)alarmResult.Content
};
}
public void StopAcquisition()
{
isRunning = false;
plc?.DisConnect();
}
}
// 数据模型
public class ProductionData
{
public float Temperature { get; set; }
public float Pressure { get; set; }
public int FlowRate { get; set; }
public uint ProductCount { get; set; }
}
public class DeviceStatus
{
public bool IsRunning { get; set; }
public bool HasAlarm { get; set; }
}
6.3 高级应用:数据解析
csharp
public class S7DataParser
{
// 解析复杂数据结构
public RecipeData ParseRecipe(byte[] rawData)
{
return new RecipeData
{
RecipeId = BitConverter.ToUInt16(rawData, 0),
RecipeName = Encoding.ASCII.GetString(rawData, 2, 20).TrimEnd('\0'),
Temperature = BitConverter.ToInt16(rawData, 22) / 10.0f,
Pressure = BitConverter.ToInt16(rawData, 24) / 100.0f,
Speed = BitConverter.ToUInt16(rawData, 26),
Duration = BitConverter.ToUInt16(rawData, 28),
Enabled = rawData[30] != 0
};
}
// 构建写入数据
public byte[] BuildRecipeData(RecipeData recipe)
{
byte[] data = new byte[32];
BitConverter.GetBytes((ushort)recipe.RecipeId).CopyTo(data, 0);
Encoding.ASCII.GetBytes(recipe.RecipeName.PadRight(20, '\0')).CopyTo(data, 2);
BitConverter.GetBytes((short)(recipe.Temperature * 10)).CopyTo(data, 22);
BitConverter.GetBytes((short)(recipe.Pressure * 100)).CopyTo(data, 24);
BitConverter.GetBytes((ushort)recipe.Speed).CopyTo(data, 26);
BitConverter.GetBytes((ushort)recipe.Duration).CopyTo(data, 28);
data[30] = (byte)(recipe.Enabled ? 1 : 0);
return data;
}
}
public class RecipeData
{
public ushort RecipeId { get; set; }
public string RecipeName { get; set; }
public float Temperature { get; set; }
public float Pressure { get; set; }
public ushort Speed { get; set; }
public ushort Duration { get; set; }
public bool Enabled { get; set; }
}
7. 故障排除
7.1 常见问题与解决方案
7.1.1 连接失败
症状 :Connect()方法返回失败
可能原因:
- IP地址错误
- PLC未开机
- 网络不通
- CPU类型或Rack/Slot参数错误
解决方案:
csharp
// 1. 检查网络连接
Ping ping = new Ping();
var reply = ping.Send("192.168.1.100");
if (reply.Status != IPStatus.Success)
{
Console.WriteLine("网络不通,请检查IP地址和网络连接");
}
// 2. 验证PLC参数
// S7-1200/1500: Rack=0, Slot=0或1
// S7-300/400: 根据实际配置设置
// 3. 尝试不同的CPU类型
foreach (var cpuType in new[] { CpuType.S71200, CpuType.S71500 })
{
plc.CpuType = cpuType;
if (plc.Connect().IsSuccess)
{
Console.WriteLine($"连接成功,CPU类型: {cpuType}");
break;
}
}
7.1.2 读取数据错误
症状 :ReadByteArray()返回失败或数据异常
可能原因:
- DB块不存在
- 地址超出范围
- PLC处于STOP模式
解决方案:
csharp
// 安全读取函数
public byte[] SafeReadData(int dbNumber, int startAddress, int length)
{
// 1. 验证参数
if (dbNumber < 1 || dbNumber > 99999)
{
Console.WriteLine("DB编号超出范围");
return null;
}
if (length < 1 || length > 65535)
{
Console.WriteLine("读取长度超出范围");
return null;
}
// 2. 分批读取大数据
const int maxBatchSize = 200;
List<byte> allData = new List<byte>();
for (int offset = 0; offset < length; offset += maxBatchSize)
{
int currentBatch = Math.Min(maxBatchSize, length - offset);
var result = plc.ReadByteArray(StoreType.DataBlock,
dbNumber,
startAddress + offset,
currentBatch);
if (!result.IsSuccess)
{
Console.WriteLine($"读取失败: {result.Message}");
return null;
}
allData.AddRange(result.Content);
}
return allData.ToArray();
}
7.1.3 数据类型转换错误
症状 :读取的数值与PLC显示不一致
可能原因:
- 字节序问题
- 数据类型不匹配
- 缩放因子未考虑
解决方案:
csharp
// 数据类型转换工具类
public class S7DataTypeConverter
{
// 考虑缩放因子的整数读取
public static float ReadScaledValue(byte[] data, int offset, float scale)
{
short rawValue = BitConverter.ToInt16(data, offset);
return rawValue / scale;
}
// 处理大端序数据
public static int ReadBigEndianInt32(byte[] data, int offset)
{
return (data[offset] << 24) |
(data[offset + 1] << 16) |
(data[offset + 2] << 8) |
data[offset + 3];
}
// S7日期时间转换
public static DateTime ReadS7DateTime(byte[] data, int offset)
{
// S7日期格式:BCD编码
int year = BcdToDec(data[offset]);
int month = BcdToDec(data[offset + 1]);
int day = BcdToDec(data[offset + 2]);
// ... 其他字段
return new DateTime(year, month, day);
}
private static int BcdToDec(byte bcd)
{
return ((bcd >> 4) * 10) + (bcd & 0x0F);
}
}
7.2 性能优化
7.2.1 批量操作优化
csharp
// 不好的做法:多次单独读取
for (int i = 0; i < 100; i++)
{
var result = plc.ReadByteArray(StoreType.DataBlock, 1, i * 2, 2);
}
// 好的做法:一次批量读取
var result = plc.ReadByteArray(StoreType.DataBlock, 1, 0, 200);
7.2.2 连接池管理
csharp
public class S7ConnectionPool
{
private Dictionary<string, SiemensS7> connections = new Dictionary<string, SiemensS7>();
public SiemensS7 GetConnection(string ipAddress)
{
if (!connections.ContainsKey(ipAddress))
{
var plc = new SiemensS7();
plc.IPAddress = ipAddress;
plc.Connect();
connections[ipAddress] = plc;
}
return connections[ipAddress];
}
public void CloseAllConnections()
{
foreach (var plc in connections.Values)
{
plc.DisConnect();
}
connections.Clear();
}
}
8. 总结
西门子S7通讯协议是一个结构完整、功能强大的工业通讯协议。通过理解其分层协议栈和报文格式,开发者可以实现稳定可靠的PLC数据交换。
关键要点:
- 协议分层:TPKT → COTP → S7,每层都有明确的功能
- 报文格式:严格按照规范构造报文头和数据部分
- 地址解析:正确理解S7地址格式和内存区域编码
- 错误处理:完善的异常处理和重试机制
- 性能优化:合理使用批量操作和连接管理