A4A : Applications et Noyaux

Bonjour,

Le framework « Ada for Automation » est fourni avec un certain nombre d’applications exemples, simple applications consoles ou avec interface graphique en GtkAda, et très bientôt je l’espère avec interface web grâce à Gnoga, un autre framework que je vous avais présenté très brièvement ici, et avec lequel je m’instruis beaucoup tout en m’amusant.

Ainsi par exemple le projet de base A4A est proposé en version console, dont le fichier projet est « a4a.gpr », et en version interface graphique avec « a4a_gui.gpr ».
Cette application est la plus simple possible et joue avec les deux octets d’entrée et sortie à sa disposition, ne mettant en œuvre que des fonctions de rotation et d’affectation.

Ce projet repose sur un noyau, partagé par les deux versions, qui gère le programme utilisateur, un serveur Modbus TCP et des clients Modbus TCP, un pour chaque serveur d’E/S.
La tâche principale collabore avec une tâche périodique annexe via la nouvelle DPM générique dont il a été question précédemment, chacune des tâches disposant d’un programme utilisateur à exécuter à l’état RUN.
Le serveur Modbus TCP comme les clients échangent leurs données avec la tâche principale également via des DPM spécifiques dont le rôle est d’assurer la cohérence des données échangées.

Via le mécanisme d’extension des projets supporté par la suite d’outils fournis par AdaCore, on peut construire des projets héritant des fichiers du projet parent et ajoutant de nouveaux fichiers ou en les substituant s’ils portent le même nom.

C’est ce mécanisme qui est utilisé par exemple avec l’application 1 présentée ici, et encore ici.

Aussi, comme cette application hérite de tout A4A, son dossier source ne contient que ce qui lui est propre, son identification, sa configuration et le programme utilisateur.

Pour cette application 1, ne disposant pas de la partie opérative on a développé une application de simulation, nommée « app1simu ».
Cette application ne nécessitant que la fonction serveur Modbus TCP on a créé un nouveau noyau issu du noyau de A4A auquel on a ôté la gestion des clients Modbus TCP.
Ce noyau logeait dans le dossier de l’application « app1simu » puisqu’il lui était spécifique.

Il en fut de même pour l’application 2 qui met en œuvre une carte Hilscher cifX en lieu et place des clients Modbus TCP.

Et encore de même avec l’application 3 qui remplace les clients Modbus TCP par un maitre Modbus RTU.

Le problème dans cette situation c’est que pour réutiliser un noyau de « app1simu », de « app2 » ou de « app3 », il eu fallut hériter de ces projets ou lister les sources que l’on souhaitait réutiliser, c’est possible, ou pire dupliquer les sources, beurk… Bref, ce n’était pas satisfaisant d’autant que je comptais multiplier les noyaux… On multiplie ce qu’on peut.

J’ai donc réorganisé le framework de sorte que les noyaux se retrouvent dans leurs propres dossiers.
Piloté par une variable externe ou un scénario depuis GPS, le dossier sélectionné est ajouté au projet en cours de traitement.

Ainsi le noyau de « app1simu » est maintenant le « kernel0 », celui de A4A est le « kernel1 », celui de « app3 » est devenu « kernel2″…

C’est beau et ça fonctionne à peu près.
La variable est définie dans un fichier de projet partagé par les autres fichiers de projets, le projet « shared.gpr », et reçoit une valeur initiale par défaut qui est « kernel1 » si elle n’est pas définie par ailleurs.
Attention cependant de laisser GPS terminer ses opérations de mise à jour des références croisées avant de changer de noyau au moyen de la liste déroulante du scénario sous peine de corruption de sa base de données, du moins avec la version Debian.
Ce n’est pas très grave puisqu’il suffit de supprimer celle-ci pour qu’elle soit reconstituée mais c’est un peu pénible.

...
   type Kernel_Type is
      ("kernel0",   --  Modbus TCP Server
       "kernel1",   --  Modbus TCP Server + Modbus TCP IO Scanning
       "kernel2",   --  Modbus TCP Server + Modbus RTU IO Scanning
       "kernel3",   --  Modbus TCP Server + one Hilscher cifX channel
       "kernel4",   --  Modbus TCP Server + Modbus TCP IO Scanning
                    --  + one Hilscher cifX channel
       "kernel5"    --  Modbus TCP Server + Modbus TCP IO Scanning
      );            --  + two Hilscher cifX channels

   Kernel : Kernel_Type := external ("Kernel", "kernel1");
...

Et l’on voit que le noyau de « app2 » est devenu le « kernel3 », que le « kernel4 » fusionne le « kernel1 » et le « kernel3 » et que le « kernel5 » ajoute un canal Hilscher au « kernel4 ».

J’ai bien sûr créé les applications exemples « app5 » qui roule avec le « kernel4 » et « app6 » motorisé par le « kernel5 », le tout en version console ou GUI.

Comme chaque canal Hilscher est susceptible de gérer un bus de terrain différent, classique ou sur Ethernet Temps réel, chaque canal est géré par une tâche indépendante qui exécute son propre programme utilisateur avec une période que l’on peut configurer.
Les tâches « fieldbus » communiquent avec la tâche principale via les DPM génériques déjà évoquées.

Ainsi l’application « app6_gui », mon top model ;-), offre :

  • un serveur Modbus TCP pour y connecter par exemple un SCADA du marché,
  • une fonction IO Scanning avec une tâche client Modbus TCP par serveur d’E/S,
  • deux canaux cifX Hilscher, pour gérer deux cartes, par exemple une PROFIBUS DP Maitre et une EtherCAT Maitre, ou pour gérer une carte à deux canaux comme par exemple un PROFIBUS DP Maitre et un CANopen Maitre,
  • une interface graphique avec GtkAda, permettant de consulter l’état des communications et de l’application et de démarrer ou arrêter les programmes utilisateur,
  • l’intégration du « cifX TCP Server » permettant la configuration et le diagnostic des cartes par SYCON.net via une connexion TCP/IP.

Je vous remercie pour votre attention et vous souhaite une très bonne et heureuse année 2016 pleine de projets, personnels comme professionnels, et de réussite.

N’hésitez pas à nous solliciter.

Cordialement,
Stéphane

A4A : « Dual Port Memory » générique en Ada

Bonjour,

Dans un système constitué de multiples tâches collaborant il est nécessaire de mettre en œuvre une communication garantissant la cohérence des données échangées entre les tâches.

L’un des mécanismes préconisés en Ada pour fournir une solution est fondé sur les types protégés.
On en trouve quelques explications par exemple ici mais le livre de Monsieur John Barnes est certainement plus didactique.

Ce mécanisme est déjà utilisé dans « Ada for Automation » notamment par exemple pour les interfaces entre la tâche principale et les tâches clients Modbus TCP ou entre la tâche principale et la tâche serveur Modbus TCP.

Pour faire court, un type protégé encapsule les données privées et protégées et doit fournir les fonctions et procédures permettant la manipulation des données. Les fonctions permettent uniquement la lecture des données protégées et comme elles ne peuvent modifier celles-ci elles peuvent être appelées de façon concurrente sans problème tandis que les procédures permettent la lecture et l’écriture des données protégées et bloquent le temps de leur exécution les appels des fonctions et procédures concurrentes.

Cependant, il se trouve que le besoin était jusque là considéré comme spécifique et cela a donc conduit à des mises en œuvre spécifiques.
Mais si l’on considère le besoin d’une façon générale on peut aboutir à une solution plus générique qui peut convenir dans une grande majorité des cas d’utilisation.
Ainsi, dans « Ada for Automation », la tâche principale est par défaut associée à une tâche périodique. Ces tâches jusque là tournaient indépendamment l’une de l’autre et j’avais remis aux calendes de les faire communiquer entre elles autrement que par variables partagées, sans gestion de la cohérence donc.

En cette fin d’année les calendes sont donc arrivées !

J’ai nommé le mécanisme « Dual Port Memory » générique par déformation professionnelle, en référence à la fameuse Dual Port Memory Hilscher dont il est souvent question dans les pages de ce site. Et aussi bien sûr parce que la similitude est bien réelle.

Vous voudrez bien vous référer au schéma ci-après pour les explications qui suivent.

Si le mécanisme est générique, les données échangées sont structurées dans des types de données propres à l’application.
Le paquetage implémentant le mécanisme générique est instancié en lui fournissant les types de données Entrées et Sorties et figure dans la partie privée du paquetage « Interface ». Entrée et Sortie sont vues ainsi par l’une des tâches, l’autre voyant bien sûr l’inverse.

Ce paquetage contient également les zones d’échanges pour les programmes utilisateur de chacune des tâches, de façon similaire aux mémoires images process, ces zones étant aussi des instances des mêmes types Entrées et Sorties.
Il contient enfin les fonctions et procédures assurant la mise à jour des zones d’E/S, ces fonctions et procédures étant appelées par les tâches en amont et aval des traitements utilisateur.

A Generic DPM in Ada
A Generic DPM in Ada

Voici la spécification du paquetage générique DPM :

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, 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.     --
-----------------------------------------------------------------------

generic
   type Inputs_Type is private;
   type Outputs_Type is private;

package A4A.Generic_Dual_Port_Memory is

   protected type Inputs_Dual_Port_Memory_Area is

      procedure Set_Data (Data_In : in Inputs_Type);

      function Get_Data return Inputs_Type;

   private
      Data : Inputs_Type;
   end Inputs_Dual_Port_Memory_Area;

   protected type Outputs_Dual_Port_Memory_Area is

      procedure Set_Data (Data_In : in Outputs_Type);

      function Get_Data return Outputs_Type;

   private
      Data : Outputs_Type;
   end Outputs_Dual_Port_Memory_Area;

   type Dual_Port_Memory is
      record
         Inputs : Inputs_Dual_Port_Memory_Area;

         Outputs : Outputs_Dual_Port_Memory_Area;
      end record;

   type Dual_Port_Memory_Access
     is access all Dual_Port_Memory;

end A4A.Generic_Dual_Port_Memory;

Et le corps :

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, 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.     --
-----------------------------------------------------------------------

package body A4A.Generic_Dual_Port_Memory is

   protected body Inputs_Dual_Port_Memory_Area is

      procedure Set_Data  (Data_In : in Inputs_Type) is
      begin
         Data := Data_In;
      end Set_Data;

      function Get_Data return Inputs_Type is
      begin
         return Data;
      end Get_Data;

   end Inputs_Dual_Port_Memory_Area;

   protected body Outputs_Dual_Port_Memory_Area is

      procedure Set_Data  (Data_In : in Outputs_Type) is
      begin
         Data := Data_In;
      end Set_Data;

      function Get_Data return Outputs_Type is
      begin
         return Data;
      end Get_Data;

   end Outputs_Dual_Port_Memory_Area;

end A4A.Generic_Dual_Port_Memory;

Pour l’exemple d’instanciation voici ci-après la spécification du paquetage réalisant celle nécessaire pour l’exemple d’interface entre la tâche principale et la tâche périodique 1.

On y trouve :

  • la définition des types Entrées et Sorties,
  • la définition des zones mémoire correspondantes pour la tâche principale et la tâche périodique 1,
  • les procédures gérant les échanges entre les zones mémoire et les données protégées,
  • et dans la partie privée, l’instance du paquetage générique.

Pour votre application, il est nécessaire d’adapter les types de données Entrées et Sorties comme vous l’avez sans doute compris.

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, 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 A4A.Generic_Dual_Port_Memory; use A4A;

package A4A.Application.Main_Periodic1 is

   --------------------------------------------------------------------
   --  Main / Periodic1 Tasks interface
   --------------------------------------------------------------------

   type Inputs is
      record
         A : Boolean := False;
         B : Word    :=     0;
         C : Byte    :=     0;
      end record;

   type Outputs is
      record
         X : Boolean := False;
         Y : Word    :=     0;
         Z : Byte    :=     0;
      end record;

   --------------------------------------------------------------------
   --  Memory areas available to Main task
   --------------------------------------------------------------------

   Main_Inputs  : Inputs;
   --  These are the Inputs from task Periodic1 to Main

   Main_Outputs : Outputs;
   --  These are the Outputs from task Main to Periodic1

   procedure Get_Main_Inputs;
   --  This procedure updates Main_Inputs from DPM
   --  Main task should call it before calling Main_Cyclic

   procedure Set_Main_Outputs;
   --  This procedure updates DPM from Main_Outputs
   --  Main task should call it after calling Main_Cyclic

   --------------------------------------------------------------------
   --  Memory areas available to Periodic1 task
   --------------------------------------------------------------------

   Periodic1_Inputs  : Outputs;
   --  These are the Inputs from task Main to Periodic1

   Periodic1_Outputs : Inputs;
   --  These are the Outputs from task Periodic1 to Main

   procedure Get_Periodic1_Inputs;
   --  This procedure updates Periodic1_Inputs from DPM
   --  Periodic1 task should call it before calling Periodic1_Run

   procedure Set_Periodic1_Outputs;
   --  This procedure updates DPM from Periodic1_Outputs
   --  Periodic1 task should call it after calling Periodic1_Run

private

   package DPM is new
     A4A.Generic_Dual_Port_Memory
       (Inputs_Type  => Inputs,
        Outputs_Type => Outputs);

   The_DPM : DPM.Dual_Port_Memory;

end A4A.Application.Main_Periodic1;

En voici le corps :

-----------------------------------------------------------------------
--                       Ada for Automation                          --
--                                                                   --
--              Copyright (C) 2012-2015, 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.     --
-----------------------------------------------------------------------

package body A4A.Application.Main_Periodic1 is

   procedure Get_Main_Inputs is
   begin
      Main_Inputs := The_DPM.Inputs.Get_Data;
   end Get_Main_Inputs;

   procedure Set_Main_Outputs is
   begin
      The_DPM.Outputs.Set_Data (Main_Outputs);
   end Set_Main_Outputs;

   procedure Get_Periodic1_Inputs is
   begin
      Periodic1_Inputs := The_DPM.Outputs.Get_Data;
   end Get_Periodic1_Inputs;

   procedure Set_Periodic1_Outputs is
   begin
      The_DPM.Inputs.Set_Data (Periodic1_Outputs);
   end Set_Periodic1_Outputs;

end A4A.Application.Main_Periodic1;

Dans la tâche principale on rajoute les deux incantations autour de l’appel au programme utilisateur :

package body A4A.Kernel.Main is
...
            A4A.Application.Main_Periodic1.Get_Main_Inputs;
            A4A.Application.Main_Cyclic;
            A4A.Application.Main_Periodic1.Set_Main_Outputs;
...

Et on procède de même avec la tâche périodique 1 :

package body A4A.Kernel is
...

   procedure Periodic1_Run is
   begin
      A4A.Application.Main_Periodic1.Get_Periodic1_Inputs;
      A4A.Application.Periodic1_Cyclic;
      A4A.Application.Main_Periodic1.Set_Periodic1_Outputs;
   end Periodic1_Run;

...

Enfin, on peut jouer avec notre interface depuis la fonction principale… :

package body A4A.Application is
...

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

      Map_Inputs;

      Map_HMI_Inputs;

      --  Playing with tasks interface
      Main_Outputs.X := Main_Inputs.A;
      Main_Outputs.Y := Main_Inputs.B;
      Main_Outputs.Z := Main_Inputs.C;

      Process_IO;

      Map_Outputs;

      Map_HMI_Outputs;

   exception

      when Error : others =>
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => Exception_Information (Error));

         Program_Fault_Flag := True;

   end Main_Cyclic;

...

… comme depuis la fonction périodique :

...

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

      --  Playing with tasks interface
      Periodic1_Outputs.A := not Periodic1_Inputs.X;
      Periodic1_Outputs.B := Periodic1_Inputs.Y + 2;
      Periodic1_Outputs.C := Periodic1_Inputs.Z + 1;

   exception

      when Error : others =>
         A4A.Log.Logger.Put (Who  => My_Ident,
                             What => Exception_Information (Error));

         Program_Fault_Flag := True;

   end Periodic1_Cyclic;

...

Il y a également un programme de test qui permet de jouer avec cette DPM que vous trouverez dans le répertoire :

A4A/test/src/test_generic_dpm.adb

Comme ça fonctionne plutôt bien, je l’ai mis à l’épreuve un peu partout et j’ai donc pas mal bougrassé le code A4A en réorganisant les différents noyaux (kernels) afin d’en faciliter la réutilisation et l’évolution et en en rajoutant d’autres, ce dont je vous entretiendrai avec plaisir l’année prochaine.

Je vous remercie pour votre attention et vous souhaite de bonnes fêtes de fin d’année.

Cordialement,
Stéphane

A4A : Ada – Raspberry Pi – netRAPID

Bonjour,

Je continue mes expériences avec le Raspberry Pi qu’on m’a prété et dont il a déjà été question dans l’article mettant en scène le netHOST.

Avec le netHOST, la communication s’établissait via une connexion TCP/IP. Dans cet article, nous allons établir une connexion avec un netRAPID via l’interface SPI disponible sur le connecteur d’extension du Raspberry Pi.

On voit sur la photo suivante à gauche le Raspberry Pi et à droite une carte d’évaluation équipée d’un netRAPID, l’espèce de timbre au centre, le tout connecté via SPI, les cinq fils noirs.

2015-11-27 16.36.15

La famille de « System on Chip » netX qui équipe tous les produits de la société Hilscher s’est étoffée avec les successeurs du netX 50 que sont les netX 51 et netX 52.
Avec le netX 10, ces SoC possèdent entre autres une interface Fast SPI qui permet via un composant matériel dédié, la SPM – pour Serial Port Memory – d’accéder à la désormais fameuse Dual Port Memory via une interface sérielle plutôt que par les traditionnels bus d’adresse et bus de données.

Cela permet d’utiliser comme processeurs hôtes des puces de surface réduite, plus économiques, et conduit à des solutions plus compactes.

Les netX 51 et 52 se distinguent de leur prédécesseur le netX 50 par leur capacité mémoire interne étendue qui permet d’exécuter les piles de protocoles optimisées sans nécessiter de mémoire RAM externe. Le netX 52 dispose de la même architecture interne que le netX 51 à ceci près qu’on lui a ôté le bus mémoire externe afin de réduire son empreinte et son coût.

Ainsi le netX 51 équipe par exemple le comX 51-CA-RE qui permet d’exécuter toutes les piles de protocoles Ethernet Temps Réel en mode esclave avec une fonctionnalité complète.

Les netX 51 et 52 disposent de deux canaux de communication tandis que le netX 10 en possède un seul. Ils sont prévus pour exécuter les piles de protocoles en version esclave uniquement.

On trouve le netX 10 équipant les comX 10 pour l’exécution des piles PROFIBUS DP, CC-Link, DeviceNet et CANopen esclaves.

Les solutions modulaires comX conviennent lorsque l’on recherche une solution de connexion aux bus de terrain interchangeable. Avec leur connecteur Hôte commun à toutes les versions et leur API également commune, il est aisé de les installer en fonction du besoin d’autant qu’ils fournissent de surcroît l’interface au bus avec l’isolation galvanique et la connectique.

Il est possible également de développer des équipements intégrant les SoC netX en tant que coprocesseur de communication lorsque le volume devient important et que les objectifs de coût le nécessitent. Cela demande cependant autrement plus d’effort que pour la mise en oeuvre d’un module comX.

Afin de faciliter la tâche des électroniciens et diminuer le « Time to Market » la société Hilscher a développé le netRAPID, une solution de module à souder directement sur le PCB comme un composant CMS. Il est également possible de le souder à la main lors de la réalisation de prototypes.

Le netRAPID contient tous les éléments actifs, le chip netX lui-même, un netX 10 pour les versions bus de terrain classiques ou un netX 52 pour les versions Ethernet Temps Réel, la Flash nécessaire au stockage du firmware et éventuellement de la configuration, les composants générant les tensions pour le cœur processeur et celles pour le bus de terrain, l’isolation galvanique…

Pour l’interface avec l’hôte, le netRAPID dispose de la classique DPM accessible sur l’interface parallèle ou sur l’interface SPI, celle qui nous intéresse pour cet article.

Et voilà où je voulais en venir !

Du point de vue applicatif, que ce soit une cifX, un comX ou un netRAPID, c’est du pareil au même. L’interface applicative est identique.

Hilscher fournit les pilotes pour tous les systèmes d’exploitation du marché dont Linux. Avec ce pilote, il est possible d’accéder à tous les produits de la gamme, que ce soit au travers d’une interface ISA, PCI, PCIExpress, DPM ou SPI.

Il est également fourni des exemples d’utilisation, notamment pour SPI, dont je me suis plus que largement inspiré pour y connecter avec « Ada for Automation ».

Taaa taaan ! Voilà, c’est dit !
Il est à présent possible de connecter un netRAPID sur un Raspberry Pi via SPI et de développer une application esclave sur un bus de terrain avec « Ada for Automation ».

Sur cette vue on distingue au fond l’IDE GNAT Pro Studio, à droite le terminal d’où on a lancé l’application et la trace résultante et à gauche en bas l’interface graphique de l’application.

Vue globale du bureau
Vue globale du bureau

La vue d’état montrant l’exécution d’un firmware PROFINET IO Device sur le netRAPID.

netRAPID cifX Status
cifX Status

Bon, il est aussi possible de développer en C/C++ hein…

D’un point de vue pratique, la carte d’évaluation du netRAPID dispose de broches de test sur l’interface hôte que j’ai connectées directement avec celles en regard sur le port d’extension du Raspberry Pi. En tout et pour tout cinq petits fils.

Si vous cherchez le brochage du port d’extension du Raspberry Pi ne cherchez plus. Monsieur Christophe BLAESS en fournit ce qu’il faut pour notre besoin du jour :
http://www.blaess.fr/christophe/2012/11/02/spi-sur-raspberry-pi-1/#more-3043

A cela près que mon ami Marco Buffa de Hilscher Italy m’a bien aidé en me procurant les dits fils dont un, le MISO dispose d’une résistance de 120 Ohms car sinon il y a quelques perturbations sur la ligne. Il m’a également donné une carte d’évaluation comX de sa conception que je vous présenterai sans doute prochainement.

Il vous faudra acquérir auprès de Hilscher France le pilote Linux qui est fourni sous forme de code source et le compiler sur le Raspberry Pi avec les bonnes options pour activer les fonctions de lecture et écriture de la SPM.
C’est trivial et je suis là en cas de besoin. Ce pilote n’est à acheter qu’une fois.

Après, hormis les fonctions d’initialisation / dés-initialisation du pilote qui sont différentes, le reste de l’API est identique et l’on se sert du netRAPID comme d’un comX ou d’une cifX.

Pour l’initialisation du pilote, il suffit de fournir le « device » qui va bien (i.e. « /dev/devspi0.0 » dans mon cas puisque j’ai connecté le Chip Select 0), et de spécifier la vitesse de transmission (le netX supporte jusqu’à 33 MHz en mode esclave, j’ai testé avec 16 MHz).

On aura au préalable activé la liaison SPI par exemple avec raspi-config dans les « Advanced Options ».

Et ça sert à quoi ?

On peut imaginer qu’en phase de prototypage cela permette de gagner du temps en disposant d’une cible plus ou moins proche de celle que l’on souhaite mettre en œuvre.

Ainsi, que vous projetiez de réaliser un module d’entrées / sorties, un codeur, un variateur, un appareil de mesure quelconque devant disposer d’une connectivité bus de terrain, vous pouvez commencer votre développement sur le Raspberry Pi.

Pourquoi pas une machine pilotée par un Raspberry Pi, avec une interface web et discutant avec un automate de ligne ?

Incrédules ? J’espère bien vous surprendre bientôt… 😉

Cordialement,
Stéphane

A4A : Mangez du booléen en Ada !

Bonjour,

Les booléens, c’est un peu la matière première de l’automaticien.

Que ce soit pour les capteurs, vanne ouverte ou fermée, pompe en marche ou à l’arrêt, porte en position haute, en position basse… comme pour les actionneurs dont les commandes sont de type tout ou rien ouvrir, fermer, chauffer, etc… une variable prenant une valeur vraie ou fausse convient pour la représentation de ces états et commandes.

Les automates programmables disposent d’instructions spécialisées pour traiter la logique combinatoire dans des langages adaptés.

Quand on prône l’usage de Ada pour cette même tâche d’automatisation on peut s’intéresser à ce que propose ce langage en la matière.

Le langage dispose du type booléen et on déclare ci-dessous trois variables de ce type avec une valeur initiale :

  -- deux variables représentant l'état de deux vannes
  Valve1_Is_Open   : Boolean := True;
  Valve2_Is_Closed : Boolean := False;

  -- une variable pilotant la commande d'une pompe
  Pump1_On : Boolean := False;

Un peu plus loin, dans le code d’une fonction, on peut écrire une équation booléenne comme celle-ci en utilisant des opérateurs :

  -- commande de la pompe
  Pump1_On := Valve1_Is_Open and Valve2_Is_Closed;

  -- on pourrait également utiliser les autres opérateurs
  -- logiques comme "not", "or" et "xor"
  Pump2_On := not Valve1_Is_Open or Valve2_Is_Closed;

  Pump3_On := Valve1_Is_Open xor Valve2_Is_Closed;

On peut bien sûr y combiner avec les opérateurs relationnels comme ici :

  Pump4_On := Valve1_Is_Open and Counter > 5;

On retrouve donc bien naturellement les mêmes opérateurs que dans le « Ladder » ou le « Structured Text ».

Je vous propose l’exemple suivant que vous pourrez compiler et exécuter, éventuellement en pas à pas avec le débogueur, et qui vous éditera la table de vérité de chacun des opérateurs évoqués ci-dessus.

Pour comprendre cet exemple, il faut savoir que le type Booléen est une énumération qui peut prendre les valeurs (False, True) dans l’intervalle donc False .. True.

Ainsi, for A in Boolean’Range loop est équivalent à for A in False .. True loop.

Il y aurait bien d’autres choses à dire sur ce bout de programme mais il n’est là que pour illustrer le propos et montrer un peu d’Ada à l’œuvre en espérant aiguiser votre curiosité.

with Ada.Text_IO; use Ada.Text_IO;

procedure Tutorial_00 is

   Valve1_Is_Open   : Boolean := True;
   Valve2_Is_Closed : Boolean := False;
   
   Pump1_On         : Boolean := False;
   Pump2_On         : Boolean := False;
   Pump3_On         : Boolean := False;

   Pump4_On         : Boolean := False;

   Counter : Integer := 10;
   
begin
   
   Pump1_On := Valve1_Is_Open and Valve2_Is_Closed;
   Pump2_On := not Valve1_Is_Open or Valve2_Is_Closed;
   Pump3_On := Valve1_Is_Open xor Valve2_Is_Closed;
   
   Pump4_On := Valve1_Is_Open and Counter > 5;
   
   for A in False .. True loop
      Put_Line ("not " & A'Img & " = " & Boolean'Image (not A));
      delay 1.0;
   end loop;
   New_Line;

   for A in Boolean'Range loop
      for B in Boolean'Range loop
         Put_Line (A'Img & " and " & B'Img & " = " & Boolean'Image (A and B));
         delay 1.0;
      end loop;
   end loop;
   New_Line;

   for A in Boolean'Range loop
      for B in Boolean'Range loop
         Put_Line (A'Img & " or " & B'Img & " = " & Boolean'Image (A or B));
         delay 1.0;
      end loop;
   end loop;
   New_Line;

   for A in Boolean'Range loop
      for B in Boolean'Range loop
         Put_Line (A'Img & " xor " & B'Img & " = " & Boolean'Image (A xor B));
         delay 1.0;
      end loop;
   end loop;
   New_Line;

   Put_Line ("Tutorial_00 finished !");
end Tutorial_00;

Chez moi ça donne ceci :

/home/slos/Ada/A4A/tutorial/exe/tutorial_00
not FALSE = TRUE
not TRUE = FALSE

FALSE and FALSE = FALSE
FALSE and TRUE = FALSE
TRUE and FALSE = FALSE
TRUE and TRUE = TRUE

FALSE or FALSE = FALSE
FALSE or TRUE = TRUE
TRUE or FALSE = TRUE
TRUE or TRUE = TRUE

FALSE xor FALSE = FALSE
FALSE xor TRUE = TRUE
TRUE xor FALSE = TRUE
TRUE xor TRUE = FALSE

Tutorial_00 finished !
[2015-11-16 23:41:39] process terminated successfully (elapsed time: 14.09s)

Pour plus d’information, je vous invite à visiter :
https://en.wikibooks.org/wiki/Ada_Programming/Operators

Cordialement,
Stéphane

A4A : Ada – Raspberry Pi – netHOST

Bonjour,

Je poursuis mes expérimentations en Ada sur le Raspberry Pi que j’évoquais dans l’article précédent.

A ce propos, on m’a très justement fait remarquer que le magazine MagPi comportait deux articles d’introduction à Ada sur cette mini machine :
https://www.raspberrypi.org/magpi/issues/6/
https://www.raspberrypi.org/magpi/issues/8/

C’est, of course, en Anglais… et il y a un peu de tout là-dedans, le matériel, le logiciel, Ada mais également le C, le Python…
C’est succinct, c’est un magazine, pas un cours.

Bon, j’ai eu tôt fait de faire fonctionner ce qui tourne autour de libmodbus, la scrutation des E/S en Modbus TCP ou RTU et le serveur Modbus TCP, et l’interface graphique en GtkAda. C’est tout tombé en marche à peine installé.

J’ai reçu dernièrement en cadeau deux netHOST de chez Hilscher et j’ai tout de suite ressenti un besoin irrépressible d’y faire fonctionner avec le Raspberry Pi.

La famille netHOST fournit un maître bus de terrain, classique comme PROFIBUS DP, CANopen ou DeviceNet, ou sur Ethernet Temps Réel comme EtherCAT, PROFINET ou Ethernet/IP, que l’on peut piloter via une interface Ethernet TCP/IP standard.

On peut donc utiliser ces maîtres déportés depuis un PC compact d’entrée de gamme sans slot PCI ou PCI Express, un portable pour faire des démonstrations ou des tests, ou un Raspberry Pi pour équiper une classe…
Par exemple, la partie opérative est connectée au netHOST qui est maître du réseau de capteurs et actionneurs.
Chaque binôme dispose d’un Raspberry Pi et peut développer son application « Ada for Automation ». A tour de rôle, chaque application accède à la partie opérative et Monsieur le Professeur compte les points.

On peut télécharger le DVD contenant la documentation et l’outil de configuration SYCON.net ici :
http://www.hilscher.com/fileadmin/big_data/en-US/Resources/zip/netHOST_Solutions_DVD_2015-07-1_V1_370_150722_15176.zip

Côté application, l’interface est identique à celle des cartes Hilscher cifX dont il est déjà abondamment question dans les pages de ce site, et il est donc relativement trivial d’utiliser un netHOST en lieu et place d’une cifX.

Bien sûr, la performance est légèrement moindre qu’avec une carte cifX, puisque l’on passe par la connexion Ethernet, et l’on mesure un temps de réponse de l’ordre de la milliseconde du netHOST. Sur une machine raisonnablement puissante, une scrutation à 5 à 10 millisecondes est tout à fait possible tandis que sur le Raspberry Pi on ne sera pas mal (entendre pas à 100% CPU) aux alentours de 20 à 30 ms.

C’est dans l’air du temps, le DVD est déjà obsolète et vous trouverez la dernière version de la bibliothèque netXTransport qui réalise l’interface cifX – TCP/IP dans la base de connaissance Hilscher :
https://kb.hilscher.com/display/CIFXDRV/netX+Diagnostic+and+Remote+Access

La bibliothèque est fournie sous forme de DLL pour Windows(R) et sous forme de code source pour Linux et autres systèmes d’exploitation.

Contrairement à une passerelle netTAP qui limite les échanges entre deux protocoles aux données cycliques en standard, le netHOST offre toute la fonctionnalité maître que proposent les cartes cifX, données cycliques d’une part, messagerie acyclique pour la lecture et l’écriture de paramètres, la configuration et le diagnostic d’autre part.

Sur le Raspberry Pi je n’ai eu qu’à installer les « autotools » et « libtools » qui sont nécessaires à la compilation de la bibliothèque elle-même, enlever du « Makefile.am » l’option de compilation (-m32) qui n’est pas nécessaire ici et lancer les incantations habituelles « ./configure », « make », « sudo su » et « make install ».

Pour « Ada for Automation » c’est un poil plus embêtant : comme j’ai intégré le côté « Device » de netXTransport, le fameux « cifX TCP Server », et que celui-ci contient des fichiers d’en-tête qui font partie du pilote Linux pour les cifX et que celui-ci n’est fourni que sous forme de code source et pas gratuitement…
Il est bien sûr tout à fait possible de se passer de ce « cifX TCP Server », (puisque le netHOST contient déjà ce même composant permettant la configuration et le diagnostic avec SYCON.net via TCP/IP), en retravaillant un peu le code Ada… C’est laissé à titre d’exercice.

Bref, c’est aussi tombé en marche assez rapidement.

Sous Windows(R), rien de plus à acheter. La DLL est fournie déjà compilée en 32 et 64 bits et les en-têtes sont disponibles sur le DVD.
Quant à « Ada for Automation », ça tourne déjà depuis un moment :
Souvenirs, souvenirs

Hey ! C’est mon 100ème article ! 🙂

Cordialement,
Stéphane