Archives par mot-clé : comX

A4A : Nouvelles – Mai 2015

Bonjour,

Bien décidé à étendre les capacités de « Ada for Automation » pour la gestion des solutions de communication Hilscher, j’ai donc entrepris de réaliser les blocs fonctions correspondants aux requêtes disponibles avec la pile de protocole EtherCAT Maitre, particulièrement celles permettant la lecture et l’écriture d’objets via l’interface « CoE » (CANopen over EtherCAT).

Le manuel documentant cette pile de protocole, que l’on trouve sur le DVD des solutions de communication, peut être téléchargé ici :
https://kb.hilscher.com/display/ECM3V0/EtherCAT+Master

Deux requêtes sont ainsi disponibles :

  • ETHERCAT_MASTER_CMD_SDO_UPLOAD_REQ pour la lecture d’un objet,
  • et ETHERCAT_MASTER_CMD_SDO_DOWNLOAD_REQ pour l’écriture.

Cependant, tandis que je dupliquais une requête déjà réalisée, je me suis dit qu’il était plus que temps de réusiner…

En effet, j’avais jusque là focalisé mon attention sur l’infrastructure de la messagerie et les requêtes diverses avait été écrites surtout pour valider cette infrastructure et fournir quelques exemples.

Or, les requêtes sont toutes fondées sur le même modèle et on peut sans doute mutualiser celui-ci :

  • le bloc est initialisé, c’est à dire qu’on lui attribue un canal de communication, et il récupère auprès de celui-ci l’identifiant de sa queue de réception qu’il utilisera pour l’en-tête de la requête.
  • la procédure « Cyclic », appelée cycliquement donc par le programme utilisateur, gère l’obtention du paquet pour la requête depuis le pool, l’élaboration de la requête, la transmission de celle-ci, la réception de la réponse et son traitement, et le retour du paquet réponse au pool.
  • à la fin du traitement, on peut soit utiliser le résultat, soit récupérer le code de la dernière erreur survenue éventuellement.

Les requêtes dans « Ada for Automation » sont implémentées sous la forme d’un bloc fonction qui n’est autre qu’une méthode d’un objet. En créant un objet « Requête » abstrait encapsulant les attributs communs, comme le graphe d’état, et les méthodes communes, comme la procédure « Cyclic », et en dérivant les objets requêtes de ce parent commun, l’écriture en est simplifiée tout comme la maintenance.

Les objets requêtes dérivés héritent des méthodes de leur parent.
En sus, ils comportent une méthode « Fill_Request », qui est appelée par la procédure « Cyclic » après que les champs communs de l’en-tête aient été remplis, pour fournir les éléments spécifiques de la requête.
Ils comportent également une méthode « Store_Result » qui traite le résultat après que les vérifications communes aient été faites.
Ces méthodes sont déclarées dans la partie privée du paquetage parent en ayant un corps spécifiant qu’elles doivent être implémentées dans l’objet dérivé.
Enfin, ils fournissent une méthode pour récupérer le résultat depuis le programme utilisateur.

Pour pouvoir hériter de la partie privée du parent, attributs et méthodes, les objets dérivés doivent être définis dans des paquetages fils de celui du parent. Aussi, il a fallu modifier quelque peu la hiérarchie des paquetages implémentant les requêtes déjà réalisées et bien sûr en modifier le code pour les insérer dans le nouveau schéma.

L’autre classe d’interaction avec les piles de protocoles rassemble les indications qui sont à l’initiative de la pile, contrairement aux requêtes qui sont à l’initiative du programme utilisateur.

Le même raisonnement peut être tenu à leur propos et l’on a donc implémenté un objet abstrait « Indication » et dérivé les indications déjà réalisées de celui-ci.

J’ai pour l’occasion retravaillé également les tests et les applications mettant en œuvre ces requêtes et indications.

C’est beau et ça fonctionne ! 😉

Vous pouvez suivre les modifications apportées via GitLab :
https://gitlab.com/ada-for-automation/ada-for-automation

Le même genre de requêtes de lecture et écriture d’objets est disponible quelle que soit la technologie de bus de terrain utilisée. J’envisage de réaliser les blocs fonctions correspondants pour Ethernet/IP Scanner, CANopen Maitre, PROFINET IO Controller…

L’interface applicative fournie par les solutions de communication Hilscher est riche et certaines fonctions sont certainement plus utiles pour l’application que d’autres qui le seront pour un outil de configuration ou de diagnostic évolué.

Il n’est donc pas question d’être exhaustif et le choix des réalisations se fera au gré des souhaits exprimés.

N’hésitez pas à nous faire part de vos remarques et propositions éventuelles via le forum ou par email (contact).

Cordialement,
Stéphane

A4A : Nouvelles – Avril 2015

Bonjour,

Ce mois d’Avril a été propice et l’actualité « Ada for Automation » est donc plus étoffée que de coutume.

Nouvelle présentation du site

Commençons par la nouvelle la plus visible, le changement de thème de WordPress. L’aspect n’est pas tant ce qui m’a conduit à en changer bien que je le trouve assez réussi. C’est surtout le « Responsive Design » ou la faculté de s’adapter à la taille de la fenêtre du navigateur qui a motivé mon choix.

Que vous accédiez au site depuis un PC, une tablette ou un smartphone, même un pas très smart, le site devrait être lisible, même si le contenu peut être indigeste…
Même sur un PC vous pouvez expérimenter en jouant avec la taille de la fenêtre et observer comment les différents éléments se replacent magiquement.

Hormis le choix et la disposition des « Widgets », le thème est standard.

J’espère que cette nouvelle présentation vous soit agréable.

Interface Homme-Machine – Gnoga et AICWL

Tandis que j’étudiais, comme évoqué dans cet article, les technologies Web pour une utilisation avec « Ada for Automation », Monsieur David BOTTON a créé Gnoga, un framework pour développer des applications Web en Ada :
http://www.gnoga.com/

Aussi, je suis particulièrement intéressé par ce cadre applicatif et je compte bien l’utiliser pour l’IHM d’applications « Ada for Automation » comme App1.

Pouvoir piloter l’arrosage de son potager depuis son smartphone ou sa tablette est à n’en pas douter un must ! 😉

Cependant, pour un affichage plus dynamique et des composants graphiques évolués, la bibliothèque ADA INDUSTRIAL CONTROL WIDGET LIBRARY de Monsieur Dmitry A. KAZAKOV est sans conteste plus adaptée :
http://www.dmitry-kazakov.de/ada/aicwl.htm

Ces deux solutions partagent quelques composants dont le serveur web de Monsieur Kazakov.

Bien sûr, GtkAda permet également de réaliser une IHM et les trois technologies ne s’opposent pas mais se complètent à merveille.

Vérification de style et correction des avertissements

L’un des avantages les plus proéminents du « libre » c’est qu’il permet d’apprendre beaucoup en étudiant le code écrit par la communauté.

Comme les projets évoqués ci-dessus mettent en œuvre des options permettant la vérification de style et générant un maximum d’avertissements lors de la compilation, je me suis penché sur le sujet et ai intégré dans les fichiers projets ces options documentées ici et là :
https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gnat_ugn/Style-Checking.html
https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gnat_ugn/Warning-Message-Control.html
https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gnat_ugn/Compiler-Switches.html

Il a fallu évidemment corriger beaucoup de choses… ce qui a été fait pour l’essentiel.

Réorganisation des tests

Je n’avais pas encore eu l’occasion de remercier Monsieur François FABIEN pour ses conseils avisés.

Comme François me l’avait suggéré il y a quelque temps déjà j’ai réorganisé les tests afin qu’ils aient leurs propres projets et répertoires.
Ainsi, ils n’interfèrent plus avec les autres projets et cela permet de rendre les choses plus « lisibles », du moins je l’espère.

HilscherX – réusinage de la communication acyclique

Le plus gros du travail sur le code a porté sur la partie permettant l’utilisation des cartes de communication Hilscher ainsi que des modules comX et netJACK qui partagent la même interface applicative.

Le principe de cette communication acyclique a été abordé dans un certains nombre d’articles, le premier de la série étant sans doute celui-ci. La page Plan du site liste les articles concernant l’infrastructure mise en œuvre dans « Ada for Automation » pour gérer ce type de communication.

Il y est question de Paquets, de Pool de paquets, de Queues de paquets, etc… Si le principe reste inchangé, l’implémentation a été revue car le problème était le suivant :

L’en-tête des paquets contient des identifiants de source et de destination qui permettent le routage de ces paquets. La documentation Hilscher explique ce routage en indiquant que les champs Source et Source_Id doivent identifier l’application et le processus émetteurs. Le procédé de routage doit en fonction de ces champs retrouver la queue de messages correspondante.

Cependant, un raccourci employé est de renseigner ces champs avec les pointeurs de queues directement comme on peut le constater couramment dans la documentation relative aux piles de protocoles. Dans le firmware des produits, le système d’exploitation rcX utilisé est un système 32 bits et les pointeurs ont une taille de 32 bits. Indiquer la valeur du pointeur de la queue permet donc un routage très rapide puisqu’il n’y a pas lieu de rechercher ce pointeur ailleurs.

Dans ma première implémentation j’avais donc utilisé le même artifice… Mais il s’est posé un problème avec ma Debian 64 bits, le pointeur de la queue ne tenait plus dans le champs !

J’ai donc revu le routage afin de passer par une table intermédiaire, la Queue_Map, qui associe les pointeurs de queues et des indices, ces indices étant utilisés dans le champs Source_ID. Et puis, comme j’ai fait quelques progrès en Ada, j’ai également retravaillé les Pool et Queue de paquets.

HilscherX – RCX_GET_DPM_IO_INFO_REQ

Dans l’interface standard Hilscher, la fameuse Dual Port Memory, les zones réservées à l’image procédé, « les IO areas » comportent 5760 octets en entrée et autant en sortie.
Dans la plupart des cas d’utilisation c’est plutôt confortable et seule une partie de ces octets sont utilisés.

Les fonctions de l’API, xChannelIORead et xChannelIOWrite, qui permettent l’accès à ces zones prennent en paramètres un offset et une taille de données, ce qui permet de ne rafraîchir que les données effectivement configurées, avec bien sûr un gain de temps à la clef.

Il est possible avec la requête RCX_GET_DPM_IO_INFO_REQ de récupérer les informations concernant les zones IO, offset et taille, et nous avons donc implémenté un bloc fonction pour cela, le test qui va avec et modifié la tâche principale du projet « A4A_HilscherX » en conséquence.

Ainsi, les projets comme « App2 » qui héritent de « A4A_HilscherX » en profitent naturellement.

HilscherX – Diagnostic générique

Le document consacré à la Dual Port Memory mentionne, pour chaque canal de communication,

  • un « Common Status » qui permet de connaître un état de la communication commun à toutes les piles de protocoles,
  • un « Extended Status » qui permet de connaître un état de la communication relatif à chaque pile de protocole et documenté dans le manuel de la pile.

L’état étendu n’est pas utilisé par toutes les piles.

Pour certaines piles comme PROFIBUS DP Maitre, CANopen Maitre ou DeviceNet Maitre, cette zone contient la structure de diagnostic fournie par l’ancienne gamme des CIF, aujourd’hui obsolète, afin de faciliter la migration d’applications vers la nouvelle gamme cifX.

Cependant les capacités des bus de terrain basés sur Ethernet Temps Réel requièrent de repenser le principe du diagnostic. Pa exemple, PROFIBUS DP ne permet que 126 nœuds sur le réseau quand EtherCAT en prévoit en théorie jusqu’à 65535 !

Aussi, le manuel de la DPM indique que le diagnostic des protocoles maîtres se passe en deux temps.

Dans un premier temps, on utilise la requête RCX_GET_SLAVE_HANDLE_REQ pour récupérer une liste de « handles » d’esclaves, configurés, actifs ou présentant une information de diagnostic disponible, selon la liste demandée.

Dans un second temps, pour chaque « handle » de la liste retournée, la requête générique RCX_GET_SLAVE_CONN_INFO_REQ permet de récupérer les informations de connexion de l’esclave considéré.

Chaque pile de protocole maître retourne une structure documentée dans le manuel propre à la pile.

Nous avons donc créé un bloc fonction pour RCX_GET_SLAVE_HANDLE_REQ et un pour RCX_GET_SLAVE_CONN_INFO_REQ et créé les applications de test pour PROFIBUS DP Maître et EtherCAT Maître.

Et puis ?

Je lorgne du côté de la partie « Motion Control » chez PLCopen :
http://www.plcopen.org/pages/tc2_motion_control/

J’aimerais bien construire une telle bibliothèque en Ada intégrée dans « Ada for Automation ». La spécification est téléchargeable sur le site.

Bien sûr, si un tel projet vous intéresse pour vos propres réalisations, travaillons ensemble !

Pour l’instant je ne dispose pas de matériel pour tests. A minima il me faudrait un variateur communicant, avec son moteur et son codeur.
Aussi, si un tel matériel traîne dans vos placards et vous embarrasse, pensez à moi avant de le jeter à la benne.

D’ailleurs, je suis preneur de tout équipement communicant pourvu qu’il fonctionne encore et que son alimentation ou sa taille ne soient pas incongrues.
Merci d’avance pour vos contributions matérielles, qui me permettront de réaliser expériences, démonstrations et articles sur ce blog comme j’ai déjà pu le faire ici avec un radar de chez Endress + Hauser ou avec du matériel Eaton !

N’hésitez pas à nous faire part de vos remarques et propositions éventuelles via le forum ou par email (contact).

Cordialement,
Stéphane

A4A : Temps Réel sous Linux

Bonjour,

Sans être un expert du Temps Réel sous Linux, je suis avec intérêt son actualité et les progrès réalisés, notamment les projets de l’OSADL et Xenomai.

Pour pallier mon ignorance crasse du domaine je me suis offert quelques livres dont celui de Monsieur Christophe BLAESS qui explique de manière très pédagogique ce qu’est le temps réel, quelles sont les limites et les raisons de ces limites des systèmes considérés, Linux, Linux RT et Xenomai, fournit outils et moult exemples… Bref un « must have » !
Solutions temps réel sous Linux

En plus son site regorge d’informations, je le recommande :
http://www.blaess.fr/christophe/

J’ai aussi investi, le mot n’est pas trop fort, ça pique un peu, dans les publications suivantes qui sont toutes aussi passionnantes :

Je lorgne sur la version 2012 de ce dernier que je m’offrirai sans doute en version PDF car c’est un gros pavé à transporter.

Je dis ça pour qu’à mon anniversaire vous sachiez quoi m’offrir… 😉

Revenons au sujet de ce jour.

Si l’on exécute l’application exemple « app1 » de « Ada for Automation » sur un Linux standard, par exemple une Debian Wheezy on obtient la figure suivante :

A4A-App1-Linux-10_13_18

Dans le fond, Iceweasel navigue sur le site de Blender et lit une vidéo, le tout pour charger un peu le système.

J’ai donc exécuté l’application avec une tâche principale périodique de période 10 ms et les statistiques d’ordonnancement montrent que :

  • 28761 cycles ont démarré à t + [0 – 100µs]
  • 2087 cycles ont démarré à t + ]100µs – 1ms]
  • 397 cycles ont démarré à t + ]1ms – 10ms]
  • 3 cycles ont démarré à t + ]10ms – 20ms]

Cela convient pour de nombreuses applications d’automatisme mais on peut sans doute faire mieux.

Dans Synaptic, le logiciel qui va bien sous Debian pour installer les paquets, une recherche sur « linux rt » donne tout de suite le noyau qui convient à votre machine.

On l’installe prestement et on redémarre avec le nouveau noyau RT.

La commande « uname -a » permet de vérifier cela.

Si on exécute l’application en tant qu’utilisateur lambda on n’observe qu’un léger mieux :

A4A-App1-Linux RT-user 10_20_58

Par contre, si on exécute l’application en tant qu’administrateur (root) on passe dans une autre dimension :

A4A-App1-Linux-RT-root-10ms-10_26_24

Il faut avoir certains droits, root les a tous, pour exécuter une application en temps réel. C’est normal vu que votre application peut potentiellement utiliser toute la ressource processeur au détriment des autres tâches.

Mais on obtient alors des statistiques d’ordonnancement qui montrent que la périodicité est bien plus stable que ci-dessus.

Bien sûr, pour qualifier un système il faudrait réaliser des tests à plus long terme. Mais cet article n’a pour ambition que d’afficher les capacités Temps Réel de « Ada for Automation ».

La vue suivante montre le serveur Modbus TCP en service :

A4A-App1-Linux-RT-root-10ms-10_26_55

Et celle-ci les clients Modbus TCP, le client 2 ne trouve pas le serveur correspondant et pour cause, il n’y en a pas :

A4A-App1-Linux-RT-root-10ms-10_27_08

Quelques minutes plus tard :

A4A-App1-Linux-RT-root-10ms-10_27_30

Et ça donne quoi avec une période de 1ms ?

Hé bien ça fonctionne !

A4A-App1-Linux-RT-root-1ms-10_48_43

A4A-App1-Linux-RT-root-1ms-10_48_58

A4A-App1-Linux-RT-root-1ms-10_49_06

Évidemment, avec du Modbus TCP, avoir une tâche qui tourne à la milliseconde n’est pas raisonnable, ça ne sert pas à grand chose.

Par contre, avec les solutions de communication Hilscher ça prend un autre relief ! 🙂

Cordialement,
Stéphane

cifX : Mise en œuvre : API – Messages – Considérations générales

Bonjour,

Dans l’article de ce jour, j’évoque quelques considérations concernant la mise en œuvre de la messagerie Hilscher, les modes Client / Serveur, le routage des messages…

Le document netX Dual-Port Memory Interface (DPM) explique que la messagerie peut être utilisée selon deux modes, en mode Client ou en mode Serveur.

Lorsque l’application envoie une requête de configuration ou une commande de lecture / écriture de données dans un esclave par exemple, elle utilise le mode Client.

Dans ce mode le client, l’application, envoie une requête et reçoit une confirmation qui contient la réponse de la pile de protocole, un code d’état avec les données éventuelles.

Cependant, il est également possible que l’application soit intéressée par des indications en provenance de la pile de protocole. Dans ce cas, elle doit répondre à ces indications en fournissant une réponse, éventuellement des données, et travaille ainsi en mode Serveur.

Par exemple, si l’application utilise une carte cifX exécutant un firmware CANopen esclave, configurée pour gérer un dictionnaire d’objets étendu, l’application va recevoir des indications à chaque requête de lecture / écriture via SDO et devra répondre à ces indications par des réponses, négatives si l’objet n’existe pas ou positives le cas échéant.

Lorsque l’application travaille uniquement en mode Client, elle ne va recevoir que les réponses à ses requêtes.
La gestion des boites aux lettres est très simple :

  • élaboration de la requête,
  • envoi dans la BAL d’émission,
  • attente de la réponse,
  • réception et analyse de la réponse.

C’est ce que l’on a déjà eu l’occasion de réaliser à plusieurs reprises pour la configuration des firmwares esclaves mais également ici par exemple.

En mode serveur uniquement, on va devoir soit scruter cycliquement la BAL de réception, soit enregistrer une fonction de rappel qui sera donc appelée lorsqu’un message arrive, et traiter dans un sélecteur chaque message arrivant en fonction du code de commande ou d’indication, puis poster dans la BAL d’émission la réponse.

Bien évidemment, il est possible que l’application soit à la fois Client et Serveur, cela ne fait que compliquer la situation et il est nécessaire de gérer les accès concurrents aux BAL. L’exemple cité précédemment est de ce type car il émet des requêtes pour lire les données et attend en retour les réponses à ses requêtes de lecture mais aussi les indications de déconnexion des esclaves.

Si en phase de configuration et pour des cas simples on peut envisager un traitement séquentiel des messages, en régime établi et dans un cas concret les messages fusent de toutes parts et il est nécessaire de mettre en place une architecture permettant de gérer ce flux d’événements.

Ces messages, réponses à des requêtes multiples, indications d’alarmes, de diagnostic, de lecture / écriture, etc. peuvent être soit gérées par un process monolithique, soit par des tâches dédiées à tel ou tel traitement.

C’est ici qu’interviennent les données de routage qui figurent dans l’en-tête des messages Hilscher. Voir la documentation.

Ainsi, une tâche de routage peut recevoir les messages et les router vers les tâches chargées de leur traitement.
Pour tout vous dire, c’est le mécanisme à l’œuvre de l’autre côté de la DPM.

Côté firmware, les tâches communiquent entre elles via des queues de messages. C’est très commun dans ce type d’application, la communication, et ça se fait relativement facilement, les queues font partie de la boite à outils de tout informaticien qui se respecte.

Côté application, on peut essayer de faire sans les queues. Dans ce cas, si une tâche est réceptionnaire de messages successifs qu’elle ne peut traiter immédiatement, la BAL de réception est occupée et ne peut donc être utilisée pour transmettre d’autres messages. Ce n’est pas bon pour la performance et dans les cas extrêmes les queues côté firmware sont saturées et il y a perte de paquets.

Cependant, il faut considérer que le flux de messages traités par le firmware est bien plus intense que celui à gérer au niveau applicatif. En effet, alors que l’application ne doit gérer que des communications acycliques, le firmware a en charge la communication cyclique avec tous les esclaves.

Il faut donc garder tête froide et ne pas se livrer à une optimisation trop précoce qui compliquerait une architecture plus qu’il serait souhaitable.

Ainsi, l’application de test / exemple livrée avec le pilote cifX pour Siemens WinAC RTX®, cf. la série d’articles relative, utilise une seule tâche, le bloc d’organisation OB1, et les messages sont gérés par des blocs fonctions implémentant des machines à états.

Cordialement,
Stéphane

cifX : Mise en œuvre : API – Messages – PROFIBUS DP V1 Class 2

Bonjour,

J’ai déjà abordé l’API cifX dans cet article d’introduction et les messages de configuration dans celui-ci entre autres.

Comme je dispose d’un équipement Endress + Hauser Micropilot M FMR 244, un instrument de mesure de niveau radar qui peut répondre à des requêtes PROFIBUS DP V1 Class 2 pour la lecture / écriture des paramètres de l’instrument, j’en profite pour vous donner cet autre exemple d’utilisation de la messagerie Hilscher.

Le document suivant de chez Endress + Hauser nous renseigne sur les éléments adressables de cet instrument :
https://portal.endress.com/wa001/dla/5000415/7572/000/08/BA00249FEN_1311.pdf

L’instrument est DP V0 et supporte une communication PROFIBUS DP V1 Class 2 mais pas la communication PROFIBUS DP V1 Class 1.

La carte Hilscher cifX 50-DP est donc configurée en PROFIBUS DP Maitre et la documentation suivante est nécessaire :
PROFIBUS DP Master Protocol API

On la trouve bien sûr également sur le DVD dans le répertoire Documentation :
6. Programming Manuals\english\4. Protocol Application Programming Interface\PROFIBUS DP Master

Cette documentation nous indique que nous pouvons utiliser les services d’un maitre DP V1 Class 1 ou ceux d’un maitre DP V1 Class 2. Dans cette manipulation nous utiliserons donc la seconde option.

Nous y trouvons la commande à employer pour la lecture d’un bloc de données d’un esclave DP V1 en p.192 :
PROFIBUS_FSPMM2_CMD_READ_REQ/CNF – V1 Class 2 Read Request

Cependant, avant il nous faut ouvrir une connexion entre le maitre Class 2, notre carte cifX, et l’esclave DP.

Nous disposons dans le document E+H au chapitre 5.5.6 Slot/index tables en p.48 de la table des données avec les paramètres nécessaires à l’élaboration de notre requête.

Ainsi essayons de lire par exemple :
Block parameters
Software revision 1 73 16 OSTRING X constant

Il nous faut l’adresse de l’esclave, dans notre cas 2, le numéro de slot, ici 1, l’index, ici 73 et la taille des données, soit 16 pour notre chaine de caractères.

L’algorithme est simple :

  • S’enregistrer auprès de la pile de protocole PROFIBUS DP Maitre pour pouvoir recevoir les indications, notamment celle que nous allons recevoir à notre demande de déconnexion, cf. « Register / Unregister an Application » dans le document netX Dual-Port Memory Interface (DPM).
  • Se connecter à l’esclave, cf. PROFIBUS_FSPMM2_CMD_INITIATE_REQ/CNF– Initiate DPV1C2 Connection
  • Envoyer notre requête de lecture, cf. PROFIBUS_FSPMM2_CMD_READ_REQ/CNF – V1 Class 2 Read Request
  • Envoyer notre requête de déconnexion, cf. PROFIBUS_FSPMM2_CMD_ABORT_REQ/CNF – Request Abort of Connection
  • Purger les indications, cf. PROFIBUS_FSPMM2_CMD_CLOSED_IND/RES – Closed Indication
  • Se dés-enregistrer.

Bien sûr, c’est un code de démonstration. On peut et on doit faire mieux !

D’où le code du jour :

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include "rcX_Public.h"
#include "cifxuser.h"
#include "cifxErrors.h"
#include "TLR_Types.h"
#include "ProfibusFspmm_Public.h"
#include "ProfibusFspmm2_Public.h"

#define IO_WAIT_TIMEOUT     10

/*****************************************************************************/
/*! Show error
 *                                                                           */

/*****************************************************************************/
void ShowError( long lError)
{
  if( lError != CIFX_NO_ERROR)
    {
      /* Read driver error description */
      char szError[1024] ={0};
      xDriverGetErrorDescription( lError,  szError, sizeof(szError));
      printf("Error: 0x%X, <%s>\r\n", lError, szError);
    }
}

static uint32_t      ulMsgId         = 0;
static uint32_t      ulCRef          = 0;

/*****************************************************************************/
/*! Register Application Request
 *   \return 0 on success                                                    */

/*****************************************************************************/

int Register_Application(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  RCX_REGISTER_APP_REQ_T    *pPktRegisterReq;
  RCX_REGISTER_APP_CNF_T    *pPktRegisterCnf;

  pPktRegisterReq = (RCX_REGISTER_APP_REQ_T *)&tSendPacket;

  pPktRegisterReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktRegisterReq->tHead.ulSrc    = 0x00;
  pPktRegisterReq->tHead.ulDestId = 0x00;
  pPktRegisterReq->tHead.ulSrcId  = 0x00;
  pPktRegisterReq->tHead.ulLen    = 0;
  pPktRegisterReq->tHead.ulId     = ulMsgId++;
  pPktRegisterReq->tHead.ulSta    = 0x00;
  pPktRegisterReq->tHead.ulCmd    = RCX_REGISTER_APP_REQ;
  pPktRegisterReq->tHead.ulExt    = 0x00;
  pPktRegisterReq->tHead.ulRout   = 0x00;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Register Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktRegisterCnf = (RCX_REGISTER_APP_CNF_T *)&tRecvPacket;
          if(pPktRegisterCnf->tHead.ulCmd != RCX_REGISTER_APP_CNF)
            {
              printf("Wrong CNF received : pPktRegisterCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktRegisterCnf->tHead.ulCmd);
            }
          else if(pPktRegisterCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktRegisterCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktRegisterCnf->tHead.ulSta);
            }
          else
            {
              printf("Register Request confirmation received.\r\n");
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! Unregister Application Request
 *   \return 0 on success                                                    */

/*****************************************************************************/

int Unregister_Application(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  RCX_UNREGISTER_APP_REQ_T    *pPktUnregisterReq;
  RCX_UNREGISTER_APP_CNF_T    *pPktUnregisterCnf;

  pPktUnregisterReq = (RCX_UNREGISTER_APP_REQ_T *)&tSendPacket;

  pPktUnregisterReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktUnregisterReq->tHead.ulSrc    = 0x00;
  pPktUnregisterReq->tHead.ulDestId = 0x00;
  pPktUnregisterReq->tHead.ulSrcId  = 0x00;
  pPktUnregisterReq->tHead.ulLen    = 0;
  pPktUnregisterReq->tHead.ulId     = ulMsgId++;
  pPktUnregisterReq->tHead.ulSta    = 0x00;
  pPktUnregisterReq->tHead.ulCmd    = RCX_UNREGISTER_APP_REQ;
  pPktUnregisterReq->tHead.ulExt    = 0x00;
  pPktUnregisterReq->tHead.ulRout   = 0x00;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Unregister Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktUnregisterCnf = (RCX_UNREGISTER_APP_CNF_T *)&tRecvPacket;
          if(pPktUnregisterCnf->tHead.ulCmd != RCX_UNREGISTER_APP_CNF)
            {
              printf("Wrong CNF received : pPktUnregisterCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktUnregisterCnf->tHead.ulCmd);
            }
          else if(pPktUnregisterCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktUnregisterCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktUnregisterCnf->tHead.ulSta);
            }
          else
            {
              printf("Unregister Request confirmation received.\r\n");
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! DPV1 Class 2 Initiate Connection Request to a PROFIBUS DP Slave
 *   \return 0 on success                                                    */

/*****************************************************************************/

int DPV1Class2_Initiate(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  PROFIBUS_FSPMM2_PACKET_INITIATE_REQ_T    *pPktInitiateReq;
  PROFIBUS_FSPMM2_PACKET_INITIATE_CNF_T    *pPktInitiateCnf;

  pPktInitiateReq = (PROFIBUS_FSPMM2_PACKET_INITIATE_REQ_T *)&tSendPacket;

  pPktInitiateReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktInitiateReq->tHead.ulSrc    = 0x00;
  pPktInitiateReq->tHead.ulDestId = 0x00;
  pPktInitiateReq->tHead.ulSrcId  = 0x00;
  pPktInitiateReq->tHead.ulLen    = PROFIBUS_FSPMM2_INITIATE_REQ_SIZE + 4 + 4;
  pPktInitiateReq->tHead.ulId     = ulMsgId++;
  pPktInitiateReq->tHead.ulSta    = 0x00;
  pPktInitiateReq->tHead.ulCmd    = PROFIBUS_FSPMM2_CMD_INITIATE_REQ;
  pPktInitiateReq->tHead.ulExt    = 0x00;
  pPktInitiateReq->tHead.ulRout   = 0x00;

  pPktInitiateReq->tData.ulRemAdd        =    2;   /* Slave address */
  pPktInitiateReq->tData.usSendTimeout   =  100;

  pPktInitiateReq->tData.bFeaturesSupported1 =  1;
  pPktInitiateReq->tData.bFeaturesSupported2 =  0;

  pPktInitiateReq->tData.bProfileFeaturesSupported1 =  0;
  pPktInitiateReq->tData.bProfileFeaturesSupported2 =  0;
  pPktInitiateReq->tData.usProfileIdentNumber       =  0;

  pPktInitiateReq->tData.tAddAddrParam.bS_Type =  0;
  pPktInitiateReq->tData.tAddAddrParam.bS_Len  =  2;
  pPktInitiateReq->tData.tAddAddrParam.bD_Type =  0;
  pPktInitiateReq->tData.tAddAddrParam.bD_Len  =  2;

  pPktInitiateReq->tData.tAddAddrParam.abAddParam[0] =  0;
  pPktInitiateReq->tData.tAddAddrParam.abAddParam[1] =  0;
  pPktInitiateReq->tData.tAddAddrParam.abAddParam[2] =  0;
  pPktInitiateReq->tData.tAddAddrParam.abAddParam[3] =  0;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Initiate Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktInitiateCnf = (PROFIBUS_FSPMM2_PACKET_INITIATE_CNF_T *)&tRecvPacket;
          if(pPktInitiateCnf->tHead.ulCmd != PROFIBUS_FSPMM2_CMD_INITIATE_CNF)
            {
              printf("Wrong CNF received : pPktInitiateCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktInitiateCnf->tHead.ulCmd);
            }
          else if(pPktInitiateCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktInitiateCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktInitiateCnf->tHead.ulSta);
              printf("ulRemAdd     = 0x%08X\r\n"
                     "bErrorDecode = 0x%02X\r\n"
                     "bErrorCode1  = 0x%02X\r\n"
                     "bErrorCode2  = 0x%02X\r\n"
                     "usDetail     = 0x%04X\r\n",
                     pPktInitiateCnf->tData.tCnfNeg.ulRemAdd,
                     pPktInitiateCnf->tData.tCnfNeg.bErrorDecode,
                     pPktInitiateCnf->tData.tCnfNeg.bErrorCode1,
                     pPktInitiateCnf->tData.tCnfNeg.bErrorCode2,
                     pPktInitiateCnf->tData.tCnfNeg.usDetail);
            }
          else
            {
              printf("Initiate Request confirmation received.\r\n");
              ulCRef = pPktInitiateCnf->tData.tCnfPos.ulCRef;
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! DPV1 Class 2 Read Request PROFIBUS DP Slave
 *   \return 0 on success                                                    */

/*****************************************************************************/

int DPV1Class2_Read(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  PROFIBUS_FSPMM2_PACKET_READ_REQ_T    *pPktReadReq;
  PROFIBUS_FSPMM2_PACKET_READ_CNF_T    *pPktReadCnf;

  pPktReadReq = (PROFIBUS_FSPMM2_PACKET_READ_REQ_T *)&tSendPacket;

  pPktReadReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktReadReq->tHead.ulSrc    = 0x00;
  pPktReadReq->tHead.ulDestId = 0x00;
  pPktReadReq->tHead.ulSrcId  = 0x00;
  pPktReadReq->tHead.ulLen    = PROFIBUS_FSPMM2_READ_REQ_SIZE;
  pPktReadReq->tHead.ulId     = ulMsgId++;
  pPktReadReq->tHead.ulSta    = 0x00;
  pPktReadReq->tHead.ulCmd    = PROFIBUS_FSPMM2_CMD_READ_REQ;
  pPktReadReq->tHead.ulExt    = 0x00;
  pPktReadReq->tHead.ulRout   = 0x00;

  pPktReadReq->tData.ulCRef     = ulCRef;   /* Connection Reference */
  pPktReadReq->tData.ulSlot     =      1;   /* Requested slot */
  pPktReadReq->tData.ulIndex    =     73;   /* Requested index */
  pPktReadReq->tData.ulLength   =     16;   /* Requested data length */

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Read Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktReadCnf = (PROFIBUS_FSPMM2_PACKET_READ_CNF_T *)&tRecvPacket;
          if(pPktReadCnf->tHead.ulCmd != PROFIBUS_FSPMM2_CMD_READ_CNF)
            {
              printf("Wrong CNF received : pPktReadCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktReadCnf->tHead.ulCmd);
            }
          else if(pPktReadCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktReadCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktReadCnf->tHead.ulSta);
            }
          else
            {
              printf("Read Request confirmation received.\r\n");
              printf("Software revision : %.16s\r\n", pPktReadCnf->tData.tCnfPos.abData);
              iResult = 0;
            }
        }
    }
  return iResult;
}

/*****************************************************************************/
/*! DPV1 Class 2 Abort Request
 *   \return 0 on success                                                    */

/*****************************************************************************/

int DPV1Class2_Abort(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;

  CIFX_PACKET   tSendPacket     = {0};
  CIFX_PACKET   tRecvPacket     = {0};

  PROFIBUS_FSPMM2_PACKET_ABORT_REQ_T    *pPktAbortReq;
  PROFIBUS_FSPMM2_PACKET_ABORT_CNF_T    *pPktAbortCnf;

  pPktAbortReq = (PROFIBUS_FSPMM2_PACKET_ABORT_REQ_T *)&tSendPacket;

  pPktAbortReq->tHead.ulDest   = 0x20; /* Channel mailbox */
  pPktAbortReq->tHead.ulSrc    = 0x00;
  pPktAbortReq->tHead.ulDestId = 0x00;
  pPktAbortReq->tHead.ulSrcId  = 0x00;
  pPktAbortReq->tHead.ulLen    = PROFIBUS_FSPMM2_ABORT_REQ_SIZE;
  pPktAbortReq->tHead.ulId     = ulMsgId++;
  pPktAbortReq->tHead.ulSta    = 0x00;
  pPktAbortReq->tHead.ulCmd    = PROFIBUS_FSPMM2_CMD_ABORT_REQ;
  pPktAbortReq->tHead.ulExt    = 0x00;
  pPktAbortReq->tHead.ulRout   = 0x00;

  pPktAbortReq->tData.ulCRef      = ulCRef;   /* Connection Reference */
  pPktAbortReq->tData.bSubnet     = PROFIBUS_FSPMM2_SUBNET_NO;
  pPktAbortReq->tData.bInstance   = PROFIBUS_FSPMM2_INSTANCE_USER;
  pPktAbortReq->tData.bReasonCode =      0;

  lRet = xChannelPutPacket(hChannel, &tSendPacket, 1000);
  if(lRet != CIFX_NO_ERROR)
    {
      ShowError( lRet);
    }
  else
    {
      printf("Abort Request sent...\r\n");
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          pPktAbortCnf = (PROFIBUS_FSPMM2_PACKET_ABORT_CNF_T *)&tRecvPacket;
          if(pPktAbortCnf->tHead.ulCmd != PROFIBUS_FSPMM2_CMD_ABORT_CNF)
            {
              printf("Wrong CNF received : pPktAbortCnf->tHead.ulCmd = 0x%08X\r\n",
                     pPktAbortCnf->tHead.ulCmd);
            }
          else if(pPktAbortCnf->tHead.ulSta != 0)
            {
              printf("Wrong CNF received : pPktAbortCnf->tHead.ulSta = 0x%08X\r\n",
                     pPktAbortCnf->tHead.ulSta);
            }
          else
            {
              printf("Abort Request confirmation received.\r\n");
              iResult = 0;
            }
        }
    }
  return iResult;
}

int Purge(HANDLE hChannel)
{
  long          lRet            = CIFX_NO_ERROR;
  int           iResult         = -1;
  int           iCount          = 3; /* Wait 3 times */

  CIFX_PACKET   tRecvPacket     = {0};

  do
    {
      iCount--;
      lRet = xChannelGetPacket(hChannel, sizeof(tRecvPacket), &tRecvPacket, 1000);
      if(lRet != CIFX_NO_ERROR)
        {
          ShowError( lRet);
        }
      else
        {
          printf("Packet received :\r\n"
                 "ulCmd      = 0x%08X\r\n"
                 "ulDest     = 0x%08X\r\n"
                 "ulSrc      = 0x%08X\r\n"
                 "ulDestId   = 0x%08X\r\n"
                 "ulSrcId    = 0x%08X\r\n",
                 tRecvPacket.tHeader.ulCmd,
                 tRecvPacket.tHeader.ulDest,
                 tRecvPacket.tHeader.ulSrc,
                 tRecvPacket.tHeader.ulDestId,
                 tRecvPacket.tHeader.ulSrcId);

          switch (tRecvPacket.tHeader.ulCmd)
            {
              case PROFIBUS_FSPMM2_CMD_CLOSED_IND:
                {
                  printf("Closed Indication received : tRecvPacket->tHead.ulSta = 0x%08X\r\n",
                         tRecvPacket.tHeader.ulState);

                  tRecvPacket.tHeader.ulDest = 0x20; /* Channel mailbox */
                  tRecvPacket.tHeader.ulCmd  = PROFIBUS_FSPMM2_CMD_CLOSED_RES;

                  lRet = xChannelPutPacket(hChannel, &tRecvPacket, 1000);
                  if(lRet != CIFX_NO_ERROR)
                    {
                      ShowError( lRet);
                    }
                  else
                    {
                      printf("Closed Response sent...\r\n");
                      iResult = 0;
                    }
                  break;
                }

              default:
                {
                  printf("Message received : tRecvPacket->tHead.ulCmd = 0x%08X\r\n",
                         tRecvPacket.tHeader.ulCmd);
                  break;
                }
            }
        }
    }
    while (iCount > 0);
  return iResult;
}

/*****************************************************************************/
/*! The main function
 *   \return 0 on success                                                    */

/*****************************************************************************/
int main(int argc, char* argv[])
{
  HANDLE hDriver  = NULL;
  HANDLE hChannel = NULL;
  long   lRet     = CIFX_NO_ERROR;
  long   lOldRet  = CIFX_NO_ERROR;

  UNREFERENCED_PARAMETER(argc);
  UNREFERENCED_PARAMETER(argv);

  /* Open the cifX driver */
  lRet = xDriverOpen(&hDriver);
  if(CIFX_NO_ERROR != lRet)
    {
      printf("Error opening driver!\r\n");
      ShowError(lRet);
    }
  else
    {
      lRet = xChannelOpen(hDriver, "cifX0", 0, &hChannel);
      if(CIFX_NO_ERROR != lRet)
        {
          printf("Error opening Channel!\r\n");
          ShowError(lRet);
        }
      else
        {
          printf("\n--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2, Slot 1, Index 73 ---\r\n");

          printf("\n--- Register Application Request ---\r\n");

          if(Register_Application(hChannel) != 0)
            {
              printf("Error registering application\r\n");
            }
          else
            {
              printf("\n--- DPV1 Class 2 Initiate Request PROFIBUS DP Slave 2 ---\r\n");

              if(DPV1Class2_Initiate(hChannel) != 0)
                {
                  printf("Error initiating connection\r\n");
                }
              else
                {
                  printf("\n--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2 ---\r\n");

                  if(DPV1Class2_Read(hChannel) != 0)
                    {
                      printf("Error reading\r\n");
                    }

                  printf("\n--- DPV1 Class 2 Abort Request PROFIBUS DP Slave 2 ---\r\n");

                  if(DPV1Class2_Abort(hChannel) != 0)
                    {
                      printf("Error aborting\r\n");
                    }

          Purge(hChannel);
        }
            }

      if(Unregister_Application(hChannel) != 0)
        {
          printf("Error unregistering application\r\n");
        }

          /* Close the communication channel */
          xChannelClose(hChannel);
        }
      /* Close the cifX driver */
      xDriverClose(hDriver);
    }
  return 0;
}

Et la trace qui nous montre que c’est tombé en marche !

On lit bien la version du logiciel : Software revision : 01.05.00

H:\Steve\GNAT\ENDRESS\C05\exe\DPV1C2_main

--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2, Slot 1, Index 73 ---


--- Register Application Request ---

Register Request sent...

Register Request confirmation received.


--- DPV1 Class 2 Initiate Request PROFIBUS DP Slave 2 ---

Initiate Request sent...

Initiate Request confirmation received.


--- DPV1 Class 2 Read Request PROFIBUS DP Slave 2 ---

Read Request sent...

Read Request confirmation received.

Software revision : 01.05.00        


--- DPV1 Class 2 Abort Request PROFIBUS DP Slave 2 ---

Abort Request sent...

Abort Request confirmation received.

Packet received :

ulCmd      = 0x00004428

ulDest     = 0x00000000

ulSrc      = 0x80169C80

ulDestId   = 0xA009A021

ulSrcId    = 0x00000000

Closed Indication received : tRecvPacket->tHead.ulSta = 0x00000000

Closed Response sent...

Error: 0x800C0019, <No packet available>

Error: 0x800C0019, <No packet available>

Unregister Request sent...

Unregister Request confirmation received.

Cordialement,
Stéphane