Commande d’un signal mécanique ferroviaire

Si cet article vous a plus, merci de le partager sur les réseaux sociaux.

Après la réalisation du pont tournant, il fallait s’attaquer à la signalisation et notamment celle correspondant à la sortie du dépôt. C’est ainsi qu’un signal mécanique, type carré violet a été implanté pour sortir du dépôt ferroviaire.

Une 040 attends l’autorisation de quitter le dépôt ferroviaire.

Présentation

Dans ce tutoriel, nous verrons comment utiliser l’Arduino pour piloter un servomoteur et commander notre signal mécanique ainsi que les 2 LEDs du signal (blanche et violette). Un bouton poussoir déclenchera la rotation du servomoteur dans un sens puis dans l’autre. Enfin les LEDs s’inverseront pour passer d’une couleur à l’autre à mi-course de la rotation du signal pour un rendu visuel sympa. Enfin nous utiliserons la mémoire intégrée à l’Arduino pour mémoriser le dernier.

Valeur ajoutée avec Arduino :

  • Une carte peu onéreuse et facile à mettre en œuvre
  • Réglage rapide des paramètres du servomoteur (vitesse) en modifiant les variables du programme
  • Possibilité de raccorder plusieurs servos sur une carte Arduino Uno

Pré-requis : Le programme est donné comme base de travail et le copier ne posera pas de problème particulier pour mettre en œuvre facilement votre premier signal mécanique. Il faudra néanmoins intervenir sur le programme si vous prévoyez de piloter d’autres signaux carrés ou autre signaux plus complexes.

Attention : Avant de relier le servo au signal, il faudra le faire tourner « à vide » pour régler ses courses. Un servo qui irait trop loin dans sa course risquerait d’endommager le signal. D’ailleurs il est recommandé sur la tige reliant le servo au signal de faire un effet « ressort » pour atténuer ces risques.

 
 
 
 

Matériel nécessaire à la réalisation

Partie électronique

  • Carte Arduino UNO
  • Servomoteur analogique miniature type FS90
  • Bouton poussoir
  • Alimentation 5V/1,5A pour la commande du servomoteur
  • Alimentation 9V/1A pour l’alimentation de la carte Arduino
  • Leds (ici violette et blanche)
  • Résistances 200Ω

Composants optionnels :

  • Shield à borniers DFR0060 (marque DF Robot)
  • Sachet de fils de connexions M/M
  • Sachet de fils de connexions M/F

Le « Shield » à borniers s’enfiche directement sur la carte Arduino et permet d’accéder à toutes les entrées/sorties à l’aide de bornier à vis. Il peut s’avérer intéressant lorsque l’on a pas mal de câblage à faire pour sécuriser les connexions.

Partie programmation

Nous allons utiliser ici la classe « Servo.h » permettant de contrôler de un à plusieurs servos avec les cartes de type Arduino.

Installation de la librairie :

Pour installer une librairie dans l’IDE Arduino (outil de développement) :

  1. Aller dans le menu « Outils », sélectionner « Gérer les librairies »
  2. Dans le champs de rechercher, renseigner « servo »
  3. Puis installer la dernière version de la librairie

À savoir :

Lorsque l’on connecte au moins 1 servo ( en utilisant l’instruction « attach() » ), cela désactivera les fonctionnalités PWM des sorties 9 et 10 d’un Arduino Uno.

La fonctionnalité PWM (Pulse Width Modulation) permet de de faire varier la tension moyenne de la sortie en fonction de la valeur appliquée (entre 0 et 255). En indiquant une valeur de 0, alors la tension sur la broche sera de 0 Volt. Avec une valeur de 127, la tension moyenne de sortie de la broche sera de 2,5 Volts, etc..

Une application simple de la sortie PWM consiste par exemple à allumer une LED de façon progressive et inversement pour l’éteindre progressivement.

Pilotage du signal mécanique

Schéma structurel

Pour piloter notre signal mécanique, ici un carré violet, nous utilisons 2 Leds (blanche et violette) que nous connectons aux sorties A4 et A5.

Le bouton poussoir de commande du servomoteur est relié à la masse commune du montage et à la broche D7. Il est ainsi relié directement à l’Arduino sans la mise en place de résistance de Pull-up. Les broches de l’Arduino pouvant remplir cette fonction si elles sont correctement paramétrées dans le programme, ce que nous verrons plus loin.

Enfin la commande du servomoteur est placée en D12 et les autres broches reliées à la masse commune du montage et au 5V délivré par une alimentation externe.

Pull-up ? Qu’est ce que c’est ? :

Une résistance de Pull-Up (ou résistance de tirage) permet dans le cadre de montages « numériques » (0 ou 1) de ne pas laisser l’entrée (ici de l’Arduino) en « l’air » lorsque le bouton poussoir n’est pas enfoncé.
Lorsqu’une entrée numérique est laissée en « l’air » (ou flottante) sans fixer son potentiel (ou masse), il y a un risque que la valeur lue en entrée fluctue entre l’état bas (0) et l’état haut (1). Un parasitage qui viendrait donc perturber le bon fonctionnement du montage (ici du programme informatique).

L’Arduino permet de s’affranchir de cette résistance physique lorsque l’on déclare l’entrée en tant que « PULLUP ». Cela permet bien évidemment de réduire le nombre de composants du montage.

Par opposition on parlera de résistance de pull-down lorsque celle-ci sera raccordée à la masse et non au VCC comme dans l’exemple ci-dessus.

Principe de fonctionnement

L’action sur le bouton poussoir positionne le signal dans un sens ou dans l’autre en reprenant l’affichage visuel blanc ou violet selon l’état.

La Led témoin présente sur la carte Arduino sera active lors de la rotation du signal.

Enfin à chaque fin de course du servo, sa position est sauvegardée dans la mémoire interne de l’Arduino. Ceci afin de restaurer la dernière position du signal lorsque la carte sera mise sous tension ultérieurement.

Le programme

Le programme doit permettre en l’état de gérer 1 servo avec ses 2 leds avec un angle allant de 0° à 80° et d’initialiser la lecture de l’état d’un bouton poussoir.

Une fois initialisé, le programme scrute en boucle l’état du bouton qui lorsqu’il est « enfoncé » déclenche la rotation du servo dans un sens ou dans l’autre.

Lorsque le servo atteint sa position de destination, celle-ci est mémorisé pour permettre lors de la reprise de l’alimentation de repartir correctement avec le dernier état « actionné ». Le servo est d’ailleurs «attaché » (instruction attach()  ) juste avant de débuter sa rotation puis « détaché » pour éviter des rebonds qui peuvent se produire.

La structure du programme utilise principalement des tableaux pour déclarer les variables afin d’étendre assez simplement les fonctionnalités par la suite et ajouter d’autres boutons/servos assez rapidement.

Le programme doit permettre en l’état de gérer 1 servo avec ses 2 leds avec un angle allant de 0° à 80° et d’initialiser la lecture de l’état d’un bouton poussoir.

/*
 * Commande SERVOS avec Arduino pour
 * Signal mécanique (exemple du carré violet)
 * Auteur: Seine Modèle Club Ferroviaire 
 * Site: https://www.modelisme-ferroviaire-rouen.fr/
*/

#include <Servo.h>
#include <EEPROM.h>

#define noBounce 50      // Délai anti-rebond boutons
#define delaiRotation 20  // Délai entre 2 crans moteurs
#define pasRotation 0.5   // Précision (lenteur) de la rotation

#define nbServos 1        // Nbre de Servos
#define nbBoutons 1       // Nbre de Boutons

// Variables pour les différents états du bouton
// Variables utilisant un tableau, ce qui permettra de gérer plusieurs boutons
// Si l'on souhaite mettre en place des signaux en plus
int tabAvoidClic[nbBoutons] = { 0 };
int tabBP_curState[nbBoutons] = { -1 };
int tabBP_lastState[nbBoutons] = { -1 };

int adresseStockage = 0;  // Valeur entre 0 et 127 (UNO = 1024Ko)
byte memoPosition;        // Octet de sauvegarde (bit0 = Servo Carré Violet, Bit1 = Servo xx suivant, etc

// Déclaration du bouton et de sa connexion à une broche
// Utilisation d'un tableau pour extension future des possibilités
const int BP_CarreViolet = 7;
int tabBP_Pins[nbBoutons] = { BP_CarreViolet };

// Déclaration des LEDS
const int ledTemoin = 13;      // Led intégrée, s'allumera lors des rotations de servo

const int Led_Violette = A5;
const int Led_Blanche = A4;

// Déclaration du servo 
// Utilisation d'un tableau pour extension future des possibilités
Servo servo_CarreViolet;
Servo listeServos [nbServos] = { servo_CarreViolet };
int listeServosPins [nbServos] = { 12 };

// Détermine les angles (fins de course) du servo
// Utilisation d'un tableau à deux entrées pour les positions gauche et droite
// Et en vue d'étendre les possibilités (ex : ajouts de servos)
float servosPositions[nbServos][2] = {
  { 0, 80 }
};

/*
 * Donne la position actuelle du Servo, s'il est fermé ou ouvert.
 * 0= Ouvert  1= Fermé
 */
bool signalOuvertFerme (int quelServo = 0) {
  Servo lireQuelServo = listeServos[quelServo];
  if (lireQuelServo.read() == servosPositions[quelServo][1]) {
    return 1;
  } else {
    return 0;
  }
}

/*
 * Mémorise la position courante du servo en fin de rotation
 * pour replacer le contexte utilisateur lors du prochain démarrage
 * (= à la reprise de l'alimentation)
 */
void signalSauvePos (int quelServo = 0) {
  if (signalOuvertFerme (quelServo) == 0) {
    bitClear(memoPosition, quelServo);
  } else {
    bitSet(memoPosition, quelServo);
  }
  
  // Par sécurité tous les autres bits sont placés à 0
  bitClear(memoPosition, 1);
  bitClear(memoPosition, 2);
  bitClear(memoPosition, 3);
  bitClear(memoPosition, 4);
  bitClear(memoPosition, 5);
  bitClear(memoPosition, 6);
  bitClear(memoPosition, 7);
  
  // Sauvegarde en EEPROM de l'octet
  EEPROM.update(adresseStockage, memoPosition);
 
}

/*
 * Inversion de l'état des LEDS
 * Lorsque le servo est à la mi-course de sa rotation
 * Permet un effet visuel plus sympa que d'attendre par exemple
 * la fin de rotation pour changer d'état
 */
void gestionLedsMiCourse ( int quelServo = 0, int ouvertFerme = 0 ) {

  switch (quelServo) {
    case 0:
    // Au changement de position l'état du voyant est inversé  
    if (ouvertFerme == 0) {
      digitalWrite(Led_Violette, HIGH);
      digitalWrite(Led_Blanche, LOW);      
    } else {
      digitalWrite(Led_Violette, LOW);
      digitalWrite(Led_Blanche, HIGH);   
    }
    break;
  }
}

/*
 * Gestion de la rotation d'un servo
 * de son point de départ à sa position d'arrivée
 */
bool rotateServo (int quelServo = 0, bool noLED = false) {

  Servo servoToRotate;  // Le servo à actionner
  float posServoDest;   // La position du Servo à atteindre
  float posCourante;    // Sa position courante

  float deltaPos;
  float valMicourse;
  
  // Sur quel servo on agit ? 
  servoToRotate = listeServos[quelServo];
  
  // Lire la position courante du servo
  posCourante = servoToRotate.read(); 

  servoToRotate.attach ( listeServosPins[quelServo] );
  
  // On récupère la valeur de la position de destination du servo à atteindre
  if ( posCourante == servosPositions[quelServo][0] ) {
    posServoDest = servosPositions[quelServo][1];  
  } else {
    posServoDest = servosPositions[quelServo][0];
  }
  
  // Calcul du Delta à parcourir pour le servo
  deltaPos = posCourante - posServoDest;
  
  // On calcule la valeur de la position mi-course du servo
  // Pour déclencher le switch des leds
  valMicourse = abs(deltaPos / 2);

  // On détermine le sens dans lequel
  // Le servo doit tourner
  if (deltaPos < 0) {
    
    for (float ii=posCourante+pasRotation; ii<posServoDest; ii+=pasRotation) {
      servoToRotate.write(ii);
      if (ii == valMicourse && noLED == false) gestionLedsMiCourse ( quelServo, 0 );
      delay(delaiRotation);
    }
    
  } else {
    
    for (float ii=posCourante-pasRotation; ii>posServoDest; ii-=pasRotation) {
      servoToRotate.write(ii);
      if (ii == valMicourse && noLED == false) gestionLedsMiCourse ( quelServo, 1 );      
      delay(delaiRotation);
    }
    
  }
  
  // En fin de rotation, l'on fixe l'angle final dans le servo
  // pour être sûr qu'il se place bien sur l'une des 2 limites
  servoToRotate.write(posServoDest);
 
  // On mémorise la position du servo
  signalSauvePos (quelServo);
  

  // On le déconnecte lorsqu'il n'est pas utilisé
  // Évite les échauffements et éventuels décalages 
  servoToRotate.detach(); 
  
  return true;
}

/*
 * Gestion de la rotation d'un servo
 * de son point de départ à sa position d'arrivée
 */

void actionsBoutonDepot (int quelBouton = 0) {
  
  // Activation de la LED témoin pour indiquer qu'un Servo est en cours de rotation
  digitalWrite(ledTemoin, HIGH);

  // Initie la rotation du servo (ici le 0 = carré violet)
  // On récupère aussi son état courant Ouvert ou Fermé
  rotateServo(0, false);
  
  // On  libère le clic sur le bouton courant
  // Et la LED Témoin d'activité
  tabAvoidClic[quelBouton] = 2;
  digitalWrite(ledTemoin, LOW);
}

/*
 * Écoute en boucle l'état du(es) bouton(s)
 */
void loop() {
  
  // Balaye les Pins d'entrée (1 à n Boutons)
  for (int zz=0; zz<nbBoutons; zz++) {

    // Check si l'un des boutons est enfoncé
    if (digitalRead(tabBP_Pins[zz]) == LOW) {
      tabBP_curState[zz] *= -1;
    } else {
      if (tabAvoidClic[zz] == 2) tabAvoidClic[zz] = 0;
    }
      
    // S'il y a eu changement d'état du bouton
    // On va faire tourner les servos
    if (tabBP_curState[zz] != tabBP_lastState[zz] && tabAvoidClic[zz] == 0 ) {

      // Quel bouton 
      tabAvoidClic[zz] = 1;
      tabBP_lastState[zz] = tabBP_curState[zz];
      digitalWrite(ledTemoin, HIGH);
  
      switch (zz) {
        case 0: actionsBoutonDepot(0); break;
        // case 1: actionsBoutonXX(1); break;
        // case 2: actionsBoutonXX(2); break;
      }
    }
  }
}

/*
 * Séquence d'initialisation de la carte
 */
void setup() {
 
  Serial.begin(9600);
  
  // Assigner la broche en PULL-UP pour le bouton poussoir
  //  Le mode PULL-UP permet de s'affranchir d'une résistance
  pinMode(BP_CarreViolet, INPUT_PULLUP);  
    
  // Assigner les broches en sortie pour les LEDS
  pinMode(ledTemoin, OUTPUT);
  pinMode(Led_Violette, OUTPUT);
  pinMode(Led_Blanche, OUTPUT);

  // Initialiser les leds du signal + la Led Témoin
  digitalWrite(ledTemoin, LOW);  
  digitalWrite(Led_Violette, LOW);
  digitalWrite(Led_Blanche, LOW);

  // Lecture de la mémoire EEPROM pour récupérer la 
  // dernière position du servo
  // Et restaurer le dernier état
  memoPosition = EEPROM.read (adresseStockage);

  // SERVO Carré Violet 
  // Restaure le contexte (position du servo)
  if (bitRead(memoPosition, 0) == 1) {
    servo_CarreViolet.write( servosPositions[0][1] );
    digitalWrite(Led_Violette, HIGH);
  } else {
    servo_CarreViolet.write( servosPositions[0][0] );
    digitalWrite(Led_Blanche, HIGH);
  }
}

Une fois initialisé, le programme scrute en boucle l’état du bouton qui lorsqu’il est « enfoncé » déclenche la rotation du servo dans un sens ou dans l’autre.

Lorsque le servo atteint sa position de destination, celle-ci est mémorisé pour permettre lors de la reprise de l’alimentation de repartir correctement avec le dernier état « actionné ». Le servo est d’ailleurs «attaché » (instruction attach()  ) juste avant de débuter sa rotation puis « détaché » pour éviter des rebonds qui peuvent se produire.

La structure du programme utilise principalement des tableaux pour déclarer les variables afin d’étendre assez simplement les fonctionnalités par la suite et ajouter d’autres boutons/servos assez rapidement.

Signalisation de notre réseau

Le carré violet est l’un des signaux qui est implanté sur notre grand réseau HO au club. D’autres signaux mécaniques sont également présents dans le notre décor et le programme Arduino a ainsi été étendu pour les commander. Si le programme complet de la gestion des signaux vous intéresse, n’hésitez pas à nous contacter.

Si vous avez trouvé cet article instructif, n’hésitez pas à le partager sur les réseaux sociaux.


Si cet article vous a plus, merci de le partager sur les réseaux sociaux.