Etude d’un passage à niveau multivoies

. Par : Dominique, JPClaude. URL : https://www.locoduino.org/spip.php?article145

Dominique a soulevé il y a quelques temps la question de la gestion d’un passage à niveau avec plusieurs voies, dont une en Y. Nous avons réfléchi depuis à généraliser le problème à des trajets multiples, parallèles, convergents ou divergents arrivants dans la zone du passage à niveau. Le problème consiste à limiter le nombre de capteurs, à déterminer le moment ou la zone est libre (ou inversement occupée) et en fonction de l’état de la zone d’activer des éléments (ouverture/fermeture de barrières, allumer ou éteindre des feux de route, etc.). Nous vous proposons donc dans cet article notre solution qui admet tout type de trajet traversant cette zone. Après une présentation du but du jeu et l’algorithme envisagé, nous faisons une rapide présentation d’un capteur optique (le TCRT5000L) et du capteur ponctuel basé sur les relais ILS, puis du capteur à effet Hall. Nous terminons par la présentation du programme ainsi qu’une méthode pour le configurer.
De plus, nous avons écrit l’essentiel des algorithmes dans une bibliothèque que vous pouvez inclure dans votre IDE Arduino. Cette bibliothèque est d’abord un exemple de "comment faire une bibliothèque" (voir aussi Monter une bibliothèque !), avant d’être une solution générale pour tous les passages à niveau !

Le but du jeu

L’objectif est de contrôler un passage sensible, comme un passage à niveau, pouvant être parcouru par plusieurs trajets dans les deux directions. Chaque trajet emprunte une zone à l’intérieur du passage, chaque voie arrivant sur le passage possède un capteur. Ici nous avons par exemple une voie avec le capteur C1 et le capteur C4 qui emprunte la zone1 et une voie avec le capteur C2 et C5 qui emprunte la zone2, de même pour C3 et C6 avec la zone3. Le passage n’est considéré comme libre que si bien évidemment l’ensemble des zones le sont. Cela permettra d’ouvrir les barrières en toute sécurité. Dans les autres cas, la barrière devra être fermée.

Figure 1. Exemple de passages parallèles
Figure 1. Exemple de passages parallèles

Mais également avec des trajets convergents (ou divergents).

Figure 2. Exemple de passage de trajets convergents et divergents
Figure 2. Exemple de passage de trajets convergents et divergents

Ici les choses se compliquent un peu car nous pouvons de C1 aller vers C4 ou C5 en empruntant la zone1. Il est à noter qu’il faut un cantonnement, on voit par exemple que l’on peut aller de C4 à C5 en empruntant la zone1. Théoriquement ce cas ne devrait pas arriver car une telle situation signifie qu’un train arrive sur le passage depuis C4 et un autre depuis C5, il va y avoir du grabuge au niveau du passage !!!!

On remarque surtout qu’il faut utiliser des capteurs ponctuels qui encadrent la zone protégée, de façon a connaitre l’entrée et la sortie des convois et en déduire les sens de circulation surtout dans les cas de trajets convergents et divergents. Un simple capteur de consommation ne suffit pas pour différencier les trajets. Par contre, un rebroussement de chemin pour passer de C4 à C5 en s’arrêtant sur la zone 1 serait possible avec ce type de capteur, mais notre programme n’en tient pas compte car le changement de sens n’est pas permis à l’intérieur du passage, mais possible seulement à l’extérieur, sans que cela réduise la richesse des manoeuvres possibles.

La situation peut se résumer de la manière suivante :
Un passage ne peut être libre que si toutes ses zones le sont.
Une zone ne peut être occupée que par un seul trajet (un couple de capteurs appartenant à cette zone). C’est ce que l’on appelle une mutuelle exclusion.
Chaque capteur est associé à une zone
Une zone peut être associée à plusieurs capteurs. Elle possède plusieurs états :

  • LIBRE : personne ne l’utilise
  • ENTREE : passage de LIBRE à OCCUPEE avec un capteur entrant
  • SORTIE : passage d’OCCUPEE à LIBRE avec un capteur sortant

L’algorithme est le suivant :

On boucle sur tous les capteurs
Si un capteur est activé
        Si sa zone est LIBRE, c’est une entrée
                On passe sa zone en ENTREE
                On la note OCCUPEE
                On note le numéro du capteur entrant
        Si sa zone est en ENTREE et le capteur est différent de celui entrant, c’est une sortie
                On passe sa zone en SORTIE
                On note le numéro du capteur sortant
	  Si sa zone est en SORTIE
		    On reste en zone SORTIE
		    On note le numéro du capteur sortant
Si le capteur est inactif (pendant le temps que le convoi libère la zone)
        Si sa zone est dans l’état SORTIE et si le capteur est le même que celui sortant
                Sa zone est LIBRE
                On efface les capteurs entrant et sortant de la zone

On termine en vérifiant l’état de l’ensemble des zones du passage
        Si elles sont toutes LIBRES on gère la libération du passage
        Sinon on reste en mode occupé

Les Capteurs

Les capteurs optiques

L’avantage des capteurs optiques est d’être actif tant qu’il y a une réflexion, ce qui facilite la détection de trains de tailles différentes car le capteur ne redeviendra inactif qu’à la fin du convoi. L’inconvénient est qu’il ne faut pas les mettre en présence d’une lumière trop forte.
Utilisation du capteur TCRT5000L :
Ce capteur peut s’activer sur une distance de 2,5 cm ce qui est amplement suffisant pour nos applications. La longueur d’onde est de 950nm et le courant de sortie est de 1mA.

Figure 3. Un capteur optique à réflexion IR
Figure 3. Un capteur optique à réflexion IR

Il se connecte de la manière suivante :

Figure 4. Branchement d'un TCRT5000 sur Arduino
Figure 4. Branchement d’un TCRT5000 sur Arduino

Et il s’intègre parfaitement entre les traverses des rails en HO :

Figure 5. Placement d'un TCRT5000 sous une voie HO
Figure 5. Placement d’un TCRT5000 sous une voie HO

Les interrupteurs à lame souple (ILS)

Ces relais, aussi appelés "REED", sont très discrets et peuvent se coller n’importe où entre les traverses, par contre il ne détecte que le début du train (aimant sous la locomotive). Il faut donc calculer un certain temps avant de désactiver son action (la durée de passage de la plus longue rame).
L’utilisation avec des relais Reed est plutôt réservée à l’échelle HO :

Figure 6. Une ampoule de relai Reed
Figure 6. Une ampoule de relai Reed

Les capteurs à effet Hall

Pour l’échelle N on préférera les capteurs à effet Hall, dont la taille est à peu près celle d’un transistor, plus plat, qui peut facilement se coller sur une traverse de voie à l’échelle N donc des plus discrets ! Ils nécessitent aussi la présence d’un aimant sous une locomotive.

Figure 7. Un capteur à effet Hall et son branchement
Figure 7. Un capteur à effet Hall et son branchement

Autres capteurs ?

Des capteurs de consommation seraient possibles comme ceux utilisés dans la détection de présence des convois dans chaque canton ou dans des zones particulières. Ils nécessitent des coupures de rails pour isoler les zones de détection. Ce ne sont pas ceux qui sont utilisés dans ce projet, mais chacun pourra faire l’adaptation.
Attention : il faut bien déterminer l’état HIGH (5V) ou LOW (GND) du capteur lors d’un passage du train pour déclarer cet état dans la fonction initSensor que nous verrons plus loin.

Le programme

Le programme commence par l’inclusion de la bibliothèque zoneMultiPath.h

#include <zoneMultiPath.h>

Cette bibliothèque définit une classe "zoneMultiPath" qui peut être instanciée soit avec les méthodes d’action par défaut du programme, soit avec vos propres méthodes d’action que vous pourrez écrire selon vos besoins.

Figure 8. Ajouter la bibliothèque
Figure 8. Ajouter la bibliothèque

Ce programme exemple montre comment utiliser les méthodes de cette classe.

Pour que le programme se compile correctement, il faut évidemment intégrer la bibliothèque dans votre IDE Arduino : après téléchargement de la bibliothèque (à la fin de cet article), utilisez le menu Croquis/Inclure une Bibliothèque/ajouter une Bibliothèque.
Vous pouvez vous reporter à l’article Installer une bibliothèque pour plus de détails.

Ensuite il faut créer une instance de la classe zoneMultiPath :
zoneMultiPath passage; // sans argument, ici, donc à utiliser avec les méthodes d'action par défaut du programme

Le programme reprend l’algorithme présenté en début d’article. Il est construit de manière à être facilement configurable. En effet toute la première partie du programme concentre les parties configurables. La première partie consiste en choix d’options (à enlever si ce n’est pas nécessaire) et la seconde en la configuration proprement dite.

Méthode de configuration du programme

Option CONSOLE

initConsole : méthode pour affichage du déroulement sur la console (le moniteur série de l’IDE), paramètre le débit (par défaut 9600 bauds). On peut définir la vitesse de transmission.
Exemple :
passage.initConsole(115200);

Option CONTROLE

initControle : méthode qui permet d’avoir deux leds (verte = libre / rouge = occupé) et un bouton pour le reset, qui peuvent être utile pour un TCO. Les deux leds garantissent de visualiser l’état du passage, sans ambiguïté. Le bouton de reset n’est pas un reset de l’Arduino, mais simplement une remise des paramètres et des états du passage par défaut.

Exemple :

#define LED_LIBRE 12                  
#define LED_OCCUPE 13                 
#define BOUTON_RESET A0
passage.initControl(LED_LIBRE,LED_OCCUPE,BOUTON_RESET);   

Option COMMANDE par défaut

initCommand : méthode qui permet de définir une sortie pour des leds, une sortie en cas de passage libre et une sortie en cas de passage occupé. Utile pour commander des accessoires externes (feux de route, barrières).
Dans le cas d’un passage libre le programme désactive la sortie LED (état LOW), active la sortie libre (état HIGH), désactive la sortie occupée (état LOW).
Dans le cas d’un passage occupé le programme active la sortie LED (état HIGH), désactive la sortie libre (état LOW), active la sortie occupée (état HIGH).

Exemple :

#define COMMANDE_LED 11               
#define COMMANDE_ZONE_LIBRE 9         
#define COMMANDE_ZONE_OCCUPEE 10  
passage.initCommand(COMMANDE_LED,COMMANDE_ZONE_LIBRE,COMMANDE_ZONE_OCCUPEE);

Option COMMANDE personnel

On peut écrire ses propres méthodes pour traiter les phases de libération et d’occupation. Cela remplace la méthode initCommand. Il faut dans ce cas instancier la classe zoneMultiPath avec le nom de deux fonctions, la première pour traiter le cas passage libre et la seconde dans le cas d’un passage occupé. Il faut bien sûr après écrire ces fonctions dans son programme.

Exemple :

zoneMultiPath passage (actionFree,actionBusy)

Les déclarations des zones

Il faut donner le nombre de zones parcourant le passage puis énumérer les index de chaque zone.

#define NOMBRE_ZONES 2              
#define INDEX_ZONE1    0                     
#define INDEX_ZONE2    1 

Les déclarations des capteurs

De même ici il faut donner le nombre de capteurs puis les énumérer.

#define NOMBRE_CAPTEURS  6        
#define VOIE1 "voie1"             
#define CAPTEUR_VOIE_1    2       
#define VOIE2 "voie2"
#define CAPTEUR_VOIE_2    3 
#define VOIE3 "voie3"          
#define CAPTEUR_VOIE_3    4
#define VOIE4 "voie4"           
#define CAPTEUR_VOIE_4    6 
#define VOIE5 "voie5"          
#define CAPTEUR_VOIE_5    7
#define VOIE6 "voie6"           
#define CAPTEUR_VOIE_6    8

La déclaration du temps d’attente

Ce paramètre sert à faire une pause entre la fin d’une occupation et la libération du passage. Pour des capteurs optiques le temps peut être assez court puisque le capteur se libère en fin de convoi, à condition que les espaces entre les wagon ne déclenchent pas une fausse fin de convoi. Pour des capteurs ponctuels, comme des ILS et capteurs à effet Hall, le temps doit être assez grand pour permettre à la fin de convoi de la plus grande rame de sortir du passage. Par défaut la valeur est de 1s (1000 ms, donc à augmenter en général).
#define ATTENTE 1000

La méthode setup

C’est ici que l’on initialise toute la configuration.

void setup() {
  passage.initConsole();                                  // optionnel
  passage.initControl(LED_LIBRE,LED_OCCUPE,BOUTON_RESET); // optionnel
  passage.initCommand(COMMANDE_LED,COMMANDE_ZONE_LIBRE,COMMANDE_ZONE_OCCUPEE);// optionnel
  passage.initRailroadCrossing(NOMBRE_CAPTEURS,NOMBRE_ZONES,ATTENTE);// obligatoire, ATTENTE par defaut = 1000ms
  passage.initSensor(CAPTEUR_VOIE_1,INDEX_ZONE1,VOIE1); // pour chaque voie: son capteur, sa zone et son nom (facultatif)
  passage.initSensor(CAPTEUR_VOIE_2,LOW,INDEX_ZONE1,VOIE2);    
  passage.initSensor(CAPTEUR_VOIE_3,LOW,INDEX_ZONE2,VOIE3);   
  passage.initSensor(CAPTEUR_VOIE_4,LOW,INDEX_ZONE2,VOIE4);
  passage.initSensor(CAPTEUR_VOIE_5,LOW,INDEX_ZONE2,VOIE5);  
  passage.initSensor(CAPTEUR_VOIE_6,LOW,INDEX_ZONE2,VOIE6);
}

Remarque : ici nous utilisons les capteurs dont l’état actif est LOW (0V), contrairement aux exemples de capteurs donnés en début d’article, pour lesquel il faudra indiquer HIGH (voir plus loin à propos de la bibliothèque).

La boucle

void loop() {passage.Loop();}

Les méthodes optionnelles

Dans ce cas, il faut utiliser l’instanciation zoneMultiPath passage (actionFree,actionBusy).
Le premier argument permet de définir vos propres actions en cas de passage libre.

void actionFree() {
        // Mettre ici les actions à faire dans le cas d'un passage libre
        Serial.println(" on utilise la fonction actionFreeZone du programme");
}

Le second argument permet de définir vos propres actions en cas de passage occupé.

void actionBusy() {
      // Mettre ici les actions à faire dans le cas d'un passage occupé
     Serial.println(" on utilise la fonction actionBusyZone du programme");
 }

Le programme ino et la bibliothèque

Le programme complet et la bibliothèque sont téléchargeables en bas de l’article. D’ailleurs, quand la bibliothèque est importée dans l’IDE, il est facile de trouver ce programme dans le menu Exemples/zoneMultiPath.

Un exemple

Voici la topologie de l’exemple, qui correspond à celui du programme téléchargeable. Il ne prend que 15% de mémoire de stockage et 400 octets de mémoire dynamique.

Figure 9. Le passage à niveau exemple dans cet article
Figure 9. Le passage à niveau exemple dans cet article

et le banc de test sur plaque d’essai devant l’Arduino Mini, supportant les 6 capteurs TCRT5000 et les 2 diodes. Le bouton reset est emprunté à une carte de 8 boutons poussoirs (provenant d’un autre projet) dont un seul est utilisé.

Figure 10. le prototype de cette réalisation
Figure 10. le prototype de cette réalisation

avec une carte prototype pour Nano que l’on trouve sur les boutiques en ligne avec une recherche de type "Prototype Adapter Expansion Board Shield Sensor Interface For Arduino NANO Card".

Figure 11 : une carte Nano pour expérimentation
Figure 11 : une carte Nano pour expérimentation

Tester et adapter le programme et la bibliothèque est simple :

  • Relier un fil branché sur OV (Gnd) d’un coté avec les pins configurées pour les capteurs (ci-dessous).
  • Lire le compte-rendu sur la console de l’IDE.

Et voici les ingrédients de cet exemple et quelques scenarii de passage de trains :

Configuration de la zone : nombre de capteurs : 6

capteur 1 voie1 zone1 : broches = 2 , etat : LIBRE
capteur 2 voie2 zone1 : broches = 3 , etat : LIBRE
capteur 3 voie3 zone2 : broches = 4 , etat : LIBRE
capteur 4 voie4 zone2 : broches = 6 , etat : LIBRE
capteur 5 voie5 zone2 : broches = 7 , etat : LIBRE
capteur 6 voie6 zone2 : broches = 8 , etat : LIBRE

Etat initial : On prendra comme hypothèse que le passage est libre (tous les capteurs sont libres). Cela dépend évidemment du placement du passage dans votre réseau. Pour moi c’est le cas le plus général. Sinon il vous faudra adapter un peu ce programme selon votre besoin. Cet état initial est restauré en appuyant sur le bouton de Reset prévu pour cela.

1) entrée depuis la voie 6

  • le capteur C6 est activé
  • -> le passage est occupé (dans la zone 2)

2) Sortie vers la voie3

  • le capteur C3 est occupé, puis libre
  • -> le passage est libre

3) entrée depuis la voie 1

  • le capteur C1 est activé
  • -> le passage est occupé (dans la zone 1)

4) entrée depuis la voie 5

  • le capteur C5 est activé
  • Sortie vers la voie 3
  • la zone 2 est libre mais pas la zone 1
  • Sortie vers la voie 2
  • le capteur C2 libère la zone 1, toutes les zones sont libres
  • -> le passage est libre

5) entrée depuis la voie 4

  • le passage est occupé (la zone 2 est occupée)
  • entrée depuis la voie 2 (la zone 1 est occupée)
  • sortie vers la voie 6 (la zone 2 est libérée, pas la zone 1)
  • sortie vers la voie 1 (la zone 1 est libérée)
  • ->le passage est libre

A propos de la bibliothèque zoneMultiPath

Le fichier zoneMultiPath.h décrit l’objet zoneMultiPath, en particulier la structure Tsensor :

        
        struct Tsensor {
            char* name;
            int pin;
            bool activeLowHigh = HIGH;
            int zoneNumber;
            int predState;
            };

Remarquez que le booléen activeLowHigh sert à choisir l’état de détection : HIGH (+5V) ou LOW (0V) : En général il est plus pratique et plus sûr d’utiliser des détecteurs dont l’état actif est LOW : appliqué sur une entrée de l’Arduino initialisé en mode INPUT_PULLUP évite les détections parasites (les entrées de l’Arduino en mode INPUT peuvent se comporter comme des antennes et ramasser tous les parasites environnants).

La fonction void initSensor (int Pin, bool Active, int Zone,char* Name = "");
comporte un paramètre Active qui permet de préciser si les capteurs utilisés sont actifs LOW ou HIGH.

La fonction loop() contient l’algorithme du passage à niveau : vous pouvez le modifier dans le fichier zoneMultiPath.cpp :

void zoneMultiPath::Loop() {
        if (control) { testResetButton();}
          boolean busy = false;
          byte sensorStatus;
          for (int i =0; i<sensorsNB;i++) {
          sensorStatus = digitalRead(sensor[i].pin);
          if (sensorStatus != sensor[i].predState ){              // si le capteur a changer d'etat
          sensor[i].predState = sensorStatus;                 // on conserve le nouvel etat
          if (sensorStatus == sensor[i].activeLowHigh) {                          // si le capteur est actif
              if ((busyZone[sensor[i].zoneNumber].busy) &             // on passe de ENTREE -> SORTIE
                  (busyZone[sensor[i].zoneNumber].state == ENTRY)
                  & (busyZone[sensor[i].zoneNumber].entry_sensor_number != i)) { // capteur de sortie != capteur entree
                      if (console){
                       Serial.print(" exit to path "); Serial.println(sensor[i].name);
                      }
                       busyZone[sensor[i].zoneNumber].state = EXIT;      // on passe en zone SORTIE
                       busyZone[sensor[i].zoneNumber].exit_sensor_number = i;  // on garde le numero de capteur de sortie
                  }
              else {if (busyZone[sensor[i].zoneNumber].state == FREE) {   // on passe de LIBRE a ENTREE
                      if (console) {
                        Serial.print(" enter from path "); Serial.println(sensor[i].name);
                      }
                      busyZone[sensor[i].zoneNumber].state = ENTRY;      // on passe en zone ENTREE
                      busyZone[sensor[i].zoneNumber].busy = true;     // la zone est occupee
                      busyZone[sensor[i].zoneNumber].entry_sensor_number = i;  // on garde le numero du capteur d'entree
                      }
                    else {if (busyZone[sensor[i].zoneNumber].state == EXIT){  // on reste en zone SORTIE mais on  change de capteur de sortie
                             if(console) {
                              Serial.print(" exit to path "); Serial.println(sensor[i].name);
                             }
                             busyZone[sensor[i].zoneNumber].exit_sensor_number = i;  // on garde le numero de capteur de sortie
                           }
                        }
                   }
             }
     else { if ((busyZone[sensor[i].zoneNumber].state == EXIT)&
                (busyZone[sensor[i].zoneNumber].exit_sensor_number == i)) {
                    busyZone[sensor[i].zoneNumber].state = FREE;
                    busyZone[sensor[i].zoneNumber].busy = false;
                    busyZone[sensor[i].zoneNumber].entry_sensor_number = -1;
                    busyZone[sensor[i].zoneNumber].exit_sensor_number = -1;}
                  }
     }
     }
  if (checkBusyZone() == 0 ) {freeZoneManagement();}else {busyZoneManagement();}
}

Conclusion

Nous avons abordé d’un point de vue algorithmique le problème de partage d’une ressource par plusieurs demandeurs et résolu le problème de gestion d’un passage critique sur un réseau pouvant être parcouru par un ensemble quelconque de trajets en minimisant le nombre de capteurs.

Le programme complet se trouve dans la bibliothèque ci-dessous. Pour l’ouvrir, installez la bibliothèque (après téléchargement) puis ouvrez le menu Exemples/zoneMultiPath/zone_multi_trajet

Voici la bibliothèque :

Bibliothèque zoneMultiPath