A4A : Exemple d’application app2 – Hilscher cifX

Bonjour,

Dans cette deuxième application exemple pour « Ada for Automation » on met en œuvre le « binding » Ada pour le pilote des cartes de communication Hilscher cifX qui avait été présenté ici.

La maquette pour cette application est celle déjà utilisée pour les articles précédents :
cifX : FDT/DTM – Un exemple intéressant
cifX : Mise en œuvre : API – Messages – PROFIBUS DP V1 Class 2
cifX : FDT/DTM – Un exemple intéressant – E+H FieldCare

La partie opérative est donc constituée du niveau en technologie radar de chez Endress+Hauser, le Micropilot M FMR244, du convertisseur PROFIBUS DP/PA de chez Pepperl+Fuchs et une carte PROFIBUS Hilscher cifX-50-DP.

Cette constellation a été configurée avec SYCON.net comme expliqué dans les articles cités.

Pour la partie commande, c’est donc l’application exemple « app2 » qui est en charge de remonter les informations issues du codeur communicant sur PROFIBUS PA vers la supervision PcVue de Arc Informatique en Modbus TCP.

Pour ce faire, on a modifié la tâche « Main_Task » de « A4A.Kernel.Main » en substituant le fichier correspondant pour lui faire gérer la carte cifX PROFIBUS DP avec le firmware Maître au lieu du client Modbus TCP.

Cela montre au passage la facilité avec laquelle vous pouvez adapter l’utilisation du framework à votre besoin.

Le code est bien sûr disponible comme à l’accoutumée :
Télécharger « Ada for Automation »

Le code de cette tâche principale modifié est présenté ci-dessous.

Après avoir démarré les tâches annexes pour la gestion des temporisateurs, celle du serveur Modbus TCP, le pilote cifX est initialisé et ouvert, puis le canal 0 de la carte « cifX0 » est ouvert, il est à présent possible d’utiliser le canal de communication pour lire et écrire les données sur PROFIBUS DP/PA.

Un extrait de ce code disponible dans « app2/src/a4a-kernel-main.adb » :

package body A4A.Kernel.Main is
...
   task body Main_Task is
...
   begin

      Log_Task_Start;

      --------------------------------------------------------------------
      -- Clock Management
      --------------------------------------------------------------------

      Clock_Handler := new Clock_Handler_Task_Type
        (Task_Priority          => System.Default_Priority,
         Task_Itf               => Clock_Handler_Interface'Access,
         Period_In_Milliseconds => 10);
      -- This task manages the real time clock calls
      -- so that the system call does not get overwhelmed
      -- Is that true ? It seems to me because a lot of automation
      -- stuff needs time.

      --------------------------------------------------------------------
      -- Modbus TCP Server Management
      --------------------------------------------------------------------

      MBTCP_Server_Task_Interface.Control.Watchdog_Time_Out_MS := 3000;
      MBTCP_Server_Task := new Server.Periodic_Task
        (Task_Priority  => System.Default_Priority,
         Task_Itf       => MBTCP_Server_Task_Interface'Access,
         Configuration  => A4A.Application.MBTCP_Server_Config.Config1'Access
        );

      A4A.Log.Logger.Put (Who  => My_Ident,
                          What => "Modbus TCP Server created...");

   --------------------------------------------------------------------
   -- Hilscher cifX 0 Management
   --------------------------------------------------------------------

      A4A.Log.Logger.Put (Who  => My_Ident,
                          What => "Initializing cifX Driver...");

      Result := cifX.Driver_Init;
      if Result /= cifX.CIFX_NO_ERROR then
         cifX_Show_Error (Result);
      else
         cifX_Driver_Init_Done := True;

         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => "Opening cifX Driver...");

         Result := cifX.Driver_Open (Driver_Handle'Access);
         if Result /= cifX.CIFX_NO_ERROR then
            cifX_Show_Error (Result);
         else
            cifX_Driver_Open_Done := True;

            A4A.Log.Logger.Put (Who  => My_Ident,
                                What => "Opening cifX Channel...");

            Result := cifX.Channel_Open
              (Driver_Handle          => Driver_Handle,
               Board_Name             => "cifX0",
               Channel_Number         => 0,
               Channel_Handle_Access  => Channel_Handle'Access);

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

On entre ensuite dans le vif du sujet avec la boucle principale qui gère son propre chien de garde avec la tâche qui l’a démarrée, -la fonction principale de l’application-, le chien de garde de la tâche serveur Modbus TCP, puis lit les entrées qui proviennent de la carte cifX, lit celles qui proviennent du serveur Modbus TCP, appelle le programme utilisateur principal si l’état de l’application est « Running » ou remet à 0 les sorties dans le cas contraire et enfin écrit les sorties dans la carte et le serveur Modbus TCP.

      --------------------------------------------------------------------
      -- Main loop
      --------------------------------------------------------------------

      Main_Loop:
      loop
...
         Result := cifX.Channel_IO_Read
           (Channel_Handle => Channel_Handle,
            Area_Number    => 0,
            Offset         => 0,
            Data_Length    => Hilscher_cifx0_Inputs'Length,
            Data_In        => Hilscher_cifx0_Inputs,
            Time_Out       => 10);

         if Result /= cifX.CIFX_NO_ERROR then
            cifX_Show_Error(Result);
         end if;

         Server.Registers_Read
           (Outputs => A4A.Memory.MBTCP_IOServer_Registers,
            Offset  => 0);

         if Task_Itf.Status.Running then
            A4A.Application.Main_Cyclic;
         else
            A4A.Memory.Hilscher_cifx0_Outputs := (others => 0);
         end if;

         Result := cifX.Channel_IO_Write
           (Channel_Handle => Channel_Handle,
            Area_Number    => 0,
            Offset         => 0,
            Data_Length    => Hilscher_cifx0_Outputs'Length,
            Data_Out       => Hilscher_cifx0_Outputs,
            Time_Out       => 10);

         if Result /= cifX.CIFX_NO_ERROR then
            cifX_Show_Error(Result);
         end if;

         Server.Inputs_Registers_Write
           (Inputs => A4A.Memory.MBTCP_IOServer_Input_Registers,
            Offset => 0);

...

Tout à une fin, cette tâche peut être terminée par un Ctrl+C ou le bouton « Quit ».
On referme tout proprement avant de se terminer.

...
      end loop Main_Loop;

      if cifX_Channel_Open_Done then
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => "Closing cifX Channel...");

         Result := cifX.Channel_Close (Channel_Handle);
         if Result /= cifX.CIFX_NO_ERROR then
            cifX_Show_Error (Result);
         end if;

         cifX_Channel_Open_Done := False;
      end if;

      if cifX_Driver_Open_Done then
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => "Closing cifX Driver...");

         Result := cifX.Driver_Close (Driver_Handle);
         if Result /= cifX.CIFX_NO_ERROR then
            cifX_Show_Error (Result);
         end if;

         cifX_Driver_Open_Done := False;
      end if;

      if cifX_Driver_Init_Done then
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => "Deinitializing cifX Driver...");

         cifX.Driver_Deinit;

         cifX_Driver_Init_Done := False;
      end if;

      A4A.Log.Logger.Put (Who  => My_Ident,
                          What => "finished !");
      Task_Itf.Status.Terminated (Task_Itf.Control.Quit);
      Main_Task_Created := False;

   end Main_Task;

Le code du programme utilisateur est des plus simples puisqu’il ne fait que transférer les données du capteur vers la supervision. Tout est dans le dossier « app2 ».

La tâche principale appelle donc la procédure principale du programme utilisateur, « A4A.Application.Main_Cyclic ».
Cette procédure est dans le paquetage « A4A.Application » et définie dans le fichier « app2/src/a4a-application.adb » :

with A4A.Memory; use A4A.Memory;

with A4A.User_Objects; use A4A.User_Objects;
with A4A.User_Functions; use A4A.User_Functions;

package body A4A.Application is

   procedure Main_Cyclic is
      My_Ident : String := "A4A.Application.Main_Cyclic";
   begin

      Map_Inputs;

      Map_HMI_Inputs;

      null;

      Map_Outputs;

      Map_HMI_Outputs;

   end Main_Cyclic;
...

Cette procédure principale appelle à son tour les fonctions et procédures utilisateur ici définies dans le paquetage « A4A.User_Functions » dans le fichier « app2/src/a4a-user_functions.adb ». Pour ce programme exemple, seule la procédure « Map_HMI_Outputs » fait quelque chose d’utile, recopier les octets dans le bon ordre au bon endroit :

...
   procedure Map_HMI_Outputs is
   begin

      Bytes_To_Word (LSB_Byte => Hilscher_cifx0_Inputs(1),
                     MSB_Byte => Hilscher_cifx0_Inputs(0),
                     Word_out => MBTCP_IOServer_Input_Registers(1));

      Bytes_To_Word (LSB_Byte => Hilscher_cifx0_Inputs(3),
                     MSB_Byte => Hilscher_cifx0_Inputs(2),
                     Word_out => MBTCP_IOServer_Input_Registers(0));

      Bytes_To_Word (LSB_Byte => Hilscher_cifx0_Inputs(4),
                     MSB_Byte => 0,
                     Word_out => MBTCP_IOServer_Input_Registers(2));

   end Map_HMI_Outputs;
...

Il existe une version de l’application en ligne de commande et une avec interface graphique.

L’interface graphique est pour l’instant identique à celle de l’application exemple « app1 » déjà évoquée ici :
A4A : app1 – Interface graphique

Voici une vue montrant l’état général :

A4A-APP2-GUI 00

Celle-ci montre l’état du serveur Modbus TCP sur lequel est connectée l’application de supervision PcVue :

A4A-APP2-GUI 01

La supervision est succincte, on y trouve la mesure et l’état remontés, ce qui suffit à ma démonstration :

A4A-APP2-Syno 00

Cette application où l’on surveille des données process à évolution lente est tout à fait représentative du type d’application que vous pouvez traiter avec « Ada for Automation » sur une machine standard, le débit de la communication sur PROFIBUS PA plafonnant à 45 kbds.

Ainsi votre aire de stockage ou votre dépôt pétrolier sera bien surveillé… 😉

Cordialement,
Stéphane