LOCODUINO

Aide
Forum de discussion
Dépôt GIT Locoduino
Flux RSS

vendredi 24 mars 2023

49 visiteurs en ce moment

Comment piloter trains et accessoires en DCC avec un Arduino (4)

Les pourquoi et comment des modules logiciels

. Par : Dominique

DIFFICULTÉ :

Ce dernier article décrit la réalisation du logiciel de notre va-et-vient.
Il est fait pour être modifié, étendu, complété par vous-même, selon vos propres désirs.
C’est donc d’abord une source d’inspiration que je vous propose selon le principe du logiciel libre, mais sans garantie de bon fonctionnement, même si le projet fonctionne bien pour moi.

En fin d’article, le source complet sera disponible.
Ce logiciel n’est pas optimisé, et les plus expérimentés pourront faire mieux (et m’en faire profiter s’ils le veulent bien).

On trouvera également quelques pistes d’évolutions qui, je l’espère, exciteront votre apétence pour Arduino.

L’Organigramme

Reportons nous aux spécifications de notre projet décrites dans l’article précédent.
Celles-ci définissent tous les ingrédients nécessaires à cette petite centrale.

Rappelons seulement qu’il s’agit de piloter une seule loco qui fera des allers et retours entre deux gares aux extrémités d’une voie unique, soit manuellement, soit automatiquement. Dans ce dernier cas, on comprend qu’il faudra faire jouer de nombreux paramètres de configuration.

A partir de ces spécifications, la réalisation logicielle se découpe en un certain nombre de blocs représentés ci-dessous.

Organigramme de la centrale va-et-vient

Pour commencer, nous allons mettre en place les ingrédients matériels dans le logiciel.

Tout d’abord, les bibliothèques nécessaires.

Définition des bibliothèques utilisées

Bibliothèque CmdrArduino

  1. // LIBRAIRIE CMDRARDUINO
  2. #include <DCCPacket.h> // bibliothèque CmdrArduino
  3. #include <DCCPacketQueue.h> // idem
  4. #include <DCCPacketScheduler.h> // idem

Cette bibliothèque prend en charge la génération du signal DCC sur la Pin 9 du Nano (elle se charge de l’initialisation de cette Pin). Elle est décrite à l’article précédent.

Bibliothèque EEPROM

  1. #include <EEPROM.h> // gestion mémoire EEPROM

Cette bibliothèque permet de lire et écrire dans la mémoire EEPROM qui est non-volatile.
Nous avons besoin de ces fonctions pour lire et écrire des paramètres de configuration que le logiciel utilisera pour initialiser des variables à chaque mise sous tension.

Bibliothèque Bounce

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

Cette bibliothèque 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 » c’est à dire de générer plusieurs contacts d’affilée et d’entrainer de fausses commandes que cette bibliothèque permet d’éviter.

Bibliothèque AVR/pgmspace

  1. #include <avr/pgmspace.h> // gestion mémoire Flash

Cette bibliothèque permet d’accéder (entre autres) à la lecture de données stockées dans la mémoire Flash de programme. Je me suis vite rendu compte que les textes prévus pour les affichages LCD (ou console) occupent la mémoire vive RAM qui est trop petite. C’est un des rares inconvénients de l’Arduino, facilement contourné par cette bibliothèque.

Bibliothèques SoftwareSerial et SerLCD

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

SoftwareSerial est nécessaire pour se servir de l’écran LCD via l’interface SerLCD.
serLCD 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.

Après les bibliothèques, voici maintenant les connexions électriques des différents organes avec les broches (Ports ou Pins) de notre Arduino.

Définition des Ports d’Entrée et Sortie de l’Arduino

Cette définition est conforme au schéma de câblage décrit dans l’article précédent.

On y trouve successivement les potentiomètres, les sorties du Booster, les boutons MManuel/Auto et Lumière, la connexion à l’écran LCD, les Leds, les détecteurs de position du train et les sorties de commande du booster LMD18200.

  1. // Pins ARDUINO
  2. #define Pot_AV A0 // Analog IN : potar vitesse Avant
  3. #define Pot_AR A1 // Analog IN : potar vitesse Arriere
  4. #define Current_Sense A2 // Analog IN : courant Booster
  5. #define Thermal_Sense A3 // Analog IN : temperature Booster
  6. #define Pin_AutoManu A4 // IN : commutateur de mode
  7. // manuel (LOW) ou automatique (HIGH)
  8. #define Pin_SW_FL A5 // IN : commutateur d'eclairage
  9. // (HIGH = ON, LOW = OFF)
  10. // les Pins 0 et 1 sont réservées pour Rx et Tx (port série)
  11. #define Pin_Tx 1 // Pour connexion au LCD via serLCD
  12. #define Pin_MAV 2 // OUT : led marche avant
  13. #define Pin_MAR 3 // OUT : led marche arriere
  14. #define Pin_Arret 4 // OUT : led arret
  15. #define Pin_ZoneAV 5 // IN : detecteur zone d'arret avant
  16. #define Pin_ZoneAR 6 // IN : detecteur zone d'arret arriere
  17. #define Fin_De_Voie 7 // IN : detecteur de fin de voie (les 2 combinées)
  18. #define Pin_Mode 8 // IN : poussoir de mode
  19. #define Pin_Out_DIR 9 // OUT : Signal DIR pour LMD18200
  20. // pin 10 reservee pour Timer 1
  21. #define Pin_Out_PWM 11 // OUT : Signal PWM pour LMD18200
  22. // HIGH en marche, LOW = stop
  23. #define Pin_DCC 12 // IN : commutateur marche/arret signal DCC
  24. #define Pin_Led_Booster 13 // OUT : led DCC OFF (continu)
  25. // ou surcharge booster (clignotant)

On respire un peu…

… avec une présentation des affichages.

Affichages LCD

On aura deux types d’affichages :

  • 3 écrans pour représenter le fonctionnement du train et suivre l’automatisme de va-et-vient ;
  • 10 écrans pour le configuration des paramètres de la centrale.

Voici d’abord les écrans de la centrale en cours de pilotage :

Mode Manuel

Au départ le commutateur Auto/Manuel est en position « Manuel ».
Les potentiomètres de vitesse sont au minimum. Le train est arrêté.
Tourner le potentiomètre de Vitesse Avant si la loco est en gare 1 ou le potentiomètre de Vitesse Arriere si la loco est en gare 2. La loco démarre
On peut consulter la vitesse (Crans DCC) sur l’afficheur LCD

La vitesse est automatiquement calculée à partir du temps séparant les passages des détecteurs et la distance configurée en EEPROM.
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.

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) ;
  • accélération constante : incrément de vitesse ajouté toutes les 1/2 secondes 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 arithmétique (diminution de la vitesse toutes les 1/2 secondes) jusqu’à atteinte de la vitesse minimum définie en configuration ;
  • avancement à vitesse minimum pendant la durée définie en configuration ;
  • arrêt en gare d’arrivée (gare 2).

Exemple de courbe de vitesse typique.

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

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 précis.

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 toutes les 1/2 secondes dans ce canton :

Revenons maintenant au programme de la centrale !

Définitions des variables et constantes

Pour fonctionner, le programme a besoin de manipuler des données dites « variables » qui sont stockées dans le SRAM (d’autres le sont en Flash). On associe cette déclaration au code d’initialisation qui trouve sa place dans la fonction Setup().

Pour la bibliothèque CmdrArduino

On doit créer une structure dps qui est manipulée par la bibliothèque.
La valeur _stop est crée pour inverser le comportement de la bibliothèque à l’arrêt : La vitesse de cran "1" assure un arrêt avec inertie, plutôt qu’un arrêt brutal.

  1. // DCC handler
  2. #define _stop 1 // 0->eStop et 1->regular stop
  3. DCCPacketScheduler dps; // structure pour la librairie CmdrArduino

Ensuite vient l’initiatisation de la structure dps, qui fait partie de la procédure setup().

  1. dps.setup(); // initialise en même temps les Pins 9 et 10

Enfin nous faut appeler de façon récurrente, donc dans loop() :

  1. dps.update();

ainsi que les commandes de vitesse et lumière dont les variables sont déterminées plus loin, en mode manuel et automatique :

  1. dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,_speed)
  1. dps.setFunctions0to4(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,FL)

Pour la configuration

Nous définissons une structure de données en EEPROM qui contiendra les valeurs nécessaires :

  1. typedef struct
  2. {
  3. int dcc_adresse; // @ DCC 128 crans. Attention, elle occupe 2 octets.
  4. byte vitesse_min; // 2..10 a determiner en pilotage manuel
  5. byte increment_acceleration; // 1..10
  6. byte duree_arret_gare1; // 0..255 secondes
  7. byte duree_arret_gare2; // 0..255 secondes
  8. byte distance_ligne; // 1..255 centimetres
  9. byte distance_gare1; // 1..255 centimetres
  10. byte distance_gare2; // 1..255 centimetres
  11. byte pas_tempo_ligne; // 1..255 pas de 1/2 seconde
  12. byte Nb_Vmin; // 1..20 nb de 1/2 secondes à Vmin avant Stop
  13. } configuration;
  14. configuration Config_Init; // record de configuration copié en EEPROM

Nous verrons l’usage de ces variables à l’occasion des procédures de configuration, et restauration.

Boutons et Capteurs

Nous avons le bouton Mode (poussoir), et les inverseurs AutoManu, Lumière, et A/M DCC.
Avec le bouton Mode nous avons prévu 13 états possibles correspondant aux affichages sur l’écran LCD (3 en fonctionnement normal et 10 en configuration).

  1. Bounce B_Mode = Bounce(Pin_Mode, 100);// bouton MODE
  2. byte prev_BM_state = 1; // etat anterieur du poussoir Mode
  3. byte BM = 0; // inactif (0) ou actif (1)
  4. boolean Mode_Config = false;
  5. byte Mode_EnCours = 0; // mode 0 : normal, affichage "DCC V DIR AV AR "
  6. // mode 1 : normal, affichage "V DIR Canton Pas"
  7. // mode 2 : normal, affichage vitesse reelle
  8. // mode 3 : config : @ dcc
  9. // mode 4 : config : V min
  10. // mode 5 : config : acceleration
  11. // mode 6 : config : t arret gare 1
  12. // mode 7 : config : t arret gare 2
  13. // mode 8 : config : l canton ligne
  14. // mode 9 : config : l canton gare 1
  15. // mode 10: config : l canton gare 2
  16. // mode 11: config : time-out n 1/2s
  17. // mode 12: config : nb 1/2s a Vmin
  18.  
  19. Bounce B_AutoManu = Bounce(Pin_AutoManu, 20); // clé AUTO/MANUEL
  20. byte prev_AutoManu_state = 0; // etat anterieur du commutateur
  21. // auto-manuel
  22. byte AutoManu = 0; // manuel (0) ou automatique (1)
  23.  
  24. Bounce B_SW_FL = Bounce(Pin_SW_FL, 20); // clé LUMIERE
  25. byte prev_FL_state = 0; // etat anterieur du bouton lumiere FL
  26. byte FL = 0; // eclairage éteint (0) ou allumé (1)
  27.  
  28. Bounce B_DCC = Bounce(Pin_DCC, 20); // clé ARRET/MARCHE DCC
  29. byte prev_ON_DCC_state = 0; // etat anterieur du bouton de signal DCC
  30. // (pour etre OFF à l'init)
  31. byte ON_DCC = 0; // DCC off (0) ou on (1)

Pour les Détecteurs de passage et l’alarme température du Booster

  1. boolean alarme_temperature = true; // LOW si T>145°C au capteur,
  2. // inversé à la mesure
  3. int mesure_courant = 0;
  4.  
  5. // detecteur de zones AV (gare 2)
  6. int detecteur_ZAV, prev_detecteur_ZAV, etat_detecteur_ZAV = 0;
  7. // detecteur de zones AR (gare 1)
  8. int detecteur_ZAR, prev_detecteur_ZAR, etat_detecteur_ZAR = 0;
  9.  
  10. unsigned long IRdebounce_time;
  11. #define IRdebtime 100 // 10 milliseconde
  12. byte IRdebounceAVH, IRdebounceAVL, IRdebounceARH, IRdebounceARL = 0;

On peut se demander pourquoi ces détecteurs sont concernés par les problèmes de rebond, puisqu’on voit ici le terme debounce.
En fait il n’est est rien, mais ici, par souci de perfection, on veut tenir compte de l’espace entre 2 voitures qui provoquerait une détection parasite. On élimine donc cette détection parasite avec un traitement similaire à celui de la bibliothèque Bounce.

Pour la tâche de clignotement des Leds

  1. // 0 = normal, >0 (true) = clignote 1 éteint ou 3 allumé
  2. byte surchargeBooster = 0;
  3. byte Rouge_Clignotant = 0;
  4. byte Jaune_Clignotant = 0;
  5. byte Vert_Clignotant = 0;

Evidemment ces Leds ne vont pas clignoter en utilisant l’instruction delay(). On va utiliser le temps système avec millis() pour déclencher les changement d’état des Leds.

Passons à l’affichage LCD

  1. serLCD lcd(Pin_Tx);

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

On définit ensuite 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 (sinon plantage assuré, c’est un écueil que nous rencontrons tous à nos débuts).

  1. prog_char string_0[] PROGMEM = "DCC V DIR AV AR "; // Mode_EnCours = 0
  2. prog_char string_1[] PROGMEM = "V DIR Canton Pas"; // Mode_EnCours = 1
  3. prog_char string_2[] PROGMEM = "Vit Km/h Cm/s "; // Mode_EnCours = 2
  4. prog_char string_3[] PROGMEM = "Adresse DCC : "; // Mode_EnCours = 3
  5. // mode configuration à partir de 4
  6. prog_char string_4[] PROGMEM = "Vitesse min : "; // Mode_EnCours = 4
  7. prog_char string_5[] PROGMEM = "Acceleration : "; // Mode_EnCours = 5
  8. prog_char string_6[] PROGMEM = "T arret gare 1: "; // Mode_EnCours = 6
  9. prog_char string_7[] PROGMEM = "T arret gare 2: "; // Mode_EnCours = 7
  10. prog_char string_8[] PROGMEM = "L canton ligne: "; // Mode_EnCours = 8
  11. prog_char string_9[] PROGMEM = "L canton gare 1:"; // Mode_EnCours = 9
  12. prog_char string_10[] PROGMEM = "L canton gare 2:"; // Mode_EnCours = 10
  13. prog_char string_11[] PROGMEM = "Time-out n 1/2s:"; // Mode_EnCours = 11
  14. prog_char string_12[] PROGMEM = "Nb 1/2s Vmin : "; // Mode_EnCours = 12
  15.  
  16. PROGMEM const char *string_table[] = // table des adresses
  17. {
  18. string_0,
  19. string_1,
  20. string_2,
  21. string_3,
  22. string_4,
  23. string_5,
  24. string_6,
  25. string_7,
  26. string_8,
  27. string_9,
  28. string_10,
  29. string_11,
  30. string_12
  31. };
  32.  
  33. char buffer[16];
  34. // tampon de recopie en RAM (doit etre au moins aussi grand que le plus grand STRING

Ce type de déclaration peut être recopié dans tous programmes faisant un usage intensif des affichages de texte.

Passons aux taches périodiques

  1. unsigned long time500, looptime;

Pour la tâche de clignotement des Leds

On va réaliser une sorte d’automate qui s’appuie sur 2 bits d’un octet (4 états possibles) pour définir les états des Leds (allumé, éteint, fixe ou clignotant).

  1. // 0 = normal, >0 (true) = clignote 1 éteint ou 3 allumé
  2. byte surchargeBooster = 0;
  3. byte Rouge_Clignotant = 0;
  4. byte Jaune_Clignotant = 0;
  5. byte Vert_Clignotant = 0;

Pour le mouvement du train

Il y a beaucoup de variables pour les modes manuel et automatique, ce qui se comprendra aisément à la lecture des modules de traitement.

  1. unsigned int AV_value = 0; // lecture du potentiometre vitesse avant
  2. unsigned int AR_value = 0; // lecture du potentiometre vitesse arriere
  3. byte speed_AV, old_speed_AV = 0; // vitesse AV (courante, précédente)
  4. byte speed_AR, old_speed_AR = 0; // vitesse AR
  5. byte _speed, vitesse = 0; // vitesse instantannee (selon direction)
  6. boolean change_V = false; // si true => change donc nouvelle commande DCC
  7. byte DIR = 1; // marche avant: gare1>gare2 (1)
  8. // ou arriere: gare2>gare1 (0)
  9. byte Canton = 0; // 0=arret quai gare1, 1=depart acceleration,
  10. // 2=ligne, 3=arrivee ralentissement,
  11. // 4= vitesse min, 5=arret quai gare2
  12. #define NBCantons 6 // 6 cantons = 6 etats automate
  13. #define NBPAS 10 // nombre de pas de 1/2 sec par etat automate et
  14. // par defaut si le detecteur n'a pas fonctionne
  15. byte PasAV[NBCantons]; // pas en Avant : 1 valeur par canton
  16. byte PasAR[NBCantons]; // pas en Ariere : 1 valeur par canton
  17. byte TO; // time-out passage canton ligne calculé d'après
  18. // le temps de passage entre capteurs
  19. unsigned long TC, MTC, VKM, VCM = 0; // duree de passage entre capteurs, moyenne,
  20. // vitesse (km/h) et vitesse (cm/s)
  21. unsigned int MMA, DDS100; // Nb de millimetre restant a parcourir
  22. // jusqu’a arret et decrement par 1/2 seconde

Toutes le déclarations étant faites, nous pouvons commencer le déroulement des instruction du setup()

Initialisations du programme

  1. #define VERSION "b3-081114"
  2.  
  3. void setup() {
  4.  
  5. lcd.setBrightness(25); // rétro-éclairage du LCD 0..30
  6. lcd.clear(); // effacement du LCD
  7. lcd.print("Vers. ");
  8. lcd.print(VERSION);
  9. lcd.selectLine(2); // positionnement en 2è ligne
  10.  
  11. ///////////// initialisation des Pins
  12. //pinMode(Pot_AV, INPUT); // entree analogique donc pas nécessaire
  13. //pinMode(Pot_AR, INPUT); // entree analogique donc pas nécessaire
  14. //pinMode(Current_Sense, INPUT); // entree analogique donc pas nécessaire
  15. pinMode(Thermal_Sense, INPUT_PULLUP);
  16. pinMode(Pin_AutoManu, INPUT_PULLUP);
  17. pinMode(Pin_SW_FL, INPUT_PULLUP);
  18. pinMode(Pin_Mode, INPUT_PULLUP);
  19. pinMode(Pin_DCC, INPUT_PULLUP);
  20. pinMode(Pin_Out_PWM, OUTPUT);
  21. //pinMode(Pin_Out_DIR, OUTPUT); // initialisé par CmdrArduino
  22. digitalWrite(Pin_Out_PWM, LOW); // DCC OFF
  23. pinMode(Pin_Led_Booster, OUTPUT);
  24. digitalWrite(Pin_Led_Booster, HIGH);
  25. // Booster OFF (allume) ou CLIGNOTANT (surcharge)
  26. pinMode(Pin_MAV, OUTPUT);
  27. pinMode(Pin_MAR, OUTPUT);
  28. pinMode(Pin_Arret, OUTPUT);
  29. pinMode(Pin_ZoneAV, INPUT_PULLUP);
  30. pinMode(Pin_ZoneAR, INPUT_PULLUP);
  31. pinMode(Fin_De_Voie, INPUT_PULLUP);
  32.  
  33. ////////////// initialisation de la bibliothèque CmdrArduino
  34. dps.setup(); // initialise les Pins 9 et 10
  35.  
  36. prev_ON_DCC_state = B_DCC.read(); // force l'etat DCC off
  37. // il faut une transition bas -> haut de Alim Rails
  38. // pour autoriser l’alimentation des rails
  39.  
  40. ////////////// Recuperation des valeurs de configuration en EEPROM
  41. _Recup_EEPROM(); // voir le détail de cette fonction plus loin
  42.  
  43. lcd.print("DCC:"); // affichage de l’adresse DCC en configuration
  44. lcd.print(Config_Init.dcc_adresse);
  45.  
  46. Mode_EnCours = 0; // initialisation de la variable
  47. Mode_Config = false; // pas de mode configuration
  48.  
  49. ///////////// test de la configuration : si l’adresse DCC == 0 ou FF (EEPROM vierge):
  50. ///////////// passage obligatoire en configuration à la 1ère mise en service
  51. if ((Config_Init.dcc_adresse == 0) || (Config_Init.dcc_adresse == 255))
  52. {
  53. Mode_EnCours = 3;
  54. Mode_Config = true;
  55. _Configuration(); // voir le détail de cette fonction plus loin
  56. }
  57.  
  58. ///////////// initialisation des compteurs de temps pour les taches periodiques
  59. time500 = millis(); // pour déclencher la fonction SR_demiseconde()
  60. looptime = micros(); // pour mesurer la durée moyenne de la LOOP()
  61. IRdebounce_time = millis(); // pour l’anti-rebond des boutons
  62.  
  63. lcd.print(" RAM:");
  64. lcd.print(_RamFree()); // affiche la quantité de RAM restant (état de santé)
  65. delay(2000); // pendant 2 secondes
  66. } // fin de la fonction setup()

Sous-programmes de setup()

La fonction setup() fait donc appel aux fonctions suivantes :

  • _Recup_EEPROM() qui permet de lire les paramètres en EEPROM et initialiser des variables,
  • _Program_EEPROM() qui permet d’écrire les paramètres en EEPROM, après
  • _Configuration() qui permet de déterminer les paramètres à écrire en EEPROM à l’aide d’un dialogue avec l’utilisateur, mettant en oeuvre l’écran LCD, boutons et potentiomètres.

Attention à la survie de l’EEPROM ! : le nombre maximum d’écritures dans l’EEPROM est de 100.000 ! Ce n’est pas rien, mais il ne faut pas en abuser, sinon le micro-controleur ne pourra plus s’en servir.
On n’écrit donc JAMAIS en permanence dans cette mémoire mais seulement de façon occasionnelle. C’est le cas des paramètres de configuration.

Fonction _Recup_EEPROM()

  1. void _Recup_EEPROM()
  2. {
  3. int i;
  4. int s = sizeof(Config_Init);
  5. byte b[s]; // tableau de lecture de l’EEPROM
  6.  
  7. for (i = 0; i < s; i++)
  8. {
  9. b[i] = EEPROM.read(i);
  10. delay(10);
  11. }
  12. // puis affectation des variables
  13. Config_Init.dcc_adresse = b[0]*256 + b[1];
  14. // on assemble les 2 octets pour faire un ‘int'
  15. Config_Init.vitesse_min = b[2];
  16. Config_Init.increment_acceleration = b[3];
  17. Config_Init.duree_arret_gare1 = b[4];
  18. Config_Init.duree_arret_gare2 = b[5];
  19. Config_Init.distance_ligne = b[6];
  20. Config_Init.distance_gare1 = b[7];
  21. Config_Init.distance_gare2 = b[8];
  22. Config_Init.pas_tempo_ligne = b[9];
  23. Config_Init.Nb_Vmin = b[10];
  24. }

Fonction _Program_EEPROM()

  1. void _Program_EEPROM()
  2. {
  3. int i;
  4. int s = sizeof(Config_Init);
  5. byte b[s];
  6.  
  7. b[0] = highByte(Config_Init.dcc_adresse); // séparation du int en 2 bytes
  8. b[1] = lowByte(Config_Init.dcc_adresse);
  9. b[2] = Config_Init.vitesse_min;
  10. b[3] = Config_Init.increment_acceleration;
  11. b[4] = Config_Init.duree_arret_gare1;
  12. b[5] = Config_Init.duree_arret_gare2;
  13. b[6] = Config_Init.distance_ligne;
  14. b[7] = Config_Init.distance_gare1;
  15. b[8] = Config_Init.distance_gare2;
  16. b[9] = Config_Init.pas_tempo_ligne;
  17. b[10] = Config_Init.Nb_Vmin;
  18. for (i = 0; i < s; i++)
  19. {
  20. EEPROM.write(i, b[i]);
  21. delay(10); // un petit délai pour garantir une bonne écriture
  22. }
  23. }

Fonction _Configuration()

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

Prenez un café et continuez la lecture !

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 est définie par rotation 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, donne une valeur comprise entre 0 et 1023. Ensuite, il est nécessaire de « démultiplier » la valeur lue pour la ramener dans la plage 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 valeur comprise entre 0 et 127.

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

La fonction tourne en boucle en affichant cette valeur jusqu’à ce qu’on n’appuie 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.

Comme il y a des processus répétitifs, j’ai écrit ces processus dans des fonctions.

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

_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.
_questionD() est un cas particulier de _questionC() qui permet de choisir une valeur dans la plage 0..512. J’en ai eu besoin pour définir la distance entre les 2 capteurs. Je peux les espacer jusqu’à 5 mètres et 11 cms.

Ces 3 fonctions sont décrites après la fonction _Configuration()

Fonction _Configuration()

  1. void _Configuration()
  2. {
  3. // version LCD (remplace la version Console via USB)
  4. // entrée : mode manuel, LUMIERE=0 - si OK suite de questions
  5. // sortie avec suite d’appuis sur bouton mode
  6. // Mode_EnCours = 3 à 13 (index des questions)
  7.  
  8. // arret du train, stop DCC booster, vitesse AV/AR=0, pots à 0
  9. vitesse = _stop;
  10. dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,vitesse);
  11. ON_DCC = 0;
  12. digitalWrite(Pin_Out_PWM, ON_DCC); // 0 = arret
  13. digitalWrite(Pin_Led_Booster, !ON_DCC); // 1 = led allumee
  14. lcd.clear();
  15.  
  16. // recopie de la chaine en Flash vers notre buffer
  17. // Cast et dereferencement nécessaire
  18. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  19. lcd.print(buffer); // affichage 1ere ligne
  20. lcd.setCursor(2, 1); // adresse DCC
  21. _questionB(); // attente d’appui sur MODE
  22. Mode_EnCours++; // question suivante
  23. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // vitesse minimale
  4. Config_Init.vitesse_min = _questionC(Config_Init.vitesse_min, 2);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // incréments d’accélération
  4. Config_Init.increment_acceleration = _questionC(Config_Init.increment_acceleration, 3);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // 1/2 durée arrêt en gare 1
  4. Config_Init.duree_arret_gare1 = _questionC(Config_Init.duree_arret_gare1, 4);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // 1/2 durée arrêt en gare 2
  4. Config_Init.duree_arret_gare2 = _questionC(Config_Init.duree_arret_gare2, 5);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // Distance zone Ligne en cm
  4. Config_Init.distance_ligne = _questionD(Config_Init.distance_ligne, 6);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // Distance zone Gare 1 en cm
  4. Config_Init.distance_gare1 = _questionC(Config_Init.distance_gare1, 7);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // Distance zone Gare 2 en cm
  4. Config_Init.distance_gare2 = _questionC(Config_Init.distance_gare2, 8);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // Nombre de 1/2 secondes maximum sur zone Ligne
  4. Config_Init.pas_tempo_ligne = _questionC(Config_Init.pas_tempo_ligne, 9);
  5. Mode_EnCours++;
  6. lcd.clear();
  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne
  3. lcd.setCursor(2, 1); // Nombre de 1/2 secondes à vitesse Vmin
  4. Config_Init.Nb_Vmin = _questionC(Config_Init.Nb_Vmin, 10);
  5. Mode_EnCours = 0;
  6. Mode_Config = false;
  7. lcd.clear();
  1. lcd.print("Fin Config ");
  2. lcd.print(_RamFree());
  3. delay(2000);
  4. }

En fin de configuration, le dernier écran affiche la quantité de mémoire SRAM restante.

Fonction _questionB()

_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.

  1. void _questionB() // adresse DCC, Mode_EnCours = 3
  2. {
  3. boolean ROK = false;
  4. boolean RXIT = false;
  5. byte Valeur = 0;
  6.  
  7. lcd.print(Config_Init.dcc_adresse); // ancienne valeur sur 2e ligne, max 3 cars
  8. lcd.print(" ->"); // choix à afficher en ligne 2, colonne 8
  9. while (!RXIT)
  10. {
  11. // on se sert de la somme des potentiometres
  12. Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 4;
  13. //division par 16 pour ramener le champ 0-2047 à 0-127
  14. lcd.setCursor(2, 6);
  15. lcd.print(Valeur);
  16. lcd.print(" "); // pour effacer les caracteres suivants eventuels
  17. if (B_Mode.update()) // Poussoir Mode change-t-il ?
  18. {
  19. if (B_Mode.risingEdge())
  20. {
  21. RXIT = true; // sortie de la boucle while
  22. }
  23. }
  24. if (B_SW_FL.update()) // COMMUTATEUR DE LUMIERE
  25. {
  26. if (B_SW_FL.read() == HIGH)
  27. {
  28. Config_Init.dcc_adresse = Valeur; // validation
  29. ROK = true;
  30. }
  31. }
  32. } // while !ROK
  33.  
  34. if (ROK)
  35. {
  36. // enregistrement en EEPROM
  37. EEPROM.write(0, highByte(Config_Init.dcc_adresse));
  38. delay(10);
  39. EEPROM.write(1, lowByte(Config_Init.dcc_adresse));
  40. lcd.print(" Ok!");
  41. delay(1000);
  42. }
  43. }

Fonction _questionC()

_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.

  1. int _questionC(int _VAL, int _index) // Mode_EnCours > 3
  2. {
  3. boolean ROK = false;
  4. boolean RXIT = false;
  5. byte Valeur = 0;
  6.  
  7. lcd.print(_VAL); // ancienne valeur sur 2e ligne, max 3 cars
  8. lcd.print(" ->"); // choix à afficher en ligne 2, colonne 8
  9. while (!RXIT)
  10. {
  11. // on se sert de la somme des potentiometres
  12. Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 4;
  13. //division par 16 pour ramener le champ 0-2047 à 0-127
  14. lcd.setCursor(2, 6);
  15. lcd.print(Valeur);
  16. if (B_Mode.update()) // Poussoir Mode change-t-il ?
  17. {
  18. if (B_Mode.risingEdge())
  19. {
  20. RXIT = true; // sortie de la boucle while
  21. }
  22. }
  23. if (B_SW_FL.update()) // COMMUTATEUR DE LUMIERE
  24. {
  25. if (B_SW_FL.read() == HIGH)
  26. {
  27. _VAL = Valeur; // validation
  28. ROK = true;
  29. }
  30. }
  31. } // while !ROK
  32. if (ROK)
  33. {
  34. // enregistrement en EEPROM
  35. EEPROM.write(_index, _VAL);
  36. lcd.print(" Ok!");
  37. delay(1000);
  38. }
  39. return(_VAL); // retour avec le paramètre modifié ou non
  40. }

Fonction _questionD()

Cette fonction diffère de _questionC() par le seul fait qu’elle permet de renseigner une valeur comprise entre 0 et 511 au lieu de 0..255. Cette valeur correspond à des centimètres, la distance entre les deux capteurs. Il est probable que cette valeur dépasse 2,50 mètres, ce qui était mon cas. Alors en fait on multiplie par 2 la valeur enregistrée en EEPROM car celle-ci est codée sur un seul octet.
L’affichage peut suivre puisqu’il peut aller jusqu’à 2047. La valeur enregistrée en EEPROM est divisée par 2 car elle reste limitée à 255 et la valeur récupérée en variable est doublée.

  1. int _questionD(int _VAL, int _index) // mode LCD uniquement, Mode_EnCours > 3, pour une valeur 0..512
  2. {
  3. boolean ROK = false;
  4. boolean RXIT = false; // pour cette reponse globale
  5. int Valeur = 0;
  6.  
  7. lcd.print(_VAL*2); // ancienne valeur double sur 2e ligne, max 3 cars
  8. lcd.print(" ->"); // choix à afficher en ligne 2, colonne 8
  9. while (!RXIT)
  10. {
  11. // on se sert de la somme des potentiometres
  12. Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 2; // division par 4 pour ramener
  13. // le champ 0-2047 à 0-511.
  14. lcd.setCursor(2, 6);
  15. lcd.print(Valeur);
  16. lcd.print(" "); // pour effacer les caracteres suivants eventuels
  17. if (B_Mode.update()) // Poussoir Mode change
  18. {
  19. if (B_Mode.risingEdge())
  20. {
  21. RXIT = true; // sortie de la fonction sans modification
  22. }
  23. }
  24. if (B_SW_FL.update()) // COMMUTATEUR DE LUMIERE
  25. {
  26. if (B_SW_FL.read() == HIGH)
  27. {
  28. _VAL = Valeur/2; // nouvelle valeur
  29. ROK = true;
  30. }
  31. }
  32. } // while !ROK
  33. if (ROK)
  34. {
  35. EEPROM.write(_index, (byte)_VAL);
  36. lcd.print(" Ok!");
  37. delay(1000);
  38. }
  39. return(_VAL);
  40. }

Abordons maintenant la boucle principale

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 pas.

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 200 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 une tache seulement lorsqu’un compteur 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 un autre projet. 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émentés à chaque 1/2 seconde. Lorsqu’une variable 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 règle du « ZERO 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 :

  1. void loop() {
  2.  
  3. int i; // variable locale, propre à loop()
  4.  
  5. /////////////////////////////////////////
  6. // Boutons et commutateurs
  7. /////////////////////////////////////////

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 directement les valeurs de vitesse des potentiomètres dans loop().

  1. if (B_AutoManu.update()) // COMMUTATEUR AUTO/MANUEL
  2. {
  3. byte AutoManu_state = B_AutoManu.read(); // variable locale
  4. if (AutoManu_state != prev_AutoManu_state)
  5. {
  6. prev_AutoManu_state = AutoManu_state;
  7. AutoManu = prev_AutoManu_state;
  8.  
  9. if (AutoManu)
  10. { // passage en mode Auto
  11. // pour le moment on suppose le train au depart en gare 1
  12. Canton = 0; // 1/2 arret en Gare1
  13. DIR = 1; // Marche Avant
  14. vitesse = 0; // initialisation vitesse
  15. PasAV[Canton]=Config_Init.duree_arret_gare1; // init 1/2 duree arret en gare 1
  16. } else
  17. { // passage en mode Manuel
  18. old_speed_AV = 0;
  19. old_speed_AR = 0;
  20. Vert_Clignotant=0;
  21. Jaune_Clignotant=0;
  22. // peu importe la position du train
  23. // mais on continue a mettre a jour sa position,
  24. // direction et vitesse
  25. }
  26. }
  27. }

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) .

  1. /////////////////////////////////////////
  2. //Commande d'eclairage FL
  3. /////////////////////////////////////////
  4. if (B_SW_FL.update()) // COMMUTATEUR DE LUMIERE
  5. {
  6. byte FL_state = B_SW_FL.read(); //high == not pushed; low == pushed
  7. if(FL_state != prev_FL_state)
  8. {
  9. prev_FL_state = FL_state;
  10. FL = prev_FL_state;
  11. if (!dps.setFunctions0to4(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,FL))
  12. {
  13. lcd.print(":");
  14. }
  15. }
  16. }

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.

  1. /////////////////////////////////////////
  2. // MODE
  3. /////////////////////////////////////////
  4. if (B_Mode.update()) // Poussoir Mode
  5. {
  6. byte BM_state = B_Mode.read(); //high == not pushed; low == pushed
  7. if(BM_state != prev_BM_state)
  8. {
  9. prev_BM_state = BM_state;
  10. BM = prev_BM_state;
  11. if (BM)
  12. // prise en compte du relachement
  13. {
  14. Mode_EnCours++;
  15. if (Mode_EnCours > 2 && !AutoManu && !FL)
  16. { // passage en mode configuration
  17. Mode_Config = true;
  18. _Configuration(); // 1 question, 1 reponse,
  19. // + programmation EEPROM en fonction de Mode_EnCours
  20. } else // mode normal
  21. {
  22. Mode_Config = false;
  23. if (Mode_EnCours > 2) Mode_EnCours = 0;
  24. // affichages 1ere et 2e ligne faits par SR_demiseconde
  25. }
  26. }
  27. }
  28. }

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

Comme l’arrêt correspond à la valeur de vitesse = 1, une valeur inférieure à 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, la vitesse maximale étant celle indiquée par le potentiomètre avant ou arrière, suivant le sens de déplacement.

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

  1. /////////////////////////////////////////
  2. //Commandes de vitesse AV et AR
  3. /////////////////////////////////////////
  4. AV_value = analogRead(Pot_AV);
  5. speed_AV = (AV_value >> 3);
  6. // division par 8 pour passer de 0-1023 a 0-127
  7. if (speed_AV < 2) //forcement un stop
  8. {
  9. speed_AV = _stop; // 0 = e_stop; 1 = _stop
  10. }
  11. if (speed_AV != old_speed_AV)
  12. {
  13. if (abs(speed_AV - old_speed_AV) < 64) // ecretage en cas de pot qui crache
  14. {
  15. old_speed_AV = speed_AV;
  16. change_V = true;
  17. }
  18. }
  19. AR_value = analogRead(Pot_AR);
  20. speed_AR = (AR_value >> 3);
  21. // division par 8 pour passer de 0-1023 a 0-127
  22. if (speed_AR < 2) //forcement un stop
  23. {
  24. speed_AR = _stop; // 0 = e_stop; 1 = _stop
  25. }
  26. if (speed_AR != old_speed_AR)
  27. {
  28. if (abs(speed_AR - old_speed_AR) < 64) // ecretage en cas de pot qui crache
  29. {
  30. old_speed_AR = speed_AR;
  31. change_V = true;
  32. }
  33. }
  34. if (change_V && !AutoManu)
  35. { // mode manuel : vitesse envoyee immediatement
  36. DIR = (speed_AV > speed_AR);
  37. if (speed_AR == _stop) DIR = 1;
  38. if (speed_AV == _stop && speed_AR == _stop)
  39. {
  40. digitalWrite(Pin_Arret, HIGH);
  41. } else
  42. {
  43. digitalWrite(Pin_Arret, LOW);
  44. }
  45. digitalWrite(Pin_MAV, DIR); // Led verte
  46. digitalWrite(Pin_MAR, !DIR); // led jaune
  47. if (DIR) { // avant
  48. vitesse = speed_AV; //
  49. _speed = -vitesse; // en avant vitesse negative
  50. } else { // arriere
  51. vitesse = speed_AR; //
  52. _speed = vitesse; // vitesse positive
  53. }
  54. if (!dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,_speed))
  55. {
  56. lcd.print("."); // détection d’anomalie
  57. }
  58. change_V = false;
  59. }

Activation de la bibliothèque CmdrArduino

La bibliothèque 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 dps.update() sert à organiser les données privées de cette librairie et notamment la file d’attente des trames à émettre.

  1. /////////////////////////////////////////
  2. // Execution CmdrArduino à chaque LOOP
  3. /////////////////////////////////////////
  4. dps.update();

Pilotage du Booster LMD18200

La libération 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.

  1. /////////////////////////////////////////
  2. // START / STOP BOOSTER
  3. /////////////////////////////////////////
  4. if (B_DCC.update()) // BOUTON ON / OFF DCC
  5. {
  6. byte ON_DCC_state = B_DCC.read();
  7. if (ON_DCC_state != prev_ON_DCC_state)
  8. {
  9. prev_ON_DCC_state = ON_DCC_state;
  10. if (!ON_DCC_state)
  11. {
  12. ON_DCC = 0;
  13. }
  14. if (ON_DCC_state)
  15. {
  16. ON_DCC = !ON_DCC; // inversion ON -> OFF -> ON ...
  17. }
  18. digitalWrite(Pin_Out_PWM, ON_DCC); // 1 = en marche
  19. digitalWrite(Pin_Led_Booster, !ON_DCC); // 0 = led OFF allumee en continu
  20. vitesse = 0; // par sécurite
  21. }
  22. }

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é je n’ai 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 signal DCC est arrêté, la led DCC stop allumée et la led STOP clignotante

  1. /////////////////////////////////////////
  2. // ALARMES BOOSTER (courant et temperature)
  3. /////////////////////////////////////////
  4. mesure_courant = analogRead(Current_Sense);
  5. alarme_temperature = !digitalRead(Thermal_Sense); // LOW si T > 145°C)
  6. // puis inversé
  7. if ((mesure_courant > 600) || alarme_temperature )
  8. {
  9. ON_DCC = 0;
  10. digitalWrite(Pin_Out_PWM, ON_DCC); // 0 = STOP
  11. digitalWrite(Pin_Led_Booster, !ON_DCC); // 1 = led Booster OFF allumée
  12. surchargeBooster = 3; // et clignotante
  13. Rouge_Clignotant = 3; // led ARRET clignotante
  14. }

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 !

C’est plus facile à faire avec des barrières infrarouge qu’avec d’autres types de détecteurs (ILS, consommation de courant, etc..). C’est pour cela que j’ai choisi cette technologie. Mais rien n’empêche de faire autrement.

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. C’est très important dans le cas des barrières infrarouges : il y a souvent une petite main (ou une grosse) qui risque de bousculer un capteur sous prétexte d’améliorer le décor qui cache ce capteur !!!. Il faut pouvoir détecter l’anomalie et y remédier. Bien entendu cela m’est arrivé !

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().

Il y a donc 2 détecteurs : Le premier, dans le sens de la marche du train, déclenche seulement une mesure de vitesse. Le deuxième termine cette mesure et déclenche le ralentissement seulement en mode automatique.

  1. /////////////////////////////////////////
  2. // CAPTEURS ZONE DE RALENTISSEMENT
  3. /////////////////////////////////////////
  4.  
  5. // traitement detecteur Zone AV (pres de gare 1)
  6. detecteur_ZAV = digitalRead(Pin_ZoneAV);
  7. if ((detecteur_ZAV!=prev_detecteur_ZAV)&&(detecteur_ZAV==1)&&(etat_detecteur_ZAV==0))
  8. { // detection debut de convoi (tete en AV, queue en AR)
  9. etat_detecteur_ZAV = 1; // etat 1 = detection initiale début de convoi
  10. IRdebounceAVH = 2; // arme tempo de confirmation après 0,2 sec
  11. } else {
  12. if ((detecteur_ZAV == 1) && (IRdebounceAVH == 0) && (etat_detecteur_ZAV == 1))
  13. { // validation debut de convoi
  14. etat_detecteur_ZAV = 2; // etat 2 = en cours de passage devant le detecteur
  15. prev_detecteur_ZAV = 1;
  16. // TRAITEMENT TETE DE CONVOI
  17. digitalWrite(Pin_Arret, HIGH);
  18. // calcul de la vitesse dans tous les cas : manuel et auto
  19. if (DIR) { // marche avant, raz TC
  20. TC = millis(); // début du comptage
  21. } else { // marche arriere, fin du comptage, TC -> calcul vitesse
  22. TC = millis() - TC;
  23. if (MTC == 0) {MTC = TC;} else {MTC = (MTC + TC) / 2;}
  24. // tend vers la moyenne en millisecondes
  25. TO = (int)(MTC/500); // nb de 1/2 secondes
  26. if (TO > (Config_Init.pas_tempo_ligne+10))
  27. {
  28. TO = Config_Init.pas_tempo_ligne; // butee
  29. }
  30. VCM = (unsigned long)Config_Init.distance_ligne; // cast 1/2 distance en cm
  31. VCM = VCM*2000/TC;
  32. VKM = VCM*576/100;
  33. }
  34. // si auto : DIR = 1 entree dans ligne (vitesse constante)
  35. // ou DIR = 0 ralentir avant arret gare 1
  36. if (AutoManu)
  37. {
  38. if (DIR) // avant
  39. {
  40. // rien a l'entree dans canton ligne
  41. } else { // arriere, sortie du canton ligne
  42. Canton = 2; // deceleration : preparation des parametres
  43. PasAR[Canton]=Config_Init.distance_gare1; // PasAR = distance capteur -> arret gare 1
  44. MMA = (unsigned int)Config_Init.distance_gare1;
  45. // init distance restant a parcourir jusqu'a vitesse min (ex: 60 cm)
  46. MMA = MMA*2;
  47. // amélioration de la précision - ordre de grandeur 120
  48. DeltaVitesse = MMA/VCM;
  49. // nombre de pas en 1/2 sec a VCM pour parcourir MMA
  50. DeltaVitesse = vitesse/DeltaVitesse;
  51. // decrement de vitesse a decompter toutes les 1/2 sec
  52. }
  53. }
  54. } else { // autres cas que debut de convoi
  55. if ((detecteur_ZAV!=prev_detecteur_ZAV)&&(detecteur_ZAV==0)&&(etat_detecteur_ZAV==2))
  56. { // transition vers 0 en cours de passage de convoi (inter-wagon ou queue)
  57. etat_detecteur_ZAV = 3; // etat 3 detection retour a 0
  58. IRdebounceAVL = 10; // armement tempo 1 sec
  59. } else {
  60. if ((detecteur_ZAV==1)&&(IRdebounceAVL!=0)&&(etat_detecteur_ZAV==3))
  61. { // interwagon a ignorer
  62. etat_detecteur_ZAV = 2;
  63. IRdebounceAVL = 0;
  64. } else {
  65. if ((detecteur_ZAV==0)&&(IRdebounceAVL==0)&&(etat_detecteur_ZAV==3))
  66. {
  67. // queue de convoi
  68. etat_detecteur_ZAV = 0;
  69. prev_detecteur_ZAV = 0;
  70. // TRAITEMENT QUEUE DE CONVOI
  71. digitalWrite(Pin_Arret, LOW);
  72. }
  73. }
  74. }
  75. }
  76. }
  77.  
  78. // traitement detecteur Zone AR (pres de gare 2)
  79. detecteur_ZAR = digitalRead(Pin_ZoneAR);
  80. if ((detecteur_ZAR!=prev_detecteur_ZAR)&&(detecteur_ZAR==1)&&(etat_detecteur_ZAR==0))
  81. { // detection debut de convoi (tete en AV, queue en AR)
  82. etat_detecteur_ZAR = 1; // etat 1 = detection initiale début de convoi
  83. IRdebounceARH = 2; // arme tempo de confirmation après 0,2 sec
  84. } else {
  85. if ((detecteur_ZAR==1)&&(IRdebounceARH==0)&&(etat_detecteur_ZAR==1))
  86. { // validation debut de convoi
  87. etat_detecteur_ZAR = 2; // etat 2 = en cours de passage devant le detecteur
  88. prev_detecteur_ZAR = 1;
  89. // TRAITEMENT TETE DE CONVOI
  90. digitalWrite(Pin_Arret, HIGH);
  91. // calcul de la vitesse dans tous les cas : manuel et auto
  92. if (!DIR) { // marche arriere, raz TC
  93. TC = millis(); // debut de comptage
  94. } else { // marche avant, fin du comptage TC -> calcul vitesse
  95. TC = millis() - TC;
  96. if (MTC == 0) {MTC = TC;} else {MTC = (MTC + TC) / 2;}
  97. // tend vers la moyenne
  98. TO = (int)(MTC/500); // nb de 1/2 secondes
  99. if (TO > Config_Init.pas_tempo_ligne + 10) { TO = Config_Init.pas_tempo_ligne; }
  100. VCM = (unsigned long)Config_Init.distance_ligne; // cast 1/2 distance en cm
  101. VCM = VCM*2000/TC;
  102. VKM = VCM*576/100;
  103. }
  104. // si auto : DIR = 1 ralentir avant arret gare 2 ou DIR = 0 entree dans ligne (vitesse constante)
  105. if (AutoManu)
  106. {
  107. if (DIR) // avant
  108. {
  109. Canton = 3; // deceleration et arret
  110. PasAV[Canton]=Config_Init.distance_gare2;
  111. // PasAV = distance capteur -> arret gare 2
  112. MMA = (unsigned int)Config_Init.distance_gare2;
  113. // init distance restant a parcourir jusqu'a arret *100
  114. MMA = MMA*2;
  115. // amélioration de la précision - ordre de grandeur 6000
  116. DeltaVitesse = MMA/VCM;
  117. // nombre de pas en 1/2 sec a VCM pour parcourir MMA
  118. DeltaVitesse = vitesse/DeltaVitesse;
  119. // decrement de vitesse a decompter toutes les 1/2 sec
  120. } else { // arriere
  121. // rien (entree dans canton ligne)
  122. }
  123. }
  124. } else {
  125. if ((detecteur_ZAR!=prev_detecteur_ZAR)&&(detecteur_ZAR==0)&&(etat_detecteur_ZAR==2))
  126. { // transition vers 0 en cours de passage de convoi (inter-wagon ou queue)
  127. etat_detecteur_ZAR = 3; // etat 3 detection retour a 0
  128. IRdebounceARL = 10; // armement tempo 1 sec
  129. } else {
  130. if ((detecteur_ZAR==1)&&(IRdebounceARL!=0)&&(etat_detecteur_ZAR==3))
  131. { // interwagon a ignorer
  132. etat_detecteur_ZAR = 2;
  133. IRdebounceARL = 0;
  134. } else {
  135. if ((detecteur_ZAR==0)&&(IRdebounceARL==0)&&(etat_detecteur_ZAR==3))
  136. {
  137. // queue de convoi
  138. etat_detecteur_ZAR = 0;
  139. prev_detecteur_ZAR = 0;
  140. // TRAITEMENT QUEUE DE CONVOI
  141. digitalWrite(Pin_Arret, LOW);
  142. }
  143. }
  144. }
  145. }
  146. }

Capteur de fin de voie

Le capteur de fin de voie (pour éviter une sortie de voie) n’est pas implémenté pour le moment mais son emplacement est prévu pour une évolution future facile. En attendant il est toujours possible d’isoler une petite portion de voie qui forcera le train à s’arrêter (mais il faudra le pousser à la main pour repartir !).

  1. /////////////////////////////////////////
  2. // CAPTEUR FIN DE VOIE
  3. /////////////////////////////////////////
  4.  
  5. if (!digitalRead(Fin_De_Voie)) // detection si LOW
  6. {
  7. // arret train immediat : eStop et led Rouge allumée
  8. // vitesse = 0, attente retour potentiometres à zero
  9. // passage en mode manuel
  10.  
  11. }

Traitement du mode automatique dans loop()

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

  1. /////////////////////////////////////////
  2. // MODE AUTOMATIQUE
  3. /////////////////////////////////////////
  4.  
  5. // voir automate dans SR_demiseconde()

Déclenchement des tâches périodiques

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

  1. /////////////////////////////////////////
  2. // TACHES PERIODIQUES
  3. /////////////////////////////////////////
  4.  
  5. if ((time500 + 500) < millis()) {
  6. time500 = time500 + 500; // pour rattraper les secondes si ça dérape
  7. SR_demiseconde(); // automate
  8. }
  9.  
  10. if ((IRdebounce_time + IRdebtime) < millis()) { // debouncing detecteurs IR
  11. IRdebounce_time = millis();
  12. if (IRdebounceAVH > 0) IRdebounceAVH--; // tend vers 0 en IRdebtime ms
  13. if (IRdebounceAVL > 0) IRdebounceAVL--; // tend vers 0 en IRdebtime ms
  14. if (IRdebounceARH > 0) IRdebounceARH--; // tend vers 0 en IRdebtime ms
  15. if (IRdebounceARL > 0) IRdebounceARL--; // tend vers 0 en IRdebtime ms
  16. }
  17.  
  18. loopduration = micros() - looptime;
  19. looptime = micros();
  20. if (loopduration > max_loopduration)
  21. {
  22. max_loopduration = loopduration;
  23. }
  24.  
  25. } // 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.

Nous allons entrer maintenant dans les détails de l’automate de circulation.
Il est constitué d’une variable d’état Canton qui peut prendre 6 valeurs (0..5) et d’une variable de direction Dir (avant ou arrière).
Cela fait 12 traitements possibles qui sont réalisé dans une instruction switch.

Mode Automatique - 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.

Il gère d’abord le clignotement des Leds, puis le mode automatique.

  1. ///////////////////////////////////
  2. void SR_demiseconde()
  3. {

Clignotement des Leds : On utilise 2 bits de poids faible d’une variable de type 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.

  1. if (surchargeBooster > 0) {
  2. digitalWrite(Pin_Led_Booster, bitRead(surchargeBooster, 1));
  3. bitWrite(surchargeBooster, 1, !bitRead(surchargeBooster, 1));
  4. }
  5.  
  6. if (Rouge_Clignotant > 0) {
  7. digitalWrite(Pin_Arret, bitRead(Rouge_Clignotant, 1));
  8. bitWrite(Rouge_Clignotant, 1, !bitRead(Rouge_Clignotant, 1));
  9. }
  10.  
  11. if (Jaune_Clignotant > 0) {
  12. digitalWrite(Pin_MAR, bitRead(Jaune_Clignotant, 1));
  13. bitWrite(Jaune_Clignotant, 1, !bitRead(Jaune_Clignotant, 1));
  14. }
  15.  
  16. if (Vert_Clignotant > 0) {
  17. digitalWrite(Pin_MAV, bitRead(Vert_Clignotant, 1));
  18. bitWrite(Vert_Clignotant, 1, !bitRead(Vert_Clignotant, 1));
  19. }

Automate du mode automatique

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

  1. //////////// MODE AUTOMATIQUE //////////////
  2. if (AutoManu) {
  3. // automate mode automatique (s'execute toutes les 1/2 secondes)
  4. digitalWrite(Pin_MAV, (DIR && (vitesse != _stop)));
  5. //Led Verte si DIR = AVANT ET PAS ARRET
  6. digitalWrite(Pin_MAR, (!DIR && (vitesse != _stop)));
  7. //Led Jaune si DIR = ARRIERE ET PAS ARRET
  8. digitalWrite(Pin_Arret, (vitesse == _stop));
  9. //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, qui peut aussi être provoquée par une détection de barrière infrarouge).
Il y a une variable Pas pour chaque Canton, donc elle se présente 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

  1. if (DIR)
  2. { // Marche avant
  3. 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)

  1. case 0: // AV : arret en gare 1 avant départ
  2. vitesse = _stop;
  3. Rouge_Clignotant=0;
  4. PasAV[Canton]--; // rester dans canton tant que PasAV > 0
  5. if (PasAV[Canton] == 0) { // passage au canton suivant
  6. Canton = 1; // passage au canton suivant : acceleration AV
  7. Vert_Clignotant=3;
  8. // vert clignotant = acceleration dans l'etat suivant
  9. }
  10. break;

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

On incrémente 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)

  1. case 1: // AV : depart de la gare 1 : acceleration jusqu'a speed_AV
  2. PasAV[Canton]--;
  3. vitesse = vitesse + Config_Init.increment_acceleration;
  4. if (vitesse > speed_AV)
  5. // acceleration constante, puis vitesse constante
  6. {
  7. vitesse = speed_AV; // vitesse de consigne atteinte
  8. Canton = 2;
  9. PasAV[Canton] = TO + 10; // marge de securite (5 secondes)
  10. // en cas de defaillance de capteur IR
  11. }
  12. Vert_Clignotant=0; // vert fixe : fin acceleration
  13. break;

2 : Le train circule à vitesse constante

On diminue le Pas d’une unité. La détection de passage doit en principe intervenir avant que Pas = 0.

  1. case 2:
  2. // AV : ligne a vitesse constante jusqu'a detecteur gare 2
  3. PasAV[Canton]--; // rester dans canton tant que PasAV > 0
  4. if (PasAV[Canton] == 0) { // passage au canton suivant
  5. Canton = 3; // passage au canton suivant : deceleration
  6. PasAV[Canton]=Config_Init.distance_gare2;
  7. // PasAV = distance capteur -> arret gare 2
  8. Vert_Clignotant=3; // vert clignotant : deceleration
  9. }
  10. break;

3 : Le train ralenti jusqu’à sa vitesse minimale

On réduit la vitesse de façon arithmétique. Celle-ci est diminuée de la variable DeltaVitesse calculée lors du passage aux capteurs).

  1. case 3: // AV : ralentissement jusqu' a V min avant arrivee en gare 2
  2. if (vitesse > Config_Init.vitesse_min)
  3. {
  4. PasAV[Canton]--; // decremente distance parcourue durant 1/2 seconde
  5. vitesse = vitesse - DeltaVitesse; // vitesse = deceleration
  6. if ((vitesse <= Config_Init.vitesse_min) ||
  7. (vitesse > 126) ||
  8. (PasAV[Canton] == 0))
  9. {
  10. vitesse = Config_Init.vitesse_min;
  11. // vitesse est une valeur absolue jamais negative
  12. Canton = 4;
  13. PasAV[Canton]=Config_Init.Nb_Vmin;
  14. // PasAV = Nb de 1/2 secondes à vitesse Vmin
  15. }
  16. }
  17. 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

  1. case 4:
  2. PasAV[Canton]--; // decompte les 1/2 secondes a vitesse Vmin
  3. if (PasAV[Canton] == 0) // si < 0 passage au canton suivant
  4. {
  5. Canton = 5; // vers canton suivant : arret gare
  6. PasAV[Canton]=Config_Init.duree_arret_gare2;
  7. // init 1/2 duree d'arret en gare 2
  8. Vert_Clignotant=0; // vert eteint
  9. }
  10. break;

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

Traitement similaire au cas 0. Mais on change la direction DIR, pour être prêt à repartir dans le sens opposé. L’état Canton est reinitialisé à 5 pour parcourir les 6 cas d’automate décrits juste après.

  1. case 5: // AV : arret en gare 2
  2. vitesse = _stop;
  3. PasAV[Canton]--; // rester dans canton tant que PasAV > 0
  4. if (PasAV[Canton] == 0) { // passage au canton suivant
  5. DIR = 0; // changement de sens : marche AR
  6. Canton = 5; // vers canton suivant (marche arrière)
  7. PasAR[Canton]=Config_Init.duree_arret_gare2;
  8. // init 1/2 duree d'arret en gare 2
  9. }
  10. break;
  11. }
  12. } // if DIR avant
  13. 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

  1. { // DIR = 0 arriere
  2. switch (Canton) {

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

  1. case 5: // AR : arret en gare 2
  2. vitesse = _stop;
  3. PasAR[Canton]--; // rester dans canton tant que PasAV > 0
  4. if (PasAR[Canton] == 0) {
  5. // passage au canton suivant : acceleration en marche arrière
  6. Canton = 4; // vers canton suivant
  7. PasAR[Canton] = 30;
  8. Jaune_Clignotant=3;
  9. }
  10. break;

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

  1. case 4: // AR : depart de gare 2 : acceleration
  2. PasAR[Canton]--;
  3. vitesse = vitesse + Config_Init.increment_acceleration;
  4. if (vitesse > speed_AR)
  5. {
  6. vitesse = speed_AR;
  7. Canton = 3; // vers canton suivant : ligne
  8. PasAR[Canton] = TO + 10;
  9. // butee de securite en cas de defaillance de capteur IR
  10. }
  11. Jaune_Clignotant=0;
  12. break;

3 : Le train circule à vitesse constante

  1. case 3: // AR : ligne a vitesse constante jusqu'a detecteur gare 1
  2. //vitesse constante
  3. PasAR[Canton]; // rester dans canton tant que PasAV > 0
  4. if (PasAR[Canton] == 0) { // passage au canton suivant
  5. Canton = 2; // vers canton suivant : deceleration
  6. PasAR[Canton]=Config_Init.distance_gare1;
  7. // PasAR = distance capteur -> arret gare 1
  8. Jaune_Clignotant=3;
  9. }
  10. break;

2 : Le train ralenti jusqu’à sa vitesse minimale

  1. case 2: // AR : ralentissement jusqu' a V min avant arrivee en gare 1
  2. if (vitesse > Config_Init.vitesse_min)
  3. {
  4. PasAR[Canton]--; // decremente distance parcourue durant 1/2 seconde
  5. vitesse = vitesse - DeltaVitesse; //vitesse = deceleration
  6. if ((vitesse <= Config_Init.vitesse_min) ||
  7. (vitesse > 126) ||
  8. (PasAR[Canton] == 0))
  9. {
  10. vitesse = Config_Init.vitesse_min;
  11. // vitesse est une valeur absolue jamais negative
  12. Canton = 1;
  13. PasAR[Canton]=Config_Init.Nb_Vmin;
  14. // PasAV = Nb de 1/2 secondes à vitesse Vmin
  15. }
  16. }
  17. break;

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

  1. case 1:
  2. PasAR[Canton]--; // decompte les 1/2 secondes a vitesse Vmin
  3. if (PasAR[Canton] == 0) { // si = 0 passage au canton suivant
  4. Canton = 0; // vers canton suivant
  5. PasAR[Canton]=Config_Init.duree_arret_gare1;
  6. // init 1/2 duree arret en gare 1
  7. Jaune_Clignotant=0;
  8. }
  9. break;

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

  1. case 0: // AR : 1/2 duree arret en gare 1
  2. vitesse = _stop;
  3. PasAR[Canton]--; // rester dans canton tant que PasAV > 0
  4. if (PasAR[Canton] == 0) { // passage au canton suivant
  5. Canton = 0; // vers canton suivant
  6. DIR = 1; // changement de sens : marche AV
  7. PasAV[Canton]=Config_Init.duree_arret_gare1;
  8. // init 1/2 duree arret en gare 1
  9. }
  10. break;
  11. } // switch canton
  12. } // if DIR arriere

Envoi des commandes DCC (vitesse et lumière)

Maintenant que les variables de l’automate ont évolué et que la vitesse à probablement changé, il faut transmettre une nouvelle commande à la bibliothèque CmdrArduino.

En fait, j’ai trouvé qu’il était préférable d’envoyer des commandes DCC toutes les 1/2 secondes, ce que le code fait ci-après :

  1. // commande DCC de vitesse et lumiere en auto
  2. _speed = vitesse;
  3. // préserver la variable vitesse qui est toujours comprise entre 0 et 127
  4. if (DIR || (vitesse == _stop))
  5. {
  6. _speed = -vitesse;
  7. // en avant le bit de poids fort est à 1 en 128 pas
  8. } // et à l'arret, la direction est = AVANT
  9. if (!dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,_speed))
  10. {
  11. _erreurDPSS = true;
  12. }
  13. if (!dps.setFunctions0to4(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,FL))
  14. {
  15. _erreurDPSL = true;
  16. } // commandes DCC
  17. } // if Automanu = Auto
  18. else
  19. { // mode manuel : repetition des commandes DCC toutes les 1/2 secondes
  20. if (!dps.setSpeed128(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,_speed))
  21. {
  22. _erreurDPSS = true;
  23. }
  24. if (!dps.setFunctions0to4(Config_Init.dcc_adresse,DCC_SHORT_ADDRESS,FL))
  25. {
  26. _erreurDPSL = true;
  27. } // commandes DCC
  28. }

Fin de l’automate en mode automatique !

Mise à jour périodique de l’affichage LCD

On profite de cette fonction périodique pour rafraichir les affichages toutes les 1/2 secondes.
Les 3 écrans possibles dépendent de la variable Mode_EnCours

Affichage de la ligne 1

  1. strcpy_P(buffer, (char*)pgm_read_word(&(string_table[Mode_EnCours])));
  2. lcd.print(buffer); // affichage 1ere ligne

Affichage de la ligne 2

  1. switch (Mode_EnCours)
  2. {
  3. case 0: // Mode_EnCours 0 : normal, affichage "DCC V DIR AV AR "
  4. lcd.setCursor(2, 1);
  5. lcd.print(Config_Init.dcc_adresse);
  6. lcd.setCursor(2, 5);
  7. lcd.print(vitesse);
  8. lcd.setCursor(2, 8);
  9. if (DIR) {lcd.print(">>");} else {lcd.print("<<");}
  10. lcd.setCursor(2, 11);
  11. lcd.print(speed_AV, DEC);
  12. lcd.setCursor(2, 14);
  13. lcd.print(speed_AR, DEC);
  14. break;
  15.  
  16. case 1: // Mode_EnCours 1 : normal, affichage "V DIR Canton Pas"
  17. lcd.setCursor(2, 1);
  18. lcd.print(vitesse);
  19. lcd.setCursor(2, 4);
  20. if (DIR) {lcd.print(">>");} else {lcd.print("<<");}
  21. lcd.setCursor(2, 8);
  22. lcd.print(Canton);
  23. lcd.setCursor(2, 14);
  24. if (DIR) {lcd.print(PasAV[Canton]);} else {lcd.print(PasAR[Canton]);}
  25. break;
  26.  
  27. case 2: // Mode_EnCours 2 : normal, affichage "Vit Km/h Cm/s "
  28. lcd.setCursor(2, 1);
  29. lcd.print(vitesse);
  30. lcd.setCursor(2, 7);
  31. lcd.print(VKM);
  32. lcd.setCursor(2, 13);
  33. lcd.print(VCM);
  34. break;
  35. }
  36. }

Code source complet de la centrale va-et-vient

Le source complet est ici :

Programme complet de la centrale

Cette version dite b3 (beta 3 datée du 8/11/2014) a été mise en service pendant tout un week-end d’exposition au Plessis-Paté (Essonne).

Durant cette exposition, j’ai pu tester l’effet des réglages de configuration, selon les différents locos qui se sont présentées. En jouant sur la distance capteur-gare ET le nombre de pas de 1/2 secondes à vitesse minimum, on peut obtenir un arrêt très réaliste devant les voyageurs qui attendent sur le quai.

Un petit automate sonore serait du plus bel effet pour animer cette gare. Il serait facile d’ajouter cette fonction à la centrale.

Avec ce dernier article, prend fin la description d’une petite centrale DCC basée sur Arduino.
J’ai testé ce type de centrale sur Uno, Nano, Mini et Mega2560, mais pas encore sur Due.
Sur Uno, Nano, Mini, le sketch occupe seulement la moitié de l’espace de stockage de programmes. Sur Mega, il occupe seulement 6%.
Les variables globales occupent 554 octets donc il reste bien assez de mémoire pour le fonctionnement.

C’est volontairement que le code n’est pas optimisé et écrit de façon simple sans astuces de programmation.

Il peut donc être amélioré, mais est-ce nécessaire ?

Ce projet a été pour moi le fruit de l’accumulation de diverses expériences sur Arduino et sur les trains.

L’exemple du va-et-vient à l’air simple, mais il demande tout de même un code important. Mais de l’automatisme simple décrit ici à quelque chose de plus sophistiqué, la tâche n’est pas énorme :

- J’ai pensé à mettre 2 trains sur une voie unique avec une gare de croisement au milieu et 2 aiguilles, comme la voie du train de St Gervais au Nid d’Aigle en Haute-Savoie.
- J’ai déjà réalisé un block system qui stoppe les trains arrivant sur un canton occupé.

Dans tous les cas, un bonne partie du code est récupérable.

Je vous invite à me faire part de vos trouvailles !

Donc à bientôt …

49 Messages

Réagissez à « Comment piloter trains et accessoires en DCC avec un Arduino (4) »

Qui êtes-vous ?
Votre message

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

Lien hypertexte

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

Rubrique « Projets »

Un chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

L’Arduino et le système de commande numérique DCC

Un décodeur d’accessoire DCC versatile basé sur Arduino

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Calcul de la vitesse d’un train miniature avec l’Arduino

La génèse d’un réseau 100% Arduino

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

Automatisation du pont FLEISCHMANN 6152 (HO) avec un ESP32 (1)

Identifier et localiser vos trains avec le RFID/NFC et un bus CAN.

Etude d’un passage à niveau multivoies

La rétro-signalisation sur Arduino

Décodeur pour aiguillage à solénoïdes sur Arduino

Un décodeur DCC pour les signaux à deux ou trois feux sur Arduino NANO/UNO

Etude d’un passage à niveau universel

Réalisation pratique d’un système de mesure de vitesse à l’échelle N

Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation

Un décodeur DCC pour 16 feux tricolores

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"

Réalisation d’un affichage de gare ARRIVEE DEPART

Ménage à trois (Ordinateur, Arduino, réseau)

Réalisation d’un va-et-vient automatique et réaliste

Souris et centrale sans fil

Communications entre JMRI et Arduino

Annonces en gare avec la RFID

Une croix de pharmacie animée avec Arduino UNO

Réalisation d’un wagon de mesure (distance et vitesse)

Une manette simple et autonome pour LaBox

Éclairer le réseau (1)

Éclairer le réseau (2)

Block Automatique Lumineux à 8 cantons analogiques

Un décodeur DCC pour les plaques tournantes Fleischmann et Roco

Éclairer le réseau (3)

Éclairer le réseau (4)

Éclairer le réseau (5)

JMRI pour Ma première centrale DCC

Rocrail pour Ma première centrale DCC

CDM-Rail pour Ma première centrale DCC (1)

CDM-Rail pour Ma première centrale DCC (2)

Banc de test pour les décodeurs DCC

Ma première manette pour les aiguillages DCC

Mon premier décodeur pour les aiguillages DCC

Boitier 3D pour la station DCC minimale

Va-et-vient pour deux trains

Affichage publicitaire avec Arduino (1)

Affichage publicitaire avec Arduino (2)

Fabrication d’un programmateur pour microcontrôleurs ATtiny

TCO Web interactif avec des ESP32 et des ESP8266 (2)

SGDD : Système de Gestion DD (1)

SGDD : Système de Gestion DD (2)

SGDD : Système de Gestion DD (3)

La PWM : Qu’est-ce que c’est ? (1)

La PWM : Qu’est-ce que c’est ? (2)

La PWM : Qu’est ce que c’est ? (3)

La PWM : Qu’est ce que c’est ? (4)

Mise en oeuvre du Bus CAN entre modules Arduino (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Un gestionnaire en C++ pour votre réseau (1)

Un gestionnaire en C++ pour votre réseau (2)

Un gestionnaire en C++ pour votre réseau (3)

Un gestionnaire en C++ pour votre réseau (4)

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

Contrôleur à télécommande infrarouge pour centrale DCC++

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

La carte Satellite V1

La carte Satellite V1

La carte Satellite V1

La carte Satellite V1

La carte Satellite V1

Les derniers articles

TCO Web interactif avec des ESP32 et des ESP8266 (2)


utpeca

Fabrication d’un programmateur pour microcontrôleurs ATtiny


Christian, Dominique, Jean-Luc

Affichage publicitaire avec Arduino (2)


catplus, Christian

Affichage publicitaire avec Arduino (1)


catplus, Christian

Un décodeur DCC pour les plaques tournantes Fleischmann et Roco


msport

Banc de test pour les décodeurs DCC


msport

Va-et-vient pour deux trains


msport

Boitier 3D pour la station DCC minimale


msport

Mon premier décodeur pour les aiguillages DCC


msport

Ma première manette pour les aiguillages DCC


msport

Les articles les plus lus

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

La PWM : Qu’est-ce que c’est ? (1)

Un chenillard de DEL

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Comment piloter trains et accessoires en DCC avec un Arduino (3)

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Une barrière infrarouge

Feux tricolores

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation pratique d’un système de mesure de vitesse à l’échelle N