Mon Réseau N avec Arduino

Aller au contenu | Aller au menu | Aller à la recherche

vendredi 1 janvier 2016

21- Nouveau réseau : Architecture et informatique

Afin de développer l'infrastructure de mon réseau par étapes, sans remettre en cause les modules développés, j'ai choisi de construire des modules fonctionnels indépendants reliés entre eux par un bus CAN

Architecture1

  • Une carte "cerveau" assure la gestion centralisée du réseau. C'est elle qui transmet les ordres aux cartes exécutantes et qui reçoit les événements des cartes de surveillance.
  • Une carte TCO s'occupe de la gestion des capteurs de consommation de courant, la représentation du réseau avec les Leds d'occupation et les clés de commande de position des aiguilles. Les positions des capteurs de consommation dont exploitées par le TCO pour afficher la position des trains et envoyer ces informations au "cerveau", ainsi que les demande de changement de position des aiguilles.
  • Une carte de Commande d'aiguilles exécute les changement des aiguilles selon les conditions du programme de circulation. Cette carte s'occupe aussi de la récupération d'informations des capteurs de circulation complémentaires (RFID, Hall, Infrarouge)
  • Une carte de Traction qui génère les signaux de puissance DCC pour toutes les locomotives (sauf celle du va et vient). Une autre carte s'occupe du pilotage du va et vient avec ses automatismes propres et une carte prendra en charge la programmation des locomotives sur la voie de programmation.
  • Une carte de Signalisation pilote les nombreuses Leds des signaux lumineux et les signaux mécaniques, y compris les passages à niveau.
  • Une carte est prévue pour gérer des capteurs supplémentaires, non liés à la circulation.
  • Une carte gère de façon centralisée les animations sonores, lumineuses du décor principalement. Elle est accompagnée d'autres cartes esclaves au fur et à mesure de l'enrichissement du réseau.

A tout cela s’ajouteront une carte de communication sans fil qui permettra le pilotage manuel à partir de 4 manettes sans fil, toutes équipées d’Arduino.

Toutes ces cartes communiqueront entre elles et surtout avec la carte « cerveau » grâce à un bus CAN à 500kb/s.

dimanche 5 juillet 2015

Le réseau N d'exposition du Club de Modélisme Ferroviaire Arpajonnais

Mieux qu'une longue description, voici les excellentes video réalisées par Antoine Bureau du Club de Modélisme Ferroviaire d'Arpajon.

Celle-ci est relayée par la Revue Aiguillages :

CMFA

Et celle-ci a été tournée à Rail Expo 2015 à Cergy-Pontoise :



LOCODUINO

jeudi 2 avril 2015

20- Nouveau Réseau : images de la construction

Debut Avril 2015

Lire la suite...

vendredi 23 janvier 2015

Visitez LOCODUINO

LOCODUINO a pour objectif de montrer tout ce que l’on peut faire en Do It Yourself avec un Arduino en modélisme ferroviaire.

LOCODUINO c’est d’abord un site communautaire où vous trouverez des articles consacrés à l’emploi de l’Arduino en modélisme ferroviaire mais aussi des articles pédagogiques sur les connaissances de base nécessaires : de l’électronique simple et de la programmation. Vous y trouverez également des présentations de shields, de breakout boards et de composants intéressants pour notre loisir.

LOCODUINO n’est pas un site commercial, il est géré par quelques passionnés de modélisme ferroviaire et de l’Arduino.

J'en fais partie, ce qui me donne l'occasion de présenter mes projets de ce blog de façon encore plus didactique.

mardi 27 mai 2014

13- Centrale Va-et-Vient (document pdf)



Ce billet permet de télécharger le document de description complet, prêt à imprimer.
Cliquez sur l'annexe.

12- Realisation du logiciel Va-et-Vient (2)

Boucle principale (loop)


La fonction loop() est exécutée de façon répétitive à l’infini. C’est dans cette fonction que toutes les tâches devront être exécutées les unes à la suite des autres.

Mais certaines tâches n’ont pas besoin d’être exécutées à chaque tour de loop ! C’est le cas de l’automate qui commande l’avancement automatique du train. Pour cet automate, on a choisi un pas d’avancement d’1/2 seconde. Chaque 1/2 seconde, l’automate avance d’un cran. 

Pour ce faire, la variable time500 est comparée au temps système millis() : si cette dernière dépasse de 500 millisecondes la valeur de time500, une procédure SR_demiseconde() est lancée et time500 est mis à jour avec la valeur lue de millis().

En parallèle, des conditions extérieures peuvent influer sur le fonctionnement et les états de l’automate : appui sur le bouton, changement d’état des clés ou détection de passage devant un capteur infrarouge. Pire, un dépassement de température du booster ! Ces événements doivent être traités en priorité, le plus vite possible : ils sont donc traités dans le fonction loop() qui s’exécute environ 2 fois par seconde.

Pour garantir un temps de réponse le plus court possible, la fonction delay() n’est JAMAIS utilisée dans loop(). Il faut donc « accrocher » les taches qui doivent attendre à la fonction SR_demiseconde() et utiliser des compteurs qu’il faut initialiser avec le nombre de 1/2 secondes à attendre. La fonction SR_demiseconde() n’a alors qu’à décrémenter le compteur et exécuter la tache seulement lorsqu’il arrive à zéro.

C’est le cas des taches suivantes :

  • la detection de passage devant les capteurs qui doit être confirmée après une temporisation de 0,2 seconde pour la tête de train et 1 seconde pour la queue du train (afin d’éliminer les éventuels espace entre wagon qui pourrait donner de fausses détection). Ici on ne sert pas de la détection de queue de train, mais cela pourrait servir dans l’avenir. On fait appel ici à une variable IRdebtime initialisée à 100 pour avoir des pas de 0,1 seconde.
  • le clignotement des leds est accroché à la fonction SR_demiseconde().
  • l’avancement de l’automate est régit par des tableaux de variables PasAR[Canton] et PasAV[Canton] qui sont initialisées à chaque changement de condition et décréments à chaque 1/2 seconde. Lorsqu’elle atteint 0, un changement d’état s’opère (s’il n’a pas eu lieu avant par une condition externe).
  • le rafraichissement de l’affichage LCD.

La seule exception à la regle du NON delay() arrive lorsque qu’il y a appel de la fonction _Configuration() qui contient des instruction delay(). Mais pour éviter tout risque dans ce cas, le train est d’abord stoppé (sa vitesse est programmée à 0 et le signal DCC est arrêté.

En plus des taches exécutées dans la boucle loop(), il faut savoir que les librairies lancées au démarrage mettent en place des taches prioritaires déclenchées par des horloges internes : les interruptions.

C’est le cas de la librairie CmdeArduino qui initialise le Timer 1. Toutes les 50 ou 100 microsecondes  environ, une interruption prend la main du processeur et s’occupe de la gestion du signal DCC : une transition d’un 0 à 1 ou 1 à 0 des trames DCC préparées par les instructions de commande de vitesse et de lumière.

Le détail des opérations de loop() est indiqué dans le corps du programme ci-dessous :

void loop() {

  

  int i; // variable locale, propre à loop()

  /////////////////////////////////////////

  // Boutons et commutateurs

  /////////////////////////////////////////

Bouton Auto/Manuel : on détecte uniquement un changement d’état (par rapport à un état précédent prev_AutoManu_state. Le passage en mode AUTO démarre l’automate : AutoManu est vrai, cette variable étant une condition pour le déroulement de l’automate dans SR_demiseconde().

Le passage en mode MANUEL stoppe l’automate  : AutoManu est faux, condition pour appliquer les valeurs de vitesse des potentiomètres dans loop().

  if (B_AutoManu.update())              // COMMUTATEUR AUTO/MANUEL

  {

    byte AutoManu_state = B_AutoManu.read(); // variable locale

    if (AutoManu_state != prev_AutoManu_state)  

    {

      prev_AutoManu_state = AutoManu_state;

      AutoManu = prev_AutoManu_state;

      if (AutoManu) 

      {                    // passage en mode Auto

      // pour le moment on suppose le train au depart en gare 1 

      Canton = 0;          // 1/2 arret en Gare1

      DIR = 1;             // Marche Avant

      vitesse = 0;         // initialisation vitesse

      PasAV[Canton]=Config_Init.duree_arret_gare1;  // init 1/2 duree arret en gare 1

      } else 

      {                    // passage en mode Manuel

        old_speed_AV = 0;

        old_speed_AR = 0;

        Vert_Clignotant=0;

        Jaune_Clignotant=0;

      // peu importe la position du train

      // mais on continue a mettre a jour sa position, direction et vitesse

      }

    }  

  }

Bouton Lumière: on détecte uniquement un changement d’état (par rapport à un état précédent prev_FL_state. Le changement d’état entraine une commande de lumière : allumage ou extinction.

Si la fonction dps.setFunctions0to4 retourne une erreur, le symbole « : » est affiché là ou le curseur se trouve (c’est une fonction de débugging) .

  /////////////////////////////////////////

  //Commande d'eclairage FL

  /////////////////////////////////////////

  if (B_SW_FL.update())                     // COMMUTATEUR DE LUMIERE

  {

    byte FL_state = B_SW_FL.read();         //high == not pushed; low == pushed

    if(FL_state != prev_FL_state)

   {

      prev_FL_state = FL_state;

      FL = prev_FL_state;

      if (!dps.setFunctions0to4(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,FL))

      {

      lcd.print(":");

      }

    }

  }

Bouton Mode: on détecte uniquement un changement d’état (par rapport à un état précédent prev_BM_state) et seulement le relâchement du bouton. Le changement entraine une mise à jour de la variable d’état Mode_En_Cours qui régit l’affichage LCD en fonctionnement standard et en configuration.

En fonctionnement standard, seuls les écrans 0, 1 et 2 sont possibles. Après 2, on retourne à 0.

En mode configuration (si les boutons Lumière et Auto/Manu sont en bas - sans lumière et mode manuel), la configuration est démarrée.

  /////////////////////////////////////////

  // MODE

  /////////////////////////////////////////

  if (B_Mode.update())                     // Poussoir Mode

  {

    byte BM_state = B_Mode.read();         //high == not pushed; low == pushed

    if(BM_state != prev_BM_state)

    {

      prev_BM_state = BM_state;

      BM = prev_BM_state;

      if (BM)

      // prise en compte du relachement

      {

        Mode_EnCours++;

        if (Mode_EnCours > 2 && !AutoManu && !FL) 

        {       // passage en mode configuration

          Mode_Config = true; 

          _Configuration();      // 1 question, 1 reponse, 

// + programmation EEPROM en fonction de Mode_EnCours

        } else // mode normal

        {

          Mode_Config = false;

          if (Mode_EnCours > 2) Mode_EnCours = 0;       

// affichages 1ere et 2e ligne faits par SR_demiseconde

        }

      }

    }     

  }

Gestion des potentiomètres de vitesse Avant et Arrière.

Comme l’arrêt correspond à la valeur de vitesse = 1, moins 2 vaut arrêt.

La commande de vitesse DCC n’est envoyée que si la vitesse change car la librairie CmdrArduino se charge de la répétition de la commande conformément à la norme NMRA.

Ne sont pris en compte que les changements de moins de 50% (écretage en cas de crachement du potentiomètre, pour éviter de saturer la librairie CmdrArduino).

En mode manuel, la vitesse est envoyée immédiatement et les leds de direction sont mises à jour

En mode auto, la vitesse est gérée par l’automate.

Rappel : en marche avant la vitesse est négative, mais positive en marche arrière

  /////////////////////////////////////////

  //Commandes de vitesse AV et AR

  /////////////////////////////////////////

  AV_value = analogRead(Pot_AV);

  speed_AV = (AV_value >> 3); //divide by 8 to take a 0-1023 range number and make it 0-127 range.

  if (speed_AV < 2)           //forcement un stop

  {

    speed_AV = _stop;         // 0 = e_stop;   1 = _stop 

  }

  if (speed_AV != old_speed_AV)

  {

    if (abs(speed_AV - old_speed_AV) < 64)  // ecretage en cas de pot qui crache

    {

      old_speed_AV = speed_AV;

      change_V = true;

    }

  }

  AR_value = analogRead(Pot_AR);

  speed_AR = (AR_value >> 3); //divide by 8 to take a 0-1023 range number and make it 0-127 range.

  if (speed_AR < 2)           //forcement un stop

  {

    speed_AR = _stop;      // 0 = e_stop;  1 = _stop 

  }

  if (speed_AR != old_speed_AR)

  {

    if (abs(speed_AR - old_speed_AR) < 64)  // ecretage en cas de pot qui crache

    {

      old_speed_AR = speed_AR;

      change_V = true;

    }

  }

  if (change_V && !AutoManu) 

    {                                // mode manuel : vitesse envoyee immediatement

    DIR = (speed_AV > speed_AR);

    if (speed_AR == _stop) DIR = 1;

    if (speed_AV == _stop && speed_AR == _stop) 

    {

digitalWrite(Pin_Arret, HIGH); 

    }else 

    {

digitalWrite(Pin_Arret, LOW);

    }

    digitalWrite(Pin_MAV, DIR);      // Led verte

    digitalWrite(Pin_MAR, !DIR);     // led jaune

    if (DIR) {                       // avant

      vitesse = speed_AV;            // 

      _speed = -vitesse;             // en avant vitesse negative

    } else   {                       // arriere

      vitesse = speed_AR;            // 

      _speed = vitesse;              // vitesse positive

    }

    if (!dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,_speed))

    {

      lcd.print(".");    // détection d’anomalie

    }

    change_V = false;

    }

      

La librairie CmdrArduino a besoin d’opérer en tâche de fond (sous interruptions) aussi souvent que possible : elle est appelée dans loop() à chaque tour. Les interruptions servent à changer les bits 0 et 1 émis par la broche DIR (9) au rythme de l’horloge TIMER1 (toutes les 50 ou 100 µs environ). La fonction dos.update() sert à organiser les données privées de cette librairie et notamment la file d’attente des trames à émettre.

  /////////////////////////////////////////

  // Execution CmdrArduino à chaque LOOP

  /////////////////////////////////////////

  dps.update();

  

La liberation du booster par la clé Alim Rails se fait par une transition bas -> haut de cette clé.

Si la clé est initialement en position basse (arrêt), lever la clé alimente les rails en DCC.

Si la clé est initialement en position haute (marche, alors que la Led rouge DCC Stop est allumée), il faut d’abord baisser la clé, puis la lever.

  /////////////////////////////////////////

  // START / STOP BOOSTER

  /////////////////////////////////////////  

  if (B_DCC.update())                      // BOUTON ON / OFF DCC

  {

    byte ON_DCC_state = B_DCC.read();

    if (ON_DCC_state != prev_ON_DCC_state)  

    {

      prev_ON_DCC_state = ON_DCC_state;

      if (!ON_DCC_state)

      {

        ON_DCC = 0;

      }

      if (ON_DCC_state)

      {

        ON_DCC = !ON_DCC;                    // inversion ON -> OFF -> ON ...

      }

      digitalWrite(Pin_Out_PWM, ON_DCC);      // 1 = en marche

      digitalWrite(Pin_Led_Booster, !ON_DCC); // 0 = led OFF allumee en continu

      vitesse = 0;                            // par sécurite

    }

  }

 

Une surveillance du booster est possible, grâce aux 2 signaux Courant et Temperature.

La valeur 600 correspond à une tension de 2,5 v, soit 1,5 A dans le booster. En réalité on n’a pas réussi à lire une valeur plausible : des essais sont à poursuivre.

L’alarme temperature se déclenche au dessus de 145 °C

En cas d’alarme, le signa DCC est arrêté, la led DCC stop allumée et la led STOP clignotante

 

  /////////////////////////////////////////

  // ALARMES BOOSTER (courant et temperature)

  /////////////////////////////////////////

  mesure_courant = analogRead(Current_Sense);

  alarme_temperature = !digitalRead(Thermal_Sense);  // LOW si T > 145°C)

// puis inversé  

  if ((mesure_courant > 600) || alarme_temperature )

  {

    ON_DCC = 0;

    digitalWrite(Pin_Out_PWM, ON_DCC);        // 0 = STOP

    digitalWrite(Pin_Led_Booster, !ON_DCC);   // 1 = led Booster OFF allumée

    surchargeBooster = 3;                     // et clignotante

    Rouge_Clignotant = 3;                     // led ARRET clignotante

  }

  


Gestion des capteurs de passage (détecteurs infrarouge) : c’est une partie plus complexe !

On sépare la détection de l’avant du train de celle de l’arrière du train, par rapport au sens de la marche.

La tête est à l’avant en marche avant, la queue est en avant en marche arrière !

On applique une temporisation de 0,2 s pour confirmer la détection de la tête de train.

On applique une temporisation de 1,0 s pour confirmer la détection de la queue du train.

On allume la Led Arrêt pendant le passage du train pour contrôler le bon fonctionnement du détecteur.

Au 1er détecteur, TC est initialisé par le temps système

Au 2e détecteur on calcule le temps de passage entre détecteurs et la vitesse du train.

Une mise à jour du « temps de traversée » est faite pour l’automate, afin de sécuriser l'arrêt final du train, si l’un des détecteurs venait à défaillir.

La mesure de vitesse est réalisée à la fois en mode manuel et en mode automatique.

En mode automatique, le 2e passage détecté déclenche la décélération.

D’autres détails seront expliqués dans l’automate SR_demiseconde(). 

  /////////////////////////////////////////

  // CAPTEURS ZONE D'ARRET

  /////////////////////////////////////////

 

  // traitement detecteur Zone AV (pres de gare 1)

  detecteur_ZAV = digitalRead(Pin_ZoneAV);

  if ((detecteur_ZAV != prev_detecteur_ZAV) && (detecteur_ZAV == 1) && (etat_detecteur_ZAV == 0))

  {  // detection debut de convoi (tete en AV, queue en AR)               

    etat_detecteur_ZAV = 1; // etat 1 = detection initiale début de convoi

    IRdebounceAVH = 2;      // arme tempo de confirmation après 0,2 sec

  } else {

    if ((detecteur_ZAV == 1) && (IRdebounceAVH == 0) && (etat_detecteur_ZAV == 1))

    { // validation debut de convoi

      etat_detecteur_ZAV = 2;  // etat 2 = en cours de passage devant le detecteur

      prev_detecteur_ZAV = 1;

      // TRAITEMENT TETE DE CONVOI

      digitalWrite(Pin_Arret, HIGH);

      if (DIR) {          // marche avant, raz TC

        TC = millis();

      } else {            // marche arriere, TC -> calcul vitesse

        TC = millis() - TC;

        if (MTC == 0) {MTC = TC;} else {MTC = (MTC + TC) / 2;}    // tend vers la moyenne

        TO = (byte)(MTC/500);                                     // nb de 1/2 secondes

        if (TO > Config_Init.pas_tempo_ligne) { TO = Config_Init.pas_tempo_ligne; }

        VCM = (1000*(unsigned long)Config_Init.distance_ligne)/TC;

        VKM = VCM*576/100;

      }

      // si auto : DIR = 1 entree dans ligne (vitesse constante) ou DIR = 0 ralentir avant arret gare 1

      if (AutoManu)

      {

        if (DIR)         // avant

        {

          // rien (entree dans canton ligne)

        } else {         // arriere

          Canton = 2;    // deceleration et arret

          PasAR[Canton]=Config_Init.distance_gare1;               

    // PasAR = distance capteur -> arret gare 1

          MMA = (unsigned int)Config_Init.distance_gare1*100;      

    // init distance restant a parcourir jusqu'a arret *100

          DDS = (unsigned int)VCM*50;                             

// init Distance parcourue par 1/2 seconde *100

        } 

      }

    } else {

      if ((detecteur_ZAV != prev_detecteur_ZAV) && (detecteur_ZAV == 0) && (etat_detecteur_ZAV == 2))

      { // transition vers 0 en cours de passage de convoi (inter-wagon ou queue)

        etat_detecteur_ZAV = 3; // etat 3 detection retour a 0

        IRdebounceAVL = 10;     // armement tempo 1 sec

      } else {

        if ((detecteur_ZAV == 1) && (IRdebounceAVL != 0) && (etat_detecteur_ZAV == 3))

        { // interwagon a ignorer

          etat_detecteur_ZAV = 2;

          IRdebounceAVL = 0;

        } else {

          if ((detecteur_ZAV == 0) && (IRdebounceAVL == 0) && (etat_detecteur_ZAV == 3))

          {

            // queue de convoi

            etat_detecteur_ZAV = 0;

            prev_detecteur_ZAV = 0;

            // TRAITEMENT QUEUE DE CONVOI

            digitalWrite(Pin_Arret, LOW);

          }

        }

      }

    }

  }

  // traitement detecteur Zone AR (pres de gare 2)

  detecteur_ZAR = digitalRead(Pin_ZoneAR);

  if ((detecteur_ZAR != prev_detecteur_ZAR) && (detecteur_ZAR == 1) && (etat_detecteur_ZAR == 0))

  {  // detection debut de convoi (tete en AV, queue en AR)               

    etat_detecteur_ZAR = 1; // etat 1 = detection initiale début de convoi

    IRdebounceARH = 2;      // arme tempo de confirmation après 0,2 sec

  } else {

    if ((detecteur_ZAR == 1) && (IRdebounceARH == 0) && (etat_detecteur_ZAR == 1))

    { // validation debut de convoi

      etat_detecteur_ZAR = 2;  // etat 2 = en cours de passage devant le detecteur

      prev_detecteur_ZAR = 1;

      // TRAITEMENT TETE DE CONVOI

      digitalWrite(Pin_Arret, HIGH);

      if (!DIR) {          // arriere, raz TC

        TC = millis();

      } else {             // avant, TC -> calcul vitesse

        TC = millis() - TC;

        if (MTC == 0) {MTC = TC;} else {MTC = (MTC + TC) / 2;}    

// tend vers la moyenne

        TO = (byte)(MTC/500);                                     

// nb de 1/2 secondes

        if (TO > Config_Init.pas_tempo_ligne) { TO = Config_Init.pas_tempo_ligne; }

        VCM = (1000*(unsigned long)Config_Init.distance_ligne)/TC;

        VKM = VCM*576/100;

      }

      // si auto : DIR = 1 ralentir avant arret gare 2 ou DIR = 0 entree dans ligne (vitesse constante)

      if (AutoManu)

      {

        if (DIR)          // avant

        {

          Canton = 3;     // deceleration et arret

          PasAV[Canton]=Config_Init.distance_gare2;               

// PasAV = distance capteur -> arret gare 2

          MMA = (unsigned int)Config_Init.distance_gare2*100;      

// init distance restant a parcourir jusqu'a arret *100

          DDS = (unsigned int)VCM*50;                             

// init Distance parcourue par 1/2 seconde *100

        } else {          // arriere

          // rien (entree dans canton ligne)

        }

      }

    } else {

      if ((detecteur_ZAR != prev_detecteur_ZAR) && (detecteur_ZAR == 0) && (etat_detecteur_ZAR == 2))

      { // transition vers 0 en cours de passage de convoi (inter-wagon ou queue)

        etat_detecteur_ZAR = 3; // etat 3 detection retour a 0

        IRdebounceARL = 10;     // armement tempo 1 sec

      } else {

        if ((detecteur_ZAR == 1) && (IRdebounceARL != 0) && (etat_detecteur_ZAR == 3))

        { // interwagon a ignorer

          etat_detecteur_ZAR = 2;

          IRdebounceARL = 0;

        } else {

          if ((detecteur_ZAR == 0) && (IRdebounceARL == 0) && (etat_detecteur_ZAR == 3))

          {

            // queue de convoi

            etat_detecteur_ZAR = 0;

            prev_detecteur_ZAR = 0;

            // TRAITEMENT QUEUE DE CONVOI

            digitalWrite(Pin_Arret, LOW);

          }

        }

      }

    }

  }

Ce capteur n’est pas implémenté pour le moment mais son emplacement est prévu pour une évolution future facile.

  /////////////////////////////////////////

  // CAPTEUR FIN DE VOIE

  /////////////////////////////////////////

  

  if (!digitalRead(Fin_De_Voie))            // detection si LOW 

  {

    // arret train immediat : eStop et led Rouge allumée

    // vitesse = 0, attente retour potentiometres à zero

    // passage en mode manuel

    

  }

Loop() ne contient aucun traitement du mode automatique. Ceux-ci sont réalisés dans la fonction SR_demiseconde().

  /////////////////////////////////////////

  // MODE AUTOMATIQUE

  /////////////////////////////////////////

  

  // voir automate dans SR_demiseconde()

  

Cette portion de code sert à déclencher les tâches périodiques, en utilisant le temps système.

  /////////////////////////////////////////

  // TACHES PERIODIQUES

  /////////////////////////////////////////

  

  if ((time500 + 500) < millis())  {

    time500 = time500 + 500;                 // pour rattraper les secondes si ça dérape

    SR_demiseconde();     // automate

  }

  

  if ((IRdebounce_time + IRdebtime) < millis()) {  // deboucing detecteurs IR

   IRdebounce_time = millis();

   if (IRdebounceAVH > 0) IRdebounceAVH--;    // tend vers 0 en IRdebtime ms

   if (IRdebounceAVL > 0) IRdebounceAVL--;    // tend vers 0 en IRdebtime ms

   if (IRdebounceARH > 0) IRdebounceARH--;    // tend vers 0 en IRdebtime ms

   if (IRdebounceARL > 0) IRdebounceARL--;    // tend vers 0 en IRdebtime ms

  }

  

  loopduration = micros() - looptime;

  looptime = micros();

  if (loopduration > max_loopduration)

  {

    max_loopduration = loopduration;

  }

} // fin de LOOP

La variable 32 bits time500 est comparée au temps système millis() et augmentée de 500 (millisecondes) à chaque concordance. Celle-ci apparait donc toutes les 1/2 secondes. Cela déclenche l’appel de la fonction SR_demiseconde().

J’ai testé divers intervals de temps (1 seconde; 1/4 seconde) et j’ai trouvé que la demi-seconde est un bon compromis.

La même technique est utilisée pour réaliser des « time-out » ou temporisations de confirmation des détecteurs de passage

Enfin, le temps système en microsecondes est aussi mesuré pour déterminer la durée d’exécution de la boucle loop().

Seule est retenue la durée la plus longue qui peut être consultée à la fin de la configuration (hormis le temps de cette configuration). Sur ce sujet, je dois chercher la cause qui fait passer cette durée du minimum d’ 1/2 miliseconde au maximum de plus de 30 milisecondes.


Fonctions de l’automate SR_demiseconde()

Nous allons maintenant passer en revue le module principal de l’automate : la fonction SR_demiseconde() qui s’exécute toutes les demi secondes.

///////////////////////////////////

void SR_demiseconde()

{

Clignotement des Leds : On utilise 2 bits de poids faible d’une variable « byte ».

Si cet octet est nul (tous les bits à 0) on ne fait rien.

Sinon, le clignotement est dérivé de la valeur du bit 1 qui est inversé à chaque appel de la fonction.

  if (surchargeBooster > 0)  {

    digitalWrite(Pin_Led_Booster, bitRead(surchargeBooster, 1));

    bitWrite(surchargeBooster, 1, !bitRead(surchargeBooster, 1));

  }

  if (Rouge_Clignotant > 0)  {

    digitalWrite(Pin_Arret, bitRead(Rouge_Clignotant, 1));

    bitWrite(Rouge_Clignotant, 1, !bitRead(Rouge_Clignotant, 1));

  }

  

  if (Jaune_Clignotant > 0)  {

    digitalWrite(Pin_MAR, bitRead(Jaune_Clignotant, 1));

    bitWrite(Jaune_Clignotant, 1, !bitRead(Jaune_Clignotant, 1));

  }

  

  if (Vert_Clignotant > 0)  {

    digitalWrite(Pin_MAV, bitRead(Vert_Clignotant, 1));

    bitWrite(Vert_Clignotant, 1, !bitRead(Vert_Clignotant, 1));

  }

Automate du mode automatique

  //////////// MODE AUTOMATIQUE //////////////

  

Mise à jour de l’état des Leds, à partir des variables DIR (direction) et vitesse.

  if (AutoManu) { // automate mode automatique (s'execute toutes les 1/2 secondes)

    digitalWrite(Pin_MAV, (DIR && (vitesse != _stop)));  

//Led Verte si DIR = AVANT ET PAS ARRET

    digitalWrite(Pin_MAR, (!DIR && (vitesse != _stop))); 

//Led Jaune si DIR = ARRIERE ET PAS ARRET

    digitalWrite(Pin_Arret, (vitesse == _stop));         

//Led Rouge si ARRET

        

Automate : Pour chaque sens de circulation (selon DIR) le mouvement du train est décomposé en 6 états appelés « Canton » de façon impropre (au départ il n’y en avait que 3, correspondant aux 3 cantons réels, puis d’autres cas sont apparus nécessaire. Mais le nom de la variable a été conservé). Dans chaque canton, une variable « Pas » évolue (partant d’une valeur initiale définie à la sortie du canton précédent, elle diminue jusqu’à 0, condition de passage au canton suivant).

Il y a une variable Pas pour chaque Canton, donc sous forme de tableau, bien que ce ne soit pas nécessaire.

A chaque appel de la fonction, la combinaison de DIR et Canton définit un module de traitement. Chaque module est décrit à l’aide de l’instruction « switch » qui est la plus pratique pour ce cas.

Cas de la marche avant : 

- 0: Le train reste à l’arrêt pendant le demi-arrêt en gare 1

- 1: Le train démarre et accélère jusqu’à atteindre la vitesse définie par le potentiomètre Avant

- 2: Le train circule à vitesse constante

- 3: Le train ralenti jusqu’à sa vitesse minimale

- 4- le train roule à vitesse minimale (devant le quai de la gare 2)

- 5: Le train s’arrête pendant le demi-arrêt en gare 2

    if (DIR)

    {                // Marche avant

      switch (Canton) {

0: Le train reste à l’arrêt pendant le demi-arrêt en gare 1

On se contente de décrémenter le Pas (configuration duree_arret_gare1)

        

        case 0:      // AV : arret en gare 1 avant départ

        vitesse = _stop;

        Rouge_Clignotant=0;

        PasAV[Canton]--;           // rester dans canton tant que PasAV > 0

        if (PasAV[Canton] == 0)  { // passage au canton suivant

          Canton = 1;              // passage au canton suivant : acceleration AV

          Vert_Clignotant=3;       // vert clignotant = acceleration dans l'etat suivant

        }

        break;

1: Le train démarre et accélère jusqu’à atteindre la vitesse définie par le potentiomètre Avant

On incréments la vitesse de la quantité en configuration increment_acceleration jusqu’à atteindre la vitesse de consigne

On initialise la durée maximale du canton suivant (en cas de défaut du détecteur)

         

        case 1:     // AV : depart de la gare 1 : acceleration jusqu'a speed_AV

        vitesse = vitesse + Config_Init.increment_acceleration;   

// acceleration constante, puis vitesse constante

        if (vitesse > speed_AV)

          {

            vitesse = speed_AV;

            Canton = 2;

            if (TO < Config_Init.pas_tempo_ligne)

            {

              PasAV[Canton] = TO + 10;

// butee de securite en cas de defaillance de capteur IR

            } else 

            {

            PasAV[Canton] = Config_Init.pas_tempo_ligne;  

            }                                                         

          }

        Vert_Clignotant=0;    // vert fixe : fin acceleration

        break;

2: Le train circule à vitesse constante

On décrémente le Pas. La détection de passage doit en principe intervenir avant que Pas = 0.

         

        case 2:               // AV : ligne a vitesse constante jusqu'a detecteur gare 2

        // vitesse constante

        PasAV[Canton]--;      // rester dans canton tant que PasAV > 0

        if (PasAV[Canton] == 0)  {      // passage au canton suivant

          Canton = 3;                   // passage au canton suivant : deceleration

          PasAV[Canton]=Config_Init.distance_gare2; 

// PasAV = distance capteur -> arret gare 2

          Vert_Clignotant=3;            // vert clignotant : deceleration

        }

        break;

3: Le train ralenti jusqu’à sa vitesse minimale

On diminue la vitesse de façon géométrique (80% de la vitesse précédente)

On calcule aussi la distance restant à parcourir que l’on compare à la configuration distance_gare2 (le code n’est pas encore conforme à cette spécification : il ne sera dans une prochaine version): ceci doit garantir l’arrêt du train exactement au point prévu, en bout de quai de gare, à condition d’avoir mesuré cette distance.

         

        case 3:     // AV : ralentissement jusqu' a V min avant arrivee en gare 2

        if (vitesse > Config_Init.vitesse_min)

        {

          PasAV[Canton] = PasAV[Canton] - vitesse/4; 

// decremente distance parcourue durant 1/2 seconde

          vitesse = vitesse*4/5;     //vitesse = deceleration geometrique;    

          if ((vitesse <= Config_Init.vitesse_min) || (vitesse > 126)) 

          {

            vitesse = Config_Init.vitesse_min;       

// vitesse est une valeur absolue jamais negative 

            Canton = 4;

            PasAV[Canton]=Config_Init.Nb_Vmin;       

// PasAV = Nb de 1/2 secondes à vitesse Vmin

          }

        }   

        break;

4- le train roule à vitesse minimale (devant le quai de la gare 2)

Pendant quelques Pas, le train roule très lentement, juste avant l’arrêt complet pour simuler la réalité au mieux

  

        case 4:   

        PasAV[Canton]--;          // decompte les 1/2 secondes a vitesse Vmin

        if (PasAV[Canton] == 0)   // si < 0 passage au canton suivant

        {

          Canton = 5;             // vers canton suivant : arret gare

          PasAV[Canton]=Config_Init.duree_arret_gare2;

// init 1/2 duree d'arret en gare 2

          Vert_Clignotant=0;     // vert eteint

        }

        break;

5: Le train s’arrête pendant le demi-arrêt en gare 2

Traitement similaire au cas 0

         

        case 5:                   // AV : arret en gare 2

        vitesse = _stop;

        PasAV[Canton]--;          // rester dans canton tant que PasAV > 0

        if (PasAV[Canton] == 0)  {      // passage au canton suivant

          DIR = 0;                      // changement de sens : marche AR

          Canton = 5;                   // vers canton suivant (marche arrière)

          PasAR[Canton]=Config_Init.duree_arret_gare2;  

// init 1/2 duree d'arret en gare 2

        }

        break;

      } 

    } // if DIR avant

    else

Cas de la marche arrière : 

- 5: Le train reste à l’arrêt pendant le demi-arrêt en gare 2

- 4: Le train démarre et accélère jusqu’à atteindre la vitesse définie par le potentiomètre Arrière

- 3: Le train circule à vitesse constante

- 2: Le train ralenti jusqu’à sa vitesse minimale

- 1- le train roule à vitesse minimale (devant le quai de la gare 1)

- 0: Le train s’arrête pendant le demi-arrêt en gare 1

    {  // DIR = 0 arriere

      switch (Canton) {

5: Le train reste à l’arrêt pendant le demi-arrêt en gare 2

        

        case 5:                     // AR : arret en gare 2

        vitesse = _stop;

        PasAR[Canton]--;            // rester dans canton tant que PasAV > 0

        if (PasAR[Canton] == 0)  {  // passage au canton suivant : acceleration en MAR

          Canton = 4;               // vers canton suivant

          Jaune_Clignotant=3;

        }

        break;

4: Le train démarre et accélère jusqu’à atteindre la vitesse définie par le potentiomètre Arrière

        case 4:                     // AR : depart de gare 2 : acceleration

        vitesse = vitesse + Config_Init.increment_acceleration;    

        if (vitesse > speed_AR) 

        {

          vitesse = speed_AR;

          Canton = 3;               // vers canton suivant : ligne

          if (TO < Config_Init.pas_tempo_ligne)

          {

            PasAR[Canton] = TO + 10;

// butee de securite en cas de defaillance de capteur IR

          } else 

          {

          PasAR[Canton] = Config_Init.pas_tempo_ligne;  

          }                                                         

        }

        Jaune_Clignotant=0;

        break;

3: Le train circule à vitesse constante

        case 3:     // AR : ligne a vitesse constante jusqu'a detecteur gare 1

        //vitesse constante

        PasAR[Canton]—;      // rester dans canton tant que PasAV > 0

        if (PasAR[Canton] == 0)  {    // passage au canton suivant

          Canton = 2;                 // vers canton suivant : deceleration

          PasAR[Canton]=Config_Init.distance_gare1;    

// PasAR = distance capteur -> arret gare 1

          Jaune_Clignotant=3;

        }

        break;

2: Le train ralenti jusqu’à sa vitesse minimale

        case 2:     // AR : ralentissement jusqu' a V min avant arrivee en gare 1   

        if (vitesse > Config_Init.vitesse_min)

        {

          PasAR[Canton] = PasAR[Canton] - vitesse/4;    

// decremente distance parcourue durant 1/2 seconde

          vitesse = vitesse*4/5;         //vitesse = deceleration geometrique;    

          if (vitesse <= Config_Init.vitesse_min || vitesse > 126) 

          {

            vitesse = Config_Init.vitesse_min; 

// vitesse est une valeur absolue jamais negative 

            Canton = 1;

            PasAR[Canton]=Config_Init.Nb_Vmin;          

// PasAV = Nb de 1/2 secondes à vitesse Vmin

          }

        }    

        break;

1- le train roule à vitesse minimale (devant le quai de la gare 1)

   

        case 1:     

        PasAR[Canton]--;          // decompte les 1/2 secondes a vitesse Vmin

        if (PasAR[Canton] == 0)  {      // si =  0 passage au canton suivant

          Canton = 0;                                  // vers canton suivant

          PasAR[Canton]=Config_Init.duree_arret_gare1; // init 1/2 duree arret en gare 1

          Jaune_Clignotant=0;

        }

        break;

0: Le train s’arrête pendant le demi-arrêt en gare 1

         

        case 0:                               // AR : 1/2 duree arret en gare 1  

        vitesse = _stop;

        PasAR[Canton]--;                      // rester dans canton tant que PasAV > 0

        if (PasAR[Canton] == 0)  {            // passage au canton suivant

          Canton = 0;                         // vers canton suivant

          DIR = 1;                            // changement de sens : marche AV

          PasAV[Canton]=Config_Init.duree_arret_gare1; // init 1/2 duree arret en gare 1

        }

        break;

      } // switch canton        

    } // if DIR arriere

Envoi des commandes DCC (vitesse et lumière)

    // commande DCC de vitesse et lumiere en auto

    _speed = vitesse;

// préserver la variable vitesse qui est toujours comprise entre 0 et 127

    if (DIR || (vitesse == _stop))

    {

      _speed = -vitesse;   // en avant le bit de poids fort est à 1 en 128 pas

    }                      // et à l'arret, la direction est = AVANT   

    if (!dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,_speed))

    {

     _erreurDPSS = true;

    }

    if (!dps.setFunctions0to4(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,FL))

    {

      _erreurDPSL = true;

    } // commandes DCC

  }   // if Automanu = Auto

Fin de l’automate en mode automatique !

On profite de cette fonction périodique pour assurer les affichages toutes les 1/2 secondes

  • un indicateur de fonctionnement suspect de la librairie CmdrArduino
  • les 3 écrans possibles en fonction de la variable Mode_EnCours

    

    // affichage des parametres en mode normal 

    lcd.clear();

    if (_erreurDPSS)

    {

      lcd.print(".");

      _erreurDPSS = false;

    }

    if (_erreurDPSL)

    {

      lcd.print(":");

      _erreurDPSL = false;

    }    

Affichage de la ligne 1

    strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));

    lcd.print(buffer);                          // affichage 1ere ligne

Affichage de la ligne 2

    switch (Mode_EnCours)

    {

      case 0:                // Mode_EnCours 0 : normal, affichage "DCC V DIR AV AR "

      lcd.setCursor(2, 1);

      lcd.print(Config_Init.dcc_adresse);

      lcd.setCursor(2, 5);

      lcd.print(vitesse);

      lcd.setCursor(2, 8);

      if (DIR) {lcd.print(">>");} else {lcd.print("<<");} 

      lcd.setCursor(2, 11);

      lcd.print(speed_AV, DEC);

      lcd.setCursor(2, 14);

      lcd.print(speed_AR, DEC);

      break;

      

      case 1:              // Mode_EnCours 1 : normal, affichage "V DIR Canton Pas"

      lcd.setCursor(2, 1);

      lcd.print(vitesse);

      lcd.setCursor(2, 4);

      if (DIR) {lcd.print(">>");} else {lcd.print("<<");} 

      lcd.setCursor(2, 8);

      lcd.print(Canton);

      lcd.setCursor(2, 14);

      if (DIR) {lcd.print(PasAV[Canton]);} else {lcd.print(PasAR[Canton]);}

      break;

      

      case 2:            // Mode_EnCours 2 : normal, affichage "Vit  Km/h  Cm/s "

      lcd.setCursor(2, 1);

      lcd.print(vitesse);

      lcd.setCursor(2, 7);

      lcd.print(VKM);

      lcd.setCursor(2, 13);

      lcd.print(VCM);

      break;

    }    

}


11- Realisation du logiciel Va-et-Vient (1)

Réalisation du logiciel avec l’IDE Arduino


Installation de l’IDE Arduino

Tous les détails de l’installation sont décrits sur le site Arduino : http://arduino.cc/en/Main/Software

L’installation est possible sous MacOS, Linux et Windows.

Pour plus de détails : http://arduino.cc/en/Guide/Environment

Et en français : http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.DebuterPresentationLogiciel



Pour ma part, j’ai installé également le logiciel Sublime Text : http://www.sublimetext.com

et son extension Arduino Stino : https://github.com/Robot-Will/Stino

Celle-ci apporte un confort supplémentaire par rapport à l’IDE Arduino seul.

Seule petite restriction : cela impose de placer les dossiers de librairies dans le dossier 



On trouvera ici le site français d’Arduino avec les références su langage C / C++ utilisé : http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.ReferenceEtendue

Ensuite il faut installer les librairies nécessaires, par la méthode décrite ici : http://arduino.cc/en/Guide/Libraries


Définition des librairies Arduino utilisées


// LIBRAIRIE CMDRARDUINO

#include <DCCPacket.h>              // librairie CmdrArduino

#include <DCCPacketQueue.h>         // idem

#include <DCCPacketScheduler.h>     // idem

Cette librairie prend en charge la génération du signal DCC sur la Pin 9 (elle se charge de l’initialisation de cette Pin).

On trouvera cette librairie ici : https://github.com/Railstars/CmdrArduino

Cette librairie est basée sur la gestion du Timer 1 (sous interruption) qui mobilise aussi la Pin 10. Celle-ci ne DOIT donc plus être utilisée.



// AUTRES LIBRAIRIES

#include <EEPROM.h>                 // librairie de gestion memoire EEPROM

Cette librairie permet de lire et écrire dans la mémoire EEPROM qui est non-volatile.

#include <Bounce.h>                 // librairie deboucing pour boutons et inters

Cette librairie permet de lire de façon fiable les actions sur les inters et boutons. En effet, ces contacts mécaniques ont l’inconvénient de « rebondir » et d’entrainer de fausses comandes que cette librairie permet d’éviter.

#include <avr/pgmspace.h>           // librairie de gestion memoire Flash

Cette librairie permet d’accéder à certaines variables internes

#include <SoftwareSerial.h>

Cette librairie est nécessaire pour la librairie serLCD

#include <serLCD.h>                 // librairie de gestion d'un LCD 16x2 via port Tx

Cette librairie permet la gestion d’un écran LCD (2 lignes de 16 caractères) avec les ordres habituels Serial.print et des commandes de positionnement simples.


Définition des Ports utilisés de l’Arduino


// Pins ARDUINO

#define Pot_AV             A0    // Analog IN : potar vitesse Avant

#define Pot_AR             A1    // Analog IN : potar vitesse Arriere

#define Current_Sense      A2    // Analog IN : courant Booster

#define Thermal_Sense      A3    // Analog IN : temperature Booster

#define Pin_AutoManu       A4    // IN  : commutateur de mode manuel (LOW) 

// ou automatique (HIGH)

#define Pin_SW_FL          A5    // IN  : commutateur d'eclairage (HIGH = ON, LOW = OFF)

                                 // les Pins 0 et 1 sont réservées pour Rx 

// et Tx (port série)

#define Pin_Tx             1     // Pour connexion au LCD via serLCD                                 

#define Pin_MAV            2     // OUT : led marche avant

#define Pin_MAR            3     // OUT : led marche arriere

#define Pin_Arret          4     // OUT : led arret

#define Pin_ZoneAV         5     // IN  : detecteur zone d'arret avant

#define Pin_ZoneAR         6     // IN  : detecteur zone d'arret arriere

#define Fin_De_Voie        7     // IN  : detecteur de fin de voie (les 2 combinées)

#define Pin_Mode           8     // IN  : poussoir de mode

#define Pin_Out_DIR        9     // OUT : Signal DIR pour LMD18200 

// pilote par CmdrArduino/Timer1

                                 // pin 10 reservee Timer 1 (ne peut être utilisée)

#define Pin_Out_PWM        11    // OUT : Signal PWM pour LMD18200

// HIGH en marche, LOW = stop

#define Pin_DCC            12    // IN  : commutateur marche/arret signal DCC 

#define Pin_Led_Booster    13    // OUT : led DCC OFF (continu) 

// ou surcharge booster (clignotant)


Définitions des variables


Pour fonctionner, le programme a besoin de manipuler des données dites « variables » qui sont stockées dans le SRAM. 

Les principales variables utilisées sont :


Pour la librairie CmdrArduino

// DCC handler

#define _stop 1                        // pour CmdrArduino 0->eStop et 1->regular stop

DCCPacketScheduler dps;                 // structure pour la librairie CmdrArduino

boolean DCC_EnService = false;          // autorise le Booster si true

boolean _erreurDPSS, _erreurDPSL = false; // pour une éventuelle détection d’erreur


Pour la configuration

typedef struct

{

    int dcc_adresse;                // @ DCC 128 crans. Attention, elle occupe 2 octets.

    byte vitesse_min;               // 2..10 a determiner en pilotage manuel

    byte increment_acceleration;    // 1..10

    byte duree_arret_gare1;         // 0..255 secondes

    byte duree_arret_gare2;         // 0..255 secondes

    byte distance_ligne;            // 1..255 centimetres

    byte distance_gare1;            // 1..255 centimetres

    byte distance_gare2;            // 1..255 centimetres

    byte pas_tempo_ligne;           // 1..255 pas de 1/2 seconde

    byte Nb_Vmin;                   // 1..20  nb de 1/2 secondes à Vmin avant Stop

} configuration;

configuration Config_Init;          // record de configuration copié en EEPROM


Pour le mouvement du train

unsigned int AV_value = 0;           // lecture du potentiometre vitesse avant

unsigned int AR_value = 0;           // lecture du potentiometre vitesse arriere

byte speed_AV, old_speed_AV = 0;     // vitesse AV (courante, précédente)

byte speed_AR, old_speed_AR = 0;     // vitesse AR

byte _speed, vitesse = 0;            // vitesse instantannee (selon direction)

boolean change_V = false;            // si true => change donc nouvelle commande DCC

byte DIR = 1;                        // marche avant: gare1>gare2 (1) 

    // ou arriere: gare2>gare1 (0)

byte Canton = 0;                     // 0=arret quai gare1, 1=depart acceleration, 

    // 2=ligne, 3=arrivee ralentissement, 

    // 4= vitesse min, 5=arret quai gare2

#define NBCantons 6                  // 6 cantons = 6 etats automate

#define NBPAS 10                     // nombre de pas de 1/2 sec par etat automate et 

    // par defaut si le detecteur n'a pas fonctionne

byte PasAV[NBCantons];               // pas en Avant : 1 valeur par canton 

byte PasAR[NBCantons];               // pas en Ariere : 1 valeur par canton 

byte TO;                             // time-out passage canton ligne calculé d'après 

    // le temps de passage entre capteurs

unsigned long TC, MTC, VKM, VCM = 0; // duree de passage entre capteurs, moyenne, 

    // vitesse (km/h) et vitesse (cm/s)

unsigned int MMA, DDS;               // Nb de millimetre restant a parcourir 

    // jusqu’a arret et decrement par 1/2 seconde


Boutons et Capteurs

Bounce B_Mode = Bounce(Pin_Mode, 100); // bouton MODE

byte prev_BM_state = 1;                // etat anterieur du poussoir Mode

byte BM = 0;                           // inactif (0) ou actif (1)

boolean Mode_Config = false;

byte Mode_EnCours = 0;                 // mode 0 : normal, affichage "DCC V DIR AV AR "

                                       // mode 1 : normal, affichage "V DIR Canton Pas"

                                       // mode 2 : normal, affichage vitesse reelle

                                       // mode 3 : config : @ dcc

                                       // mode 4 : config : V min

                                       // mode 5 : config : acceleration

                                       // mode 6 : config : t arret gare 1

                                       // mode 7 : config : t arret gare 2

                                       // mode 8 : config : l canton ligne

                                       // mode 9 : config : l canton gare 1

                                       // mode 10: config : l canton gare 2

                                       // mode 11: config : time-out n 1/2s

                                       // mode 12: config : nb 1/2s a Vmin

                                       

Bounce B_AutoManu = Bounce(Pin_AutoManu, 20); // clé AUTO/MANUEL

byte prev_AutoManu_state = 0;          // etat anterieur du commutateur auto-manuel

byte AutoManu = 0;                     // manuel (0) ou automatique (1)

Bounce B_SW_FL = Bounce(Pin_SW_FL, 20);// clé LUMIERE

byte prev_FL_state = 0;                // etat anterieur du bouton lumiere FL

byte FL = 0;                           // eclairage éteint (0) ou allumé (1)

Bounce B_DCC = Bounce(Pin_DCC, 20);    // clé ARRET/MARCHE DCC

byte prev_ON_DCC_state = 0;            // etat anterieur du bouton de signal DCC (pour etre OFF à l'init)

byte ON_DCC = 0;                       // DCC off (0) ou on (1)


Pour la tâche de clignotement des Leds

byte surchargeBooster = 0;     // 0 = OK, >0 = surcharge : clignote 1 éteint ou 3 allumé

byte Rouge_Clignotant = 0;     // 0 = normal, >0 (true) =  clignote 1 éteint ou 3 allumé

byte Jaune_Clignotant = 0;     // 0 = normal, >0 (true) =  clignote 1 éteint ou 3 allumé

byte Vert_Clignotant = 0;      // 0 = normal, >0 (true) =  clignote 1 éteint ou 3 allumé


Pour les Détecteurs de passage

boolean alarme_temperature = true;      // LOW si T>145°C au capteur, 

      //inversé à la mesure

int mesure_courant = 0; 

int detecteur_ZAV, prev_detecteur_ZAV, etat_detecteur_ZAV = 0;      

// detecteur de zones AV (gare 2)

int detecteur_ZAR, prev_detecteur_ZAR, etat_detecteur_ZAR = 0;      

// detecteur de zones AR (gare 1)

unsigned long IRdebounce_time;

#define IRdebtime  100                  // 10 milliseconde

byte IRdebounceAVH, IRdebounceAVL, IRdebounceARH, IRdebounceARL = 0;


Pour les taches périodiques

unsigned long time500, looptime;

int loopduration;                      // pour avoir idee de la duree de LOOP

int max_loopduration;


Pour l’écran LCD

serLCD lcd(Pin_Tx);

Cette ligne définit la classe « lcd » et initialise le port 1 


On définit ici les chaines de caractères qui seront affichées en 1ère ligne sur l’écran LCD

Ces chaines sont définies pour être stockées en mémoire Flash (avec le programme) et récupérées au coup par coup et une seule à la fois en SRAM, pour éviter de la saturer (plantage assuré).


prog_char string_0[]  PROGMEM = "DCC V DIR AV AR ";   // Mode_EnCours = 0

prog_char string_1[]  PROGMEM = "V DIR Canton Pas";   // Mode_EnCours = 1

prog_char string_2[]  PROGMEM = "Vit  Km/h  Cm/s ";   // Mode_EnCours = 2

prog_char string_3[]  PROGMEM = "Adresse DCC :   ";   // Mode_EnCours = 3  mode configuration à partir de 3

prog_char string_4[]  PROGMEM = "Vitesse min :   ";   // Mode_EnCours = 4

prog_char string_5[]  PROGMEM = "Acceleration :  ";   // Mode_EnCours = 5

prog_char string_6[]  PROGMEM = "T arret gare 1: ";   // Mode_EnCours = 6

prog_char string_7[]  PROGMEM = "T arret gare 2: ";   // Mode_EnCours = 7

prog_char string_8[]  PROGMEM = "L canton ligne: ";   // Mode_EnCours = 8

prog_char string_9[]  PROGMEM = "L canton gare 1:";   // Mode_EnCours = 9

prog_char string_10[] PROGMEM = "L canton gare 2:";   // Mode_EnCours = 10

prog_char string_11[] PROGMEM = "Time-out n 1/2s:";   // Mode_EnCours = 11

prog_char string_12[] PROGMEM = "Nb 1/2s Vmin :  ";   // Mode_EnCours = 12

PROGMEM const char *string_table[] =    // table des adresses

{   

  string_0,

  string_1,

  string_2,

  string_3,

  string_4,

  string_5,

  string_6,

  string_7,

  string_8,

  string_9,

  string_10,

  string_11,

  string_12

 };

char buffer[16];    // tampon de recopie en RAM (doit etre au moins aussi grand que le plus grand STRING


Initialisations (fonction setup)


void setup() {

  lcd.setBrightness(25);// rétro-éclairage du LCD 0..30

  lcd.clear();// effacement du LCD

  lcd.print("Initialisations ");// affichage en 1ère ligne

  lcd.selectLine(2);// positionnement en 2è ligne

  ///////////// initialisation des Pins

  //pinMode(Pot_AV, INPUT);  // entree analogique donc pas nécessaire

  //pinMode(Pot_AR, INPUT);  // entree analogique donc pas nécessaire

  //pinMode(Current_Sense, INPUT);  // entree analogique donc pas nécessaire

  pinMode(Thermal_Sense, INPUT_PULLUP);

  pinMode(Pin_AutoManu, INPUT_PULLUP);

  pinMode(Pin_SW_FL, INPUT_PULLUP);

  pinMode(Pin_Mode, INPUT_PULLUP);

  pinMode(Pin_DCC, INPUT_PULLUP);

  pinMode(Pin_Out_PWM, OUTPUT);

  //pinMode(Pin_Out_DIR, OUTPUT);       // initialisé par CmdrArduino

  digitalWrite(Pin_Out_PWM, LOW);       // DCC OFF

  pinMode(Pin_Led_Booster, OUTPUT);

  digitalWrite(Pin_Led_Booster, HIGH);  // Booster OFF (allume) ou CLIGNOTANT (surcharge)

  pinMode(Pin_MAV, OUTPUT);

  pinMode(Pin_MAR, OUTPUT);

  pinMode(Pin_Arret, OUTPUT);

  pinMode(Pin_ZoneAV, INPUT_PULLUP);

  pinMode(Pin_ZoneAR, INPUT_PULLUP);

  pinMode(Fin_De_Voie, INPUT_PULLUP);

  ////////////// initialisation de la librairie CmdrArduino

  dps.setup();                   // initialise les Pins 9 et 10

  prev_ON_DCC_state = B_DCC.read();   // force l'etat DCC off

// il faut une transition bas -> haut de Alim Rails

// pour autoriser l’alimentation des rails

   

  ////////////// Recuperation des valeurs de configuration en EEPROM 

  _Recup_EEPROM();// voir le détail de cette fonction plus loin

  

  lcd.print("DCC:");// affichage de l’adresse DCC en configuration

  lcd.print(Config_Init.dcc_adresse);

  Mode_EnCours = 0;// initialisation de la variable

  Mode_Config = false;// pas de mode configuration 

  

  ///////////// test de la configuration : si l’adresse DCC == 0 ou FF (EEPROM vierge):       

  ///////////// passage obligatoire en configuration

  if ((Config_Init.dcc_adresse == 0) || (Config_Init.dcc_adresse == 255))

  {

    Mode_EnCours = 3;

    Mode_Config = true;

    _Configuration();    // voir le détail de cette fonction plus loin

  }

  ///////////// iniialisation des compteurs de temps pour les taches periodiques

  time500 = millis();// pour déclencher la fonction SR_demiseconde()

  looptime = micros();// pour mesurer la durée moyenne de la LOOP()

  IRdebounce_time = millis();// pour l’anti-rebond des boutons

  lcd.print(" RAM:");

  lcd.print(_RamFree());

  delay(2000);

  Print_EEPROM();   // voir le détail de cette fonction plus loin

}// fin de la fonction setup()


La fonction setup() a fait appel aux 3 fonctions suivantes :

_Recup_EEPROM() à laquelle on ajoutera la fonction symétrique _Program_EEPROM(), puis Print_EEPROM()

et enfin _Configuration()


//////////////////////////////////////

void _Recup_EEPROM()

{

  int i;

  int s = sizeof(Config_Init);

  byte b[s];// tableau de lecture de l’EEPROM

  

  for (i = 0; i < s; i++)

  {

    b[i] = EEPROM.read(i);

    delay(10);

  }

// puis affectation des variables

  Config_Init.dcc_adresse = b[0]*256 + b[1]; 

// on assemble les 2 octets pour faire un ‘int'

  Config_Init.vitesse_min = b[2];

  Config_Init.increment_acceleration = b[3]; 

  Config_Init.duree_arret_gare1 = b[4];

  Config_Init.duree_arret_gare2 = b[5];

  Config_Init.distance_ligne = b[6];

  Config_Init.distance_gare1 = b[7];

  Config_Init.distance_gare2 = b[8];

  Config_Init.pas_tempo_ligne = b[9];

  Config_Init.Nb_Vmin = b[10];

}


//////////////////////////////////////

void _Program_EEPROM()

{

  int i;

  int s = sizeof(Config_Init);

  byte b[s];

  

  b[0] = highByte(Config_Init.dcc_adresse);  // séparation du int en 2 bytes

  b[1] = lowByte(Config_Init.dcc_adresse);

  b[2] = Config_Init.vitesse_min;

  b[3] = Config_Init.increment_acceleration;

  b[4] = Config_Init.duree_arret_gare1;

  b[5] = Config_Init.duree_arret_gare2;

  b[6] = Config_Init.distance_ligne;

  b[7] = Config_Init.distance_gare1;

  b[8] = Config_Init.distance_gare2;

  b[9] = Config_Init.pas_tempo_ligne;

  b[10] = Config_Init.Nb_Vmin;

 for (i = 0; i < s; i++)

  {

    EEPROM.write(i, b[i]);        //

    delay(10);                    // un petit délai pour garantir une bonne écriture

  }

}


///////////////////////////////////////

void Print_EEPROM()// cette fonction n’est pas très esthétique

{// mais elle permet de vérifier le bon fonctionnement

  lcd.clear();

  lcd.print("EEP ");                             //4

  lcd.print(Config_Init.dcc_adresse);            //+3=7

  lcd.print(" ");

  lcd.print(Config_Init.vitesse_min);            //+2=9

  lcd.print(" ");

  lcd.print(Config_Init.increment_acceleration); //+2=11

  lcd.print(" ");

  lcd.print(Config_Init.duree_arret_gare1);      //+2=13

  lcd.print(" ");

  lcd.print(Config_Init.duree_arret_gare2);      //+2=15

  lcd.selectLine(2);

  lcd.print(Config_Init.distance_ligne);         //3

  lcd.print(" ");

  lcd.print(Config_Init.distance_gare1);         //+3=6

  lcd.print(" ");

  lcd.print(Config_Init.distance_gare2);         //+3=9

  lcd.print(" ");

  lcd.print(Config_Init.pas_tempo_ligne);        //+3=12

  lcd.print(" ");

  lcd.print(Config_Init.Nb_Vmin);                //+3=15

  delay(2000);

}


La fonction _Configuration() est plus compliquée. Elle met en oeuvre un écran LCD par paramètre. 

Le texte de la question apparait sur la 1ere ligne du LCD

La valeur pré-existante est affichée sur la 2e ligne, suivie de « -> » 

Puis la valeur définie par l’opérateur à l’aide des 2 potentiomètres de vitesse !


Cette méthode permet de se passer d’un clavier 0..9. On utilise les potentiomètres car il est possible de lire leur position angulaire qui, après conversion analogique-digitale, donnera une valeur comprise entre 0 et 1023. Mais la précision des potentiomètres n’est pas bonne et il est nécessaire de « démultiplier » la valeur lue pour la ramener dans l’échelle 0..127.

Pour ce faire, on ajoute les lectures des 2 potentiomètres (résultat compris entre 0 et 2048) puis on divise le résultat par 16 : on obtient une plage comprise entre 0 et 127

Ce résultat est affiché à droite de « -> ».


La fonction tourne en boucle en affichant cette valeur tant qu’on n’appuie pas sur le bouton MODE.

Pour enregistrer une nouvelle valeur (affichée à droite de « -> »), il faut lever la clé LUMIERE puis appuyer sur MODE, puis abaisser la clé LUMIERE.

Dans ce cas, la valeur est écrite en EEPROM (« OK » apparait pendant 1 seconde) puis la configuration passe au paramètre suivant.

Sinon, rien n’est changé et le passage au paramètre suivant est réalisé. On peut ainsi consulter la configuration en laissant la clé LUMIERE en bas et en appuyant successivement sur MODE.


La fonction _Configuration() fait appel aux fonctions _questionB() et _questionC().

_questionB() traite uniquement le cas de l’adresse DCC qui occupe 2 octets de configuration en EEPROM

_questionC() traite les autres paramètres qui occupent seulement 1 octet.


void _Configuration()

{

  // version LCD (remplace la version Console via USB)

  // entrée : mode manuel, LUMIERE=0 - si OK suite de questions

  // sortie avec suite d’appuis sur bouton mode

  // Mode_EnCours = 3 à 13 (index des questions)

  

  // arret du train, stop DCC booster, vitesse AV/AR=0, pots à 0

  vitesse = _stop;

  dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,vitesse);

  ON_DCC = 0;

  digitalWrite(Pin_Out_PWM, ON_DCC);      // 0 = arret

  digitalWrite(Pin_Led_Booster, !ON_DCC); // 1 = led allumee 

  lcd.clear();

  

  // recopie de la chaine en Flash vers notre buffer

  // Cast et dereferencement nécessaire

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// adresse DCC

  _questionB();// attente d’appui sur MODE

  Mode_EnCours++;// question suivante

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// vitesse minimale

  Config_Init.vitesse_min = _questionC(Config_Init.vitesse_min, 2);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// incréments d’accélération

  Config_Init.increment_acceleration = _questionC(Config_Init.increment_acceleration, 3);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1); // 1/2 durée arrêt en gare 1

  Config_Init.duree_arret_gare1 = _questionC(Config_Init.duree_arret_gare1, 4);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// 1/2 durée arrêt en gare 2

  Config_Init.duree_arret_gare2 = _questionC(Config_Init.duree_arret_gare2, 5);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// Distance zone Ligne en cm

  Config_Init.distance_ligne = _questionC(Config_Init.distance_ligne, 6);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// Distance zone Gare 1 en cm

  Config_Init.distance_gare1 = _questionC(Config_Init.distance_gare1, 7);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// Distance zone Gare 2 en cm

  Config_Init.distance_gare2 = _questionC(Config_Init.distance_gare2, 8);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// Nombre de 1/2 secondes maximum sur zone Ligne

  Config_Init.pas_tempo_ligne = _questionC(Config_Init.pas_tempo_ligne, 9);   

  Mode_EnCours++;

  lcd.clear();

  

  strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours]))); 

  lcd.print(buffer);                    // affichage 1ere ligne

  lcd.setCursor(2, 1);// Nombre de 1/2 secondes à vitesse Vmin

  Config_Init.Nb_Vmin = _questionC(Config_Init.Nb_Vmin, 10);   

  Mode_EnCours = 0;

  Mode_Config = false;

  lcd.clear();

  

  lcd.print("Fin Config ");

  lcd.print(_RamFree());

  lcd.setCursor(2, 1); 

  lcd.print(max_loopduration);

  max_loopduration = 0;

  delay(2000);

}


_questionB() sert à renseigner uniquement la valeur de l’adresse DCC de la loco en service et de l’enregistrer en EEPROM. Elle n’a pas de paramètre en entrée.

La clé LUMIERE doit être positionnée en bas (lumière éteinte).

Après affichage de la valeur initiale une boucle « while » tourne tant que le booléen RXIT est faux.

Le booléen ROK passe à vrai si on lève la clé LUMIERE.

RXIT passe à vrai si on appuie sur le bouton MODE : on sort de la boucle sans modification si ROK est faux ou en modifiant le paramètre si ROK est vrai.


////////////////////////////

void _questionB()                             // adresse DCC, Mode_EnCours = 3

{

  boolean ROK = false;

  boolean RXIT = false; 

  byte Valeur = 0;

  

  lcd.print(Config_Init.dcc_adresse);       // ancienne valeur sur 2e ligne, max 3 cars

  lcd.print(" ->");                         // choix à afficher en ligne 2, colonne 8

  while (!RXIT) 

  {

                                 // on se sert de la somme des potentiometres

    Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 4; 

//division par 16 pour ramener le champ 0-2047 à 0-127

    lcd.setCursor(2, 6);

    lcd.print(Valeur);

    lcd.print("  ");             // pour effacer les caracteres suivants eventuels

    if (B_Mode.update())         // Poussoir Mode change-t-il ?

    {

      if (B_Mode.risingEdge()) 

      {

        RXIT = true;             // sortie de la boucle while

     }

    }

    if (B_SW_FL.update())        // COMMUTATEUR DE LUMIERE

    {

      if (B_SW_FL.read() == HIGH)

      {        

        Config_Init.dcc_adresse = Valeur; // validation 

        ROK = true;

      } 

    }

  }        // while !ROK

  

  if (ROK) 

  {

// enregistrement en EEPROM

    EEPROM.write(0, highByte(Config_Init.dcc_adresse)); 

    delay(10);

    EEPROM.write(1, lowByte(Config_Init.dcc_adresse));

    lcd.print(" Ok!");

    delay(1000);

  }

}


_questionC() sert à renseigner les autres paramètres et les enregistrer en EEPROM. Elle a 2 paramètres en entrée : l’index du paramètre et sa valeur initiale.

La clé LUMIERE doit être positionnée en bas (lumière éteinte).

Après affichage de la valeur initiale une boucle « while » tourne tant que le booléen RXIT est faux.

Le booléen ROK passe à vrai si on lève la clé LUMIERE.

RXIT passe à vrai si on appuie sur le bouton MODE : on sort de la boucle sans modification si ROK est faux ou en modifiant le paramètre si ROK est vrai.


/////////////////////////////////////

int _questionC(int _VAL, int _index)        // Mode_EnCours > 3

{

  boolean ROK = false;

  boolean RXIT = false;

  byte Valeur = 0;

  

  lcd.print(_VAL);                // ancienne valeur sur 2e ligne, max 3 cars

  lcd.print(" ->");               // choix à afficher en ligne 2, colonne 8

  while (!RXIT) 

  {

                                  // on se sert de la somme des potentiometres

    Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 4;

//division par 16 pour ramener le champ 0-2047 à 0-127

    lcd.setCursor(2, 6);

    lcd.print(Valeur);

    if (B_Mode.update())          // Poussoir Mode change-t-il ?

    {

      if (B_Mode.risingEdge()) 

      {

        RXIT = true;              // sortie de la boucle while

     }

    }

    if (B_SW_FL.update())         // COMMUTATEUR DE LUMIERE

    {

      if (B_SW_FL.read() == HIGH)

      {

        _VAL = Valeur;// validation

        ROK = true;

      } 

    }

  }        // while !ROK

  if (ROK) 

  {

// enregistrement en EEPROM

    EEPROM.write(_index, _VAL);

    lcd.print(" Ok!");

    delay(1000);

  }

  return(_VAL);// retour avec le paramètre modifié ou non

}


Voici des exemples d’écrans obtenus : 



En fin de configuration, le dernier écran affiche la quantité de mémoire SRAM restant et la plus grande durée de la boucle principale LOOP (en micro-secondes) depuis le dernier affichage.



10- Description de la realisation de la centrale Va-et-Vient

Description de la réalisation


La réalisation de cette nouvelle centrale, destinée à assurer le mouvement d'un train entièrement automatique, est décrite à des fins principalement didactiques. Celle-ci comprend en effet la résolution d’un ensemble de problématiques au sein d’un même projet Arduino, tels que :

- choix d’un modèle Arduino bien adapté aux connexions choisies,

- choix du booster,

- choix de la librairie de génération du signal DCC sous interruption et sa mise en oeuvre,

- fonctionnement multi-tâches, et aussi temps réel que possible,

- mise en oeuvre d’une interface utilisateur simple mais complète à base d’écran LCD et très peu de boutons,

- mise en oeuvre d’un automate programmable,

- intégration des capteurs de passage dans les évolutions de l’automate,

- suivi rapide de ce qui se passe dans la centrale (suivi de l’automate),

- et bien d’autres qui apparaitront plus loin.

La réalisation présentée ici est complète : elle couvre la totalité des besoins à l’exception du circuit ferroviaire et des trains. 

Elle est simple et très peu coûteuse (un Arduino Nano et un booster LMD18200, une alimentation 12V 2A, quelques Leds, interrupteurs et 2 potentiomètres, ainsi qu’un afficheur LCD coûtent au maximum 50€).

Le besoin est d’animer un réseau à voie unique ou un diorama en démonstration lors d'une exposition. Le trafic doit être automatique pour permettre aux organisateurs de se consacrer pleinement aux visiteurs. Chaque modéliste ferroviaire peut facilement trouver l’application de ce projet dans son propre réseau : il suffit de dédier une voie à ce projet, sans liaison avec le reste du réseau (rien n’empêche d’ailleurs de relier ce réseau au reste, avec l’aide d’un inverseur bipolaire pour couper la centrale Arduino durant cette liaison).

Voici le cahier des charges et les ingrédients nécessaires à la réalisation de cette centrale :


Fonctionnalités


Scénario : Sur une longueur de voie (paramètrable), une locomotive part d'une gare de départ en accélérant doucement, jusqu'à un palier de vitesse "avant", passe un premier détecteur de passage, circule à vitesse constante jusqu'à un deuxième détecteur, à partir duquel elle entame un ralentissement réaliste et une entrée en gare d'arrivée avec arrêt en douceur.

Après un temps d'arrêt, la locomotive repart en sens inverse et retourne à la gare de départ en marche arrière avec une vitesse "arrière" éventuellement différente.


Les fonctionnalités ont été décrites en détail dans le billet précédent.


Les ingrédients


 Vu le faible nombre de ports nécessaires, j'ai choisi un Arduino Nano, tout à fait suffisant avec ses 22 ports. 


Microcontrolleur : Atmel ATmega328

Voltage (niveau logique) : 5 V

Voltage (alimentation recommendée) : 7-12 V

Voltage (limites d’alimentation) : 6-20 V

Ports Digitaux I/O : 14 (of which 6 provide PWM output)

Ports Analogiques : 8

Port USB (programmation et alimentation) : 1

Current maximum par port I/O : 40 mA

Mémoire Flash : 32 KB (ATmega328) dont 2 KB occupés par le bootloader

SRAM :  2 KB (ATmega328)

EEPROM :  1 KB (ATmega328)

Vitesse d’horloge : 16 MHz

Dimensions : 1,85 x 4,32 cm

Au départ je n’avais pas prévu d’écran LCD car j’utilisais abondamment la console série de l’IDE Arduino, pour la mise au point, la configuration, l’affichage des informations pertinentes. Par la suite, j’ai décidé de ne pas  utiliser d’ordinateur extérieur : il a fallu greffer l’écran LCD. 

Ne disposant pas de ports en nombre suffisant pour une connexion parallèle (7 au minimum, dont un pour le rétro-éclairage), j’ai choisi un écran LCD équipé d’un convertisseur Série SerLCD : 3 fils suffisent pour le faire fonctionner (+5, 0v et Tx1).

 

Il faut évidemment le booster LMD18200, capable de délivrer 3 A: Ne pas oublier de relier la borne Brake au 0v.

Puis 2 détecteurs à barrières infrarouge (faits maison et décrits par ailleurs dans ce blog).  

Et enfin : 2 potentiomètres (10 K), 7 Leds (2 vertes, 3 jaunes et 2 rouges), 4 interrupteurs (inverseurs à clé) et un bouton poussoir. Sans oublier du fil de diverses couleurs, quelques résistance, fer à souder et soudure et des dominos de raccordement

Remarques :

  • La mémoire Flash permet de stocker le programme visé sans problème. celui-ci n’occupe que la moitié de la place disponible et permet donc de futures extensions.
  • La mémoire EEPROM est réservée au stockage des paramètres de configuration
  • La mémoire SRAM est petite (2K) et nécessitera des optimisations qui sont expliquées dans le code.

La compilation du logiciel décrit ci-après indique :

Binary sketch size: 16004 bytes (of a 30720 byte maximum, 52.10 percent).

Estimated memory use: 531 bytes (of a 2048 byte maximum, 25.93 percent).



9- Spécifications et mode d'emploi de la centrale Va-et-Vient

Présentation





Au centre, 2 potentiomètres : Vitesse en Marche Avant et Vitesse en Marche arrière

A droite, de bas en haut :

- 1 interrupteur ON/OFF : arrêt de la Centrale vers le bas, marche vers le haut.

- 1 Led verte : présence d’une alimentation 12 à 15 V.

- 2 Leds jaunes : présence de l’énergie (DCC) sur les rails (une led pour chaque polarité de la tension).

- 1 Led rouge : arrêt du signal DCC (manuel ou sur défaut).

- 1 interrupteur d’envoi de l’énergie DCC sur les rails (arrêt en bas, marche en haut).

le résultat est affiché sur les 2 Leds jaunes (présence) ou la Led rouge (arrêt).

A gauche, de bas en haut :

- 1 Led jaune : mouvement du train en marche arrière.

- 1 Led rouge : arrêt du train ou passage d’un détecteur infrarouge.

- 1 Led verte : mouvement du train en marche avant.

A gauche/centre, de bas en haut :

- 1 clé Lumière (fonction FL) : arrêt vers le bas, marche vers le haut.

- 1 clé Auto / Manuel : Manuel vers le bas, Auto vers le haut.

- 1 bouton poussoir Mode (choix des affichages et configuration)

En dessous, un écran LCD de 2 lignes de 16 caractères rétro-éclairé.


Fonctionnalités

Pilotage d’une seule locomotive ou train en mode DCC, 128 crans de vitesse. L’adresse DCC est programmable.

Un écran LCD permet de visualiser les paramètres de fonctionnement.

Deux réglages de vitesse indépendants pour la marche avant et la marche arrière:

- en mode manuel, c’est la vitesse la plus grande qui impose le sens de la marche;

- en mode automatique, chaque sens dispose de sa propre vitesse;

- en mode configuration, les valeurs de 0 à 128 sont obtenues par l’addition de la rotation des 2 boutons.

Un commutateur permet la mise en ou hors service de l’éclairage de la locomotive.

Un commutateur permet le choix entre un pilotage manuel ou un Va-et-Vient automatique.

Un bouton poussoir « MODE » permet de choisir parmi 3 affichages et 10 écrans de configuration.

3 Leds, à gauche, visualisent la marche du train :

- vert : marche avant

- rouge : arrêt.  Cette Led s’allume aussi au passage du train devant un capteur IR

- jaune : marche arrière

3 Leds, à droite, visualisent le fonctionnement de la centrale

- vert : présence tension si une alimentation 12 à 15 V est raccordée et en service

- rouge : aucun courant n’est envoyé aux rails (arrêt du Booster)

- jaune (paire) : visualise la tension sur les rails (l’inversion du courant permet d’allumer les 2 Leds)

Un commutateur Arrêt/Marche allume la centrale qui démarre une séquence d’initiatisation.

Un commutateur d’alimentation des rails autorise l’envoi du courant DCC sur les rails (il est recommandé de couper l’alimentation DCC des rails avant d’installer ou retirer une loco).

Mise en route


Le commutateur ON/OFF permet la mise en route de la centrale. Les séquence suivantes se déroulent :

- l’écran LCD s’allume

- la Led rouge DCC Stop s’allume

- L’écran LCD affiche successivement :

--- C.M.F.A. ---

Va et Vient N

Initialisations

DCC:4 RAM:1247

L’écran affiche l’adresse DCC enregistrée et la mémoire disponible 

EEP 4 3 2 5 5

80 25 25 20 6 

L’écran affiche les valeurs de configuration stockées en EEProm

DCC V DIR AV AR

 4   1  >> 1  1

L’écran 1 affiche l’adresse DCC, la vitesse (en crans DCC), la direction (>> avant, << arrière) en usage et les valeurs de vitesse données par les potentiomètres.

A ce stade, si le commutateur Auto/Manuel est en position Manuel, le train peut être piloté avec les 2 potentiomètres.

La lumière est commandée par le commutateur Lumière 

Un appui sur le bouton MODE permet d’afficher :

V DIR Canton Pas

1  >>  0     0

L’écran 2 affiche la vitesse du train, sa direction et l’état d’automate (utile seulement en mode AUTO)

 

Un dernier appui sur le bouton MODE permet d’afficher :

Vit  Km/h  Cm/s

30    69    12

L’écran 3 affiche la vitesse du train, en Crans DCC, en Km/h et en Cm/s

Un nouvel appui revient à l’écran 1, si les 2 commutateurs Lumière et Auto/Manu ne sont pas en position basse (Lumière éteinte et mode Manuel). Sinon, la centrale passe en mode configuration :

Configuration

Si les 2 commutateurs Lumière et Auto/Manu sont en position basse (Lumière éteinte et mode Manuel), la centrale stoppe le train (Leds DCC Rails éteintes, DCC stop allumée) et permet de modifier les 10 paramètres suivants. 

Pour changer un paramètre, tourner les 2 potentiomètres de vitesse jusqu’à l’affichage de la valeur désirée, puis lever le commutateur de Lumière et appuyer sur le bouton Mode (l’indication « OK » reflète l’enregistrement du paramètre). Remettre aussitôt le commutateur Lumière en position basse.

Si le commutateur Lumière n’est pas levé avant l’appui sur Mode, le paramètre n’est pas modifié.

L’appui sur Mode permet de passer au paramètre suivant.

Adresse DCC :  

4 -> x   

 

1- L’adresse DCC de la loco doit être comprise entre 1 et 127.

Vitesse min :  

3 -> x 

2- La vitesse (cran DCC) minimal de la loco peut être déterminée en marche manuelle (tester en marche avant et arrière)

Acceleration : 

2 -> x 

3- Les incréments de crans de vitesse en accélération permettent un réalisme plus grand.

T arret gare 1: 

5 -> x

4- La durée d’arrêt en gare de départ (gare 1) en secondes

T arret gare 2: 

5 -> x

5- La durée d’arrêt en gare d’arrivée (gare 2) en secondes

L canton ligne: 

80 -> x

6- La longueur du canton central, entre les 2 capteurs IR, en Cm

L canton gare 1:

25 -> x

7- La longueur du canton de départ, entre la gare 1 et le capteur IR 1, en Cm

L canton gare 2:

25 -> x

8- La longueur du canton d’arrivée, entre la gare 2 et le capteur IR 2, en Cm

Time-out n 1/2s:

20 -> x

9- La durée maximum supportée de transit entre les 2 capteurs IR (évite le dépassement de la gare si un capteur ne fonctionne pas)

Nb 1/2s Vmin : 

6 -> x

10- La durée en 1/2 sec de la phase finale à vitesse minimale, avant arrêt au quai.


Mode Manuel


Le commutateur Auto/Manuel est en position « Manuel ». 

Les potentiomètres de vitesse sont au minimum (vitesse affichée : 1).

Le commutateur Alim Rails est en position haute et les Leds DDC Rails sont allumées (sinon manoeuvrer le commutateur vers le bas puis vers le haut car seuls les changements sont pris en compte).

Tourner le potentiomètre de Vitesse Avant si la poco est en gare 1 ou le potentiomètre de Vitesse Arriere si la poco est en gare 2. La loco démarre 

Consulter la vitesse (Crans DCC) sur l’afficheur LCD : 



La vitesse est calculée à partir du temps séparant les passages des détecteurs et la distance configurée dans le paramètre 6.

Il est possible de connaitre la vitesse réelle, mesurée entres les passages devant les 2 capteurs Infrarouges en appuyant sur le bouton Mode, le commutateur Lumière étant en position haute (allumé). Cette mesure facilite la configuration par la connaissance de la vitesse en cm/s et la mesure des sections de voies. 


Il est important de procéder à quelques trajets manuels car le logiciel enregistre les durées de passage et détermine certains paramètres à partir de cette expérience.

Si un fonctionnement erratique est constaté en mode automatique, il se peut que cela soit dû à une insuffisance de tests de trajets en mode manuel.


Mode Automatique


Le train doit, au préalable être placé au niveau de la gare de départ (gare 1).

Lorsque la clé Auto/Manuel est levée, le fonctionnement en mode Automatique démarre.

Les séquences suivantes se déroulent automatiquement :

  • 1/2 arrêt en gare de départ (valeur en configuration, paramètre 4);
  • accélération constante : incrément de vitesse ajouté toutes les 1/2 secondes (paramètre 3) jusqu’à ce que la vitesse Avant (potentiomètre haut) soit atteinte;
  • vitesse constante jusqu’à détection de la loco par le détecteur 2;
  • décélération géométrique (4/5 de la vitesse calculés toutes les 1/2 secondes) jusqu’à atteinte de la vitesse minimum;
  • avancement à vitesse minimum pendant la durée du paramètre 10;
  • arrêt en gare d’arrivée (2). 

Exemple de courbe de vitesse typique.

Le processus se déroule de façon symétrique dans l’autre sens :

  • 1/2 arrêt en gare de d’arrivée (valeur en configuration, paramètre 5);
  • accélération constante : incrément de vitesse ajouté toutes les 1/2 secondes (paramètre 3) jusqu’à ce que la vitesse Arriere (potentiomètre bas) soit atteinte;
  • vitesse constante jusqu’à détection de la loco par le détecteur 2;
  • décélération géométrique (4/5 de la vitesse calculés toutes les 1/2 secondes) jusqu’à atteinte de la vitesse minimum;
  • avancement à vitesse minimum pendant la durée du paramètre 10;
  • arrêt en gare de départ (1).

Le passage entre les 2 capteurs donne toujours lieu à un calcul de la vitesse. La centrale connait donc la vitesse du train au passage du capteur d’arrivée, au début de la séquence de décélération.

Puisqu’elle connait la distance entre ce capteur et le point d’arrivée, elle peut assurer l’arrêt du train à ce point.

Réglages en mode automatique :

- placer la loco à la gare de départ

- tourner la clé en mode AUTO

- régler la vitesse Avant avec le potentiomètre haut

- régler la vitesse Arrière avec le potentiomètre bas

- observer le mouvement du train

- si nécessaire, modifier les paramètres de configuration

On peut observer le déroulement des phases de l’automate en affichant l’écran N°2 qui présente la vitesse, la direction, le N° de Canton et le pas qui se décréments dans ce canton :




Plan d'implantation de la centrale avec le réseau


Les connexions entre la centrale et le réseau sont représentées sur ce schéma. La centrale a besoin d’une alimentation (12 à 15 V maximum). Elle alimente directement les 2 rails (DCC Rails). Les 2 détecteurs de passage sont reliés chacun par 3 fils (+5v, masse/Gnd et signal)



mardi 13 mai 2014

8- Une centrale dédiée à un automatisme de Va-et-Vient (présentation)

Ce nouveau billet introduit une nouvelle centrale destinée à assurer le mouvement d'un train entièrement automatique (mais avec un mode manuel tout de même !).

Le besoin est celui de tout club ayant à mettre un réseau ou un diorama en démonstration lors d'une exposition. 

Au moins un sous-ensemble du trafic doit être automatique pour permettre aux organisateurs de se consacrer pleinement aux visiteurs. Mais cela peut s'appliquer aussi à nos réseaux personnels. 

Il s'agit d'animer un train, une loco ou un autorail sur une voie indépendante, avec sa propre centrale. 


A partir du moment où il est facile de construire une petite centrale basée sur un module Arduino capable de générer un signal DCC vers un Booster relié à la voie, d'y ajouter quelques détecteurs de passage (infrarouge comme dans ce site ou détection de courant avec sortie adaptée à l'Arduino), et d'écrire les lignes de code d'un automatisme, le projet devient rapidement réalité. 


Voici le cahier des charges et les ingrédients nécessaires à la réalisation de cette centrale dont j'ai commencé la réalisation qui sera détaillée dans ce site :


__FONTIONNALITES__



Scénario : Sur une longueur de voie (paramètrable), une locomotive part d'une gare de départ en accélérant doucement, jusqu'à un palier de vitesse "avant", passe un premier détecteur de passage, circule à vitesse constante jusqu'à un deuxième détecteur, à partir duquel elle entame un ralentissement réaliste et une entrée en gare d'arrivée avec arrêt en douceur.

Après un temps d'arrêt, la locomotive repart en sens inverse et retourne à la gare de départ en marche arrière avec une vitesse "arrière" éventuellement différente.

Voici le genre de courbe de vitesse que j'ai voulu réaliser :

/public/courbedevitesse.jpg|Courbe De Vitesse||Courbe De Vitesse, mai 2014

Le cahier des charges

Pilotage d’une seule locomotive en mode DCC, 128 crans de vitesse.

  • L’adresse DCC est programmable.

  • Un écran LCD permet de visualiser et modifier les paramètres de fonctionnement.

  • Deux réglages de vitesse indépendants pour la marche avant et la marche arrière.
- en mode manuel, c’est la vitesse la plus grande qui impose le sens de la marche.
- en mode automatique, chaque sens dispose de sa propre vitesse
- en mode configuration, les valeurs de 0 à 128 sont obtenues par l’addition de la rotation des 2 boutons

  • Un commutateur permet la mise en ou hors service de l’éclairage de la locomotive.

  • Un commutateur permet le choix entre le pilotage manuel ou le Va-et-Vient automatique.

  • Un bouton poussoir « MODE » permet de choisir parmi 3 affichages et 10 écrans de configuration.

  • 3 Leds visualisent la marche du train :
        - vert : marche avant
        - rouge : arrêt. Cette Led s’allume aussi au passage du train devant un capteur IR
        - jaune : marche arrière

  • 3 Leds visualisent le fonctionnement de la centrale
       - vert : présence tension si une alimentation 12 à 15 V est raccordée et en service
       - rouge : aucun courant n’est envoyé aux rails (arrêt du Booster)
       - jaune (une paire de leds, une pour chaque sens de la polarité) : visualise la tension sur les rails

  •  Un commutateur Arrêt/Marche allume la centrale qui démarre une séquence d’initiatisation.

  • Un commutateur d’alimentation des rails autorise l’envoi du courant DCC sur les rails (il est recommandé de couper l’alimentation DCC des rails avant d’installer ou retirer une loco).


 Les ingrédients


Vu le faible nombre de ports nécessaires, j'ai choisi un Arduino Nano.


 et le traditionnel booster LMD18200


ainsi que mes barrières à détecteurs infrarouge bien aimées


 * Un petit réseau d'essais


avec deux capteurs délimitant les zones de gare de départ et d'arrivée




 * J'ai ajouté un afficheur LCD, de 2 lignes de 16 caractères rétro-éclairé et équipé d'une interface série connectée à TX, +5 et Gnd

 La centrale prend cette forme là :



A suivre...

dimanche 6 octobre 2013

7- Architecture générale de la Centrale DCC avec TCO



Lire la suite...

jeudi 23 mai 2013

6- Construction d'un TCO (boitier et câblage)





Lire la suite...

5- Construction d'un Chalet Savoyard à l'échelle N



Voici quelques photos de la réalisation de mon chalet savoyard, à partir de feuilles de bois tirées d'un petit cageot, de pics à brochette, cure-dents, plastique d'emballages et papier :

Un exemple de cagette qui convient bien : Les plis du bois constituent les angles des murs. Deux morceaux en forme de L forment les murs.

cajette.jpg

Le chalet est à l'échelle N (c'est petit !). Je suis parti de photos prises sur place en Savoie et j'ai fait un croquis à l'échelle 1/160e.

Découpe au cutter et ciseaux, collage à la colle à bois.


chalet6.1.jpg

Les fenêtres sont découpées dans du plastique d'emballage transparent, collé à l'interieur à l'époxy. Les montants des fenêtres sont représentés par de fines bandes de scotch d'emballage marron. Les rideaux sont en papier japon froissé et collé.

IMG_2542.jpg

chalet3.jpg

chalet4.jpg

chalet5.jpg

chalet6.jpg


Le toit est recouvert de tavaillons réalisés en minuscules rectangles de papier (le plus fastidieux car 1 x 2 mm chacun environ), collé à la colle à bois et recouvert de vernis teinté.

chalet7.jpg

Le balcon est réalisé avec des chutes de bois et des cure-dents, comme le tas de bois sur le coté.

chalet8.jpg

chalet9.jpg

chalet10.jpg

C'est un sacré boulot que de creuser un pic à brochette pour réaliser les gouttières !

Il reste à installer des led de couleur dans le chalet et simuler la lumière changeante d'une TV et quelques lampes !!!

Suivant : Construction d'un TCO (boitier et câblage)

Précédent : Détecteur de passage RFID

mercredi 17 avril 2013

4- Un détecteur de passage RFID pour la reconnaissance du train.



Lire la suite...

lundi 18 février 2013

3- Un détecteur de passage à barrière infrarouge



Lire la suite...

vendredi 8 février 2013

2- Construction de la mini centrale



Lire la suite...

jeudi 7 février 2013

1- Mini centrale DCC à construire avec Arduino

Définitions du projet.

Lire la suite...