西门子S7通信 含C#代码
- 前言
- 一、S7协议
- 1.协议概述
- 第5层TPKT
- 第6层COTP
- 第7层S7 communication
- 2.一次完整的S7(读)通讯
- 二、完整代码
- 三、附错误码
前言
本文主要介绍西门子的S7Comm协议(含源码)
和西门子PLC通讯,.Net平台中有不少已经封装好的动态库,使用起来有一些局限性。此文章主要是自己拼装S7协议报文来实现通讯。
一、S7协议
S7Comm(S7 Communication)是西门子专有的协议,是西门子S7通讯协议簇里的一种。
1.协议概述
完整的一个S7通信请求包含7层。
OSI层 | S7协议 |
---|---|
第一层:物理层 | Frame |
第二层:数据链路层 | Ethernet II |
第三层:网络层 | Internet Protocol Version4 |
第四层:传输层 | Transmission Control Protocol |
第五层:会话层 | TPKT |
第六层:表示层 | COTP |
第七层:应用层 | S7 Communication |
其中,第1~4层会由计算机自己完成(底层驱动程序,对应C#代码中socket对象,我们只需拼接后3层报文)
。关于后三层报文有大量博客阐述,其中有些说法是错误的,最好的是有个PLC自己去尝试总结。
第5层TPKT
应用程数据传输协议,介于TCP和COTP协议之间;这是一个传输服务协议,主要用来在COTP和TCP之间建立桥梁;
第6层COTP
COTP 是 OSI 7层协议定义的位于TCP之上的协议。COTP 以“Packet”为基本单位来传输数据,这样接收方会得到与发送方具有相同边界的数据;
第7层S7 communication
这一层和用户数据相关,对PLC数据的读取报文在这里完成;
2.一次完整的S7(读)通讯
完整的一次plc通讯应包含
- TCP三次握手,对应C#中socket.Connect()方法;
- 发起COTP请求,报文第六个字节为PDU类型 0x0e;
- 建立通信,此时包含完整的7层S7协议,S7 communication中功能码0xf0可以判断;
- 发送请求数据;
- 实际还应有断开请求,关闭socket(TCP四次挥手),代码中未包含。
对于一些字节的处理在代码中有相应描述,好在有很多字节是固定的。
PDU typ类型有:
0x1: ED Expedited Data,加急数据
0x2: EA Expedited Data Acknowledgement,加急数据确认
0x4: UD,用户数据
0x5: RJ Reject,拒绝
0x6: AK Data Acknowledgement,数据确认
0x7: ER TPDU Error,TPDU错误
0x8: DR Disconnect Request,断开请求
0xC: DC Disconnect Confirm,断开确认
0xD: CC Connect Confirm,连接确认
0xE: CR Connect Request,连接请求
0xF: DT Data,数据传输
功能码附录:
0x00 CPU services CPU服务
0xf0 Setup communication 建立通信
0x04 Read Var 读取值
0x05 Write Var 写入值
0x1a Request download 请求下载
0x1b Download block 下载块
0x1c Download ended 下载结束
0x1d Start upload 开始上传
0x1e Upload 上传
0x1f End upload 上传结束
0x28 PI-Service 程序调用服务
0x29 PLC Stop 关闭PLC
二、完整代码
代码如下(示例):
using System;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Threading;
namespace comtest
{
internal class Program
{
static Task task;
static void Main(string[] args)
{
JpS7();
while (true)
{
}
}
private static void JpS7()
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect("192.168.188.102", 102);
task = new Task(() =>
{
while (true)
{
byte[] bytes = new byte[4];
socket.Receive(bytes);
Console.WriteLine(BitConverter.ToString(bytes).Replace("-", " 0x"));
byte[] num = bytes.ToList().GetRange(2, 2).ToArray();
Array.Reverse(num);
ushort num1 = BitConverter.ToUInt16(num, 0);
byte[] bytes1 = new byte[num1 - 4];
socket.Receive(bytes1);
Console.WriteLine(BitConverter.ToString(bytes1).Replace("-", " 0x"));
Console.WriteLine(num1);
if (bytes1[15] == 0x04)
{
if (bytes1[14] == 0x00)//errorcode 成功了才有下面分析,否则根据错误码提示相应错,错误字典可见西门子
{
var bytedatalength = bytes1.ToList().GetRange(11, 2).ToArray();
Array.Reverse(bytedatalength);
ushort datalenth = BitConverter.ToUInt16(bytedatalength, 0);
if (bytes1[16] == 1)//暂时分析一个item 多item逐渐扩展
{
byte[] bytedatas = new byte[datalenth - 4];
//bytedatas就是整个返回的数据,根据位置,可以推测出字节范围处理求值
bytedatas = bytes1.ToList().GetRange(bytes1.Count() - (datalenth - 4), datalenth - 4).ToArray();
byte[] bytef2 = new byte[4];
bytef2 = bytedatas.ToList().GetRange(4, 4).ToArray();
Array.Reverse(bytef2);
var f2data = BitConverter.ToSingle(bytef2, 0);
Console.WriteLine(f2data);
byte[] bytef3 = new byte[4];
bytef2 = bytedatas.ToList().GetRange(0, 4).ToArray();
Array.Reverse(bytef2);
var f3data = BitConverter.ToSingle(bytef2, 0);
Console.WriteLine(f3data);
}
}
}
}
});
task.Start();
ConnectRequest(socket);
SetupCommunication(socket);
ReadVar(socket);
}
private static void ConnectRequest(Socket socket)
{
byte[] sendcotp = new byte[]
{
0x03,//版本号
0x00,//保留字段
0x00,0x16,//该报文长度22 假如大于255,则0x00有值,最大长度
0x11,//cotp协议段字节数 17
0xe0,//PDU类型 0xe0代表CR connect request连接请求
0x00,0x00,//源标识
0x00,0x2e,//目标标识
0x00,
0xc1 ,//src-tsap
0x02,//参数长度
0x01,//01:PG通讯、02:OP通讯、03:S7单边通讯、10:S7双边通讯
0x00,//
0xc2,//dst-tsap
0x02,
0x03,
0x00,//前一位为rack*2,后一位为slot插槽,假如机架号为2插槽为10则应为0x4a
0xc0,//TPDU的size
0x01,
0x0a//2^10
};
socket.Send(sendcotp);
}
private static void SetupCommunication(Socket socket)
{
byte[] s7con = new byte[]
{
//tpkt段
0x03,
0x00,
0x00,
0x19,
//cotp段 固定
0x02,
0xf0,
0x80,
//s7communication段(header)
0x32,//固定0x32开头 72是访问优化的地址块
0x01,//发送请求 0x02相应请求不带数据 0x03响应并带数据 0x07 扩展
0x00,0x00,0xff,0xff,//暂时固定
0x00,0x08,//parameter长度
0x00,0x00,//data长度(请求固定为0)
//s7communication段(parameter)
0xf0, //功能码,f0建立通信
0x00,
0x00,0x03,//可能是请求队列
0x00,0x03,//可能是响应队列
0x03,0xc0,//960
};
socket.Send(s7con);
Thread.Sleep(100);
}
private static void ReadVar(Socket socket)
{
byte[] buffer = new byte[]
{
0x03 ,0x00 ,0x00 ,0x1f ,
0x02 , 0xf0 , 0x80 ,
0x32 ,
0x01 ,
0x00 ,
0x00 ,
0x00 ,
0x00 ,
0x00 , 0x0e ,//parameter里面14个字节
0x00 ,0x00 ,
//Db20 db20 = (Db20)plc.ReadStruct(typeof(Db20), 20, 420);
0x04 ,//读请求
0x01 ,//item个数 即结构个数
0x12 ,//结构标识
0x0a ,//当前item下数据个数10
0x10 ,//寻址模式 对应DB20.DBD0
0x02 ,//读取数据类型01 bit,02 byte,03 char,04 word,05 int,dword,counter
0x00 ,0x0a ,//读取长度,对应Db20结构体共21个字节
0x00 ,0x14 ,//块编号 对应DB20中的20
0x84 ,//84对应DB 83M,82Q,81I,80直接访问外设 87全局变量 86局部变量 1c计数器 1d定时器
0x00 ,0x0d ,0x20//开始字节以及位
//0000 0000 0000 0 000
// 字节地址 位地址
//0x0d20转2进制0000 1101 0010 0000 因为一个字节8位用3位2进制即可标识,此处末3位000代表从0位开始取,1101 0010 0转10进制即为420
};
socket.Send(buffer);
}
}
}
三、附错误码
0x0000 成功
0x0110 块号无效
0x0111 请求长度无效
0x0112 参数无效
0x0113 块类型无效
0x0114 找不到块
0x0115 块已存在
0x0116 块被写保护
0x0117 块/操作系统更新太大
0x0118 块号无效
0x0119 输入的密码不正确
0x011A PG资源错误
0x011B PLC资源错误
0x011C 协议错误
0x011D 块太多(与模块相关的限制)
0x011E 不再与数据库建立连接,或者S7DOS句柄无效
0x011F 结果缓冲区太小
0x0120 块结束列表
0x0140 可用内存不足
0x0141 由于缺少资源,无法处理作业
0x8001 当块处于当前状态时,无法执行请求的服务
0x8003 S7协议错误:传输块时发生错误
0x8100 应用程序,一般错误:远程模块未知的服务
0x8104 未在模块上实现此服务或报告了帧错误
0x8204 对象的类型规范不一致
0x8205 复制的块已存在且未链接
0x8301 模块上的内存空间或工作内存不足,或者指定的存储介质不可访问
0x8302 可用资源太少或处理器资源不可用
0x8304 无法进一步并行上传。存在资源瓶颈
0x8305 功能不可用
0x8306 工作内存不足(用于复制,链接,加载AWP)
0x8307 保持性工作记忆不够(用于复制,链接,加载AWP)
0x8401 S7协议错误:无效的服务序列(例如,加载或上载块)
0x8402 由于寻址对象的状态,服务无法执行
0x8404 S7协议:无法执行该功能
0x8405 远程块处于DISABLE状态(CFB)。该功能无法执行
0x8500 S7协议错误:帧错误
0x8503 来自模块的警报:服务过早取消
0x8701 寻址通信伙伴上的对象时出错(例如,区域长度错误)
0x8702 模块不支持所请求的服务
0x8703 拒绝访问对象
0x8704 访问错误:对象已损坏
0xD001 协议错误:非法的作业号
0xD002 参数错误:非法的作业变体
0xD003 参数错误:模块不支持调试功能
0xD004 参数错误:作业状态非法
0xD005 参数错误:作业终止非法
0xD006 参数错误:非法链路断开ID
0xD007 参数错误:缓冲区元素数量非法
0xD008 参数错误:扫描速率非法
0xD009 参数错误:执行次数非法
0xD00A 参数错误:非法触发事件
0xD00B 参数错误:非法触发条件
0xD011 调用环境路径中的参数错误:块不存在
0xD012 参数错误:块中的地址错误
0xD014 参数错误:正在删除/覆盖块
0xD015 参数错误:标签地址非法
0xD016 参数错误:由于用户程序错误,无法测试作业
0xD017 参数错误:非法触发号
0xD025 参数错误:路径无效
0xD026 参数错误:非法访问类型
0xD027 参数错误:不允许此数据块数
0xD031 内部协议错误
0xD032 参数错误:结果缓冲区长度错误
0xD033 协议错误:作业长度错误
0xD03F 编码错误:参数部分出错(例如,保留字节不等于0)
0xD041 数据错误:非法状态列表ID
0xD042 数据错误:标签地址非法
0xD043 数据错误:找不到引用的作业,检查作业数据
0xD044 数据错误:标签值非法,检查作业数据
0xD045 数据错误:HOLD中不允许退出ODIS控制
0xD046 数据错误:运行时测量期间非法测量阶段
0xD047 数据错误:“读取作业列表”中的非法层次结构
0xD048 数据错误:“删除作业”中的非法删除ID
0xD049 “替换作业”中的替换ID无效
0xD04A 执行'程序状态'时出错
0xD05F 编码错误:数据部分出错(例如,保留字节不等于0,...)
0xD061 资源错误:没有作业的内存空间
0xD062 资源错误:作业列表已满
0xD063 资源错误:触发事件占用
0xD064 资源错误:没有足够的内存空间用于一个结果缓冲区元素
0xD065 资源错误:没有足够的内存空间用于多个结果缓冲区元素
0xD066 资源错误:可用于运行时测量的计时器被另一个作业占用
0xD067 资源错误:“修改标记”作业过多(特别是多处理器操作)
0xD081 当前模式下不允许使用的功能
0xD082 模式错误:无法退出HOLD模式
0xD0A1 当前保护级别不允许使用的功能
0xD0A2 目前无法运行,因为正在运行的函数会修改内存
0xD0A3 I / O上活动的“修改标记”作业太多(特别是多处理器操作)
0xD0A4 '强制'已经建立
参考以下作者文章:
链接: 西门子S7协议及报文格式详解