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;

    }    

}