Bonjour à tous,
Comme annoncé dans le sujet
http://forum.locoduino.org/index.php?topic=98.msg8459#new, voici une présentation de mon réseau hivernal en N.
Description du réseau et des cartes de détectionLe réseau dans son état quasi actuel se présente comme suit. Il s'agit d'une gare sur une voie unique avec une gare cachée permettant le croisement.
Le réseau est découpé en zones comme suit:
- GC1G: gare cachée 1 gauche
- GC1D: gare cachée 1 droite
- GC2G: gare cachée 2 gauche
- GC2D: gare cachée 2 droite
- L1: ligne
- L2: ligne
- G1: gare voie 1
- G1: gare voie 2
Le réseau est en DCC. Toutes les zones sont équipées pour détecter les trains par consommation de courant. Les zones des 2 gares sont en plus équipées de diodes pour le freinage ABC. Les aiguilles A1 à A5 sont actionnées par une carte à 8 relais.
Les cartes de détection se composent de diodes pour d'une part assurer la détection de la consommation de courant, d'autre part générer l'asymétrie du signal DCC requise pour le freinage. Ces diodes supplémentaires sont commutées par des relais.
La tension de détection actionne un optocoupleur qui génère le signal logique. Le réseau fonctionnant en DCC, la détection est mono-alternance, c'est ensuite par logiciel que l'on assure la continuité de la détection. L'Arduino, un Mega, tourne suffisamment vite pour ne rien manquer.
ProgrammeLa structure du programme s'inspire des principes décrits ici:
https://paulmurraycbr.github.io/ArduinoTheOOWay.html.
Classe TacheLa première classe définit la notion de tâche.
class Tache
{
public:
static Tache *pListeTaches;
Tache ();
static void setupTaches (void);
static void loopTaches (void);
virtual void setup (void) = 0;
virtual void loop (void) = 0;
private:
Tache *pProchaineTache;
};
Toutes les autres classes du projet héritent de
Tache et doivent définir les méthodes
setup et
loop. Le corps de cette classe est:
#include <Arduino.h>
#include "Tache.h"
Tache *Tache::pListeTaches = NULL;
Tache::Tache ()
{
pProchaineTache = pListeTaches;
pListeTaches = this;
} // Tache
void Tache::setupTaches (void)
{
for (Tache *p = pListeTaches; p; p = p->pProchaineTache)
{
p->setup ();
} // for
} // setupTaches
void Tache::loopTaches (void)
{
for (Tache *p = pListeTaches; p; p = p->pProchaineTache)
{
p->loop ();
} // for
} // loopTaches
À chaque appel du constructeur, la nouvelle tâche est ajoutée en tête de la liste de tâches. Les méthodes de classe
setupTaches et
loopTaches appelées respectivement dans les fonctions principales
setup et
loop parcourent cette liste pour appeler les méthodes virtuelles redéfinies par les classes descendantes de celle-ci.
Cette technique est utilisée entre autres dans les bibliothèques Commanders et Accessories de Thierry et dans ScheduleTable, SlowMotionServo et LightDimmer de Jean-Luc.
Classe LedIl est toujours intéressant de vérifier visuellement que l'Arduino ne s'est pas planté. Faire clignoter la led de la carte est un bon moyen de vérifier cela. Tant qu'elle clignote, aucune tâche ne bloque le processeur et tout va bien.
#include <stdint.h>
#include "Tache.h"
class LED: public Tache
{
public:
LED (uint8_t xPin, uint32_t nxPeriode);
void setup ();
void loop ();
void allumer (void);
void eteindre (void);
void clignoter (void);
private:
const uint8_t Pin;
uint8_t eEtat;
const uint32_t nPeriode;
uint32_t tDernier;
};
#include "LED.h"
#define E_OFF 0
#define E_ON 1
#define E_BLINK_ON 2
#define E_BLINK_OFF 3
LED::LED (uint8_t xPin, uint32_t nxPeriode) :
Pin (xPin),
nPeriode (nxPeriode) {}
void LED::setup (void)
{
eEtat = E_OFF;
pinMode (Pin, OUTPUT);
} // LED::setup
void LED::loop (void)
{
uint32_t nDeltaTemps;
switch (eEtat)
{
case E_OFF:
break;
case E_ON:
break;
case E_BLINK_OFF:
nDeltaTemps = millis () - tDernier;
if (nDeltaTemps > nPeriode)
{
eEtat = E_BLINK_ON;
tDernier = millis ();
} // if
break;
case E_BLINK_ON:
nDeltaTemps = millis () - tDernier;
if (nDeltaTemps > nPeriode)
{
eEtat = E_BLINK_OFF;
tDernier = millis ();
} // if
break;
} // switch
digitalWrite (Pin, (eEtat == E_ON) || (eEtat == E_BLINK_ON));
} // LED::loop
void LED::allumer (void)
{
eEtat = E_ON;
} // LED::allumer
void LED::eteindre (void)
{
eEtat = E_OFF;
} // LED::eteindre
void LED::clignoter (void)
{
if (eEtat == E_OFF)
{
eEtat = E_BLINK_OFF;
tDernier = millis ();
}
else if (eEtat == E_ON)
{
eEtat = E_BLINK_ON;
tDernier = millis ();
} // if
} // LED::clignoter
Le constructeur reçoit le numéro de la pin et la période de clignotement en paramètre. Tout se passe principalement dans la méthode
loop qui implante une machine d'états. On remarque l'importance de gérer le temps à l'aide de
millis () et non avec
delay (). Il est en effet important de ne pas bloquer dans cette fonction.
Les états possibles sont définis par des instructions
#define plutôt qu'avec un enum. Le but était de s'assurer que les états soient codés dans une variable de type
uint8_t. Je ne connaissais pas les
enum class de C++ 11 et leur possibilité de spécifier le type sous-jacent lorsque j'ai commencé ce projet !
La led s'utilise comme suit dans le fichier .ino.
---
LED led_board (LED_BOARD, PER_CLIGN);
...
void setup ()
{
...
Tache::setupTaches ();
led_board.clignoter ();
} // setup
Le prochain post décrira les autres classes plus spécifiques à cette application.
Meilleures salutations.
Marc-Henri