LOCODUINO

Comment piloter trains et accessoires en DCC avec un Arduino

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.

ATTENTION ! Ce logiciel utilise une bibliothèque qui est maintenant obsolète. Vous trouverez une réalisation parfaitement réalisable dans l’article Réalisation d’un va-et-vient automatique et réaliste .

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

// LIBRAIRIE CMDRARDUINO
#include <DCCPacket.h>             // bibliothèque CmdrArduino
#include <DCCPacketQueue.h>        // idem
#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

#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

#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

#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

#include <SoftwareSerial.h>
#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.

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

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.

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

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

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

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

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 :

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

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 :

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

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

Bounce B_Mode = Bounce(Pin_Mode, 100);// bouton MODE
byte prev_BM_state = 1;               // etat anterieur du poussoir Mode
byte BM = 0;                          // inactif (0) ou actif (1)
boolean Mode_Config = false;
byte Mode_EnCours = 0;                // mode 0 : normal, affichage "DCC V DIR AV AR "
                                      // mode 1 : normal, affichage "V DIR Canton Pas"
                                      // mode 2 : normal, affichage vitesse reelle
                                      // mode 3 : config : @ dcc
                                      // mode 4 : config : V min
                                      // mode 5 : config : acceleration
                                      // mode 6 : config : t arret gare 1
                                      // mode 7 : config : t arret gare 2
                                      // mode 8 : config : l canton ligne
                                      // mode 9 : config : l canton gare 1
                                      // mode 10: config : l canton gare 2
                                      // mode 11: config : time-out n 1/2s
                                      // mode 12: config : nb 1/2s a Vmin
                                       
Bounce B_AutoManu = Bounce(Pin_AutoManu, 20); // clé AUTO/MANUEL
byte prev_AutoManu_state = 0;                 // etat anterieur du commutateur
                                              // auto-manuel
byte AutoManu = 0;                            // manuel (0) ou automatique (1)

Bounce B_SW_FL = Bounce(Pin_SW_FL, 20); // clé LUMIERE
byte prev_FL_state = 0;                 // etat anterieur du bouton lumiere FL
byte FL = 0;                            // eclairage éteint (0) ou allumé (1)

Bounce B_DCC = Bounce(Pin_DCC, 20); // clé ARRET/MARCHE DCC
byte prev_ON_DCC_state = 0;         // etat anterieur du bouton de signal DCC
                                    // (pour etre OFF à l'init)
byte ON_DCC = 0;                    // DCC off (0) ou on (1)

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

boolean alarme_temperature = true; // LOW si T>145°C au capteur, 
					                         // inversé à la mesure
int mesure_courant = 0;
 
// detecteur de zones AV (gare 2)
int detecteur_ZAV, prev_detecteur_ZAV, etat_detecteur_ZAV = 0;      
// detecteur de zones AR (gare 1)
int detecteur_ZAR, prev_detecteur_ZAR, etat_detecteur_ZAR = 0;      
					
unsigned long IRdebounce_time;
#define IRdebtime  100             // 10 milliseconde
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

// 0 = normal, >0 (true) =  clignote 1 éteint ou 3 allumé
byte surchargeBooster = 0; 
byte Rouge_Clignotant = 0;     
byte Jaune_Clignotant = 0; 
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

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

prog_char string_0[]  PROGMEM = "DCC V DIR AV AR ";   // Mode_EnCours = 0
prog_char string_1[]  PROGMEM = "V DIR Canton Pas";   // Mode_EnCours = 1
prog_char string_2[]  PROGMEM = "Vit  Km/h  Cm/s ";   // Mode_EnCours = 2
prog_char string_3[]  PROGMEM = "Adresse DCC :   ";   // Mode_EnCours = 3
// mode configuration à partir de 4
prog_char string_4[]  PROGMEM = "Vitesse min :   ";   // Mode_EnCours = 4
prog_char string_5[]  PROGMEM = "Acceleration :  ";   // Mode_EnCours = 5
prog_char string_6[]  PROGMEM = "T arret gare 1: ";   // Mode_EnCours = 6
prog_char string_7[]  PROGMEM = "T arret gare 2: ";   // Mode_EnCours = 7
prog_char string_8[]  PROGMEM = "L canton ligne: ";   // Mode_EnCours = 8
prog_char string_9[]  PROGMEM = "L canton gare 1:";   // Mode_EnCours = 9
prog_char string_10[] PROGMEM = "L canton gare 2:";   // Mode_EnCours = 10
prog_char string_11[] PROGMEM = "Time-out n 1/2s:";   // Mode_EnCours = 11
prog_char string_12[] PROGMEM = "Nb 1/2s Vmin :  ";   // Mode_EnCours = 12

PROGMEM const char *string_table[] = 	                // table des adresses
{   
  string_0,
  string_1,
  string_2,
  string_3,
  string_4,
  string_5,
  string_6,
  string_7,
  string_8,
  string_9,
  string_10,
  string_11,
  string_12
 };

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

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

Passons aux taches périodiques

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

// 0 = normal, >0 (true) =  clignote 1 éteint ou 3 allumé
byte surchargeBooster = 0; 
byte Rouge_Clignotant = 0; 
byte Jaune_Clignotant = 0;
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.

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

  #define VERSION "b3-081114"
  
  void setup() {

  lcd.setBrightness(25); // rétro-éclairage du LCD 0..30
  lcd.clear();           // effacement du LCD
  lcd.print("Vers. ");
  lcd.print(VERSION);
  lcd.selectLine(2);     // positionnement en 2è ligne

  ///////////// initialisation des Pins
  //pinMode(Pot_AV, INPUT);         // entree analogique donc pas nécessaire
  //pinMode(Pot_AR, INPUT);         // entree analogique donc pas nécessaire
  //pinMode(Current_Sense, INPUT);  // entree analogique donc pas nécessaire
  pinMode(Thermal_Sense, INPUT_PULLUP);
  pinMode(Pin_AutoManu, INPUT_PULLUP);
  pinMode(Pin_SW_FL, INPUT_PULLUP);
  pinMode(Pin_Mode, INPUT_PULLUP);
  pinMode(Pin_DCC, INPUT_PULLUP);
  pinMode(Pin_Out_PWM, OUTPUT);
  //pinMode(Pin_Out_DIR, OUTPUT);   // initialisé par CmdrArduino
  digitalWrite(Pin_Out_PWM, LOW);   // DCC OFF
  pinMode(Pin_Led_Booster, OUTPUT);
  digitalWrite(Pin_Led_Booster, HIGH);  
    // Booster OFF (allume) ou CLIGNOTANT (surcharge)
  pinMode(Pin_MAV, OUTPUT);
  pinMode(Pin_MAR, OUTPUT);
  pinMode(Pin_Arret, OUTPUT);
  pinMode(Pin_ZoneAV, INPUT_PULLUP);
  pinMode(Pin_ZoneAR, INPUT_PULLUP);
  pinMode(Fin_De_Voie, INPUT_PULLUP);

  ////////////// initialisation de la bibliothèque CmdrArduino
  dps.setup();                      // initialise les Pins 9 et 10

  prev_ON_DCC_state = B_DCC.read(); // force l'etat DCC off
  // il faut une transition bas -> haut de Alim Rails
  // pour autoriser l’alimentation des rails
   
  ////////////// Recuperation des valeurs de configuration en EEPROM 
  _Recup_EEPROM();     // voir le détail de cette fonction plus loin
  
  lcd.print("DCC:");   // affichage de l’adresse DCC en configuration
  lcd.print(Config_Init.dcc_adresse);
 
  Mode_EnCours = 0;    // initialisation de la variable
  Mode_Config = false; // pas de mode configuration 
  
  ///////////// test de la configuration : si l’adresse DCC == 0 ou FF (EEPROM vierge):
  ///////////// passage obligatoire en configuration à la 1ère mise en service
  if ((Config_Init.dcc_adresse == 0) || (Config_Init.dcc_adresse == 255))
  {
    Mode_EnCours = 3;
    Mode_Config = true;
    _Configuration();         // voir le détail de cette fonction plus loin
  }
 
  ///////////// initialisation des compteurs de temps pour les taches periodiques
  time500 = millis();         // pour déclencher la fonction SR_demiseconde()
  looptime = micros();        // pour mesurer la durée moyenne de la LOOP()
  IRdebounce_time = millis(); // pour l’anti-rebond des boutons
 
  lcd.print(" RAM:");
  lcd.print(_RamFree()); // affiche la quantité de RAM restant (état de santé)
  delay(2000);           // pendant 2 secondes
} // 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()

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

Fonction _Program_EEPROM()

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

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

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

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.

void _questionB()                           // adresse DCC, Mode_EnCours = 3
{
  boolean ROK = false;
  boolean RXIT = false; 
  byte Valeur = 0;
  
  lcd.print(Config_Init.dcc_adresse);       // ancienne valeur sur 2e ligne, max 3 cars
  lcd.print(" ->");                         // choix à afficher en ligne 2, colonne 8
  while (!RXIT) 
  {
    // on se sert de la somme des potentiometres
    Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 4; 
    //division par 16 pour ramener le champ 0-2047 à 0-127
    lcd.setCursor(2, 6);
    lcd.print(Valeur);
    lcd.print("  ");             // pour effacer les caracteres suivants eventuels
    if (B_Mode.update())         // Poussoir Mode change-t-il ?
    {
      if (B_Mode.risingEdge()) 
      {
        RXIT = true;             // sortie de la boucle while
     }
    }
    if (B_SW_FL.update())        // COMMUTATEUR DE LUMIERE
    {
      if (B_SW_FL.read() == HIGH)
      {        
        Config_Init.dcc_adresse = Valeur; // validation 
        ROK = true;
      } 
    }
  } // while !ROK
  
  if (ROK) 
  {
    // enregistrement en EEPROM
    EEPROM.write(0, highByte(Config_Init.dcc_adresse)); 
    delay(10);
    EEPROM.write(1, lowByte(Config_Init.dcc_adresse));
    lcd.print(" Ok!");
    delay(1000);
  }
}

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.

int _questionC(int _VAL, int _index)  // Mode_EnCours > 3
{
  boolean ROK = false;
  boolean RXIT = false;
  byte Valeur = 0;
  
  lcd.print(_VAL);          // ancienne valeur sur 2e ligne, max 3 cars
  lcd.print(" ->");         // choix à afficher en ligne 2, colonne 8
  while (!RXIT) 
  {
    // on se sert de la somme des potentiometres
    Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 4;
    //division par 16 pour ramener le champ 0-2047 à 0-127
    lcd.setCursor(2, 6);
    lcd.print(Valeur);
    if (B_Mode.update())    // Poussoir Mode change-t-il ?
    {
      if (B_Mode.risingEdge()) 
      {
        RXIT = true;        // sortie de la boucle while
     }
    }
    if (B_SW_FL.update())   // COMMUTATEUR DE LUMIERE
    {
      if (B_SW_FL.read() == HIGH)
      {
        _VAL = Valeur;      // validation
        ROK = true;
      } 
    }
  }  // while !ROK
  if (ROK) 
  {
    // enregistrement en EEPROM
    EEPROM.write(_index, _VAL);
    lcd.print(" Ok!");
    delay(1000);
  }
  return(_VAL);  // retour avec le paramètre modifié ou non
}

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.

int _questionD(int _VAL, int _index)  // mode LCD uniquement, Mode_EnCours > 3, pour une valeur 0..512
{
  boolean ROK = false; 
  boolean RXIT = false;               // pour cette reponse globale
  int Valeur = 0;
  
  lcd.print(_VAL*2);                  // ancienne valeur double sur 2e ligne, max 3 cars
  lcd.print(" ->");                   // choix à afficher en ligne 2, colonne 8
  while (!RXIT) 
  {
    // on se sert de la somme des potentiometres
    Valeur = (analogRead(Pot_AV) + analogRead(Pot_AR)) >> 2; // division par 4 pour ramener
                                                             // le champ 0-2047 à 0-511.
    lcd.setCursor(2, 6);
    lcd.print(Valeur);
    lcd.print("  ");                  // pour effacer les caracteres suivants eventuels
    if (B_Mode.update())              // Poussoir Mode change
    {
      if (B_Mode.risingEdge()) 
      {
        RXIT = true;                  // sortie de la fonction sans modification
      }
    }
    if (B_SW_FL.update())             // COMMUTATEUR DE LUMIERE
    {
      if (B_SW_FL.read() == HIGH)
      {
        _VAL = Valeur/2;              // nouvelle valeur
        ROK = true;
      } 
    }
  }  // while !ROK
  if (ROK) 
  {
    EEPROM.write(_index, (byte)_VAL);
    lcd.print(" Ok!");
    delay(1000);
  }
  return(_VAL);
}

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 :

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 directement 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, 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

  /////////////////////////////////////////
  //Commandes de vitesse AV et AR
  /////////////////////////////////////////
  AV_value = analogRead(Pot_AV);
  speed_AV = (AV_value >> 3); 
  // division par 8 pour passer de 0-1023 a 0-127
  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); 
  // division par 8 pour passer de 0-1023 a 0-127
  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;
  }

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.

  /////////////////////////////////////////
  // Execution CmdrArduino à chaque LOOP
  /////////////////////////////////////////
  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.

  /////////////////////////////////////////
  // 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é 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

  /////////////////////////////////////////
  // 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 !

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.

  /////////////////////////////////////////
  // CAPTEURS ZONE DE RALENTISSEMENT
  /////////////////////////////////////////
 
  // 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);
      // calcul de la vitesse dans tous les cas : manuel et auto
      if (DIR) {         // marche avant, raz TC
        TC = millis();   // début du comptage
      } else {           // marche arriere, fin du comptage, TC -> calcul vitesse
        TC = millis() - TC;
        if (MTC == 0) {MTC = TC;} else {MTC = (MTC + TC) / 2;}    
        // tend vers la moyenne en millisecondes
        TO = (int)(MTC/500);          // nb de 1/2 secondes
        if (TO > (Config_Init.pas_tempo_ligne+10))
        {
          TO = Config_Init.pas_tempo_ligne;  // butee
        }
        VCM = (unsigned long)Config_Init.distance_ligne;  // cast 1/2 distance en cm
        VCM = VCM*2000/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 a l'entree dans canton ligne
        } else {         // arriere, sortie du canton ligne
          Canton = 2;    // deceleration : preparation des parametres
          PasAR[Canton]=Config_Init.distance_gare1;  // PasAR = distance capteur -> arret gare 1
          MMA = (unsigned int)Config_Init.distance_gare1; 
          // init distance restant a parcourir jusqu'a vitesse min (ex: 60 cm)
          MMA = MMA*2;  
          // amélioration de la précision - ordre de grandeur 120
          DeltaVitesse = MMA/VCM;  
          // nombre de pas en 1/2 sec a VCM pour parcourir MMA
          DeltaVitesse = vitesse/DeltaVitesse;  
          // decrement de vitesse a decompter toutes les 1/2 sec
        } 
      }
    } else {  // autres cas que debut de convoi
      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);
      // calcul de la vitesse dans tous les cas : manuel et auto
      if (!DIR) {          // marche arriere, raz TC
        TC = millis();    // debut de comptage
      } else {            // marche avant, fin du comptage TC -> calcul vitesse
        TC = millis() - TC;
        if (MTC == 0) {MTC = TC;} else {MTC = (MTC + TC) / 2;} 
         // tend vers la moyenne
        TO = (int)(MTC/500);        // nb de 1/2 secondes
        if (TO > Config_Init.pas_tempo_ligne + 10) { TO = Config_Init.pas_tempo_ligne; }
        VCM = (unsigned long)Config_Init.distance_ligne;  // cast 1/2 distance en cm
        VCM = VCM*2000/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;        
          // init distance restant a parcourir jusqu'a arret *100
          MMA = MMA*2;                                            
          // amélioration de la précision - ordre de grandeur 6000
          DeltaVitesse = MMA/VCM;                                 
          // nombre de pas en 1/2 sec a VCM pour parcourir MMA
          DeltaVitesse = vitesse/DeltaVitesse;                    
          // decrement de vitesse a decompter toutes les 1/2 sec
        } 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);
          }
        }
      }
    }
  }

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

  /////////////////////////////////////////
  // 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
    
  }

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

  /////////////////////////////////////////
  // MODE AUTOMATIQUE
  /////////////////////////////////////////
  
  // 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().

  /////////////////////////////////////////
  // 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()) {  // debouncing 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.

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.

///////////////////////////////////
void SR_demiseconde()
{

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.

  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

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

  //////////// MODE AUTOMATIQUE //////////////
  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, 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

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

        case 1:  // AV : depart de la gare 1 : acceleration jusqu'a speed_AV
        PasAV[Canton]--;
        vitesse = vitesse + Config_Init.increment_acceleration;   
        if (vitesse > speed_AV)                    
        // acceleration constante, puis vitesse constante
        {
          vitesse = speed_AV;       // vitesse de consigne atteinte
          Canton = 2;
          PasAV[Canton] = TO + 10;  // marge de securite (5 secondes)
                                    // en cas de defaillance de capteur IR
        }
        Vert_Clignotant=0;          // vert fixe : fin acceleration
        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.

        case 2:               
        // AV : ligne a vitesse constante jusqu'a detecteur gare 2
        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 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).

        case 3:  // AV : ralentissement jusqu' a V min avant arrivee en gare 2
        if (vitesse > Config_Init.vitesse_min)
        {
          PasAV[Canton]--;   // decremente distance parcourue durant 1/2 seconde
          vitesse = vitesse - DeltaVitesse;  // vitesse = deceleration    
          if ((vitesse <= Config_Init.vitesse_min) ||
              (vitesse > 126) ||
              (PasAV[Canton] == 0)) 
          {
            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. 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.

        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 marche arrière 
          Canton = 4;               // vers canton suivant
          PasAR[Canton] = 30;
          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
        PasAR[Canton]--;
        vitesse = vitesse + Config_Init.increment_acceleration;    
        if (vitesse > speed_AR) 
        {
          vitesse = speed_AR;
          Canton = 3;               // vers canton suivant : ligne
          PasAR[Canton] = TO + 10;
          // butee de securite en cas de defaillance de capteur IR
        }
        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]--;  // decremente distance parcourue durant 1/2 seconde
          vitesse = vitesse - DeltaVitesse;  //vitesse = deceleration   
          if ((vitesse <= Config_Init.vitesse_min) ||
              (vitesse > 126) ||
              (PasAR[Canton] == 0)) 
          {
            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)

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 :

    // 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
  else
  { // mode manuel : repetition des commandes DCC toutes les 1/2 secondes
    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
  }

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

    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;
    }    
}

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

51 Messages

  • Bravo Dominique !
    C’est un régal de lire de tels programmes.
    Bien sûr, je n’ai pas tout compris (mais ça viendra).
    C’est le plus long article du site (!), mais tout est détaillé et même commenté par le menu.
    Je pense que, justement, il était essentiel que ce programme ne soit PAS optimisé pour qu’on le comprenne. Et ce sera un excellent exercice de l’optimiser.

    Répondre

  • Merci beaucoup Denis,

    Cet article est le fruit d’une expérience qui a abouti à un résultat qui marche. J’ai noté dès le départ mes recherches et raisonnements pour expliquer le logiciel.
    La configuration en particulier a été un gros problème au début jusqu’à ce que je trouve l’astuce de déclarer les chaînes en Flash pour décharger la Ram.

    Ensuite la bibliothèque CmdrArduino nécessite de forcer des répétitions car elle ne remplit pas le temps de commandes répétées. Je vais essayer de l’améliorer (vive l’open source).

    Répondre

  • Super !! enfin du DCC pas chère ..et libre !
    n’étant pas équipé en DCC,
    vous avez un programme pour "programmer les locos" ?
    je passerai bien au DCC mais si il faut que j’achète une centrale pour programmer les locos .. aie !

    Répondre

  • Merci pour votre question : la partie programmation viendra évidemment prochainement mais je ne peux dire quand exactement.

    En attendant vous avez 2 possibilités :

    • aller dans un club : ils ont tous une centrale faite pour cela ;
    • utiliser la bibliothèque CmdrArduino qui contient des fonctions de programmation que j’avoue ne pas avoir testé pour le moment.

    Répondre

  • En réponse aux deux messages ci-dessus, je confirme que la bibliothèque CmdrArduino est fonctionnelle. J’ai pu faire mes débuts sans centrale "du commerce" avec.

    Une autre solution abordable est le petit boitier SPROG. Très bon marché, open source, et déjà très bien intégré avec JMRI. Après plus d’un an de développement de ma centrale, j’ai fini par opté pour cette solution !

    Répondre

  • Merci Zebulon91,
    Je suis d’accord qu’il existe des modules tout faits comme le SPROG qui sont abordables. Mais ce n’est pas le sujet ici :

    • je démontre principalement qu’on peut générer du DCC avec un Arduino, ce que vous confirmez et je vous en remercie.
    • par la même occasion on peut construire un système complet, ici un va et vient qui est simple : c’est beaucoup moins cher qu’avec une centrale du commerce
    • on n’a pas besoin de PC
    • je ne suis pas certain qu’on mette moins de temps à maitriser JMRI que la programmation Arduino !! (On peut rêver !)

    Toute la question est dans l’architecture que l’on choisit pour son réseau.
    La presse et le Web abondent de sujets relatifs aux solutions commerciales toutes faites.
    Ici on tente de réaliser tout ou partie par soi-même, d’abord parce que c’est gratifiant et aussi pour en faire plus en payant moins !
    Des discussions sur l’architecture sont d’ailleurs ouvertes sur le Forum.

    Amicalement

    Répondre

  • Bonjour,
    J’ai lu avec grand profit tous les articles du site ; en particulier ceux sur le DCC. Ils sont tous d’une très grande qualité et remplis d’informations précieuses. Bravo et merci aux auteurs.

    Sur la base de ce qui a été présenté (bibliothèque Cmdr Arduino, booster LM18200), je me suis lancé dans le DCC : j’ai d’abord fait une maquette, puis un montage ’propre’ de générateur DCC (un seul montage pour piloter 8 locos ; interface homme machine ’hard’ avec encodeurs rotatifs et boutons-poussoirs). Tout fonctionne très bien, à l’exception d’un phénomène transitoire à la mise sous tension du montage (et/ou lors d’un reset soft, sans re-démarrage électrique - en ré-ouvrant le moniteur USB de l’IDE Arduino, par exemple) : la locomotive présente sur le circuit (je fais les tests avec une seule loco, pour l’instant) se met en marche à pleine vitesse pendant un court instant (parfois elle change de sens). Le phénomène se produit très tôt, peut-être même avant le début du setup (qui dure assez longtemps, avec traces sur le moniteur USB et sur un écran LCD - on voit très bien que le transitoire se produit très tôt).

    Ceci m’étonne d’autant plus que n’ayant pas acheté un montage LM18200 tout fait, j’ai ajouté, sur le circuit, des portes logiques pour gérer en dur la sécurité (alarme de t°) et la mise en action de la sortie (signal de sortie actif si Brake = Low et PWM = High ; sortie désactivée dans le cas contraire).
    Evidemment, le plus tôt possible dans le setup(), bien avant de faire le dps.setup(), je positionne correctement (à Low) la pin Arduino qui, par l’intermédiaire des portes mentionnées, désactive la sortie du LM18200. (Cette commande de marche-arrêt fonctionne d’ailleurs parfaitement, ultérieurement. La tension de sortie est correcte en régime établi, et tout fonctionne, etc...) De plus, à ce moment, la vitesse est positionnée à zéro, mais on n’a pas encore émis de dps.setSpeed, ni même de dps.update().
    Mais rien n’y fait.
    Il est vraiment étonnant que le générateur DCC, avant même d’être configuré, arrive non seulement à produire un signal ’correct’, indiquant de plus une vitesse très grande, avec une adresse ad’hoc pour le décodeur, mais, de plus, ’force’ le LM18200 à émettre ce signal amplifié ! C’est vraiment difficile à imaginer.
    Je n’ai pas encore regardé à l’oscillo ce qui se passe pendant ce transitoire (s’il est possible de voir quelque chose). Mais j’ai déjà vérifié circuits et programme.

    Hypothèse : Je ne connais pas bien le DCC. Le décodeur ESU que j’utilise doit pouvoir fonctionner en DCC et en analogique. Lors de sa configuration, j’ai dû laisser cette option par défaut. Au démarrage, le LM18200 émettrait, pendant un court instant, une tension continue suffisante pour mettre en marche le moteur (en mode analogique) ?
    Une telle impulsion de tension, capable de produire un tel effet, n’est pas rassurante non plus (pour la tenue à long terme du matériel stationnant sur les voies, et des décodeurs de celui-ci).

    Qu’en pensez-vous ?
    Avez-vous déjà constaté de tels phénomènes ?
    Je n’avais, jusqu’ici, pas constaté de comportement chaotique des pins Arduino lors d’un démarrage ou d’un reset.
    Cela peut-il venir du LM18200 ? En cas de reset, il reste tout le temps alimenté en 12V stabilisé. Il n’y a que les pins de commande qui peuvent voir leur tension varier.
    Dans ce cas, il faudra couper la sortie du LM18200 pendant
    le setup (relais commandé par une pin Arduino, par exemple ?).
    D’avance merci.

    Répondre

  • Bonjour Jacques,

    Les décodeurs DCC permettent un fonctionnement soit Digital, soit Analogique. Dans ce dernier cas, la vitesse est proportionnelle à la tension.

    Ce qui se passe dans votre cas, c’est la présence d’une tension analogique continue (positive ou négative suivant le sens, au hazard, de conduction des transistors du LMD18200) à la mise sous tension (ou au reset de l’Arduino, quand on lance le moniteur), avant que le signal DCC ne soit généré, après le Setup.

    Cela provoque un lancement de la loco à vitesse max dans un sens ou un autre.

    Ce phénomène ne se produit pas avec la réalisation que j’ai décrite, mais il est souvent constaté avec la MS2 de Marklin.

    Ce qu’il faut faire est simple : empêcher le LMD18200 d’émettre une tension continue au démarrage, en veillant à ce que le signal PWM reste LOW jusqu’à la fin du Setup.

    Jetez un oeil (à l’oscillo) sur la tension des rails à l’init et dites nous si cette hypothèse est bonne ou pas !

    Cordialement
    Dominique

    Répondre

    • Merci de cette réponse rapide.

      Elle conforte les hypothèses invoquées pour expliquer ce comportement surprenant. Cela rassure un peu d’avoir confirmation que ce type de phénomène a déjà été observé.
      Et avec l’oscilloscope, il n’y a plus de doute.

      Le problème surgit avant le setup ; c’est net.
      Le LM18200 débite parce que la pin Arduino chargée de l’activer passe à 1.2V pendant un peu moins de 2s (1.2, c’est suffisant pour faire un niveau haut sur les portes logiques). La tension émise est continue et stable à 12V (ou -12V), en synchronisme avec la pin de commande (une tension continue ? normal : à ce moment le signal DCC pour l’entrée Dir n’a pas encore été activé). Une fois le setup commencé, la pin Arduino est bien à zéro, conformément à la programmation, et la sortie DCC est bloquée.

      J’avais couplé directement la pin Arduino à l’entrée des portes logiques contrôlant PWM et Brake du LM18200 : il doit se produire un phénomène de ’tirage’ non sollicité. Le remède est donc simple : une résistance de tirage de 2,2k entre la pin et la terre est suffisante pour faire chuter cette tension résiduelle non désirée sous le seuil ’haut’, sans nuire au fonctionnement ultérieur (niveau haut voisin de 5V) et tout marche merveilleusement bien.

      J’aurais dû le deviner ; mais si on parle toujours des résistances de tirage pour les entrées, pour les sorties ... c’est nettement plus rare. D’où cette fatale omission. Il est vrai que ça ne doit vraiment compter que pendant le reset, période pendant laquelle les pins ne sont plus ’pilotées’, semble-il.
      Il n’est donc pas inutile, non plus, de prévoir un circuit de contrôle de l’activation du LM18200 qui ne sollicite qu’une pin Arduino.

      J’en ai profité pour regarder la sortie de mesure de l’intensité du LM18200. Il sera difficile d’en faire quelque chose : la tension est une suite de pics à fréquence élevée fluctuant dans une grande plage de valeurs, avec une ligne de base de -20mV. Il faudrait filtrer énergiquement, mais la ’sortance’ du circuit ne le permet certainement pas de façon passive. Et pourquoi cet offset ?
      Que peut mesurer une entrée analogique dans de si défavorables conditions ? C’est inexploitable.

      Le petit Uno s’avère donc capable :

      • de contrôler 4 expandeurs d’entrées-sorties (4x16 E/S) pilotés par I2C (rien que l’interface pour les 8 locos en utilise 8x6 : encodeurs rotatifs et boutons-poussoirs),
      • de tenir à jour, en liaison directe, un afficheur LCD à 4 lignes de 20 caractères,
      • de générer le signal DCC avec CmdrDCC pour 8 locos (au moins) en le répétant très régulièrement pour contourner la lacune signalée de cette bibliothèque,
      • et de réaliser de petits traitements annexes (rampes de démarrage, de freinage [simulation d’une inertie, mais instantanément ’activable’ ou ’débrayable’, quelle que soit la vitesse], gestion du couplage de deux locos sur le même convoi).
        Et il reste encore une bonne marge sur les mémoires (sram et programme) !

      Il n’y a pas de temps perdu : tout les évènements sont gérés par interruption, certains rebonds sont évités en utilisant le timer2, DCC utilisant le timer1.
      Les encodeurs sont gérés au travers des expandeurs : il a fallu adapter plusieurs librairies existantes.
      Je n’ai pas eu de problèmes de perturbation de l’I2C par le bruit électronique environnant ; il est vrai que j’ai pris beaucoup de précautions (séparation de la partie logique et de la partie puissance [LM18200] dans des boîtiers distincts, blindage complet des boîtiers et des câbles de liaison entre modules, alimentation de puissance séparée).

      Et Locoduino m’a fourni nombre d’informations utiles, claires et fiables. Encore merci aux auteurs.

      Répondre

      • Je poursuis les essais de mon système DCC.

        Avec la maquette sur breadboard, j’avais constaté une anomalie.
        Sur le montage final, sur circuits imprimés, le même défaut réapparaît :
        l’utilisation de l’arrêt d’urgence, en version générale ou pour une seule loco, produit des effets gênants.

        Après l’appel à dps.eStop(), l’arrêt est très ’énergique’, mais les locos ne redémarrent pas normalement. Elles restent au ralenti.
        L’anomalie peut disparaître progressivement, mais le plus souvent, il faut arrêter tout le système, une désactivation du LM18200 ne suffisant pas.
        Ensuite, tout redevient normal.
        A noter : Les mêmes décodeurs (ESU) réagissent normalement à un arrêt d’urgence collectif ou individuel élaboré par un ensemble JMRI+ Sprog.

        Que peut-il se passer ?

        • Un effet de la contre-fem de freinage (probable vu l’effet ’énergique’) sur le décodeur ?
          Mais pourquoi faut-il aller jusqu’à l’arrêt de tout le système pour obtenir sa restauration, la désactivation du LM18200 privant tout aussi bien les décodeurs de toute tension ?
        • Un effet sur le LM18200 ? Comment le décodeur pourrait-il lui renvoyer une contre-fem suffisante pour le perturber ? D’autant que je travaille seulement sous 12V. Et que l’effet n’est pas toujours aussi net sur toutes les locos.
        • Un bug de mon code ? Le remplacement de l’eStop par un arrêt simple (commande de vitesse à 0, ie cran +/-1 sur les locos concernées) supprime toute perturbation. Et comment s’opérerait un retour progressif à la normale ?
        • Un bug dans la librairie CmdrDCC ? C’est peu vraisemblable qu’il n’ait pas déjà été découvert.

        Bon, on peut se passer d’un tel arrêt d’urgence, la désactivation du LM18200 finissant par arrêter tout le monde, mais moins rapidement, il est vrai.

        Un expert aurait-il une idée ?
        D’avance merci.

        Répondre

  • Bonjour,

    Je n’utilise jamais l’arrêt d’urgence de la bibliothèque car j’estime qu’on n’en a pas besoin :

    • un arrêt normal respecte l’inertie et est plus réaliste.
    • en cas de problème majeur, on coupe le PWM du LMD18200, mais ça ne devrait pas arriver

    De ce fait, je n’ai pas testé l’arrêt d’urgence de la biblio.
    Je vais tâcher de regarder cela à la rentrée.

    Répondre

  • Pour faciliter l’analyse du phénomène constaté avec les e-stop, j’ai étudié le code de CmdrDCC.
    On peut rapprocher les e-stop d’autres commandes émises à l’initialisation du générateur DCC : reset packet et idle packet qui, eux, ne posent pas de difficultés.
    Un tableau permet de mieux comparer les caractéristiques de ces différentes commandes.
    NB : il y a un pb de présentation, tabulations ou blancs sont mal gérés à l’affichage.

    Nom du packet : reset packet idle packet e stop packet (global) e stop packet
    Data : 0x00 0x00 0x71 0x41
    Adress : 0x00 0xFF 0x00 adress_loco
    Repeat : 20 10 10 10
    Kind : reset_packet_kind idle_packet_kind e_stop_packet_kind e_stop_packet_kind

    Les spécialistes de DCC sauront dire si les data 0x71 ou 0x41 associées aux deux types de e-stop sont appropriées.
    D’avance merci à eux.

    Répondre

  • Merci pour ces précisions :)

    Pour commencer, une comparaison avec la norme NMRA est nécessaire.
    Mais je n’ai pas les outils sous la main pour le moment et je reviendrai un peu plus tard sur cette réponse.

    cordialement
    Dominique

    Répondre

  • Sur le forum Arduino, il y a eu une discussion sur ce sujet avec l’auteur de la bibliothèque Don Goodman-Wilson :

    Railstars CmdrArduino lib help

    Répondre

  • La discussion du forum Arduino vient d’être relancée ce 06/08 !
    Un pb avec eStop constaté sur Uno, mais pas de détails pour l’instant...

    Répondre

  • Je viens de le voir en effet.

    Mais je me demande toujours dans quel(s) cas on peut avoir besoin du eStop !!!

    Couper tous les trains revient à couper le courant au booster.
    Couper un train : quels cas d’urgence le nécessite ? S’il n’y a pas de cantonnement, ni de block system, ni de gestion des trains plus ou moins élaborée, je comprend qu’il puisse y avoir des situations de type "accident".

    C’est pour cela que j’étudie le "cerveau" qu’il faut pour éviter les eStop.

    Neanmoins c’est mieux sans bug !

    Répondre

  • Bonjour Dominique,

    merci pour ces articles dont les concepts m’intéressent beaucoup.

    Je m’interroge sur l’utilité du calcul de la vitesse réelle (entre les deux capteurs) :

    • est-ce que la vitesse réelle d’une locomotive est proportionnelle à la consigne transmise en DCC ?
    • après étalonnage, pourrait-on déduire la vitesse réelle de la locomotive à partir de la seule consigne de vitesse transmise en dcc ?
    • est-ce que votre mesure de vitesse vient palier un manque de fiabilité entre la vitesse de consigne et la vitesse réelle, ou est-ce que votre mesure de vitesse est en quelque sorte un étalonnage permanent ?

    merci beaucoup.

    Répondre

  • Comment piloter trains et accessoires en DCC avec Arduino (4) 1er novembre 2015 20:34, par Dominique

    Bonjour Tanguy et merci pour votre intérêt à cet article.

    La vitesse réelle en km/h c’est à dire la vitesse réelle en cm/s multipliée par le facteur d’échelle, est toujours une information qui intéresse le modélisme. Quand on peut la mesurer, on le fait ! Ici la présence des 2 capteurs permet de l’obtenir avec seulement un peu de logiciel.

    Maintenant, le rapport entre les crans DCC et la vitesse réelle sont plus compliqués car la relation est rarement linéaire, elle n’est jamais valable sur toute la plage DCC entre 2 et 126.

    Pour obtenir une relation fiable il faut étalonner chaque machine. C’est à dire gérer une table de correspondance avec quelques points caractéristiques comme la vitesse minimum, maximum, 30km/h et 60 km/h. Ensuite il faut faire des interpolations.

    j’y reviendrai prochainement dans d’autres articles à venir.

    Cordialement.

    Répondre

  • Bonjour,

    J’ai lu que vous avez de problème de la lecture du courant. J’ai construit un circuit pareille et j’ai utilisé une resistence de 2,2 k a la broche de mesure de courant. J’ai arrivé a cette valeur avec ce calcul :

    Imax = 0,000377 A/ A of output current * 6 A of output current
    Imax = 0,002262 A
    R = Umax/Imax = 5 V/ 0,002262 A = 2210 Ohm

    Donc 2,2 k Ohm. Avec cette valeur, si on consomme 1 A, ça devra donne 0,8294 V sur le pin d’arduino. J’ai testé avec un decodeur et un moteur et la valeur donnée est 0,062 V pour une consommation de au moins 300 mA (0,248 V). Est-ce que vous avez faire d’autres essais ?

    Merci,
    Stephan

    Répondre

  • Bonsoir Stephan,

    J’ai renoncé finalement à utiliser la broche C/S (current sense) du LMD18200 car les mesures que je trouve ne sont pas cohérentes et même aléatoires.

    J’ai eu une meilleure idée : on trouve facilement sur Internet des petits circuits (breakout) à base de Max471 avec la recherche "3A Range Current Sensor Module Professional MAX471 Module For arduino" :

    Il en existe avec des calibres de 5A, 10A, etc..

    On insère ce circuit en série avec l’alimentation continue du LMD18200 (RS+ coté +12V, RS-coté LMD18200, GND au GND). A cet endroit c’est du courant continu et la mesure est fiable.

    Il suffit de lire la sortie OUT sur une broche analogique :

    aRead = analogRead(DCC_Amp);    // lecture 1V/A
      Courant = aRead * 5;            // conversion en mA

    Une valeur de courant maximum me permet même de désactiver le LMD18200 en cas de dépassement de cette valeur, ce qui arrive en cas de court-circuit. C’est très utile et très simple !

    Répondre

  • J’ai lu avec intérêt votre réalisation.

    Deux questions simples :

    • potentiomètres linéaires ou logari1thmiques ?
    • quels décodeurs simples conseillerez-vous ?

    Avec mes remerciements anticipés

    Gérard

    Répondre

  • Bonjour,

    • Potentiomètre linéaire : choisissez la qualité, un modèle qui ne crache pas !
    • décodeur : je n’ai pas de préférence, je prend ce que je trouve (même les moins chers) : on ne se sert que des fonctions bien standard (vitesse, direction, lumière).
      Cela ne vous empêche pas de regarder et ajuster les valeurs des CVs. Pour le moment je programme les CVs avec une MS2 de Marklin, en attendant que je fasse mon programmateur. La bibliothèque le permet en grande partie.

    Répondre

  • Bonjour,
    J’ai chargé le fichier .zip du programme complet de la centrale mais au moment de la compil j’ai le message d’erreur suivant :
    compilation terminated.

    Utilisation de la bibliothèque CmdrArduino-master prise dans le dossier : C :\Users\laurence\Documents\Arduino\libraries\CmdrArduino-master (legacy)
    Utilisation de la bibliothèque EEPROM version 2.0 dans le dossier : C :\Program Files\Arduino\hardware\arduino\avr\libraries\EEPROM
    exit status 1
    Error compiling for board Arduino/Genuino Uno.

    j’ai bien la bibliothèque EEPROM dans IDE Arduino, je ne comprends pas.
    Pouvez m’aider ?

    Répondre

    • Bonjour,
      je ne vois pas bien de quelle erreur il s’agit. Il doit y avoir plus de détails que ça.
      Pouvez-vous faire un copier-coller de la fenêtre en bas de l’IDE Arduino et indiquer la version de cet IDE.

      a tout hazard, vous devez garder uniquement le nom "CmdrArduino" sans le "-master" pour la bibliotheque

      Répondre

      • C’est sympa de répondre aussi rapidement, tellement rapidement que je n’ai pas eu le temps de formuler en réponse la solution à propos de mon erreur.
        J’ai découvert que le problème ne venait pas de la bibliothèque EEPROM mais de la modification de la bibliothèque Bounce1 en Bounce2. J’ai corrigé et ça marche.
        Désolé et merci.
        J’aurai sans doute d’autres besoins plus tard car je débute avec Arduino et j’ai un réseau train en DCC MultiMaus avec beaucoup de composants électroniques que je compte diminuer et me passer des commandes DCC MultiMaus.

        Répondre

  • Bonjour,
    C’est de nouveau moi. J’exécute petit à petit le programme de la centrale vers B3 j’en suis arrivé à contrôler la vitesse,la direction et maintenant je souhaite inscrire l’adresse de la loco en EEPROM.
    Je rencontre une difficulté avec la lecture sur mon afficheur LCD 16x2. je n’ai pas d’erreur de compil mais pas d’affichage !! Ci dessous mon petit programme.

    #include // librairie de gestion memoire Flash
    #include <SoftwareSerial.h>
    #include <serLCD.h> // librairie de gestion d’un LCD 16x2 via port Tx

    #define Pin_Tx 2 //
    /////////////////////
    // afficheur LCD
    serLCD lcd(Pin_Tx) ;
    const char string_0[] PROGMEM = "DCC V DIR AV AR " ; // Mode_EnCours = 0
    const char string_1[] PROGMEM = "V DIR Canton Pas" ; // Mode_EnCours = 1
    const char string_2[] PROGMEM = "Vit Km/h Cm/s " ; // Mode_EnCours = 2

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

    string_0,
    string_1,
    string_2,
     ;
    char buffer[16] ; // tampon de recopie en RAM
    void setup()
    //Serial.begin(9600) ;
    lcd.setBrightness(25) ;
    lcd.clear() ;
    lcd.selectLine(1) ;
    delay(1000) ;

    void loop()
    strcpy_P(buffer, (char*)pgm_read_word(&(string_table[0]))) ; // adresse DCC
    //Serial.print(buffer) ;// affichage 1ere ligne
    lcd.print(buffer) ;// affichage 1ere ligne
    lcd.setCursor(2, 3) ;
    delay(2000) ;

    Mon afficheur ne fonctionne pas avec la PIN TX 1 mais pas de problème avec les PINS 2 à 8. Je peux afficher le contenu du "buffer" en mode Serial mais pas avec la commande "strcpy" de PROGMEM !!
    Merci de m’aider.

    Répondre

    • Désolé pour le retard, voyages et vacances, pas toujours connecté ...

      Je n’ai pas regardé attentivement votre programme qui doit s’inspirer du mien, mais je vous recommande de ne pas utiliser les pins 0 et 1 réservées pour la liaison USB (port série standard). La pin Tx 1 est toujours utilisée par le port USB donc ne l’utilisez pas pour l’afficheur, il y aurait un risque de conflit par moments (notamment lors des téléversements de logiciel).

      Je ne vois pas dans votre sketch de déclaration du port série destiné au LCD avec la bibliothèque SoftwareSerial, ça peut expliquer ...

      Quand au strcpy... je vous conseille de regarder le site Arduino.cc consacré à PROGMEM

      Bon courage

      Voir en ligne : http://https://www.arduino.cc/en/Re...

      Répondre

  • Bonjour,
    Je progresse, j’arrive enfin à afficher le contenu de la mémoire Flash sur un lcd. Merci pour votre réponse mais après beaucoup d’essais j’ai simplement augmenté la capacité du buffer de [16] à [18] et ça marche.Pourtant j’avais beaucoup lu. A titre d’infos la capacité du buffer dans votre programme ne passait pas avec mon afficheur !!! et je suis branché sur la pin 2 et non sur la pin TX 1. Je poursuis mon initiation à bientôt sans doute.

    Répondre

    • Merci pour vos bonnes nouvelles.

      Sachez que nous avons passé aussi beaucoup de temps à faire des essais avant d’acquérir l’expérience que nous avons. Les articles sont le fruit de ces essais lorsqu’ils deviennent fructueux. Nous n’aimons pas diffuser des informations qui n’ont pas été vérifiées.

      Si vous trouvez des résultats différents, c’est que les conditions dans lesquelles vous vous trouvez sont différentes, parfois très peu. Si vous comparez soigneusement, vous allez finir par trouver la raison.

      Je serais très intéressé de savoir pourquoi vous trouvez cette différence. Quelles sont les longueurs de vos chaines de caractères (les textes enregistrés en flash que vous affichez sur LCD). Je suis prêt à parier qu’il y en a un qui fait plus de 16 caractères.

      Bon courage

      Répondre

  • Bonjour,
    Je vous remercie de garder le contact malgré mes questions de débutant.
    J’ai de nouveau testé mon petit programme, j’ai remis le BUFFER à [16] et ça ne marche pas en revanche à [17] ça marche !! toujours sur la PIN 2 alors que sur TX1 ça ne marche pas et dans tous les cas il n’y a pas d’erreur de compil. J’utilise les trois textes suivants : "DCC V DIR AV AR " - "V DIR Canton Pas" -
    "Vit Km/h Cm/s " .
    Mon afficheur un modèle tout nouveau 16x2 avec trois connexions montées d’origine (VCC, GND RX). Maintenant je vais tester la mémoire EEPROM.
    Bien amicalement.

    Répondre

  • Vous devriez commencer par mettre le même nombre de caractères dans toutes vos chaines, exactement comme dans l’article, en prenant soin d’aligner les chaines verticalement pour que les " (guillemets) soient bien les uns au dessous des autres.

    Si vous avez modifié le programme de l’article, cela peut expliquer le problème. Je ne connais pas votre programme, donc je ne peux que supposer.

    Répondre

    • Un autre conseil : coupez votre problème en deux :

      • essayez d’abord de récupérer les chaines de caractères de la Progmem en les affichant d’abord sur le moniteur avec un Serial.println()
      • ensuite, si c’est concluant, affichez les sur votre LCD

      Dans ma réalisation j’ai utilisé un afficheur équipé d’une interface série nécessitant la bibliothèque SerialLCD

      On trouve plus facilement maintenant des afficheurs équipé d’une interface I2C qui s’utilisent simplement comme ceci :

      #include <Wire.h> 
      #include <LiquidCrystal_I2C.h>
      
      LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
      
      void setup()
      {
        lcd.init();                      // initialize the lcd 
       
        // Print a message to the LCD.
        lcd.backlight();
        lcd.print("Hello, world!");
      }
      
      void loop()
      {
      }

      Répondre

  • Bonjour,
    Le programme fonctionne, j’ai par contre supprimé tout ce qui avait un rapport avec le mode automatique ce sera pour plus tard. J’ai tout de même rencontré un petit problème que j’ai résolu mais sans bien comprendre le pourquoi. Mon afficheur indiquait à l’adresse DCC : 27135, alors que l’EEPROM était encore vierge sans adresse DCC !!! J’ai donc modifié la ligne de code ==255 par 27135 et j’ai eu accès et codé l’adresse 3 de ma loco. J’ai ensuite rétabli la ligne de code avec la valeur ==255 et tout fonctionne normalement et l’afficheur indique bien DCC : 3. Je n’ai pas trouvé dans le programme une entrée pour saisir une première fois l’adresse DCC d’une loco il y a certainement quelque chose qui m’échappe. Je vais maintenant finaliser mon boitier de commande faire des essais sur mon réseau et passer ensuite à l’étude pour commander plusieurs locos et là j’ai besoin de pistes car mon réseau est déjà configuré en cantons et sécurisé. J’ai le projet de refaire un TCO et d’adapter à Arduino la rétro-signalisation et çà ce n’est pas un problème. Je suis emballé par votre démarche et je suis le forum avec attention mais je ne peux pas m’en mêler je suis pas assez ferré en programmation. Pensez aux tuyaux pour commander plusieurs locos.A bientôt.

    Répondre

  • Lecture des variables de configuration 23 novembre 2018 12:53, par SAVORNIN Pierre

    Bonjour !

    Tout d’abord, merci et bravo pour tous ces articles.

    Je suis sur le point d’attaquer sérieusement la question Arduino, pour passer en digital un réseau HO que je monte pour mes petits enfants (j’ai 75 balais...). Or j’avais acheté il y a deux ans un système Roco avec Multimaus 10810 (rouge), qui a fonctionné, mais qui ne permet pas de lire les CV. Je pense qu’une centrale Arduino doit pouvoir combler cette lacune.
    Question 1 : peut-on se passer de lire les CV, par exemple au prix d’une reprogrammation complète des locos ?
    Question 2 : pourriez-vous m’indiquer une référence précise d’article Locoduino décrivant une centrale munie de cette fonction lecture ? Je possède un Arduino Uno qui n’attend qu’une utilisation !
    Merci d’avance !

    Répondre

    • Lecture des variables de configuration 23 novembre 2018 14:26, par Dominique

      Merci pour vos encouragements ;)

      Vous trouverez toutes les réponses dans les articles 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), Bibliothèque DCCpp et tous ceux qui s’en inspirent, tant sur ce site que sur le forum.

      En général on n’a pas besoin ni de lire tous les CVS, ni de reprogrammer une loco, les valeurs par défaut du constructeur ne sont pas idiotes. Seule l’adresse doit être programmée car les locos doivent toutes avoir des adresses différentes.

      Répondre

      • Lecture des variables de configuration 13 janvier 2021 22:42, par Patrick Bondoux

        La question posée (lecture des CV par un petit outil à base d’Arduino) me semble pertinente pour au moins un CV : l’adresse du décodeur d’une loco (comme vous l’indiquez d’ailleurs dans votre réponse).
        Cependant, et sauf erreur, je ne trouve pas dans Locoduino un article dédié qui permettrait (facilement ?) de réaliser ce petit outil. Existe-t-il ou faudrait-il l’écrire ?
        PS : (Gérard, ancien du CMFA, m’a parlé ce soir de celui que vous lui aviez réalisé pour ses locos en N !).

        Je me joins au concert de louanges adressées au site Locoduino qui est une pure merveille. Bravo !

        Répondre

        • Lecture des variables de configuration 13 janvier 2021 23:16, par msport

          Vous avez un montage de Thierry qui permet de lire et écrire les CV :
          Bibliothèque DcDccNanoController sous-titré Une petite centrale toute prête… article224

          Par ailleurs, Dominique a publié Réalisation d’un va-et-vient automatique et réaliste dont la première action est lire l’adresse de la loco posée sur la voie.

          Le plus dur sera de choisir.

          Voir en ligne : Réalisation d’un va-et-vient automatique et réaliste

          Répondre

          • Lecture des variables de configuration 13 janvier 2021 23:34, par Dominique

            Bonjour Patrick,
            ça fait un sacré bout de temps qu’on sait faire ça à Locoduino. Le premier est celui qui se trouve dans la centrale du va et vient du réseau N d’expo du CMFA. Depuis il y a eu les articles sur le va et vient, sur DCCpp et le dernier est dans LaBox sur le forum. En réalité il y a plusieurs CVs à lire pour les adresses courtes et longues. A ta disposition pour plus de détails. Dominique.

            Répondre

        • Lecture des variables de configuration 13 janvier 2021 23:27, par msport

          Et d’ailleurs encore plus simple : une BaseStation composée d’un UNO, d’un shield moteur, d’une alimentation 12V (décrite dans les articles ci-dessus) et de deux straps. Avec le sketch éponyme elle permet de lire et écrire les CV sur la voie de programmation à partir du Serial Monitor. Voir les commandes ci-dessous.

          Voir en ligne : Commands for DCCpp BaseStation

          Répondre

  • Bonsoir,

    J’ai un soucis je désire passer les broches A4, A5 en A6, A7 et ca ne fonctionne pas !!!

    J’ai bien changer au niveau des déclarations #define
    #define Pin_AutoManu A6
    #define Pin_SW_FL A7

    Mais sans succès.

    La raison est que j’ai un LiquidCrystal_I2C qui fonctionne uniquement sur A4 & A5.

    Bien cordialement
    Pierre

    Répondre

  • Bonjour
    J’ai 80 ans ,je viens de débuter sur l’Arduino il y a une semaine et je suis intéressé par la commande multiple de trains pour débuter un nouveau circuit de train que je dois encore construire. Dans un premier temps ,uniquement 4 locos qui doivent circuler indépendamment sur le circuit. J’ai lu avec intérêt votre rubrique précitée et l’on parle de décodeurs à installer dans les machines (machine 2,3,) .Est ce des décodeurs du commerce ou bien des fabrication "maison" ?.
    Avant un déménagement ,qui ne m’a pas permis de "récupérer" mon ancien circuit que j’avais conçu comme électronicien de l’ancienne école (commande de chaque loco par émission/réception). Je constate que l’informatique me permettra beaucoup plus de souplesse.Merci

    Répondre

  • bonjour Monsieur, j’ai essayé de téléchargé le programme que vous avez publié dans un arduino uno tout et bien réglé port etc. mais ça ne veut pas le compilé j’ai téléchargé les librairies bounce1 ETC ,je me demande s’il n’y a pas un problème de librairie je suis avec WINDOWS7 j’ai essayé plusieurs IDE ARDUINO pouvez vous me dire quel IDE avez vous utilisé et si vous avez les librairies comme elles ne sont pas inclus dans le ZIP je vous remercie d’avance article super mais je ne suis pas un grand expert MERCI.

    Répondre

  • La bibliothèque Cmdrarduino est complètement obsolète -je vais le mentionner dans d’article qui reste intéressant pour comprendre le DCC. Utilisez maintenant DCCpp et la réalisation de l’article Réalisation d’un va-et-vient automatique et réaliste.

    Voir en ligne : Réalisation d’un va-et-vient automatique et réaliste

    Répondre

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 »

Les derniers articles

Les articles les plus lus