基于QtPy (PySide6) 的PLC-HMI工程实战记录(一)创建PLC工程模板项目
2026/8/16 21:52:30 网站建设 项目流程

一个PLC-HMI实战工程的过程记录。


一、新建TIA工程,组态PLC时可以直接指定PLC型号,也可以不指定CPU,使用unspecified CPU:

这样做的好处是保持模板的通用性并且在硬件确定之后不用手动逐个输入组态硬件,连接到已通电的CPU,使用“在线”、“硬件检测”就可以直接把实际的硬件读取到项目中。

使用这种读取在线PLC的方式组态,有一个坑点是:必须给在线硬件分配PROFINET 设备名称,并与程序中的名称一致!否则会组态失败。

二、新建程序分组,命名为“系统OB”:

添加OB30、OB82、OB100、OB86、OB122等必要和有可能用到的OB系统块。

三、新建PLC数据类型

这个命名为“AreaVar”的数据类型,包含了数据分区(I/QM/DB)、DB编号、数据起点、数据字节长度等信息,可用来进行各种以字节为单位的块间操作。这个数据类型是本工程与上位机通信的基础类型,它指定了数据报文的位置和长度。

四、导入之前导出保存的CRC校验表DB文件:

“添加新的外部文件”、“从源生成块”,这样就把外部的db文件转换为项目中的DB块。

  • 新建分组“背景类DB”,并将生成的DB块拖入:

五、创建TCP连接并组态和编程连接:

创建连接之前需要将CPU指定为具有网络通信能力的型号,这里指定为1513-1PN。

1、新建程序块分组:功能块

2、在分组中新建FB,命名为“上位通信”,此功能块负责与上位机通信。

3、打开上位通信FB,开始编程。

4、从指令库中将TCON拖入FB的网络,会自动创建一个DB块:TCON_DB。

5、点击“开始组态”按钮,对连接进行组态:

在打开的组态窗口中:

  • 伙伴:未指定
  • 伙伴主动建立连接
  • 连接数据:新建
  • 作为一个模板,无需为PLC特意指定IP地址,当在实际的项目中改变了CPU的IP地址后,连接参数中的地址会自动随之改变为新的。

这样就将PLC创建为TCP主站。

可以看到,在系统块的程序资源中自动生成了2个数据块:

PLC_1_Connection_DB,用于存储连接参数。

TCON_DB则是TCON指令的背景数据块:

这些都是自动生成和分配的,无需人工参与,只要知道其用途即可。

6、编译一下,确认生成无误:

7、为上位通信FB块创建一个数据块:上位通信_工作DB,并在DB中定义一个Struct,作为TCON指令的参数容器:

8、在上位通信FB中为TCON指令分配参数:

9、在OB1中调用上位通信FB

调用时会自动创建一个背景DB,将其命名为:上位通信_背景DB,并拖入“背景类DB”分组中。这个分组中的DB全部为背景类的DB,无需人工干预和编辑。

10、在上位通信_工作DB中补充发送和接收的参数容器:

11、在上位通信FB中继续编程,拖入发送和接收指令,增加接收和发送功能

12、在创建两个相同的DB块,用来存放上行和下行报文

13、继续完善上位通信FB,把发送和接收指令的DATA端口指向新建的两个报文DB

14、创建FC块,命名为“_build_AreaVal”

功能是由Area、DBnum、offset、byteCount这几个参数来生成一个自定义的区域变量:AreaVal

接口:

Input 数据类型
Area Byte
DBNum UInt
Offset UInt
byteCount UInt
InOut
destAreaVar "AreaVar"

scl:

#destAreaVar.Area := #Area; #destAreaVar.DBNum := #DBNum; #destAreaVar.Offset := #Offset; #destAreaVar.byteCount := #byteCount;

15、创建FC块,命名为“组态上行报文”,用来组态上行报文

接口:

Input
frameHeader1 Byte 帧头字节1
frameHeader2 Byte 帧头字节2
frameEnd1 Byte 帧尾字节1
frameEnd2 Byte 帧尾字节2
srcAreaVars Array[*] of "AreaVar" 源数据区域,支持多区域输入
DB_CRC_TABLE Array[0..255] of Word CRC查表运算的表格
Output
InOut
destArea "AreaVar" 目标区域,存放上行报文的字节
Temp
Temp_1 "AreaVar"
wCRC Word
i DInt
pos DInt 字节临时指针
bTemp Byte
wTemp Word
nIndex DInt
varTemp "AreaVar"
tmp_DI1 DInt
tmp_DI2 DInt
Constant

Return
组态上行报文 Void

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 #pos := 2; //#tmp_DI1 := LOWER_BOUND(ARR := #srcAreaVars, DIM := 1); //#tmp_DI2 := UPPER_BOUND(ARR := #srcAreaVars, DIM := 1); FOR #i := LOWER_BOUND(ARR := #srcAreaVars, DIM := 1) TO UPPER_BOUND(ARR := #srcAreaVars, DIM := 1) DO #varTemp := #srcAreaVars[#i]; ////////组态区域变量的信息(本帧数据的区域和起点、长度)////// POKE(area := #destArea.Area, //本帧数据的区域 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #varTemp.Area); #pos := #pos + 1; POKE(area := #destArea.Area, //字节占位 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #varTemp.Area); #pos := #pos + 1; POKE(area := #destArea.Area, //本帧数据的DB号 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #varTemp.DBNum); //value := DINT_TO_BYTE(IN:=#varTemp.DBNum)); #pos := #pos + 4; POKE(area := #destArea.Area, //本帧数据的起始字节偏移 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #varTemp.Offset); #pos := #pos + 4; POKE(area := #destArea.Area, //本帧数据的字节数 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #varTemp.byteCount); #pos := #pos + 4; ///////////////////////////////组态帧数据内容///////////////////////// POKE_BLK(area_src := #varTemp.Area, dbNumber_src := #varTemp.DBNum, byteOffset_src := #varTemp.Offset, area_dest := #destArea.Area, dbNumber_dest := #destArea.DBNum, byteOffset_dest := #pos, count := #varTemp.byteCount); #pos := #pos + #varTemp.byteCount; END_FOR; ///////////////////////////////组态帧尾//////////////////////////// POKE(area := #destArea.Area, //帧尾1 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #frameEnd1); #pos := #pos + 1; POKE(area := #destArea.Area, //帧尾2 dbNumber := #destArea.DBNum, byteOffset := #pos, value := #frameEnd2); #pos := #pos + 1; /////////////////////////////////CRC16校验和////////////////////////////// #destArea.byteCount := #pos; #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 := #pos, value := #wCRC); #pos := #pos + 2; #destArea.byteCount := #pos;

16、在“上位通信_工作DB”中增加帧头和帧尾的定义:

17、在“上位通信”FB中编程,组态上行报文:

上行报文的区域暂时不填,具体到实际的PLC再填。

18、创建FC块,命名为“解析下行数据”:

接口:

Input
frameHeader1 Byte 帧头字节1
frameHeader2 Byte 帧头字节2
frameEnd1 Byte 帧尾字节1
frameEnd2 Byte 帧尾字节2
rsv_area_var "AreaVar" 接收下行报文的DB块
DB_CRC_TABLE Array[0..255] of Word
Output
DONE Bool
InOut

Temp
i DInt 0.0
pos DInt 4.0 字节临时指针
tmp_head Byte 8.0
tmp_end Byte 9.0
tmp_areaVar "AreaVar" 10.0
bTemp Byte 28.0
wTemp Word 30.0
wCRC Word 32.0
nIndex Int 34.0
Constant

Return
解析下行数据 Void

scl:

#DONE := FALSE; ////////////////////////////解析帧头/////////////////////////// #pos := 0; // 字节指针 #tmp_head := PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos); //帧头1 IF #frameHeader1 <> #tmp_head THEN #DONE := FALSE; RETURN; END_IF; #pos := #pos + 1; #tmp_head := PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos); //帧头2 IF #frameHeader1 <> #tmp_head THEN #DONE := FALSE; RETURN; END_IF; #pos := #pos + 1; IF PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos) = #frameEnd1 THEN IF PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos + 1) = #frameEnd2 THEN #DONE := TRUE; RETURN; END_IF; END_IF; /////////////////////////解析区域变量的参数//////////////////// #tmp_areaVar.Area := PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos); //帧区域 #pos := #pos + 2; #tmp_areaVar.DBNum := DWORD_TO_DINT(IN := PEEK_DWORD(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos)); //帧DB号 #pos := #pos + 4; #tmp_areaVar.Offset := DWORD_TO_DINT(PEEK_DWORD(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos)); //帧字节偏移 #pos := #pos + 4; #tmp_areaVar.byteCount := DWORD_TO_DINT(PEEK_DWORD(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos)); //帧字节长度 #pos := #pos + 4; #tmp_areaVar.bit := DWORD_TO_DINT(PEEK_DWORD(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos)); //帧位(bit) #pos := #pos + 4; //pos目前位于数据区之前 ////////////////////////////解析数据内容和CRC////////////////////////// //解析CRC #wCRC := 16#FFFF; FOR #i := 0 TO #pos + #tmp_areaVar.byteCount + 1 DO //读取接收DB从0到数据长度,再多读4位(帧尾) #bTemp := PEEK(area := #rsv_area_var.Area, byteOffset := #i, dbNumber := #rsv_area_var.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; #wCRC := #wCRC; #pos := #pos + #tmp_areaVar.byteCount; //pos目前位于数据区之后,帧尾之前 IF #wCRC <> PEEK_WORD(area := #rsv_area_var.Area, byteOffset := #pos + 2, dbNumber := #rsv_area_var.DBNum) THEN //CRC失败 #DONE := FALSE; RETURN; END_IF; ////////////////////////////解析帧尾/////////////////////////////////// #tmp_end := PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos); //帧尾1 IF #frameEnd1 <> #tmp_end THEN #DONE := FALSE; RETURN; END_IF; #pos := #pos + 1; #tmp_end := PEEK(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos); //帧尾2 IF #frameEnd2 <> #tmp_end THEN #DONE := FALSE; RETURN; END_IF; #pos := #pos + 1; //此时的pos位于帧尾后,CRC前 #pos := #pos - #tmp_areaVar.byteCount - 2; //把pos移回到数据区的起始 /////////////////////////////////如果是bool////////////////////////////// IF #tmp_areaVar.bit <> 16#FFFFFFFF THEN POKE_BOOL(area := #tmp_areaVar.Area, ////写一个bool位 dbNumber := #tmp_areaVar.DBNum, byteOffset := #tmp_areaVar.Offset, bitOffset := DINT_TO_INT(IN := #tmp_areaVar.bit), value := PEEK_BOOL(area := #rsv_area_var.Area, dbNumber := #rsv_area_var.DBNum, byteOffset := #pos, bitOffset := #tmp_areaVar.bit)); /////////////////////////////////如果不是bool////////////////////////////// ELSE //写块数据 POKE_BLK(area_src := #rsv_area_var.Area, dbNumber_src := #rsv_area_var.DBNum, byteOffset_src := #pos, area_dest := #tmp_areaVar.Area, dbNumber_dest := #tmp_areaVar.DBNum, byteOffset_dest := #tmp_areaVar.Offset, count := #tmp_areaVar.byteCount); END_IF; #DONE := TRUE;


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询