基于QtPy (PySide6) 的PLC-HMI工程项目(三)PLC上行数据的准备
本次的通信对象是西门子S7-1500。
一、新建数据类型
打开博途,新建1500项目,CPU选1513-1PN,新建数据类型,命名为:AreaVar
这个命名为AreaVar的数据类型,包含了数据分区(I/QM/DB)、DB编号、数据起点、数据字节长度等信息,可用来进行各种以字节为单位的块间操作。
二、新建FC块,命名为:create_up_msg,用来组态上行报文
接口定义:
Input 数据类型 备注
frameHeader1 Byte 帧头字节1
frameHeader2 Byte 帧头字节2
srcAreaVar "AreaVar" 区域变量,该区域变量所指区域内的字节就是上行数据的有效字节
frameEnd1 Byte 帧尾字节1
frameEnd2 Byte 帧尾字节2
DB_CRC_TABLE Array[0..255] of Word CRC查表运算的表格
InOut
destArea "AreaVar" 目标区域变量,组态好的字节存放在这里用以发送
Temp
Temp_1 "AreaVar"
wCRC Word
i DInt
bTemp Byte
wTemp Word
nIndex DInt
scl:
////////////////////////////组态帧头/////////////////////////// POKE(area := #destArea.Area, dbNumber := #destArea.DBNum, byteOffset := 0, value := #frameHeader1); //帧头1 POKE(area := #destArea.Area, dbNumber := #destArea.DBNum, byteOffset := 1, value := #frameHeader2); //帧头2 ////////组态区域变量的信息(本帧数据的区域和起点、长度)////// POKE(area := #destArea.Area, //本帧数据的区域 dbNumber := #destArea.DBNum, byteOffset := 2, value := #srcAreaVar.Area); POKE(area := #destArea.Area, //字节占位 dbNumber := #destArea.DBNum, byteOffset := 3, value := #srcAreaVar.Area); "tmp_MDint" := #srcAreaVar.DBNum; //tmp_MDint:MD1000 POKE_BLK(area_src := 16#83, //DB编号 dbNumber_src := 0, byteOffset_src := 1000, area_dest := #destArea.Area, dbNumber_dest := #destArea.DBNum, byteOffset_dest := 4, count := 4); "tmp_MDint" := #srcAreaVar.Offset; //tmp_MDint:MD1000 POKE_BLK(area_src := 16#83, //地址偏移 dbNumber_src := 0, byteOffset_src := 1000, area_dest := #destArea.Area, dbNumber_dest := #destArea.DBNum, byteOffset_dest := 8, count := 4); "tmp_MDint" := #srcAreaVar.byteCount; //tmp_MDint:MD1000 POKE_BLK(area_src := 16#83, //变量字节数 dbNumber_src := 0, byteOffset_src := 1000, area_dest := #destArea.Area, dbNumber_dest := #destArea.DBNum, byteOffset_dest := 12, count := 4); ///////////////////////////////组态帧数据内容///////////////////////// POKE_BLK(area_src := #srcAreaVar.Area, dbNumber_src := #srcAreaVar.DBNum, byteOffset_src := #srcAreaVar.Offset, area_dest := #destArea.Area, dbNumber_dest := #destArea.DBNum, byteOffset_dest := 16, count := #srcAreaVar.byteCount); ///////////////////////////////组态帧尾//////////////////////////// POKE(area := #destArea.Area, dbNumber := #destArea.DBNum, byteOffset := 16 + #srcAreaVar.byteCount, value := #frameEnd1); //帧尾1 POKE(area := #destArea.Area, dbNumber := #destArea.DBNum, byteOffset := 17 + #srcAreaVar.byteCount, value := #frameEnd2); //帧尾2 /////////////////////////////////CRC16校验和////////////////////////////// #destArea.byteCount := 18 + #srcAreaVar.byteCount; #wCRC := 16#FFFF; FOR #i := 0 TO #destArea.byteCount-1 DO #bTemp := PEEK(area := #destArea.Area, byteOffset := #i, dbNumber := #destArea.DBNum); #wTemp := #wCRC XOR BYTE_TO_WORD(IN := #bTemp); #wTemp := #wTemp AND 16#00FF; #nIndex := WORD_TO_INT(IN := #wTemp); #wCRC := SHR(IN := #wCRC, N := 8) XOR #DB_CRC_TABLE[#nIndex]; END_FOR; //写入CRC字 POKE(area := #destArea.Area, dbNumber := #destArea.DBNum, byteOffset := 18 + #srcAreaVar.byteCount, value := #wCRC); #destArea.byteCount := 20 + #destArea.byteCount;完整的上行报文帧结构:
帧头1 + 帧头2 + 本次数据的区域参数(分区、DB号、字节偏移、字节长度)+ 数据字节块 + 帧尾1 + 帧尾2 + CRC字
三、CRC的查表表格
新建全局DB块,命名为DB_CRC_TABLE,数据类型为:Array[0..255] of Word,Array的内容为:这个是CRC16的查表运算映射表
[ 16#0000,16#C0C1,16#C181,16#0140,16#C301,16#03C0,16#0280,16#C241, 16#C601,16#06C0,16#0780,16#C741,16#0500,16#C5C1,16#C481,16#0440, 16#CC01,16#0CC0,16#0D80,16#CD41,16#0F00,16#CFC1,16#CE81,16#0E40, 16#0A00,16#CAC1,16#CB81,16#0B40,16#C901,16#09C0,16#0880,16#C841, 16#D801,16#18C0,16#1980,16#D941,16#1B00,16#DBC1,16#DA81,16#1A40, 16#1E00,16#DEC1,16#DF81,16#1F40,16#DD01,16#1DC0,16#1C80,16#DC41, 16#1400,16#D4C1,16#D581,16#1540,16#D701,16#17C0,16#1680,16#D641, 16#D201,16#12C0,16#1380,16#D341,16#1100,16#D1C1,16#D081,16#1040, 16#F001,16#30C0,16#3180,16#F141,16#3300,16#F3C1,16#F281,16#3240, 16#3600,16#F6C1,16#F781,16#3740,16#F501,16#35C0,16#3480,16#F441, 16#3C00,16#FCC1,16#FD81,16#3D40,16#FF01,16#3FC0,16#3E80,16#FE41, 16#FA01,16#3AC0,16#3B80,16#FB41,16#3900,16#F9C1,16#F881,16#3840, 16#2800,16#E8C1,16#E981,16#2940,16#EB01,16#2BC0,16#2A80,16#EA41, 16#EE01,16#2EC0,16#2F80,16#EF41,16#2D00,16#EDC1,16#EC81,16#2C40, 16#E401,16#24C0,16#2580,16#E541,16#2700,16#E7C1,16#E681,16#2640, 16#2200,16#E2C1,16#E381,16#2340,16#E101,16#21C0,16#2080,16#E041, 16#A001,16#60C0,16#6180,16#A141,16#6300,16#A3C1,16#A281,16#6240, 16#6600,16#A6C1,16#A781,16#6740,16#A501,16#65C0,16#6480,16#A441, 16#6C00,16#ACC1,16#AD81,16#6D40,16#AF01,16#6FC0,16#6E80,16#AE41, 16#AA01,16#6AC0,16#6B80,16#AB41,16#6900,16#A9C1,16#A881,16#6840, 16#7800,16#B8C1,16#B981,16#7940,16#BB01,16#7BC0,16#7A80,16#BA41, 16#BE01,16#7EC0,16#7F80,16#BF41,16#7D00,16#BDC1,16#BC81,16#7C40, 16#B401,16#74C0,16#7580,16#B541,16#7700,16#B7C1,16#B681,16#7640, 16#7200,16#B2C1,16#B381,16#7340,16#B101,16#71C0,16#7080,16#B041, 16#5000,16#90C1,16#9181,16#5140,16#9301,16#53C0,16#5280,16#9241, 16#9601,16#56C0,16#5780,16#9741,16#5500,16#95C1,16#9481,16#5440, 16#9C01,16#5CC0,16#5D80,16#9D41,16#5F00,16#9FC1,16#9E81,16#5E40, 16#5A00,16#9AC1,16#9B81,16#5B40,16#9901,16#59C0,16#5880,16#9841, 16#8801,16#48C0,16#4980,16#8941,16#4B00,16#8BC1,16#8A81,16#4A40, 16#4E00,16#8EC1,16#8F81,16#4F40,16#8D01,16#4DC0,16#4C80,16#8C41, 16#4400,16#84C1,16#8581,16#4540,16#8701,16#47C0,16#4680,16#8641, 16#8201,16#42C0,16#4380,16#8341,16#4100,16#81C1,16#8081,16#4040 ]
四、简单验证一下CRC的准确性
- 创建源数据块src_DB(DB1),DB内为混合类型的数据,内容如下:
- 创建一个数据类型为Array[0..100] of Byte的DB块(DB5),命名为bytes_DB,用来存放上行报文:
- PLC程序的OB1:
1、定义两个tmp区域变量,TMP_AreaVar1、TMP_AreaVar2,先组态5个字节数据测试一下:
- 编程OB1:
程序段1:scl
#TMP_AreaVar1.Area := 16#84; //DB区 #TMP_AreaVar1.DBNum := 1; //DB号 #TMP_AreaVar1.Offset := 0; //字节偏移 #TMP_AreaVar1.byteCount := 5; //长度 #TMP_AreaVar2.Area := 16#84; #TMP_AreaVar2.DBNum := 5; #TMP_AreaVar2.Offset := 0; #TMP_AreaVar2.byteCount := 5;程序段2:
下载并运行程序,监控DB5的数据:
组态好的上行报文数据解析:
- python:
import crcmod crc16 = crcmod.predefined.Crc('modbus') # 测试数据(和 PLC 一样) data = bytes([0x41,0x41,0x84,0x84,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x11,0x22,0x33,0x44,0x55,0x42,0x42]) # 计算 CRC crc16.update(data) result = crc16.crcValue # 16 位结果 print(f"Python CRC: {result:04X}")运行截图:
PLC内和python的CRC运算结果相符。
