Hello everyone,
I am developing a program to automate the Modbus RS485 RTU network of a B&R X20CP0484-1 PLC in Automation Studio 4.12.2.93. The aim of my program is to automatically scan all Modbus RTU devices connected (with DRV_mbus library) to my system via the X20CS1030 module (ADR’IF6.ST2.IF1’).
My level
I’m just starting out with B&R PLCs and the Automation Studio environment, so I’m gradually discovering best practices and how to structure objects/files in a project.
My setup
- Automation Studio version : 4.12.2.93
- PLC : B&R X20CP0484-1 on X20BB57
- Communication module : X20CS1030 (configured RS485, 19200 bps, Even parity, 1 stop bit)
Library used : DRV_mbus
The devices, which support Modbus RTU via RS485. I’m using the datasheet from the supplier of devices. To identify the devices, I’m using the following Modbus register addresses (according to Sensirion documentation):
- 0x4103: Modbus slave address (uint16 / 1 registers) (R/W)
- 0x6000 to 0x6009 : Product Name (string[20] / 10 registers) (R)
- 0x600A to 0x6013: Article Code (string[20] / 10 registers) (R)
- 0x6014 to 0x601D: Serial Number (string[20] / 10 registers) (R)
I’ve already configured the interface of X20CS1030 :
I’ve already programmed one simple program but I have still have some issue regarding the function “ConvertRegistersToString”.
PROGRAM _INIT
(* Initialisation au démarrage *)
ScanState := SCAN_IDLE;
ReadState := READ_IDLE;
FoundMFCCount := 0;
ScanInProgress := FALSE;
ScanComplete := FALSE;
(* Effacer les tableaux *)
FOR i := 1 TO 20 DO
FoundMFCs[i] := 0;
FoundMFCDetails[i].SlaveAddress := 0;
FoundMFCDetails[i].IsValid := FALSE;
FoundMFCDetails[i].ProductName := '';
FoundMFCDetails[i].SerialNumber := '';
FoundMFCDetails[i].ArticleCode := '';
END_FOR
END_PROGRAM
PROGRAM _CYCLIC
(* Machine d'états principale *)
CASE ScanState OF
SCAN_IDLE:
(* Attente du démarrage *)
IF StartScan THEN
StartScan := FALSE;
ScanState := SCAN_INIT;
ScanInProgress := TRUE;
ScanComplete := FALSE;
FoundMFCCount := 0;
TotalAddressesScanned := 0;
LastErrorCode := 0;
(* Effacer les résultats précédents *)
FOR i := 1 TO 20 DO
FoundMFCs[i] := 0;
FoundMFCDetails[i].SlaveAddress := 0;
FoundMFCDetails[i].IsValid := FALSE;
END_FOR
END_IF
SCAN_INIT:
(* Initialisation de la communication Modbus *)
MBMOpen_0.enable := TRUE;
MBMOpen_0.pDevice := ADR(DeviceName);
MBMOpen_0.pMode := ADR(Mode);
MBMOpen_0.pConfig := 0; (* Pas de data object *)
MBMOpen_0.timeout := ScanTimeout;
MBMOpen_0.ascii := 0; (* Mode RTU *)
MBMOpen_0();
IF MBMOpen_0.status = 0 THEN
(* Succès - sauvegarder l'identifiant *)
ModbusIdent := MBMOpen_0.ident;
CurrentAddress := ScanStartAddress;
ScanState := SCAN_START;
ELSIF MBMOpen_0.status <> 65535 THEN
(* Erreur *)
LastErrorCode := MBMOpen_0.status;
ScanState := SCAN_ERROR;
END_IF
SCAN_START:
(* Démarrer le scan à l'adresse courante *)
IF CurrentAddress <= ScanEndAddress THEN
RetryCount := 0;
ScanState := SCAN_CHECK_ADDRESS;
(* Calculer la progression *)
ScanProgress := TO_USINT(TO_REAL(CurrentAddress - ScanStartAddress) / TO_REAL(ScanEndAddress - ScanStartAddress + 1) * 100.0);
ELSE
ScanState := SCAN_COMPLETE;
END_IF
SCAN_CHECK_ADDRESS:
(* Tester la présence d'un esclave à l'adresse courante *)
(* Lecture d'un registre de test *)
MBMCmd_0.enable := TRUE;
MBMCmd_0.ident := ModbusIdent;
MBMCmd_0.mfc := 3; (* Read Holding Registers *)
MBMCmd_0.node := CurrentAddress;
MBMCmd_0.data := ADR(TempBuffer);
MBMCmd_0.offset := REG_TEST;
MBMCmd_0.len := 1;
MBMCmd_0();
(* Gérer le timeout *)
TON_Timeout(IN := MBMCmd_0.enable, PT := T#200ms);
IF MBMCmd_0.status = 0 AND NOT TON_Timeout.Q THEN
(* Succès - MFC trouvé *)
IF FoundMFCCount < 20 THEN
FoundMFCCount := FoundMFCCount + 1;
FoundMFCs[FoundMFCCount] := CurrentAddress;
FoundMFCDetails[FoundMFCCount].SlaveAddress := CurrentAddress;
CurrentMFCIndex := FoundMFCCount;
(* Passer à la lecture des détails *)
ReadState := READ_PRODUCT_NAME;
ScanState := SCAN_READ_DETAILS;
MBMCmd_0.enable := FALSE;
TON_Timeout(IN := FALSE);
ELSE
(* Tableau plein *)
ScanState := SCAN_NEXT_ADDRESS;
END_IF
ELSIF MBMCmd_0.status <> 0 AND MBMCmd_0.status <> 65535 THEN
(* Erreur ou pas de réponse *)
IF MBMCmd_0.status = mbERR_NODE_TOUT OR TON_Timeout.Q THEN
(* Timeout - pas de MFC à cette adresse *)
MBMCmd_0.enable := FALSE;
TON_Timeout(IN := FALSE);
ScanState := SCAN_NEXT_ADDRESS;
ELSIF RetryCount < MaxRetries THEN
(* Réessayer *)
RetryCount := RetryCount + 1;
MBMCmd_0.enable := FALSE;
(* Petit délai avant retry *)
TON_Delay(IN := TRUE, PT := T#50ms);
IF TON_Delay.Q THEN
TON_Delay(IN := FALSE);
MBMCmd_0.enable := TRUE;
END_IF
ELSE
(* Erreur après retries *)
LastErrorCode := MBMCmd_0.status;
LastErrorAddress := CurrentAddress;
MBMCmd_0.enable := FALSE;
TON_Timeout(IN := FALSE);
ScanState := SCAN_NEXT_ADDRESS;
END_IF
END_IF
SCAN_READ_DETAILS:
(* Lire les détails du MFC trouvé *)
CASE ReadState OF
READ_PRODUCT_NAME:
(* Lire le nom du produit *)
MBMCmd_0.enable := TRUE;
MBMCmd_0.ident := ModbusIdent;
MBMCmd_0.mfc := 3; (* Read Holding Registers *)
MBMCmd_0.node := CurrentAddress;
MBMCmd_0.data := ADR(TempBuffer);
MBMCmd_0.offset := REG_PRODUCT_NAME;
MBMCmd_0.len := 16; (* 32 bytes = 16 registres *)
MBMCmd_0();
IF MBMCmd_0.status = 0 THEN
(* Convertir les données Big Endian en chaîne *)
ConvertRegistersToString(ADR(TempBuffer), ADR(FoundMFCDetails[CurrentMFCIndex].ProductName), 16);
MBMCmd_0.enable := FALSE;
(* Délai entre lectures *)
TON_Delay(IN := TRUE, PT := T#50ms);
IF TON_Delay.Q THEN
TON_Delay(IN := FALSE);
ReadState := READ_ARTICLE_CODE;
END_IF
ELSIF MBMCmd_0.status <> 0 AND MBMCmd_0.status <> 65535 THEN
(* Erreur - passer au suivant *)
MBMCmd_0.enable := FALSE;
ReadState := READ_ARTICLE_CODE;
END_IF
READ_ARTICLE_CODE:
(* Lire le code article *)
MBMCmd_0.enable := TRUE;
MBMCmd_0.ident := ModbusIdent;
MBMCmd_0.mfc := 3;
MBMCmd_0.node := CurrentAddress;
MBMCmd_0.data := ADR(TempBuffer);
MBMCmd_0.offset := REG_ARTICLE_CODE;
MBMCmd_0.len := 16;
MBMCmd_0();
IF MBMCmd_0.status = 0 THEN
ConvertRegistersToString(ADR(TempBuffer), ADR(FoundMFCDetails[CurrentMFCIndex].ArticleCode), 16);
MBMCmd_0.enable := FALSE;
TON_Delay(IN := TRUE, PT := T#50ms);
IF TON_Delay.Q THEN
TON_Delay(IN := FALSE);
ReadState := READ_SERIAL_NUMBER;
END_IF
ELSIF MBMCmd_0.status <> 0 AND MBMCmd_0.status <> 65535 THEN
MBMCmd_0.enable := FALSE;
ReadState := READ_SERIAL_NUMBER;
END_IF
READ_SERIAL_NUMBER:
(* Lire le numéro de série *)
MBMCmd_0.enable := TRUE;
MBMCmd_0.ident := ModbusIdent;
MBMCmd_0.mfc := 3;
MBMCmd_0.node := CurrentAddress;
MBMCmd_0.data := ADR(TempBuffer);
MBMCmd_0.offset := REG_SERIAL_NUMBER;
MBMCmd_0.len := 8; (* 16 bytes = 8 registres *)
MBMCmd_0();
IF MBMCmd_0.status = 0 THEN
ConvertRegistersToString(ADR(TempBuffer), ADR(FoundMFCDetails[CurrentMFCIndex].SerialNumber), 8);
FoundMFCDetails[CurrentMFCIndex].IsValid := TRUE;
MBMCmd_0.enable := FALSE;
ReadState := READ_COMPLETE;
ELSIF MBMCmd_0.status <> 0 AND MBMCmd_0.status <> 65535 THEN
FoundMFCDetails[CurrentMFCIndex].IsValid := TRUE;
MBMCmd_0.enable := FALSE;
ReadState := READ_COMPLETE;
END_IF
READ_COMPLETE:
(* Lecture terminée pour ce MFC *)
ReadState := READ_IDLE;
ScanState := SCAN_NEXT_ADDRESS;
END_CASE
SCAN_NEXT_ADDRESS:
(* Passer à l'adresse suivante *)
CurrentAddress := CurrentAddress + 1;
TotalAddressesScanned := TotalAddressesScanned + 1;
(* Vérifier si on doit arrêter *)
IF StopScan THEN
StopScan := FALSE;
ScanState := SCAN_COMPLETE;
ELSE
(* Délai entre adresses *)
TON_Delay(IN := TRUE, PT := T#100ms);
IF TON_Delay.Q THEN
TON_Delay(IN := FALSE);
ScanState := SCAN_START;
END_IF
END_IF
SCAN_COMPLETE:
(* Scan terminé *)
ScanInProgress := FALSE;
ScanComplete := TRUE;
ScanProgress := 100;
(* Fermer la communication Modbus *)
MBMClose_0.enable := TRUE;
MBMClose_0.ident := ModbusIdent;
MBMClose_0();
IF MBMClose_0.status = 0 THEN
MBMClose_0.enable := FALSE;
ScanState := SCAN_IDLE;
END_IF
SCAN_ERROR:
(* Gestion d'erreur *)
ScanInProgress := FALSE;
(* Essayer de fermer la communication *)
IF ModbusIdent <> 0 THEN
MBMClose_0.enable := TRUE;
MBMClose_0.ident := ModbusIdent;
MBMClose_0();
IF MBMClose_0.status = 0 THEN
MBMClose_0.enable := FALSE;
ModbusIdent := 0;
ScanState := SCAN_IDLE;
END_IF
ELSE
ScanState := SCAN_IDLE;
END_IF
END_CASE
END_PROGRAM
PROGRAM _EXIT
(* Fermeture propre *)
IF ModbusIdent <> 0 THEN
MBMClose_0.enable := TRUE;
MBMClose_0.ident := ModbusIdent;
MBMClose_0();
END_IF
END_PROGRAM
(* Fonction de conversion Big Endian vers chaîne *)
FUNCTION ConvertRegistersToString
VAR_INPUT
pRegisters : POINTER TO UINT;
pString : POINTER TO STRING;
NumRegisters : UINT;
END_VAR
VAR
i, j : UINT;
pBytes : POINTER TO USINT;
pDest : POINTER TO USINT;
TempByte : USINT;
END_VAR
pBytes := pRegisters;
pDest := pString;
(* Convertir chaque registre (Big Endian) *)
FOR i := 0 TO (NumRegisters * 2 - 1) BY 2 DO
(* Swap bytes pour chaque registre *)
TempByte := pBytes[i];
pDest[i] := pBytes[i + 1];
pDest[i + 1] := TempByte;
END_FOR
(* S'assurer que la chaîne se termine par NULL *)
pDest[NumRegisters * 2] := 0;
END_FUNCTION
I don’t know why it doesn’t work. Someone can help me to finish this program, please.