e!COCKPIT : Exemple de gestion Modbus

Cet article étant quelque peu long, il a été découpé en plusieurs pages, vous pouvez les lire l'une après l'autre ou les consulter dans leur intégralité


Introduction

Afin de réaliser le communication Modbus depuis un automate WAGO (type PFC100/200), nous avons trois grandes solutions :

  • Utiliser le configurateur Modbus présent dans le logiciel e!COCKPIT
  • Utiliser les bibliothèques WAGO contenant les blocs fonctionnels permettant la gestion d'une communication Modbus
  • Tout redévelopper les communications

Nous ne donnerons ici aucune explication sur la troisième possibilité, car ce n'est pas le but de cet article.

La première solution sera écartée de cet article, car, même si, de prime abord, elle peut sembler facilement utilisable et rapide à mettre en service, elle comporte, néanmoins, de nombreux points qui nous empêchent de l'utiliser. Parmi eux, nous ne citerons que ceux-ci :

  • Adresse IP / Adresse Esclave non modifiable par le programme. Nous nous retrouvons donc dans une solution figée pour le projet qui ne peut plus être portable sur une autre application presque identique.
  • Diagnostic de la communication très léger.
  • Requêtes impossibles à modifier depuis le programme.
  • Variables crées directement dans le programme, difficulté d'utiliser des tableaux de variables.

Ces points peuvent ne pas déranger suivant les applications. Mais dans notre cas et notre manière d'organiser nos programmes, ils le sont (voir les articles Conception d'un programme automate (1/3),(2/3),(3/3)).

Nous utiliserons donc les bibliothèques Modbus fournies par WAGO avec le logiciel e!COCKPIT

 


Les bibliothèques

Où plutôt, la bibliothèque, car il n'y en a qu'une seule.

Cette bibliothèque est, lors de la rédaction de cet article, à sa version 1.1.3.0 et elle comporte les blocs fonctionnels nécessaires à la gestion d'une communication Modbus, que ce soit en mode série ou en mode Ethernet.

Coté Master

Les deux blocs fonctionnels à utiliser seront  donc :

  • Pour une communication série 
  • Pour une communication Ethernet TCP

En dehors de tous les paramètres qui sont liés à la communication, nous pouvons remarquer que les deux blocs fonctionnels travaillent avec les mêmes informations, à savoir :

  • xTrigger : déclenchement de l'envoi de la requête (cette variable est mise à FALSE par le bloc lui-même lorsque la requête a été exécutée)
  • utQuery : une structure contenant les informations de la requête.
  • utResponse : une structure contenant les informations renvoyées par l'esclave / serveur

En travaillant avec ces deux blocs fonctionnels, nous pourrons donc arriver à réaliser un programme gérant une communication modbus. Cette communication sera rapidement modifiable au niveau de la couche physique puisqu'il suffira de changer de bloc fonctionnel, sans toucher ni à la génération des requêtes ni au traitement des réponses.

 

 Coté Slave 

Les deux blocs fonctionnels à utiliser seront  donc :

  • Pour une communication série
  • Pour une communication Ethernet TCP

Les mêmes constatations que pour les blocs Master peuvent être faites.

En dehors des informations liées à la connexion, les deux blocs fonctionnels travaillent avec 4 tableaux :

Ces quatre tableaux contiennent les informations qui seront utilisées pour les échanges de données et utilisés en fonction des codes fonctions reçus par le Master :

No Description Data Type Access Access by Functioncodes
1 Discrete Input Area ARRAY[..] OF BOOL Read only FC02
2 Discrete Output Area ARRAY[..] OF BOOL Read / Write FC01 FC05 FC15
3 Input Register Area ARRAY[..] OF WORD Read only FC04
4 Holding Register Area ARRAY[..] OF WORD Read / Write FC03 FC06 FC16 FC22 FC23

Les bornes mini et maxi des tableaux donneront les adresses mini et maxi des registres modbus utilisables.

Par exemple, un tableau défini pour les Holding Register Area allant de 150 à 7500 défini comme suit :

VAR
 awHoldingRegisters:ARRAY[150..7500] OF WORD;
END_VAR

permettra un accès en lecture / écriture aux adresses des registres modbus allant de 150 à 7500.

 


Mise en place

Pour des raisons de simplification au niveau des tests, la communication modbus sera réalisée en TCP. Nous utiliserons donc les blocs fonctionnels TCP de la bibliothèque WagoAppPlcModbus.

Nous allons dans un premier temps créer l'arborescence de notre projet de la manière suivante :

 

Définition des variables et renseignement du bloc coté esclave :

PROGRAM PLC_MODBUS_SLAVE
VAR
  FbSlave:WagoAppPlcModbus.FbMbSimpleServerTcp;
  xOpen: BOOL;
  wPort: WORD;
  utKeepAlive: WagoAppPlcModbus.typKeepAlive;
  bUnitId: BYTE;
  IIdentifyObject: WagoAppPlcModbus.I_IdentifyBaseObject;
  axDiscreteInputs:ARRAY[1..10] OF BOOL;
  axCoils:ARRAY[1..10] OF BOOL;
  awInputRegisters:ARRAY[1..10] OF WORD;
  awHoldingRegisters:ARRAY[1..1000] OF WORD;
END_VAR

FbSlave(
  xOpen:= xOpen,
  wPort:=wPort ,
  utKeepAlive:=utKeepAlive ,
  xIsOpen=> ,
  xError=> ,
  oStatus=> ,
  udiConnectedClients=> ,
  bUnitId:= bUnitId,
  IIdentifyObject:= IIdentifyObject,
  axDiscreteInputs:= axDiscreteInputs,
  axCoils:= axCoils,
  awInputRegisters:= awInputRegisters,
  awHoldingRegisters:=awHoldingRegisters ,
  oMbAccessInfo=> );

 

Définition des variables et renseignement du bloc coté maitre :

PROGRAM PLC_MODBUS_MASTER
VAR
  fbMaster:WagoAppPlcModbus.FbMbMasterTcp;
  xConnect: BOOL;
  sHost: STRING;
  wPort: WORD;
  utKeepAlive: WagoAppPlcModbus.typKeepAlive;
  eFrameType: WagoAppPlcModbus.eMbFrameType;
  tTimeOut: TIME;
  utQuery: WagoAppPlcModbus.typMbQuery;
  xTrigger: BOOL;
  utResponse: WagoAppPlcModbus.typMbResponse;
END_VAR

fbMaster(
  xConnect:= xConnect,
  sHost:= sHost,
  wPort:=wPort ,
  utKeepAlive:=utKeepAlive ,
  eFrameType:=eFrameType ,
  tTimeOut:= tTimeOut,
  utQuery:= utQuery,
  xTrigger:= xTrigger,
  utResponse:=utResponse ,
  xIsOpen=> ,
  xError=> ,
  oStatus=> );

 


Modification des déclarations de variables coté slave

Afin de pouvoir lire et écrire depuis notre programme dans les données d'échange modbus, les tableaux de variables d'échange vont être déplacés dans la liste de variable globale modbus.

Le nouveau code prend donc la forme suivante :

GVL_MODBUS
VAR_GLOBAL
  axDiscreteInputs:ARRAY[1..10] OF BOOL;
  axCoils:ARRAY[1..10] OF BOOL;
  awInputRegisters:ARRAY[1..10] OF WORD;
  awHoldingRegisters:ARRAY[1..1000] OF WORD;
END_VAR

PROGRAM PLC_MODBUS_SLAVE
VAR
  FbSlave:WagoAppPlcModbus.FbMbSimpleServerTcp;
  xOpen: BOOL;
  wPort: WORD;
  utKeepAlive: WagoAppPlcModbus.typKeepAlive;
  bUnitId: BYTE;
  IIdentifyObject: WagoAppPlcModbus.I_IdentifyBaseObject;
END_VAR

FbSlave(
  xOpen:= xOpen,
  wPort:=wPort ,
  utKeepAlive:=utKeepAlive ,
  xIsOpen=> ,
  xError=> ,
  oStatus=> ,
  udiConnectedClients=> ,
  bUnitId:= bUnitId,
  IIdentifyObject:= IIdentifyObject,
  axDiscreteInputs:= gvl_modbus.axDiscreteInputs,
  axCoils:= gvl_modbus.axCoils,
  awInputRegisters:= gvl_modbus.awInputRegisters,
  awHoldingRegisters:=gvl_modbus.awHoldingRegisters ,
  oMbAccessInfo=> );

 

Initialisation des variables coté slave

Nous allons créer une action afin d'initialiser certaines variables, cette action sera appelée au premier tour de cycle du programme. Il faudra donc aussi créer une variable qui stocke cette information.

  Définition de la variable xinit :

xinit:BOOL;

Définition du code appelant l'action init :

IF NOT xinit THEN
  ACT_INIT();
  xinit:=TRUE;
END_IF

Action ACT_INIT:

xOpen:=TRUE;
wPort:=502;
utKeepAlive.xEnable:=FALSE;
bUnitId:=1;

 


Modification des déclarations de variables coté master

Afin de pouvoir lire et écrire depuis notre programme dans les données utilisées par le bloc maitre, les informations concernant les requêtes et les réponses des esclaves vont être déplacées dans la liste de variable globale modbus.

Le nouveau code prend donc la forme suivante :

GVL_MODBUS
VAR_GLOBAL
  axDiscreteInputs:ARRAY[1..10] OF BOOL;
  axCoils:ARRAY[1..10] OF BOOL;
  awInputRegisters:ARRAY[1..10] OF WORD;
  awHoldingRegisters:ARRAY[1..1000] OF WORD;
  utQuery: WagoAppPlcModbus.typMbQuery;
  utResponse: WagoAppPlcModbus.typMbResponse;
END_VAR

PROGRAM PLC_MODBUS_MASTER
VAR
  fbMaster:WagoAppPlcModbus.FbMbMasterTcp;
  xConnect: BOOL;
  sHost: STRING;
  wPort: WORD;
  utKeepAlive: WagoAppPlcModbus.typKeepAlive;
  eFrameType: WagoAppPlcModbus.eMbFrameType;
  tTimeOut: TIME;
  xTrigger: BOOL;
END_VAR

fbMaster(
  xConnect:= xConnect,
  sHost:= sHost,
  wPort:=wPort ,
  utKeepAlive:=utKeepAlive ,
  eFrameType:=eFrameType ,
  tTimeOut:= tTimeOut,
  utQuery:= GVL_MODBUS.utQuery,
  xTrigger:= xTrigger,
  utResponse:=GVL_MODBUS.utResponse ,
  xIsOpen=> ,
  xError=> ,
  oStatus=> );

 

Initialisation des variables coté master

Nous allons créer une action afin d'initialiser certaines variables, cette action sera appelée au premier tour de cycle du programme. Il faudra donc aussi créer une variable qui stocke cette information.

 Définition de la variable xinit :

xinit:BOOL;

Définition du code appelant l'action init :

IF NOT xinit THEN
  ACT_INIT();
  xinit:=TRUE;
END_IF

Action ACT_INIT:

xConnect:=TRUE;
sHost:='192.168.1.102'; // Adresse IP de l'esclave (ici l'adresse IP de notre PFC)
wPort:=502;
utKeepAlive.xEnable:=FALSE;
eFrameType:=2;
tTimeOut:=T#100MS;

 


Définition des requêtes

Pour notre exemple, nous allons réaliser, dans un premier temps, une requête de lecture.

Cette requête aura donc pour code fonction 03 et l'adresse du premier registre à lire sera le registre 10 et la quantité à lire 30.

Modification de l'action ACT_INIT pour intégrer  la définition de notre requête.

xConnect:=TRUE;
sHost:='192.168.1.102';
wPort:=502;
utKeepAlive.xEnable:=FALSE;
eFrameType:=2;
tTimeOut:=T#100MS;

GVL_MODBUS.utQuery.bUnitId:=1;
GVL_MODBUS.utQuery.bFunctionCode:=03;
GVL_MODBUS.utQuery.uiReadAddress:=10;
GVL_MODBUS.utQuery.uiReadQuantity:=30;

 

 


Création des taches pour appel des programmes Modbus

 Afin d'appeler les deux programmes que nous avons crées, nous allons définir deux taches d'appel.

 

Création de la tâche pour le programme Master

Création de la tâche pour le programme Slave

Nous allons paramétrer ces tâches pour qu'elles évoluent toutes les 25ms avec une priorité de 14 (voir le tableau des priorités de tâches dans l'article suivant) et qu'elles appellent nos programmes PLC_MODBUS_MASTER et PLC_MODBUS_SLAVE.

 

 

 

 Une fois nos deux tâches créées, nous allons pouvoir tester notre programme.