博圖TIA中ModbusRTU Over TCP/IP通訊的實現


博圖TIA中ModbusRTU Over TCP/IP通訊的實現

在學習使用SCL通信時,查看了博途SCL實現自定義ModbusRtu Over TCP功能塊這個文檔,主要使用了IP解析部分的程序.

后來想着研究一下ModbusRTU Over TCP/IP通訊,所以在TIA V16中按照教程做了一遍,因理解能力與作者的有些出入,所以重新做個筆記.

在照着做的過程中,主要實現過程包括IP地址字符串解析函數封裝、ModbusCRC校驗算法函數封裝、Socket發送、接收、報文拼接、報文解析等。具體步驟如下:

設備組態

IP地址解析FC函數

IP地址解析FC函數SCL源

FUNCTION "IpStringParse" : Void
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : bootloader
VERSION : 0.1
//IP地址解析FC函數
   VAR_INPUT 
      IP : String;
   END_VAR

   VAR_OUTPUT 
      iparr : Array[0..3] of Byte;
   END_VAR

   VAR_TEMP 
      pos : Int;
      ip_temp : String;
      len_temp : Int;
      index : UInt;
   END_VAR


BEGIN
	REGION 處理IP地址字符串
	    FILL_BLK(IN := 0,
	             COUNT := 20,
	             OUT => #iparr[0]);
	    #ip_temp := #IP;
	    //查詢第一個'.'的位置
	    #pos := FIND(IN1 := #ip_temp, IN2 := '.');
	    WHILE #pos <> 0 AND #index < 3 DO
	        //截取第一個'.'之前的字符串,並轉換為數值
	        #iparr[#index] := UINT_TO_BYTE(STRING_TO_UINT(LEFT(IN := #ip_temp, L := #pos - 1)));
	        #len_temp := LEN(#ip_temp);
	        //去除第一個.之前的字符,得到新的字符串
	        #ip_temp := MID(IN := #ip_temp, L := #len_temp - #pos, P := #pos + 1);
	        //截取新字符串中查詢第一個'.'的位置
	        #pos := FIND(IN1 := #ip_temp, IN2 := '.');
	        #index := #index + 1;
	    END_WHILE;
	    
	    //將最后一部分轉換為數值存在入
	    #iparr[#index] := UINT_TO_BYTE(STRING_TO_UINT(IN := #ip_temp));
	END_REGION
END_FUNCTION

CrcModbus校驗FC函數

CrcModbus校驗FC函數SCL源

FUNCTION "CrcModbusFun" : Void
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : bootloader
VERSION : 0.1
//CRCMODBUS校驗FC函數
   VAR_INPUT 
      Command : Variant;
      dataLen : Int;
   END_VAR

   VAR_TEMP 
      buffer : Array[0..#MaxLen] of Byte;
      i : Int;
      j : Int;
      Crcreg : Word;
      Len : Int;
   END_VAR

   VAR CONSTANT 
      MaxLen : Int := 255;
   END_VAR


BEGIN
	#Crcreg := 16#FFFF;
	IF #dataLen = 0 OR #dataLen > CountOfElements(IN := #Command) - 2 THEN
	    //#Status := 01;
	    RETURN;
	ELSE
	    //#Status := 00;
	    #Len := #dataLen;
	END_IF;
	
	//將數據轉到緩沖區
	VariantGet(SRC := #Command,
	           DST => #buffer);
	
	//計算CRC校驗碼
	FOR #i := 0 TO (#Len - 1) DO
	    #Crcreg := #Crcreg XOR #buffer[#i];
	    FOR #j := 0 TO 7 DO
	        IF (#Crcreg AND 16#1) = 1 THEN
	            #Crcreg := SHR_WORD(IN := #Crcreg, N := 1);
	            #Crcreg := #Crcreg XOR 16#A001;
	        ELSE
	            #Crcreg := SHR_WORD(IN := #Crcreg, N := 1);
	        END_IF;
	    END_FOR;
	END_FOR;
	#buffer[#Len + 1] := SHR_WORD(IN := #Crcreg, N := 8);
	#buffer[#Len] := #Crcreg AND 16#ff;
	
	//將緩沖區數據再寫入到指針所指向的區域
	VariantPut(SRC := #buffer,
	           DST := #Command);
	
END_FUNCTION

輪詢令牌分發功能塊FB

輪詢令牌分發功能塊FB SCL源

FUNCTION_BLOCK "token"
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : bootloader
VERSION : 0.1
//輪詢令牌分發功能塊
   VAR_INPUT 
      interval : Time := T#50ms;
      MultReqNums : Int;
      TurnArr : Variant;
   END_VAR

   VAR_OUTPUT 
      Status : Int;
   END_VAR

   VAR 
      token : Word;
      IntervalTon {InstructionName := 'TON_TIME'; LibVersion := '1.0'} : TON_TIME;
   END_VAR

   VAR_TEMP 
      tempArr : Array[0..#MaxReqNums] of Bool;
      i : Int;
      turnTriger : Bool;
   END_VAR

   VAR CONSTANT 
      MaxReqNums : Int := 20;
   END_VAR


BEGIN
	#turnTriger := #IntervalTon.Q;
	#IntervalTon(IN := NOT #IntervalTon.Q,
	             PT := #interval);
	
	//檢查輸入參數是否正確
	IF #MultReqNums > UDINT_TO_INT( CountOfElements(#TurnArr)) OR #MultReqNums > #MaxReqNums THEN
	    RETURN;
	    #Status := 8001;
	END_IF;
	
	//檢查外部指針是不是布爾數組
	IF (TypeOfElements(#TurnArr) <> Bool) THEN
	    #Status := 8002;
	    RETURN;
	END_IF;
	
	IF #turnTriger THEN
	    #token := #token + 1;
	    IF #token >= #MultReqNums THEN
	        //Statement section IF
	        #token := 0;
	    END_IF;
	END_IF;
	
	//先將所有的復歸
	FOR #i := 0 TO #MultReqNums DO
	    #tempArr[#i] := FALSE;
	END_FOR;
	
	//把當前token置1
	#tempArr[#token] := TRUE;
	
	VariantPut(SRC:=#tempArr,
	           DST:=#TurnArr);
	#Status := 0;
	
END_FUNCTION_BLOCK

ModbusRTUOverTCP功能塊FB

ModbusRTUOverTCP功能塊FB SCL源

FUNCTION_BLOCK "ModbusRtuOverTcp"
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : bootloader
VERSION : 0.1
//ModbusRTUOverTCP功能塊
   VAR_INPUT 
      Start : UInt;
      Length : UInt;
      IpAddr : String;
      Reg : Bool;
      ConnectID : CONN_OUC;
      Deviceld : Byte;
      timeOut : Time := T#50ms;
   END_VAR

   VAR_IN_OUT 
      Outdate : Variant;
   END_VAR

   VAR 
      ConnectParams {InstructionName := 'TCON_IP_v4'; LibVersion := '1.0'; S7_SetPoint := 'False'} : TCON_IP_v4 := (64, (), (), true, ([()]), 502, ());
      TSEND_C_Instance {InstructionName := 'TSEND_C'; LibVersion := '3.2'} : TSEND_C;
      TRCV_C_Instance {InstructionName := 'TRCV_C'; LibVersion := '3.2'; S7_SetPoint := 'False'} : TRCV_C;
      CommandBytes : Array[0..11] of Byte;
      start_recv : Bool;
      RecBuffer : Array[0..255] of Byte;
      index : Int;
      step : Int;
      R_TRIG_Instance {InstructionName := 'R_TRIG'; LibVersion := '1.0'; S7_SetPoint := 'False'} : R_TRIG;
      startSend : Bool;
      timeOutResponseTon {InstructionName := 'TON_TIME'; LibVersion := '1.0'; S7_SetPoint := 'False'} : TON_TIME;
      xTimeOutResPonse : Bool;
      init : Bool;
   END_VAR

   VAR_TEMP 
      ipArrayTemp : Array[0..3] of Byte;
      count : Int;
   END_VAR


BEGIN
	(* 
	輸入參數說明:
	Start:讀取保持寄存器的起始地址
	Length:讀取保持寄存器的個數
	IPAddr:IP 地址字符串
	Req:請求指令(只接受邊沿信號)
	DeviceID: 設備單元ID
	ConnectID:網絡連接資源ID(背景數據塊不同時,需要保證唯一性)
	輸入輸出參數:
	Outdata:指向讀取的數據保存區域的指針 *)
	
	//除始化IP地址,相同背景DB的功能塊,只需要解析一次
	//
	
	// ---------------------------------------------------------------------------------------------------------
	// ConnectParams 靜態變量 類型 TCON_IP_v4
	// ConnectParams 設置硬件接口 InterfaceId
	// ConnectParams 連接ID賦值 ID
	// ConnectParams 設置連接類型 ConnectionType
	// ConnectParams 設置主動連接 ActiveEstablished
	// ConnectParams IP地址解析 RemoteAddress
	// ConnectParams 設置遠程設備端口 RemotePort
	// 
	IF NOT #init THEN
	    
	    #ConnectParams.ID := #ConnectID;
	    #ConnectParams.ActiveEstablished := TRUE;
	    "IpStringParse"(IP := #IpAddr,
	                    iparr => #ipArrayTemp);
	    #ConnectParams.RemoteAddress.ADDR[1] := #ipArrayTemp[0];
	    #ConnectParams.RemoteAddress.ADDR[2] := #ipArrayTemp[1];
	    #ConnectParams.RemoteAddress.ADDR[3] := #ipArrayTemp[2];
	    #ConnectParams.RemoteAddress.ADDR[4] := #ipArrayTemp[3];
	    #ConnectParams.RemotePort := 502;
	    #init := TRUE;
	END_IF;
	// ---------------------------------------------------------------------------------------------------------
	//拼寫ModbusRTU報文 03功能碼
	#CommandBytes[0] := #Deviceld;
	#CommandBytes[1] := 3;
	#CommandBytes[2] := UINT_TO_BYTE(#Start / 256);
	#CommandBytes[3] := UINT_TO_BYTE(#Start MOD 256);
	#CommandBytes[4] := UINT_TO_BYTE(#Length / 256);
	#CommandBytes[5] := UINT_TO_BYTE(#Length MOD 256);
	
	//計算CRC校驗
	"CrcModbusFun"(Command:=#CommandBytes,
	               dataLen:=6);
	// ---------------------------------------------------------------------------------------------------------
	//檢測到發送指令
	#R_TRIG_Instance(CLK := #Reg);
	IF #R_TRIG_Instance.Q AND NOT #TSEND_C_Instance.BUSY THEN
	    #step := 0;
	    #startSend := TRUE;
	ELSE
	    #startSend := FALSE;
	END_IF;
	
	#TSEND_C_Instance(REQ := #startSend,
	                  CONT := 1,
	                  LEN := 8,
	                  CONNECT := #ConnectParams,
	                  DATA := #CommandBytes);
	
	#timeOutResponseTon(IN := #xTimeOutResPonse,
	                    PT := #timeOut);
	
	CASE #step OF
	    0:
	        //等待發送完成
	        IF #TSEND_C_Instance.DONE THEN
	            #start_recv := TRUE;
	            #step := 10;
	        END_IF;
	        //等待接收
	    10:
	        #xTimeOutResPonse := TRUE;
	        //接收指令為一個異步指令,需多個掃描周期完成
	        #TRCV_C_Instance(EN_R := #start_recv,
	                         CONT := TRUE,
	                         LEN := #Length * 2 + 5,
	                         CONNECT := #ConnectParams,
	                         DATA := #RecBuffer);
	        //等待接收完成
	        IF #TRCV_C_Instance.DONE THEN
	            #start_recv := FALSE;
	            //將數據移動到指針所指的區域
	            #count := MOVE_BLK_VARIANT(SRC := #RecBuffer, COUNT := #Length * 2, SRC_INDEX := 3, DEST_INDEX := 0, DEST => #Outdate);
	            #step := 20;
	        ELSIF #TRCV_C_Instance.ERROR OR #timeOutResponseTon.Q THEN
	            //至少有一套出現過故障
	            #xTimeOutResPonse := 0;
	            #step := 30;
	        END_IF;
	    20:
	        //接收成功也要復歸計時器
	        #xTimeOutResPonse := 0;
	END_CASE;
	
END_FUNCTION_BLOCK

data數據塊DB

data數據塊DB SCL源

DATA_BLOCK "data"
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : bootloader
VERSION : 0.1
NON_RETAIN
   VAR 
      interval : Time;
      turn : Array[0..10] of Bool;
      turn_Rtrig : Array[0..10] of Bool;
      turn_Rtrig_1 : Array[0..10] of Bool;
      outdata10 : Array[0..19] of Byte;
      outdata11 : Array[0..19] of Byte;
      outdata20 : Array[0..19] of Byte;
      outdata21 : Array[0..19] of Byte;
   END_VAR


BEGIN
   interval := T#50ms;

END_DATA_BLOCK

多重背景塊FB

主程序

輪詢、並發模擬

S7-PLCSIM AdvanceV3.0 可以支持通信模擬.

Modbus 從站或服務器可以用modbus slave軟件模擬.在客戶機中分別利用Modbus slave 模擬兩個支持ModbusRTU 串口服務器IP地址分別為192.168.159.1 和192.168.159.2,每個服務器創建2個設備,協議選擇ModbusRtu over TCP,並取消勾選忽略設備ID 選項.

收發報文監視如下:

數據解析

接收到的數據保存在字節數組中,具體的數據類型取決於協議對寄存器的約定,如果需要批量解析為整形或浮點型,可以新建一個大小一致的存儲區,數組中元素數據類型為協議約定的數據類型,然后可以用POKE_BLK 指令完成,這里浮點數並沒有考慮大小端的問題.

POKE_BLK(area_src:=16#84,
         dbNumber_src:=1,
         byteOffset_src:=50,
         area_dest:=16#84,
         dbNumber_dest:=1,
         byteOffset_dest:=90,
         count:=20);

POKE_BLK(area_src := 16#84,
         dbNumber_src := 1,
         byteOffset_src := 50,
         area_dest := 16#84,
         dbNumber_dest := 1,
         byteOffset_dest := 110,
         count := 20);

總結

ModbusRTU Over TCP/IP通訊就是通過TCP 傳輸ModbusRTU 報文,其中ModbusRTU 報文格式可以查詢相關文檔,CRC校驗分為查表法和計算法,兩者各有優缺點,在程序塊編寫過程中,對於重復邏輯應采用循環結構如WHILE、FOR 等;對於輸入參數為不定長數組的,形參需要設置為Variant 指針,對於內存區的批量讀寫操作,可以使用PEEK 和POKE 指令、Move_BLK、Move_BLK_Variant、Fill_BLK、VariantPut、VariantGet等指令.以上功能塊部分程序僅為了強化博途間接尋址、程序結構、SCL、以及程序封裝應用,實際工程應用時,可以適當修改.

聲明

本文主要內容及思路來源於博途SCL實現自定義ModbusRtu Over TCP功能塊,因原文有些地方描述不是很詳細,所以在調試時,花了寫時間查找原因,我在原文的基礎上,自己做了測試,並深化了細節.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM