西门子通讯协议-S7COMM报文

     - (1)建立TCP连接      Socket.Connect

     - (2)发送访问请求     COTP

     - (3)交换通信信息     Setup Communication

     - (4)执行相关操作     读、写、PLC启停、时间、上传下载

一、CTOP请求连接

static void Main(string[] args)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    //socket.Connect("192.168.2.1", 102);      // 200Smart
    socket.Connect("192.168.151.200", 102);// 1500
}

COTP(socket);// 如果需要判断是否连接成功:通过异常捕获

static void COTP(Socket socket)
{
    List<byte> bytes = new List<byte>();
    // TPKT
    bytes.Add(0x03);
    bytes.Add(0x00);
    bytes.Add(0x00);
    bytes.Add(0x16);
    // COTP
    bytes.Add(0x11);//   10#17
    bytes.Add(0xe0);

    bytes.Add(0x00);
    bytes.Add(0x00);
    bytes.Add(0x00);
    bytes.Add(0x00);
    bytes.Add(0x00);

    bytes.Add(0xc0);
    bytes.Add(0x01);
    bytes.Add(0x0a);

    bytes.Add(0xc1);   // 源设备(上位机PC)通信配置      S7协议   西门子设备之间也是要使用
    bytes.Add(0x02);
    bytes.Add(0x01);   //[17]
    bytes.Add(0x00);   //[18]

    bytes.Add(0xc2);   // PLC   
    bytes.Add(0x02);
    bytes.Add(0x03);   // [21]
    bytes.Add(0x01);   // [22] 机架号0  插槽号1

    socket.Send(bytes.ToArray(), 0, bytes.Count, SocketFlags.None);
}

TCP为Socket对象的三次握手,发送COTP连接请求后收到PLC的响应报文。

二、SetupCommunication通讯数据交换

 

PLC响应数据:S7-Parameter-PDU length  240,表示PLC最大处理数据能力为240个字节

static void SetupCommunication(Socket socket)
{
    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x19);
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    reqBytes.Add(0x00);
    reqBytes.Add(0x08);// Paremeter部分字节长度

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// Data部分字节长度

    // S7-Parameter
    reqBytes.Add(0xf0);// Function
    reqBytes.Add(0x00);

    reqBytes.Add(0x00);
    reqBytes.Add(0x03);// Calling

    reqBytes.Add(0x00);
    reqBytes.Add(0x03);// Called

    reqBytes.Add(0x03);
    reqBytes.Add(0xc0);// PDU


    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}

三、S7COMM-Read报文

TPKT-整个请求字节数:后台会自动修改

Item[*]-数据块编号:请求数据地址为DB数据块时为DB数据块编号,请求其他数据时写0;

Item[*]-数据地址:举例M100.3的位置100.3表示为

单次读响应报文

static void Read(Socket socket)
{
    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    // 初始化无意义,只做点位,后续做修改  ,注意第161行
    reqBytes.Add(0x00);
    reqBytes.Add(0x19);// 注意下后台再修改---
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);// ROSCTR

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    reqBytes.Add(0x00);
    reqBytes.Add(0x0e);// Paremeter部分字节长度。注意后面修改

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// Data部分字节长度

    // S7-Parameter
    reqBytes.Add(0x04);// Function
    reqBytes.Add(0x01);// Item的个数

    reqBytes.AddRange(ItemQ0_4());// 组装请求Q0.4的Item

    ushort len = (ushort)reqBytes.Count;
    byte[] lenBytes = BitConverter.GetBytes(len);// 小端
    reqBytes[2] = lenBytes[1];
    reqBytes[3] = lenBytes[0];



    // 
    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}

// Q0.4  位  Q区0号字节中的4号位
static List<byte> ItemQ0_4()
{
    List<byte> items = new List<byte>();
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x01);

    items.Add(0x00);
    items.Add(0x01);//请求一个

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x82);

    // 地址计算
    int byteAddr = 0;
    byte bitAddr = 4;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}
static List<byte> ItemMB10()
{
    List<byte> items = new List<byte>();
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);

    items.Add(0x02);// Byte  - 0x02

    items.Add(0x00);
    items.Add(0x03);//请求三个   MB0  MB1  MB2

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x83);  //Q区

    // 地址计算
    int byteAddr = 10;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}
static List<byte> ItemDBD2()
{
    List<byte> items = new List<byte>();
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);

    items.Add(0x06);// DWord  - 0x06

    items.Add(0x00);
    items.Add(0x01);//请求一个

    items.Add(0x00);
    items.Add(0x01);// DB编号

    items.Add(0x84);  //DB区

    // 地址计算
    int byteAddr = 2;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}

请求地址MB10读取一个B返回的数据长度为一个字节时,PLC响应的数据长度为0x08,数据长度位数。

 

连续读响应报文

连续请求返回的数据不为最后一个且位奇数字节时,会出现占位字节

下图位连续请求两个Q区单位数据时PLC的响应报文

请求的即将返回长度<PDU(PLC最大处理数据量,通过数据交换通讯获取)

/// 关于Fill Byte:
/// 明确:读请求的时候   可以进行多个Item的分组  
///     比如:第一个Item  Q0.4(1)     返回Data中一个字节
///           第二个Item  MW10(1)     返回Data中二个字节  ,不管请求多少都是偶数个
///           第三个Item  MB5(3)      返回Data中一个字节 , 
///           第四个Item  MB1(1)      返回Data中一个字节
///           Parameter中请求三个Item   Data中分别响应三个Item
/// Item[1]   1Byte     奇数个   需要填充
/// Item[2]   2Byte     偶数个   不需要填充
/// Item[3]   3Byte     奇数个   需要填充
/// Item[4]   1Byte     奇数个   不需要填充
/// 结论:
/// 1、返回的Data中的Item是否在最后一个
/// 2、返回的Data中的Item中的数据字节数是否奇数个
/// 如果前两条件满足,则添加Fill Byte

连续请求示例:

static void Read(Socket socket)
{
    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    // 初始化无意义,只做点位,后续做修改  ,注意第161行
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// 注意下后台再修改---
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);// ROSCTR

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    reqBytes.Add(0x00);
    reqBytes.Add(0x26);// Paremeter部分字节长度。注意后面修改    14   26   1A     38  26

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// Data部分字节长度

    // S7-Parameter
    reqBytes.Add(0x04);// Function
    reqBytes.Add(0x03);// Item的个数

    reqBytes.AddRange(ItemQ0_4());// 组装请求Q0.4的Item
    //reqBytes.AddRange(ItemMB10());// 组装请求MB10的Item
    //reqBytes.AddRange(ItemVW100());// 组装请求VW100的Item
    //reqBytes.AddRange(ItemVD0());// 组装请求VD0的Item
    //reqBytes.AddRange(ItemDBD2());// 组装请求VD0的Item

    ushort len = (ushort)reqBytes.Count;
    byte[] lenBytes = BitConverter.GetBytes(len);// 小端
    reqBytes[2] = lenBytes[1];
    reqBytes[3] = lenBytes[0];



    // 
    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}
static List<byte> ItemQ0_4()
{
    List<byte> items = new List<byte>();
    #region Q0.4
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x01);

    items.Add(0x00);
    items.Add(0x01);//请求一个   Q0.4  Q0.5  Q0.6    结果:返回异常。如何一个
    // 1、按字节、字、双字的方式一个次请求
    // 2、要求一个Item一个Bit     多个Item

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x82);  //Q区

    // 地址计算
    int byteAddr = 0;
    byte bitAddr = 4;
    byteAddr = (byteAddr << 3) + bitAddr;

    // int   [3][2][1][0]
    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));
    #endregion

    #region MW10
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x04);

    items.Add(0x00);
    items.Add(0x01);//请求一个 

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x83);  //M区

    // 地址计算
    byteAddr = 10;
    bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));
    #endregion

    #region MB10
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x02);

    items.Add(0x00);
    items.Add(0x01);//请求一个 

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x83);  //M区

    // 地址计算
    byteAddr = 10;
    bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));
    #endregion

    #region MB1
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x02);

    items.Add(0x00);
    items.Add(0x01);//请求一个 

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x83);  //Q区

    // 地址计算
    byteAddr = 1;
    bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));
    #endregion

    return items;
}

字符串读取

static void ReadString(Socket socket)
{
    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    // 初始化无意义,只做点位,后续做修改  ,注意第161行
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// 注意下后台再修改---
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);// ROSCTR

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    reqBytes.Add(0x00);
    reqBytes.Add(0x0e);// Paremeter部分字节长度。注意后面修改    14   26   1A   

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// Data部分字节长度

    // S7-Parameter
    reqBytes.Add(0x04);// Function
    reqBytes.Add(0x01);// Item的个数

    reqBytes.AddRange(ParamString());// 组装请求Q0.4的Item

    ushort len = (ushort)reqBytes.Count;
    byte[] lenBytes = BitConverter.GetBytes(len);// 小端
    reqBytes[2] = lenBytes[1];
    reqBytes[3] = lenBytes[0];


    // 
    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}
//DB1.DBB6
static List<byte> ParamString()
{
    List<byte> items = new List<byte>();
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);

    items.Add(0x03);// Char

    items.Add(0x00);
    items.Add(0x07);//请求五个

    items.Add(0x00);
    items.Add(0x01);// DB编号

    items.Add(0x84);  //DB区

    // 地址计算
    int byteAddr = 6;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    //BitConverter.GetBytes(byteAddr);  // [0][1][2][]
    //  /256  进行计算
    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}

请求字符串数据时需要加增加两个读取位置;

PLC响应返回的数据中前两个0a代表DB块中分配的控件,05表示当前所存的字符长度;

四、S7COMM-Write报文

单次写响应报文

static void Write(Socket socket)
{
    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    // 初始化无意义,只做点位,后续做修改  ,注意第161行
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// 注意下后台再修改---
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);// ROSCTR

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    //reqBytes.Add(0x00);
    //reqBytes.Add(0x26);// Paremeter部分字节长度。注意后面修改    14   26   1A     38  26

    //reqBytes.Add(0x00);
    //reqBytes.Add(0x00);// Data部分字节长度

    List<byte> paramList = ParameterItemM0_0();
    //List<byte> paramList = ParameterItemVW10();
    ushort pl = (ushort)paramList.Count;
    byte[] plenBytes = BitConverter.GetBytes(pl);
    reqBytes.Add(plenBytes[1]);
    reqBytes.Add(plenBytes[0]);

    List<byte> dataList = DataItemM0_0();
    //List<byte> dataList = DataItemVW10();
    ushort dl = (ushort)dataList.Count;
    byte[] dlenBytes = BitConverter.GetBytes(dl);
    reqBytes.Add(dlenBytes[1]);
    reqBytes.Add(dlenBytes[0]);

    // 
    reqBytes.AddRange(paramList);
    reqBytes.AddRange(dataList);

    ushort len = (ushort)reqBytes.Count;
    byte[] lenBytes = BitConverter.GetBytes(len);
    reqBytes[2] = lenBytes[1];
    reqBytes[3] = lenBytes[0];

    // 
    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}
static List<byte> ParameterItemM0_0()
{
    List<byte> items = new List<byte>();
    items.Add(0x05);//功能码:写入动作
    items.Add(0x01);// Items的个数  Data的Item个数据与Parameter的Item个数匹配

    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x01);

    items.Add(0x00);
    items.Add(0x01);

    items.Add(0x00);
    items.Add(0x00);

    items.Add(0x83);  //Q区

    // 地址计算
    int byteAddr = 0;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}

static List<byte> DataItemM0_0()
{
    List<byte> items = new List<byte>();
    items.Add(0x00);
    items.Add(0x03);
    items.Add(0x00);
    items.Add(0x01);
    items.Add(0x01);// 表示写入数据,Bit(0x00   0x01)
    return items;
}
static List<byte> ParameterItemVW10()
{
    List<byte> items = new List<byte>();
    items.Add(0x05);//功能码:写入动作
    items.Add(0x01);// Items的个数  Data的Item个数据与Parameter的Item个数匹配

    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x04);

    items.Add(0x00);
    items.Add(0x02);

    items.Add(0x00);
    items.Add(0x01);

    items.Add(0x84);  //Q区

    // 地址计算
    int byteAddr = 10;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}

static List<byte> DataItemVW10()
{
    List<byte> items = new List<byte>();
    items.Add(0x00);
    items.Add(0x04);

    items.Add(0x00);
    items.Add(0x20);// 写入的位数

    items.Add(0x00);
    items.Add(0x7b);// 表示写入数据

    items.Add(0x00);
    items.Add(0x7c);// 表示写入数据
    return items;
}

 连续写响应报文

static void Write(Socket socket)
{
    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    // 初始化无意义,只做点位,后续做修改  ,注意第161行
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// 注意下后台再修改---
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);// ROSCTR

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    //reqBytes.Add(0x00);
    //reqBytes.Add(0x26);// Paremeter部分字节长度。注意后面修改    14   26   1A     38  26

    //reqBytes.Add(0x00);
    //reqBytes.Add(0x00);// Data部分字节长度

    //List<byte> paramList = ParameterItemM0_0();
    //List<byte> paramList = ParameterItemVW10();
    List<byte> paramList = ParameterItemMulit();
    ushort pl = (ushort)paramList.Count;
    byte[] plenBytes = BitConverter.GetBytes(pl);
    reqBytes.Add(plenBytes[1]);
    reqBytes.Add(plenBytes[0]);

    //List<byte> dataList = DataItemM0_0();
    //List<byte> dataList = DataItemVW10();
    List<byte> dataList = DataItemMulit();
    ushort dl = (ushort)dataList.Count;
    byte[] dlenBytes = BitConverter.GetBytes(dl);
    reqBytes.Add(dlenBytes[1]);
    reqBytes.Add(dlenBytes[0]);

    // 
    reqBytes.AddRange(paramList);
    reqBytes.AddRange(dataList);

    ushort len = (ushort)reqBytes.Count;
    byte[] lenBytes = BitConverter.GetBytes(len);
    reqBytes[2] = lenBytes[1];
    reqBytes[3] = lenBytes[0];

    // 
    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}
static List<byte> ParameterItemMulit()
{
    List<byte> items = new List<byte>();
    items.Add(0x05);//功能码:写入动作
    items.Add(0x03);// Items的个数  Data的Item个数据与Parameter的Item个数匹配


    // VB10
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x02);// 类型 02 Byte   03  Char   04 Word   06  DWord

    items.Add(0x00);
    items.Add(0x01);// 写一个值

    items.Add(0x00);
    items.Add(0x01);

    items.Add(0x84);  //V

    // 地址计算
    int byteAddr = 10;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    // VB100 VB101   VB102 VB103
    // VW100         VW102
    //items = new List<byte>();
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x04);// 类型 02 Byte   03  Char   04 Word   06  DWord

    items.Add(0x00);
    items.Add(0x02);// 写一个值

    items.Add(0x00);
    items.Add(0x01);

    items.Add(0x84);  //V

    // 地址计算
    byteAddr = 100;
    bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    // VD110
    //items = new List<byte>();
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x06);// 类型 02 Byte   03  Char   04 Word   06  DWord

    items.Add(0x00);
    items.Add(0x01);// 写一个值

    items.Add(0x00);
    items.Add(0x01);

    items.Add(0x84);  //V

    // 地址计算
    byteAddr = 110;
    bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}

static List<byte> DataItemMulit()
{
    // VB10   Byte  
    List<byte> items = new List<byte>();
    items.Add(0x00);
    items.Add(0x04);//Byte/Word/DWord

    items.Add(0x00);
    items.Add(0x08);// 写入的位数

    items.Add(0x0a);//10#10

    items.Add(0x00);// Fill byte  当出现奇数个字节   需要填充

    // VW100  VW102    123   7B   124   7C
    items.Add(0x00);
    items.Add(0x04);//Byte/Word/DWord

    items.Add(0x00);
    items.Add(0x20);// 写入的位数    2个字   4个字节   10#32位   16#20

    items.Add(0x00);//
    items.Add(0x7b);//10#123
    items.Add(0x00);//
    items.Add(0x7c);//10#124


    // VD110   4.5     40 90 00 00
    items.Add(0x00);
    items.Add(0x04);

    items.Add(0x00);
    items.Add(0x20);

    items.Add(0x40);
    items.Add(0x90);
    items.Add(0x00);
    items.Add(0x00);

    return items;
}

与对于奇数字节的数据需要手动调整占位字节

 字符串写入

测试PLC为西门子200,西门子1200/1500写入,需要再字符Byte前必须添加两个字节:

1.0x00 空间  一般大于等于字符数即可,小于不行

2.0x00 字符数 

static void S7String(Socket socket)
{
    // 

    // 针对Byte数据进行处理

    List<byte> reqBytes = new List<byte>();
    // TPKT
    reqBytes.Add(0x03);
    reqBytes.Add(0x00);
    // 初始化无意义,只做点位,后续做修改  ,注意第161行
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);// 注意下后台再修改---
    // COTP
    reqBytes.Add(0x02);
    reqBytes.Add(0xf0);
    reqBytes.Add(0x80);   // 1000 0000
    // S7-Header
    reqBytes.Add(0x32);
    reqBytes.Add(0x01);// ROSCTR

    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);
    reqBytes.Add(0x00);

    // Parameter
    List<byte> paramList = ParameterItemString();
    ushort pl = (ushort)paramList.Count;
    byte[] plenBytes = BitConverter.GetBytes(pl);
    reqBytes.Add(plenBytes[1]);
    reqBytes.Add(plenBytes[0]);

    // Data
    List<byte> dataList = DataItemString();
    ushort dl = (ushort)dataList.Count;
    byte[] dlenBytes = BitConverter.GetBytes(dl);
    reqBytes.Add(dlenBytes[1]);
    reqBytes.Add(dlenBytes[0]);

    // 
    reqBytes.AddRange(paramList);
    reqBytes.AddRange(dataList);

    ushort len = (ushort)reqBytes.Count;
    byte[] lenBytes = BitConverter.GetBytes(len);
    reqBytes[2] = lenBytes[1];
    reqBytes[3] = lenBytes[0];

    // 
    socket.Send(reqBytes.ToArray(), 0, reqBytes.Count, SocketFlags.None);
}

static List<byte> ParameterItemString()
{
    List<byte> items = new List<byte>();
    items.Add(0x05);//功能码:写入动作
    items.Add(0x01);// Items的个数  Data的Item个数据与Parameter的Item个数匹配


    // VB20
    items.Add(0x12);

    items.Add(0x0a);
    items.Add(0x10);
    items.Add(0x03);// 类型 02 Byte   03  Char   04 Word   06  DWord

    items.Add(0x00);
    items.Add(0x05);// 写一个值

    items.Add(0x00);
    items.Add(0x01);

    items.Add(0x84);  //V

    // 地址计算
    int byteAddr = 20;
    byte bitAddr = 0;
    byteAddr = (byteAddr << 3) + bitAddr;

    items.Add((byte)(byteAddr / 256 / 256 % 256));
    items.Add((byte)(byteAddr / 256 % 256));
    items.Add((byte)(byteAddr % 256));

    return items;
}
static List<byte> DataItemString()
{
    string str = "Hello";
    byte[] strBytes = Encoding.UTF8.GetBytes(str);

    List<byte> items = new List<byte>();
    items.Add(0x00);
    items.Add(0x09);// String

    int bitCount = strBytes.Length;
    items.Add(BitConverter.GetBytes(bitCount)[1]);
    items.Add(BitConverter.GetBytes(bitCount)[0]);// 写入的位数

    items.AddRange(strBytes);

    return items;
}

五、S7COMM-其他功能报文

PLC   200Smart      并不对所有PLC有效    1200  1500 无法操作
 Step7    s7   0x32标准协议   对200Smart进行操作
博途     S7   0x72标准协议   对1200   1500
区别:结构变化很大    72 包含的内容更多

S7COMM通讯限制:

PLC  Run 请求与响应

static void Run(Socket socket)
{
    string s = "P_PROGRAM";
    byte[] sb = Encoding.ASCII.GetBytes(s);

    byte[] runBytes = new byte[] {
        // TPKT
        0x03,0x00,0x00,0x25,
            // COTP
        0x02,0xf0,0x80,
        // Header
        0x32,0x01,
        0x00,0x00,0x00,0x00,
        // PL
        0x00,0x14,
        // DL
        0x00,0x00,
        // Parameter
        0x28,  // 启动标识
        0x00,0x00,0x00,0x00,0x00,0x00,0xfd,
        0x00, 0x00,
        0x09,
        // P_PROGRAM  9个字符对应的16进制Ascii值
        0x50,0x5f,0x50,0x52,0x4f,0x47,0x52,0x41,0x4d
    };
    try
    {
        socket.Send(runBytes);
        int count = socket.Receive(new byte[20]);
    }
    catch (Exception ex)
    {

    }
}

PLC  Stop请求与响应

static void Stop(Socket socket)
{
    byte[] stopBytes = new byte[] {
        // TPKT
        0x03,0x00,0x00,0x21,
            // COTP
        0x02,0xf0,0x80,
        // Header
        0x32,0x01,
        0x00,0x00,0x00,0x00,
        // PL
        0x00,0x10,
        // DL
        0x00,0x00,
        // Parameter
        0x29,// Stop标识
        0x00,0x00,0x00,0x00,0x00,
        0x09,
        // P_PROGRAM  9个字符对应的16进制Ascii值
        0x50,0x5f,0x50,0x52,0x4f,0x47,0x52,0x41,0x4d
    };
    socket.Send(stopBytes);
}

时间获取与设置请求与响应

static void ReadTime(Socket socket)
{
    byte[] readTimeBytes = new byte[] {
        // TPKT
        0x03,0x00,0x00,0x1d,
            // COTP
        0x02,0xf0,0x80,
        // Header
        0x32,0x07, // UserData
        0x00,0x00,0x00,0x00,
        // PL
        0x00,0x08,
        // DL
        0x00,0x04,

        // Parameter
        0x00,0x01,0x12,

        0x04,// Parameter中当前字节后的字节数
        0x11, 0x47,
        0x01,// SubFunction  Read Clock
        0x00,
        // Data
        0x0a,0x00,0x00,0x00
    };
    socket.Send(readTimeBytes);
}

PLC响应数据:

Year 1:值为90-99时表示19XX年,否则表示20XX年;

获取的数据是16进制,需要直接转成10进制,例如Year2表示22年数据是16#22需要直接转为10#22而不是10#34;

六、附录:

附录1:COTP->PDU type已知枚举值

附录2:S7Header->ROSCTR已知枚举值

附录3:S7Header->Error class已知枚举值

附录4:S7Parameter->Error code已知枚举值

附录5:S7Parameter->Function已知枚举值

附录6:S7Parameter->Item->Syntax Id已知枚举值

附录7:S7Parameter->Item->Transport size常见值

附录8:S7Parameter->Item->Area常见值

附录9:S7Data->Item->Return code已知枚举值

附录10:Userdata已知枚举值

附录11:PI service names已知枚举值

附录14:SZL-ID 类型

 

posted @ 2024-05-11 18:12  ZHIZRL  阅读(189)  评论(0编辑  收藏  举报