A4A : Infrastructure Messagerie Hilscher cifX – Layer 1

Bonjour,

L’interface commune à tous les produits Hilscher est la fameuse mémoire double accès (ou Dual Port Memory) décrite dans ce document : netX Dual Port Memory Interface

On y trouve décrits la structure de celle-ci et les fonctions communes à tous les firmwares, quel que soit le protocole utilisé.

Notamment, on remarquera qu’il existe une boite aux lettres (BAL) d’émission et une de réception pour chaque canal de communication permettant les échanges acycliques.

On peut accéder à cette DPM directement mais il est préférable d’utiliser le pilote fourni, celui qui va bien pour le système d’exploitation considéré, et qui présente la non moins fameuse API cifX Device Driver.

Cette interface est écrite en C et Hilscher fournit les en-têtes nécessaires à l’écriture de votre programme d’application dans ce langage ou le C++. Il y a depuis la dernière mise à jour du DVD également un exemple en C#.

Ces en-têtes contiennent les définitions, constantes, structures, prototypes de fonctions… en C donc.

Cependant, pour les autres langages il faut écrire une petite couche d’adaptation. Pour Ada, cette couche s’appelle un « binding » et ce binding constitue donc la première couche à écrire pour pouvoir utiliser cette API.

« Ada for Automation » fournit donc avec ce binding les fonctions qui sont nécessaires pour envoyer et recevoir les messages qui constituent l’interface avec le système qui s’exécute sur le netX, le SoC équipant les produits Hilscher.

Vous trouverez tout ce qui est relatif aux cartes Hilscher dans le dossier du projet « A4A/hilscherx ».

Sont définis les paquetages suivants dans le dossier « A4A/hilscherx/src » :

  • « A4A.Protocols.HilscherX » : le père de l’arborescence
  • « A4A.Protocols.HilscherX.cifX_User » : importe les définitions de l’en-tête « cifXUser.h »
  • « A4A.Protocols.HilscherX.cifX_Errors » : importe les définitions de l’en-tête « cifXErrors.h »
  • « A4A.Protocols.HilscherX.rcX_Public » : importe les définitions de l’en-tête « rcX_Public.h »

L’en-tête « cifXUser.h » fournit les structures et prototypes des fonctions de l’API cifX qui permettent l’accès à la DPM.

Dans l’en-tête « rcX_Public.h » on trouve toutes les fonctions, sous la forme de messages, requêtes et confirmations.

Le paquetage « A4A.Protocols.HilscherX.rcX_Public » est le père d’une arborescence dont les paquetages fils sont nommés selon la fonction qu’ils contiennent et regroupent les définitions, structures et objets afférents.

Ainsi le paquetage « A4A.Protocols.HilscherX.rcX_Public.rcX_Firmware_Identify » qui va nous servir bientôt.

Il y a aussi un dossier « A4A/hilscherx/test » qui contient donc de menues applications de test.

Pour illustrer mon propos, voyons l’exemple de code « test_put_get_message.adb » ci-après.

C’est relativement trivial et la trace ci-dessous parle d’elle-même.

  1. Initialisation du pilote
  2. Ouverture du pilote
  3. Ouverture du canal 0 de la cifX 0
  4. Élaboration de la requête et envoi de celle-ci
  5. Réception de la réponse et affichage du résultat
  6. Fermeture dans l’ordre inverse
-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2014, Stephane LOS                --
--                                                                   --
-- This library is free software; you can redistribute it and/or     --
-- modify it under the terms of the GNU General Public               --
-- License as published by the Free Software Foundation; either      --
-- version 2 of the License, or (at your option) any later version.  --
--                                                                   --
-- This library is distributed in the hope that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of    --
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU --
-- General Public License for more details.                          --
--                                                                   --
-- You should have received a copy of the GNU General Public         --
-- License along with this library; if not, write to the             --
-- Free Software Foundation, Inc., 59 Temple Place - Suite 330,      --
-- Boston, MA 02111-1307, USA.                                       --
--                                                                   --
-- As a special exception, if other files instantiate generics from  --
-- this unit, or you link this unit with other files to produce an   --
-- executable, this  unit  does not  by itself cause  the resulting  --
-- executable to be covered by the GNU General Public License. This  --
-- exception does not however invalidate any other reasons why the   --
-- executable file  might be covered by the  GNU Public License.     --
-----------------------------------------------------------------------


with Ada.Text_IO;

with Interfaces.C;

with A4A; use A4A;

with A4A.Protocols.HilscherX.cifX_Errors;
use A4A.Protocols.HilscherX.cifX_Errors;

with A4A.Protocols.HilscherX.cifX_User;

with A4A.Protocols.HilscherX.rcX_Public.rcX_Firmware_Identify;
use A4A.Protocols.HilscherX.rcX_Public.rcX_Firmware_Identify;

procedure Test_Put_Get_Message is

   package cifX renames A4A.Protocols.HilscherX.cifX_User;

   Driver_Handle  : aliased cifX.Driver_Handle_Type;
   Channel_Handle : aliased cifX.Channel_Handle_Type;

   Result : DInt;

   cifX_Driver_Init_Done  : Boolean := False;
   cifX_Driver_Open_Done  : Boolean := False;
   cifX_Channel_Open_Done : Boolean := False;

   Request      : cifX.cifX_Packet_Access;
   Confirmation : cifX.cifX_Packet_Access;

   Fw_Ident_Req : aliased RCX_FIRMWARE_IDENTIFY_REQ_T;
   Fw_Ident_Cnf : aliased RCX_FIRMWARE_IDENTIFY_CNF_T;

   procedure cifX_Show_Firmware_Identification is
      What : String := CRLF
        & "***********************************************" & CRLF
        & "          Firmware Identification" & CRLF & CRLF
        & "Firmware Version :" & CRLF
        & "Major    : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Version.Major'Img & CRLF
        & "Minor    : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Version.Minor'Img & CRLF
        & "Build    : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Version.Build'Img & CRLF
        & "Revision : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Version.Revision'Img & CRLF
        & CRLF
        & "Firmware Name : "
        & Interfaces.C.To_Ada
        (Fw_Ident_Cnf.Data.Fw_Identification.Fw_Name.Name) & CRLF
        & CRLF
        & "Firmware Date :" & CRLF
        & "Year  : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Date.Year'Img & CRLF
        & "Month : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Date.Month'Img & CRLF
        & "Day   : "
        & Fw_Ident_Cnf.Data.Fw_Identification.Fw_Date.Day'Img & CRLF
        & "***********************************************" & CRLF;
   begin
      Ada.Text_IO.Put_Line (What);
   end cifX_Show_Firmware_Identification;

   procedure Close is
   begin

      if cifX_Channel_Open_Done then
         Ada.Text_IO.Put_Line (Item => "Closing Channel");
         Result := cifX.Channel_Close (Channel_Handle);
         if Result /= CIFX_NO_ERROR then
            cifX.Show_Error(Result);
         end if;
         cifX_Channel_Open_Done := False;
      end if;

      if cifX_Driver_Open_Done then
         Ada.Text_IO.Put_Line (Item => "Closing Driver");
         Result := cifX.Driver_Close (Driver_Handle);
         if Result /= CIFX_NO_ERROR then
            cifX.Show_Error(Result);
         end if;
         cifX_Driver_Open_Done := False;
      end if;

      if cifX_Driver_Init_Done then
         Ada.Text_IO.Put_Line (Item => "Deinitializing cifX Driver");
         cifX.Driver_Deinit;
         cifX_Driver_Init_Done := False;
      end if;

   end Close;

begin

   Ada.Text_IO.Put_Line (Item => "Test_CifX : Basic messaging...");

   Ada.Text_IO.Put_Line (Item => "Initializing cifX Driver");
   Result := cifX.Driver_Init;
   if Result /= CIFX_NO_ERROR then
      cifX.Show_Error(Result);
   else
      cifX_Driver_Init_Done := True;
   end if;

   if cifX_Driver_Init_Done then

      Ada.Text_IO.Put_Line (Item => "Opening Driver");
      Result := cifX.Driver_Open (Driver_Handle'Access);
      if Result /= CIFX_NO_ERROR then
         cifX.Show_Error(Result);
      else
         cifX_Driver_Open_Done := True;
      end if;

   end if;

   if cifX_Driver_Open_Done then

      Ada.Text_IO.Put_Line (Item => "Opening Channel");
      Result := cifX.Channel_Open
        (Driver_Handle          => Driver_Handle,
         Board_Name             => "cifX0",
         Channel_Number         => 0,
         Channel_Handle_Access  => Channel_Handle'Access);

      if Result /= CIFX_NO_ERROR then
         cifX.Show_Error(Result);
      else
         cifX_Channel_Open_Done := True;
      end if;

   end if;

   if cifX_Channel_Open_Done then

      Ada.Text_IO.Put_Line (Item => "Sending Request");

      Fw_Ident_Req.Head.Dest    := 16#20#;
      Fw_Ident_Req.Head.Src     := 0;
      Fw_Ident_Req.Head.Dest_Id := 0;
      Fw_Ident_Req.Head.Src_Id  := 0;
      Fw_Ident_Req.Head.Len     := 4;
      Fw_Ident_Req.Head.Id      := 1;
      Fw_Ident_Req.Head.State   := 0;
      Fw_Ident_Req.Head.Cmd     := RCX_FIRMWARE_IDENTIFY_REQ;
      Fw_Ident_Req.Head.Ext     := 0;
      Fw_Ident_Req.Head.Rout    := 0;

      Fw_Ident_Req.Data.Channel_Id := 0;

      Request := To_cifX_Packet (Fw_Ident_Req'Unrestricted_Access);

      Result := cifX.Channel_Put_Packet
        (Channel_Handle => Channel_Handle,
         Send_Packet    => Request.all,
         Time_Out       => 10);
      if Result /= CIFX_NO_ERROR then
         cifX.Show_Error (Result);
      else

         Confirmation :=
           To_cifX_Packet (Fw_Ident_Cnf'Unrestricted_Access);

         Result := cifX.Channel_Get_Packet
           (Channel_Handle => Channel_Handle,
            Size           => Fw_Ident_Cnf'Size / 8,
            Recv_Packet    => Confirmation.all,
            Time_Out       => 10);
         if Result /= CIFX_NO_ERROR then
            cifX.Show_Error (Result);
         else
            cifX_Show_Firmware_Identification;
         end if;
      end if;

   end if;

   Close;

end Test_Put_Get_Message;

Et la trace bavarde :

C:\GNAT\Projects\A4A\hilscherx\exe\test_put_get_message
Test_CifX : Basic messaging...
Initializing cifX Driver
Opening Driver
Opening Channel
Sending Request

***********************************************
          Firmware Identification

Firmware Version :
Major    :  2
Minor    :  6
Build    :  7
Revision :  1

Firmware Name : EtherNet/IP Scanner

Firmware Date :
Year  :  2013
Month :  12
Day   :  3
***********************************************

Closing Channel
Closing Driver
Deinitializing cifX Driver

[2014-06-10 12:22:55] process terminated successfully, elapsed time: 01.50s

Ça fonctionne donc, avec les remarques déjà formulées dans les articles précédents :

  • La réponse peut être reçue dans un intervalle de temps plus ou moins important.
    Si l’on intègre ce code dans votre boucle temps réel, sa durée d’exécution risque d’être augmentée de façon significative.
  • Si plusieurs requêtes sont formulées en même temps, il n’est pas garanti que les réponses parviennent dans l’ordre attendu.
  • Il est possible que des indications soient reçues en lieu et place de la réponse attendue.

Cela convient cependant pour tester rapidement une requête ou si l’application fait un usage très modéré de la messagerie.

L’article suivant évoquera les mécanismes mis en œuvre pour gérer correctement ces flux de messages.

Cordialement,
Stéphane