LOCODUINO

Un automatisme de Passage à Niveau

Avec un simple Pro Mini

.
Par : Dominique, JPClaude

DIFFICULTÉ :

Un éminent membre de mon club butait depuis un certain temps sur un automatisme de passage à niveau à partir d’une logique à relais.

Il faut dire que ce passage supporte une double voie, dont une branche est en "Y". Le cahier des charges impose de ne lever les barrières que lorsque toute la zone est libre de trains.

J’ai décidé de relever le défi avec un Arduino et d’ajouter un ultime raffinement : le clignotement réaliste des feux rouges (coté route) à leds.

Voici les détails de la réalisation que vous pourrez certainement adapter à votre réseau.

Le cahier des charges

Le passage à niveau doit protéger une double voie, dont l’une des branches est en "Y". Il y a donc 5 sections de voies de part et d’autre du passage à niveau.

Sur chaque branche, sont disposés 2 détecteurs de type interrupteur à lame souple, "ILS".
Il y en a donc 10 au total.

Le plus proche du passage à niveau doit déclencher la fermeture des barrières lorsque le train va vers le passage.
Le plus éloigné du passage à niveau doit déclencher l’ouverture des barrières lorsque le train s’éloigne du passage. Attention toutefois, les barrières ne doivent pas s’ouvrir tant qu’il reste un autre train dans la zone comprise entre tous les détecteurs éloignés.

Tant que les barrières ne sont pas complètement ouvertes, les feux rouges de chaque coté, sur la route doivent clignoter. Comme dans la réalité, ce sont des lampes à incandescence qui s’allume et s’éteignent avec une certaine inertie, on reproduira cette inertie sur des Leds rouges, gräce aux commandes analogiques.

Enfin, l’ouverture et la fermeture des barrières est opéré par un moteur Fulgurex livré avec le passage à niveau. Ce moteur devra donc être alimenté dans un sens ou dans l’autre pendant 4 à 5 secondes environ. Les feux restent donc en service pendant cette manoeuvre.

Le matériel nécessaire

Compte tenu de ce cahier des charges, un Arduino Mini suffira. L’absence de port USB est compensée par le fait que je dispose d’interface "Bob" d’Elektor qui se raccordent facilement sur le Mini dont les pins sont sur un des petits cotés. On trouve également sur le web des interfaces USB-serie pour presque rien. Dernièrement j’ai même trouvé un Arduino Mini avec l’interface USB-série séparée, le tout pour 6€ environ (avec le chip original FT232 de FTDI).

Attention : il faut bien relier la sortie TxD de l’interface USB avec l’entrée RxD du Mini, ainsi que la sortie TxD du Mini avec l’entrée RxD de l’interface USB. Ensuite on relie le Vcc au Vcc, le Gnd au Gnd et la sortie DTR de l’interface USB à la patte marquée GRN sur le Mini (c’est ce qui permet à l’IDE Arduino de passer le Mini en mode programmation).

Arduino Pro-Mini

Les ILS sont raccordés directement sur 10 pins de l’Arduino, les communs des ILS étant regroupés sur le 0V.

Les feux sont réalisés avec 2 Leds rouges en série, sur une pin analogique (à sortie en PWM) du Mini.

Les commandes du moteur sont faites avec une carte à 2 relais qui se pilote par 2 sorties du Mini. J’ai choisi 2 pins analogiques, utilisées en digital.

Une extension à 2 relais unipolaires

Pour alimenter l’Arduino, les Leds et les relais, j’ai réalisé une petite alimentation régulée par un 7805, avec un pont de diode et quelques condensateurs. J’ai préféré cela au régulateur embarqué dans le Mini, qui me semble un peu sous-dimensionné pour alimenter les relais et les Leds.

Au total, on peut s’en sortir avec 10€ de matériel, sans les ILS ni le passage à niveau.

Le schéma

Sur ce schéma, la carte à 2 relais est représentée par seulement 2 relais, n’ayant pas le dessin de l’élément acheté qui se raccorde par 4 contacts : relai 1, relai 2, 5V et 0V.

Schéma du Passage à Niveau

La source d’alimentation est prise sur celle du moteur Fulgurex. Comme sa tension peut être comprise entre 8 et 15 volts, le régulateur externe 7805 tiendra bien le choc (la tension maximum est de 40V au lieu de 12V pour un Arduino Mini).

La réalisation

Une planchette en bois sert de support direct aux composants. Elle permet un étiquetage clair des connexions.

Les ingrédients sur une planchette

Petite anecdote : Lors d’un essai précédent, avant l’ajout de la petite alimentation 5V, j’avais utilisé le régulateur intégré de l’Arduino Mini. Résultat d’une erreur : le régulateur intégré est parti en fumée. Mais tout le reste de l’Arduino est intact : quel chance, mais aussi quelle robustesse ! C’est pour cela qu’on peut voir une zone noircie sur le Mini. Depuis, j’ai refait le montage avec un Mini tout neuf.

Les dominos ordinaires aurait pu être avantageusement remplacés par cette superbe plaque à borniers.

Le programme

C’est la partie la plus intéressante du projet !
Je précise tout de suite que ce n’est pas le meilleur programme possible : il ne couvre pas tous les cas possibles. Ca se coince parfois lors de manoeuvres spéciales.
Mais c’est un programme convenable pour les cas ordinaires et assez simple à comprendre pour les débutants.

Nous verrons plus loin que cet article aura une suite, justement pour explorer un peu plus cet automate qui n’est pas si trivial que ça en a l’air.

On commence donc par la déclaration des ports d’entrée et sortie :

// Pins utilisées pour les connexions aux detecteurs Reed
#define ReedA1    2  //RA1 = O1-29G
#define ReedB1    3  //RB1 = F1-29G
#define ReedC1    4  //RB2 = F1-29D
#define ReedD1    5  //RD2 = O1-29D
#define ReedA2    6  //RA2 = O2-30G
#define ReedB2    7  //RB2 = F2-30G
#define ReedC2    8  //RC2 = F2-3031D
#define ReedD2    9  //RD2 = O2-3031D
#define ReedA3   A2  //RA3 = O2-31G
#define ReedB3   A3  //RB3 = F2-31G

// Pins utilisées pour la Led et le PAN
#define LED      13    // on board
#define Feux     11    // PWM ou digital OUT
#define PANPWM   12    // not used
#define PAN1     A0    //digital OUT IN2 relai
#define PAN2     A1    //digital OUT IN1 relai
#define PANMOVE  5000  // duree d'activation du moteur Fulgurex

Puis, pour éviter tout problème de contact au niveau des ILS, on utilise intensivement la bibliothèque Bounce.

#include <Bounce2.h>  // librairie de gestion des contacts Reed

// Instantie 8 objets Bounce avec une durée de 10 millisecond de debounce
Bounce bounceA1 = Bounce(); 
Bounce bounceB1 = Bounce(); 
Bounce bounceC1 = Bounce(); 
Bounce bounceD1 = Bounce(); 
Bounce bounceA2 = Bounce(); 
Bounce bounceB2 = Bounce(); 
Bounce bounceC2 = Bounce(); 
Bounce bounceD2 = Bounce(); 
Bounce bounceA3 = Bounce();
Bounce bounceB3 = Bounce();

Petit programme, donc peu de variables :

// Variables
boolean RA1, RA2, RB1, RB2, RC1, RC2, RD1, RD2, RA3, RB3 = false;
int etat1, etat2 = 0;  // libre
boolean dir1, dir2 = false;
boolean etat_PAN = true;  // true=ouvert, false=fermé
unsigned long TimerFeux, TimerPAN;
int fadeValue;  // valeur variable de la durée du fading des feux
int fading = 10;  // 10 croit/decroit ou 1000 allume/eteint
int cycle = 0;   // 0 eteint, 1 croit, 2 allumé, 3 decroit
int panValue = PANMOVE; //durée impulsion ouverture et fermeture pour Fulgurex
boolean Fulgurex = false;  // true seulement pour envoyer le courant au moteur

A ce stade, j’avoue que la déclaration de plusieurs variables de même type sur la même ligne avec la dernière = 0, n’entraine pas nécessairement que la première (et les autres) soient aussi = 0

Le Setup

// SETUP
void setup() {
  // entrées
  pinMode(ReedA1,INPUT_PULLUP);
  pinMode(ReedB1,INPUT_PULLUP);
  pinMode(ReedC1,INPUT_PULLUP);
  pinMode(ReedD1,INPUT_PULLUP);
  pinMode(ReedA2,INPUT_PULLUP);
  pinMode(ReedB2,INPUT_PULLUP);
  pinMode(ReedC2,INPUT_PULLUP);
  pinMode(ReedD2,INPUT_PULLUP);
  pinMode(ReedA3,INPUT_PULLUP);
  pinMode(ReedB3,INPUT_PULLUP);
  // sorties
  pinMode(LED,OUTPUT);
  pinMode(Feux,OUTPUT);
  pinMode(PAN1,OUTPUT);
  pinMode(PAN2,OUTPUT);
  digitalWrite(PAN1, HIGH);
  digitalWrite(PAN2, HIGH);
  //définition des objets bounce
  bounceA1.attach(ReedA1); 
  bounceB1.attach(ReedB1);
  bounceC1.attach(ReedC1);
  bounceD1.attach(ReedD1);
  bounceA2.attach(ReedA2);
  bounceB2.attach(ReedB2);
  bounceC2.attach(ReedC2);
  bounceD2.attach(ReedD2);
  bounceA3.attach(ReedA3);
  bounceB3.attach(ReedB3);
  bounceA1.interval(50);
  bounceB1.interval(50);
  bounceC1.interval(50);
  bounceD1.interval(50);
  bounceA2.interval(50);
  bounceB2.interval(50);
  bounceC2.interval(50);
  bounceD2.interval(50);
  bounceA3.interval(50);
  bounceB3.interval(50);
  
  TimerFeux = millis(); 
}

La boucle Loop contient 4 types d’actions :

  • La détection des ILS
  • La gestion de l’automate de clignotement des feux
  • La gestion du moteur Fulgurex (ouverture ou fermeture) pendant le temps défini par PANMOVE

Détections des ILS

Chaque détection est faite par la bibliothèque Bounce et entraîne une action spécifique à chaque ILS.

 // Update the debouncer
  bounceA1.update ( );
  bounceB1.update ( );
  bounceC1.update ( );
  bounceD1.update ( );
  bounceA2.update ( );
  bounceB2.update ( );
  bounceC2.update ( );
  bounceD2.update ( );
  bounceA3.update ( );
  bounceB3.update ( );
 
   // Une transition de HIGH à LOW entraine une action (fonction)
   if ( bounceA1.fell() ) {
     action1();    
   }  
   if ( bounceB1.fell() ) {
     action2();    
   }  
   if ( bounceC1.fell() ) {
     action3();    
   }
   if ( bounceD1.fell() ) {
     action4();    
   }  
   if ( bounceA2.fell() ) {
     action5();    
   }  
   if ( bounceA3.fell() ) {
     action5();    
   }  
   if ( bounceB2.fell() ) {
     action6();    
   }  
   if ( bounceB3.fell() ) {
     action6();    
   }
  if ( bounceC2.fell() ) {
     action7();    
   }  
   if ( bounceD2.fell() ) {
     action8();    
   }

On notera que les actions pour la branche déviée de l’"Y" sont les même que celles de la branche droite.

On verra le contenu de ces actions un peu plus loin.

L’automate des feux

Il est bien évidemment interdit d’utiliser l’instruction delay().

L’astuce consiste à détecter l’écoulement d’un laps de temps défini par la variable "fading" et à invoquer le fonction FeuxON(). C’est cette dernière qui réalise l’automate par une série d’états associés chacun à une valeur particulière de"fading", ce que nous verrons plus loin.

   digitalWrite(LED, etat_PAN );
   if ((TimerFeux + fading) < millis())
   {
     TimerFeux = TimerFeux + fading;
     if (!etat_PAN)
     {
       FeuxON();
     }
   }

Cette petite partie fait simplement avancer l’automate FeuxON() à chaque période définie par la variable fading dont la valeur change dans l’automate.

On peut obtenir ainsi un comportement sophistiqué de manière simple.

L’automate du moteur de barrière

Du même principe, il stoppe le moteur à la fin d’une tempo initialisée dans une des actions liées aux détections d’ILS.

 // commande de barrière (impulsion de duree panValue)  
   if (Fulgurex)
   {
     FeuxON();                      // le feu reste allume pendant la manoeuvre
     if ((TimerPAN + panValue) < millis())  // depassement timer
     {
       Fulgurex = false;
       digitalWrite(PAN2, HIGH);       // arret relais Fulgurex
       digitalWrite(PAN1, HIGH);
       if (etat_PAN) 
       {
         FeuxOFF();                  // extinction feu à la fin de l'ouverture
       }
     }
   }

On notera que le feu rouge pour les voitures doit rester allumé pendant la manœuvre de la barrière.

Les actions en réponse aux détections ILS

L’état libre ou occupé des voies est représenté par les variables état1 et état2.
Pour tenir compte de la possibilité de la présence de plusieurs trains dans la zone, chaque entrée de train se traduit par l’incrémentation de la variable étatx.

La sortie des trains, par symétrie, se fait par décrémentation de cette variable.

Il faut qu’elle passe à zéro pour constater que la zone est libre !

void action1()    // detecteur loin a gauche du PaN voie 1
{
/*- RA1 - O1-29G   
           -> si etat1=false, alors etat1=true, dir1=ADroite (entrée par la gauche de la voie 1, direction vers droite)
           -> si etat1=true, alors etat1=false,  (sortie par la gauche de la voie 1)
              et -> si etat2=false (voie 2 libre), alors etat_PAN=ouvert (ouverture PAN autorisée)
*/

  if (etat1 == 0)   // etat libre, entrée par la gauche voie 1
  {
    etat1++;     // voie 1 occupée
    dir1=true;  // vers la droite
  } else {        // sinon pas libre donc c'est une sortie vers la gauche
                  // ou une autre entree par la gauche
    if (!dir1)    // sortie
    {
      if (etat1 > 0) 
      {
        etat1--;  // voie 1 libre
      }
    } else {      // entree
      etat1++;
      dir1=true;
    }
    
    if ((etat1 == 0) && (etat2 == 0))  // si voies 1 et 2 libres
    {
      etat_PAN=true;  // ouverture si l'autre voie est libre
      FeuxON();
      OuvreBarriere();
    }
  }  
}
/////////////////////
void action2()    // detecteur pres à gauche du PaN voie 1
{
// RB1 - F1-29G   
//            -> si etat1>0 et dir1=ADroite alors etat_PAN = ferme (fermeture PAN)

  if (etat1 && dir1 && etat_PAN) // vers la droite 2e détecteur à gauche du PaN
  {
    etat_PAN=false; // fermeture 
    FeuxON();
    FermeBarriere();
  }  
}
/////////////////////
void action3()    // detecteur près à droite du PaN voie 1
{
//- RC1 - F1-29D   
//             -> si etat1>0 et dir1=AGauche alors etat_PAN = ferme (fermeture PAN)

  if (etat1 && !dir1 && etat_PAN)  // vers la gauche 2e détecteur à droite du PaN
  {
    etat_PAN=false; // fermeture
    FermeBarriere();
    FeuxON();
  }  
}
/////////////////////
void action4()    // detecteur loin a droite du PaN voie 1
{
/*- RD1 - O1-29D  
           -> si etat1=false, alors etat1=true, dir1=AGauche (entrée par la droite de la voie 1, direction vers gauche)
           -> si etat1=true,  alors etat1=false (sortie par la droite de la voie 1)
              et si etat2=false (voie 2 libre), alors etat_PAN=ouvert (ouverture PAN)
*/
              
  if (etat1 == 0)    // etat libre, entrée par la droite voie 1
  {
    etat1++;         // voie 1 occupee
    dir1=false;      // vers la gauche
  } else {           // sinon pas libre donc c'est une sortie vers la droite
                     // ou une nouvelle entree par le droite
    if (dir1)        // sortie
    {
      if (etat1 > 0)
      {
        etat1--;
      }
    } else {        // sinon  entree
      etat1++;
      dir1=false;
    }
    if ((etat1==0) && (etat2==0))
    {
      etat_PAN=true;
      FeuxON();
      OuvreBarriere();
    }
  }  
}
/////////////////////
void action5()
{
/*- RA2 ou RA3
- O2-30G   -> si etat2=false, alors etat2=true, dir2=ADroite (entrée par la gauche de la voie 2, direction vers droite)
- O2-31G   -> si etat2=true alors etat2=false (sortie par la gauche de la voie 2)
              et -> si etat1=false (voie 1 libre), alors etat_PAN=ouvert (ouverture PAN)
*/
              
  if (etat2 == 0)    // voie 2 libre, entree par la gauche
  {
    etat2++;
    dir2=true;
  } else {         // sinon pas libre donc c'est une sortie vers la gauche
                   // ou nouvelle entree par la gauche
    if (!dir2)      // sortie
    {
      if (etat2 > 0)
      {
        etat2--;
      }
    } else {      // sinon entree
      etat2++;
      dir2=true;
    }
    if ((etat1==0) && (etat2==0))
    {
      etat_PAN=true;
      OuvreBarriere();
    }
  }   
}
/////////////////////
void action6()
{
//- RB2 ou RB3 - F2-30G ou F2-31G   
//            -> si etat2=true et dir2=ADroite alors etat_PAN = ferme (fermeture PAN)

  if (etat2 && dir2 && etat_PAN)
  {
    etat_PAN=false;
    FeuxON();
    FermeBarriere();
  }  
}
/////////////////////
void action7()
{
//- RC2 - F2-3031D 
//          -> si etat2=true et dir2=AGauche alors etat_PAN = ferme (fermeture PAN)

  if (etat2 && !dir2 && etat_PAN)
  {
    etat_PAN=false;
    FeuxON();
    FermeBarriere();
  }     
}
/////////////////////
void action8()
{
/*- RD2 
- O2-3031D 
          -> si etat2=0, alors etat2=true, dir2=AGauche (entrée par la droite de la voie 2, direction vers gauche)
           -> si etat2=true  alors etat2=false (sortie par la droite de la voie 2 et )
              et -> si etat1=false (voie 1 libre), alors etat_PAN=ouvert (ouverture PAN)
*/
              
  if (etat2 == 0)    //entree par la droite, direction vers gauche
  {
    etat2++;
    dir2=false;
  } else {          // sinon sortie vers la droite
                    // ou nouvelle entree 
    if (dir2)
    {
      if (etat2 > 0)
      {
        etat2--;
      }
    } else {
      etat2++;
      dir2=false;
    }
    if ((etat1==0) && (etat2==0))
    {
      etat_PAN=true;
      FeuxON();
      OuvreBarriere();
    }
  }      
}

Les fonctions d’ouverture et fermeture de la barrière

Le fait de passer la variable Fulgurex à true va démarrer le décomptage du temps dans la loop.

void OuvreBarriere()
{
  digitalWrite(PAN1, LOW);  // commande relai sens ouvrir
  TimerPAN = millis();
  Fulgurex = true;
}

void FermeBarriere()
{
  digitalWrite(PAN2, LOW);  // commande relai sens ouvrir
  TimerPAN = millis();
  Fulgurex = true;
}

Le clignotement réaliste des feux

Cette partie peut être réutilisée dans beaucoup d’animations lumineuses réalistes.

On considère 4 phases dans l’allumage des feux :

  • une phase de luminosité croissante pendant 1/10e de seconde
  • une phase de luminosité maximum pendant 1 seconde
  • une phase de luminosité décroissante pendant 1/10e de seconde
  • une phase de luminosité nulle pendant 1/2 seconde

On passe d’une phase à l’autre à l’écoulement de la variable fading, initialisée à la phase précédente et testée avec le temps système dans la loop.

void FeuxON()
{
  switch (cycle)
  {
    case 0: //éteint >> démarrage croissance 
    cycle = 1;
    fading = 10;
    fadeValue = 10;
    break;
    //
    case 1: //croissance
    analogWrite(Feux, fadeValue);
    fadeValue = fadeValue + 10;
    if (fadeValue > 255)
    {
      fading = 1000;
      cycle = 2;
    }
    break;
    //
    case 2: //allumé >> démarrage décroissance
    cycle = 3;
    fading = 10;
    fadeValue = 255;
    break;
    //
    case 3: // decroissance
    analogWrite(Feux, fadeValue);
    fadeValue = fadeValue - 10;
    if (fadeValue < 0)
    {
      fading = 500;
      cycle = 0;
    }
    break;
  }
}

L’extinction des feux

void FeuxOFF()
{
  analogWrite(Feux, 0);    // éteint
}

Le code complet

On trouvera dans le document joint le code complet du passage à niveau.

Dans ce code j’ai ajouté une fonction de DEBUG qui utilise la console de l’IDE Arduino pour afficher un message à chaque événement et l’état des principales variables.

Il y a aussi une possibilité de simuler les détections d’ILS en tapant des chiffres au clavier.

On peut faire disparaître du code l’ensemble de la fonction de DEBUG en mettant la définition au début en commentaire.

//#define DEBUG      // a mettre en commentaire pour enlever les echanges avec la console

Le sketch du Passage à Niveau (version 0.9 du 1/03/2015)

L’installation in situ a eu lieu et le montage fonctionne bien... dans presque tous les cas... sauf quelques uns ;(.
Comme toujours dans un projet, il y a quelques améliorations que je ne manquerai pas de vous donner dans un prochain article.

Mais en réalité c’est une nouvelle manière de raisonner et un nouvel automate qui vous sera présenté par Jean-Pierre très prochainement, car le passage à niveau est un cas d’école !!!

Ensuite, nous y ajouterons l’incontournable sonnerie qui accompagne toujours la fermeture de la barrière. Le tout dans notre Mini, avec un lecteur de carte SD en plus : OUI il arrive à tout faire !

Enfin, nous espérons vous proposer une nouvelle réalisation avec un circuit imprimé.

Donc ne ratez pas les prochains articles !

24 Messages

  • Un automatisme de Passage à Niveau 2 mai 2016 10:34, par Jean VEDIE

    Tout est parfait le seul regret est la détection par ILS ! celle par détecteur de consommation de courant ? qui éviterai de coller des aimants sous le matériel roulant et plus discret !

    Répondre

  • Un automatisme de Passage à Niveau 2 mai 2016 14:42, par Dominique

    Merci pour le compliment !

    Les ILS sont le choix du modéliste qui a installé le passage à niveau. Il fait ses détections par ILS. La solution décrite dans cet article peut supporter toutes sortes de détecteurs auxquels il faudra adapter le schéma et le logiciel.

    D’ailleurs vous en verrez d’autres dans les prochains articles.

    Je vous encourage à nous faire part de vos choix et réalisations sur le Forum Locoduino.

    Répondre

  • Un automatisme de Passage à Niveau 2 mai 2016 14:53, par Dominique

    La discussion sur le forum est là.

    Voir en ligne : http://forum.locoduino.org/index.ph...

    Répondre

  • Un automatisme de Passage à Niveau 18 juin 2018 15:34, par lionel

    Bonjour.
    merci pour cet article, et les commentaires détaillés du programme !

    Avez-vous des nouvelles de la sonnerie du PN ?
    (la gestion de la carte SD m’intéresse ...)

    Répondre

  • Un automatisme de Passage à Niveau 13 mai 2019 15:39, par Philippe

    Bonjour,
    pourrait on raccordés deux servos pour commandé la fermeture des barrières ?

    Répondre

  • Un automatisme de Passage à Niveau 13 mai 2019 17:00, par Philippe

    Électriquement à l’arduino

    Répondre

    • Un automatisme de Passage à Niveau 13 mai 2019 17:37, par JPClaude

      J’avais fait un programme dans ce genre pour une traversée de PN en un seul sens, cela peut peut être vous donnez des idées pour votre cas.
      Voici le programme, ATTENTION les deux servos sont tête bêche ce qui veut dire qu’ils tournent en sens inverse cela est dû à la limite de leur emprise en les couchant sur le coté.

      /*****************************************************************************
       * Gestion d'une barriere dans un seul sens de direction par
       * 2 capteurs un pour l'entree du train et l'autre pour la sortie, ILS, detecteur de consommation ou autre
       * 2 servos de type SG90 mis tete beche sur leur cote afin de limiter leur emprise
       * ce qui fait que quand un servo tourne dans un sens l'autre tourne dans l'autre sens
       * et 2 feux routiers qui clignotent
       ***************************************************************************/
      
      
      #include <Servo.h>
      #include <Bounce2.h>
      
      #define PIN_CAPTEUR_OUVRE 2   
      #define PIN_CAPTEUR_FERME 3   
      #define INTERVAL 50
      
      #define PIN_FEU_DROIT 4
      #define PIN_FEU_GAUCHE 5
      #define ALLUME 0
      #define ETEINT 255
      #define VITESSE_CLIGNOTEMENT 250
      
      #define PIN_SERVO_DROIT A2  
      #define PIN_SERVO_GAUCHE A3 
      
      // a regler en fonction des servos
      
      #define VITESSE_SERVO 80
      #define ANGLE_FERMETURE 1300
      #define ANGLE_OUVERTURE 1900
      #define PAS_ANGULAIRE 20
      
      /***********************************************************************
       * les feux routiers
       **************************************************************************/
       
      int cycle = 0;                                        // cycles de clignotement
      int changement = VITESSE_CLIGNOTEMENT;                // vitesse de clignotement
      unsigned long timerLed = millis();                    // timer pour le clignotement
      
      /******************************************************************************
       * les capteurs
       ********************************************************************************/
      
      Bounce capteurOuverture;                              // anti rebond
      Bounce capteurFermeture;                              // anti rebond
      
      /************************************************************************************
       * les servos
       **************************************************************************************/
      
      int angleFerme = ANGLE_FERMETURE;                     // angle de fermeture
      int angleOuvre = ANGLE_OUVERTURE;                     // angle pour l'ouverture
      int angleDroit;                                       // angle actuel du servo 1
      int angleGauche;                                      // angle actuel du servo 2
      int pasServo = PAS_ANGULAIRE;                         // pas angulaire
      enum mode {montee,descente,arret};
      mode sens = arret;                                    // sens du mouvement de la barriere
      enum etat {ouvert, ferme, encours};
      etat situation = ouvert;                              // situation de la barriere
      bool actif = false;                                   // etat de la manoeuvre                                      
      unsigned long timerServo;                             // timer pour le servo
      int duree = VITESSE_SERVO;                            // vitesse des servos            
      Servo servoDroit;                                     // servo de la premiere barriere
      Servo servoGauche;                                    // servo de la seconde barriere
      
      /*****************************************************************************
       * clignotement des feux routiers
       ***************************************************************************/
       
      void clignote() {
      if (timerLed + changement < millis())     // vitesse de clignotement
          {
          timerLed = millis();                  // reinit le timer
          switch (cycle) {
                  case 0 : {analogWrite(PIN_FEU_DROIT,ALLUME); analogWrite(PIN_FEU_GAUCHE,ALLUME);cycle = 1;break;}      // on allume
                  case 1 : {analogWrite(PIN_FEU_DROIT,ETEINT); analogWrite(PIN_FEU_GAUCHE,ETEINT);cycle = 0;break;}      // on eteint
              }
          }
      }
      
      /*****************************************************************************
       * initialisation de l'ouverture des barrieres
       *****************************************************************************/
       
      void ouverture() {
      if (situation != ouvert) {                      // si pas deja ouvert
          servoDroit.attach(PIN_SERVO_DROIT);           // on attache les servos
          servoGauche.attach(PIN_SERVO_GAUCHE);  
          actif = true;                               // on devient actif           
          angleDroit = angleFerme;                    // une des barrieres va vers le bas (inversion des servos)
          angleGauche = angleOuvre;                   // l'autre en symetrie va vers le haut
          timerServo = millis();                      // MAJ du timer
          sens = montee;                              // on monte           
          situation = encours;                        // manoeuvre en cours
          }
      }
      
      /**************************************************************************
       * initialisation de la fermeture des barrieres
       ***************************************************************************/
       
      void fermeture() {
      if (situation != ferme ) {                      // si pas deja ferme
          servoDroit.attach(PIN_SERVO_DROIT);                          // on attach les servos
          servoGauche.attach(PIN_SERVO_GAUCHE);
          actif = true;                               // on devient actif
          angleDroit = angleOuvre;                        // une des barrieres va vers le haut (inversion des servos)
          angleGauche = angleFerme;                        // l'uatre en symetrie vers le bas
          timerServo = millis();                      // MAJ des timers
          timerLed = millis();
          sens = descente;                            // on descend           
          situation = encours;                        // manoeuvre en cours   
          }
      }
      
      /*******************************************************************************
       * manoeuvre de la bbariere
       *********************************************************************************/
       
      void manoeuvre() {
        if (actif){
          if ((timerServo + duree) < millis()){             // vitesse des servos
                  if (sens == montee) {angleDroit += pasServo; angleGauche -= pasServo;          // on monte
                                      if (angleDroit >= angleOuvre) {           // en bout de course
                                          actif = false;                    // on n'est plus actif
                                          sens = arret;                     // on est a l'arret
                                          situation = ouvert;               // la barriere est ouverte
                                          analogWrite(PIN_FEU_DROIT,ETEINT);           // on eteint les feux routiers
                                          analogWrite(PIN_FEU_GAUCHE,ETEINT);
                                          servoDroit.detach();                  // on detache les servos
                                          servoGauche.detach();
                                          return;}                          // termine
                                      }
                  if (sens == descente) {angleDroit -= pasServo; angleGauche += pasServo;      // on descend
                                      if (angleDroit <= angleFerme) {         // en bout de course
                                        actif = false;                    // on n'est plus actif
                                        sens = arret;                     // on est a l'arret
                                        situation = ferme;                // la barriere est fermee
                                        servoDroit.detach();                  // on detache les servos
                                        servoGauche.detach();
                                        return;}                          // termine
                                      }
                  servoDroit.writeMicroseconds(angleDroit);                       // on avance de l'angle  
                  servoGauche.writeMicroseconds(angleGauche);
                  analogWrite(PIN_FEU_DROIT,ALLUME);                                   // on allume les feux routiers
                  analogWrite(PIN_FEU_GAUCHE,ALLUME);
                  timerServo = millis();
              }
          }
      }
      
      /*******************************************************************
       * setup
       ******************************************************************/
       
      void setup()
      {
          servoDroit.attach(PIN_SERVO_DROIT);servoDroit.writeMicroseconds(angleOuvre);
          servoGauche.attach(PIN_SERVO_GAUCHE);servoGauche.writeMicroseconds(angleFerme);
          pinMode(PIN_CAPTEUR_OUVRE,INPUT_PULLUP);capteurOuverture.attach(PIN_CAPTEUR_OUVRE);capteurOuverture.interval(INTERVAL);
          pinMode(PIN_CAPTEUR_FERME, INPUT_PULLUP);capteurFermeture.attach(PIN_CAPTEUR_FERME);capteurFermeture.interval(INTERVAL);
          analogWrite(PIN_FEU_DROIT,ETEINT);
          analogWrite(PIN_FEU_GAUCHE,ETEINT);
      }
      
       /*******************************************************************
        * la boucle
        ********************************************************************/
        
      void loop()
      {    
           if (situation == ferme) { clignote();}
           if (capteurOuverture.fell()) {ouverture();}
           if (capteurFermeture.fell()) {fermeture();}
           if (situation == encours) {manoeuvre();}
           capteurOuverture.update();
           capteurFermeture.update();
           
       }

      Répondre

  • Un automatisme de Passage à Niveau 13 mai 2019 17:43, par Philippe

    Merci beaucoup

    Répondre

  • Un automatisme de Passage à Niveau 9 décembre 2019 01:41, par tresorier21000

    Bonjour

    Nouveau venu, je découvre ce site et ce forum très intéressant.

    Pour information, sur le fonctionnement d’un PN à SAL :
    les feux :

    • s’allument dès le déclenchement de l’arrivée du train
    • s’éteignent dès le début de l’ouverture des barrières

    Les barrières :

    • commencent la fermeture 7s après le déclenchement de l’arrivée du train (et donc 7s après l’allumage des feux)
    • se ferment et s’ouvrent en 8 à 10s

    Les sonneries :

    • fonctionnent dès le déclenchement de l’arrivée du train et l’allumage des feux
    • s’arrêtent quand les barrières sont fermées.

    J’aurai sans doute des questions sur l’usage d’arduino !
    A bientot

    Répondre

  • Un automatisme de Passage à Niveau 9 décembre 2019 07:04, par Dominique

    Merci pour ces précisions, et pouvez-vous en mentionner la source et l’époque.

    Répondre

    • Un automatisme de Passage à Niveau 9 décembre 2019 11:58, par tresorier21000

      Bien sur !

      La source, c’est le référentiel IN323 du 15/10/1980 de la SNCF (Equipement des PN et réglementaiton article 9) que je ne peux pas diffuser ici. Il est en cours de réécriture pour tenir compte de l’arrêté ministériel du 18/03/1991 rectifié en 2017. Mais rien ne changera sur cette partie de fonctionnement.
      Vous pouvez vous poster à un PN pour constater ces valeurs.
      Je n’ai pas mis toutes les tolérences, mais pour le préavis (7s indiqué avant la descente des barrières) c’est 6 à 8s.

      Par contre, je n’utiliserai pas les détections individuelles, puisque le vrai cablage d’un PN totalise l’ensemble des provenances des trains (les relais d’annonce) sur un seul relais (la commande de la signalisation routière). C’est donc celui-ci qui servira à commander les feux, barrières et sonneries.
      Le réarmement de chaque annonce se fait par un passage sur le PN (1 zone courte par voie) qui réarme l’annonce déclenchée et par extension le relais totalisateur des annonces.

      Pour être précis, la signalisation, les postes d’aiguillage et les PN, c’était mon métier pendant 40 ans !
      J’ai encore une petite activité de recherche sur les PN et j’ai besoin de faire un maquettage, d’où mon besoin d’un arduino.

      Au plaisir

      Répondre

  • Un automatisme de Passage à Niveau 9 décembre 2019 14:03, par Dominique

    Alors là, bravo !

    En cas de besoin, on sera là pour vous aider.

    Cordialement

    Répondre

    • Un automatisme de Passage à Niveau 9 décembre 2019 18:19, par tresorier21000

      De même !

      Par contre, je n’ai pas de connaissance en modélisme.
      J’ai joué en vrai dans des postes d’aiguillage, des TCO, des PN,...

      A bientot

      Répondre

    • Un automatisme de Passage à Niveau 12 février 2020 14:40, par tresorier21000

      Bonjour

      Je suis en train de réaliser un passage à niveau au plus près de la réalité.
      Tout fonctionne correctement (il me manque encore le son), mais je me heurte à un problème d’affichage sur un écran.

      Je voudrais afficher en permanence sur un écran LCD I2C, l’état de 4 relais pour indiquer l’état du PN.
      J’arrive bien à faire fonctionner mon affichage dans un programme isolé en utilisant la fonction delay().
      Par contre, il n’est pas possible de bloquer l’ensemble du programme avec cette fonction.
      Comment pourrais-je m’en sortir ?
      J’avais pensé à utiliser MsTimer2, mais là, je suis bloqué !

      Merci de votre aide

      mon croquis ecran :

      /* Fonctionnement d’un ecran LCS I2C sur Mega *****************
      * Branchement en +VCC, GND, SDA (20) et SLC (21)
      * Informations provenant de 4 contats de relais
      * branchés en A0, A1, A2 et A3 avec Resistances et capa
      * pour gérer les rebonds (avec un trigger sur la carte finale
      * ***********************************************************/

      /* programme non compatible avec le reste du passage à niveau
      * du fait de l’utilisation de la fonction delay(1000) pour
      * l’affichage des différents états à l’écran (blocage du prog)
      * *********************************************************/

      #include <LiquidCrystal_PCF8574.h>
      #include <Wire.h>
      #include <MsTimer2.h>

      LiquidCrystal_PCF8574 lcd(0x27) ; // set the LCD address to 0x27 for a 16 chars and 2 line display
      int show = -1 ;
      #define PIN_S0 A0
      #define PIN_S1 A1
      #define PIN_S2 A2
      #define PIN_S3 A3
      boolean etat0 ;
      boolean etat1 ;
      boolean etat2 ;
      boolean etat3 ;
      int etatbouton = 1 ;

      void setup()
      // declaration des boutons en entrée
      pinMode (PIN_S0, INPUT) ;
      pinMode (PIN_S1, INPUT) ;
      pinMode (PIN_S2, INPUT) ;
      pinMode (PIN_S3, INPUT) ;

      // MsTimer2 : : set (1000, ecran) ; periode de 1s
      // MsTimer2 : : start() ;

      int error ;
      Serial.begin(115200) ;
      Serial.println("LCD...") ;
      // wait on Serial to be available on Leonardo
      while (!Serial) ;
      Serial.println("Dose : check for LCD") ;
      // Voir http://playground.arduino.cc/Main/I... pour tester le port I2C.
      Wire.begin() ;
      Wire.beginTransmission(0x27) ;
      error = Wire.endTransmission() ;
      Serial.print("Error : ") ;
      Serial.print(error) ;
      if (error == 0)
      Serial.println(" : LCD found.") ;
      show = 0 ;
      lcd.begin(16, 2) ; // initialize the lcd
      else
      Serial.println(" : LCD not found.") ;
      // fin if
      // fin setup()

      void loop()
      etat0 = digitalRead(PIN_S0) ;
      etat1 = digitalRead(PIN_S1) ;
      etat2 = digitalRead(PIN_S2) ;
      etat3 = digitalRead(PIN_S3) ;
      lcd.setBacklight(255) ;
      lcd.home() ;
      lcd.clear() ;

      Serial.print(etat0) ;
      Serial.print(etat1) ;
      Serial.print(etat2) ;
      Serial.print(etat3) ;

      switch (etatbouton)
      case 1 : if (etat0 == LOW) //HIGH avec bascule de schmitt (inversion du signal) sinon LOW
      lcd.setCursor(0, 0) ;
      lcd.print(" PN a 298 metres ") ;
      lcd.setCursor(0, 1) ;
      lcd.print(" ") ;

      case 2 : if (etat1 == LOW)
      lcd.setCursor(0, 0) ;
      lcd.print(" PN a 240 metres ") ;
      lcd.setCursor(0, 1) ;
      lcd.print(" Ferme") ;

      case 3 : if (etat2 == LOW)
      lcd.setCursor(0, 0) ;
      lcd.print(" Danger ") ;
      lcd.setCursor(0, 1) ;
      lcd.print(" Ne pas franchir ") ;

      case 4 : if (etat3 == LOW)
      lcd.setCursor(0, 0) ;
      lcd.print(" Route barree") ;
      lcd.setCursor(0, 1) ;
      lcd.print(" travaux") ;

      case 5 : if (etat0==HIGH && etat1==HIGH && etat2==HIGH && etat3==HIGH)
      lcd.setCursor(0, 0) ;
      lcd.print(" Etat du PN ") ;
      lcd.setCursor(0, 1) ;
      lcd.print(" indetermine") ;

      delay(1000) ;

      //fin de loop

      Répondre

      • Un automatisme de Passage à Niveau 12 février 2020 17:18, par msport

        Bonjour,
        vous avez fait le bon diagnostic avec delay.
        A sa place, mettez la mise à jour de votre lcd dans un if genre millis > old_millis + raffraichissement et màj de old_millis ...
        Utilisez plutôt le forum, cela vous évitera de voir vos accolades disparaitre.

        Répondre

  • Un automatisme de Passage à Niveau 3 avril 2020 18:56, par M. Resse jean pierre

    Bonjour.
    Ce tuto m’intéresse beaucoup car j’ai un PN un peut dans cette configuration mais plus "compliqué".
    En effet, deux voies à double sens avec chacune une sortie dont un croisement pour le dépôt en sortie du PN.
    Ma question est de savoir si il y a possibilité de remplacer les ILS pas des LEDS Infra Rouge ( il y en a qui se placent entre les traverses) pour ne pas avoir à instaler des aimants sur tout le matériel roulant.
    Merci pour votre réponse et bravo pour ce site.
    Cordialement JP.

    Voir en ligne : http://locoduino.org/spip.php++cs_INTERRO++article117

    Répondre

    • Un automatisme de Passage à Niveau 3 avril 2020 20:16, par Dominique

      Oui c’est possible en respectant la logique des capteurs (équivalent à un ILS du point de vue logique). Pour le reste vous pouvez adapter le programme à votre réseau.

      Répondre

  • Un automatisme de Passage à Niveau 4 avril 2020 10:39, par M. Resse jean pierre

    Bonjour.
    Merci pour votre réponse rapide, je vais essayé d’adapter ce montage avec les IR et je vous teins au courent de la suite .
    A bientôt JP.

    Répondre

  • Un automatisme de Passage à Niveau 29 octobre 2020 19:48, par Frédéric

    Bonjour,

    J’ai réalisé un PN simple pour un train LGB. Gestion de deux servos moteur, du clignotement de deux LED, détection par ils. Lors du rajout ultérieur de la sonnerie sur une carte arduino uno avec un DFplayer mini, j’ai observé qu’à la lecture du fichier son mp3, les servos étaient perturbés par cette lecture (notamment au démarrage de la lecture sur la carte SD). J’avais logiquement utilisé la librairie softwareserial pour émuler une nouvelle connexion série pour ma carte DFplayer avec l’arduino UNO, celui ci ne disposant nativement que d’une seule sortie série (déjà utilisée par la connexion USB)
    Je précise que les servos étaient alimentés par une alimentation régulée 5V dédiée avec un GND commun sur l’arduino
    J’avais choisi le DFplayer mini car celui ci est peu couteux, facile à programmer avec la bibliothèque DFRobotDFPlayerMini, et disposant d’un petit amplificateur intégré.
    Disposant d’une carte MEGA, J’ai reconnecté le DFplayer sur la sortie serial2. Dans ce contexte, je n’observe plus aucune perturbation de fonctionnement sur les servos, tout fonctionne parfaitement. Pour autant c’est un peu dommage d’utiliser une carte MEGA pour une application finalement assez simple
    Je tenais à vous faire part de ce retour d’expérience, je pense qu’il doit être possible de tout réunir sur une carte UNO, mais à l’instant je ne trouve pas la solution
    Pourriez vous me donner votre avis sur ce sujet

    Votre site est une vrais source d’inspiration, je vous félicite pour tout ce travail réalisé

    Frédéric

    Répondre

  • Un automatisme de Passage à Niveau 10 mai 2021 11:31, par Pierre

    Bonjour, cela fait quelques jours que j’essais de remplacer les relais par des servos, mais je sèche. Auriez-vous une piste à me proposer ?
    Merci.

    Répondre

  • Un automatisme de Passage à Niveau 10 mai 2021 14:28, par msport

    Bonjour,
    vous n’avez pas vu le 8e commentaire ?
    sinon, lisez la série d’articles Passage à niveau géré par Arduino (1 à 5)
    Et pour un dépannage passez sur le forum.
    Cordialement

    Voir en ligne : Passage à niveau géré par Arduino (5)

    Répondre

Réagissez à « Un automatisme de Passage à Niveau »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Projets »

Les derniers articles

Les articles les plus lus