Un décodeur DCC pour 16 feux tricolores

Basé (ou pas) sur Commanders et Accessories

. Par : Dominique, JPClaude, Thierry. URL : https://www.locoduino.org/spip.php?article199

Cet article a pour objectif de réaliser des décodeurs de signaux ferroviaires. L’approche se fera en trois parties.

  • La première consiste à réaliser ces décodeurs en se basant sur l’article de Nicolas Zin. Dans cette approche le programme reçoit un message DCC qu’il doit entièrement gérer.
  • La seconde approche se base sur les bibliothèques Commanders et Accessories de Thierry Paris. Les deux fournissent une grande quantité de méthodes facilitant grandement la réalisation des commandes analogiques ou DCC pour des accessoires.
  • Enfin la dernière solution propose une interface utilisant ces mêmes bibliothèques mais cachant leur complexité et facilitant la configuration de décodeurs de signaux ferroviaires à deux et/ou trois feux.

L’Arduino Mega 2560 peut-il alimenter 16 leds continuellement ?

16 feux tricolores à led, cela fait 48 leds. Mais dans chaque feu, une seule led est allumée et consomme moins de 10 milli-ampères.

Un Arduino Mega 2560 dispose de 54 ports, donc peut piloter 16 feux, et les 16 leds allumées en permanence ne mettent pas l’Arduino en surcharge.

JPEG - 971 kio

L’information ne saute pas aux yeux sur la fiche des caractéristiques.

Mais après croisement de nombreuses informations, il en ressort que :

  • La somme de tous les courants (entrant ou sortant) des ports J0-J7 (digital pins 14 et 15), G2 (digital pin 39), A0-A7 (digital pins 22 à 29) ne doit pas dépasser 200 mA.
  • La somme de tous les courants (entrant ou sortant) des ports C0-C7 (digital pins 30 à 37), G0-G1 (digital pins 40 et 41), D0-D7 (digital pins 18 à 21 et 38), L0-L7 (digital pins 42 à 49) ne doit pas dépasser 200 mA.
  • La somme de tous les courants (entrant ou sortant) des ports G3-G4 (aucune pin), B0-B7 (digital pins 10 à 13 et 50 à 53), H0-H7 (digital pins 6 à 9 et 16, 17) ne doit pas dépasser 200 mA.
  • La somme de tous les courants (entrant ou sortant) des ports E0-E7 (digital pins 0, 1, 2, 3, 5), G5 (digital pin 4) ne doit pas dépasser 100 mA.
  • La somme de tous les courants (entrant ou sortant) des ports F0-F7 (analog pins 0 à 7), K0-K7 (analog pins 8 à 15) ne doit pas dépasser 100 mA.

Au total le courant total du Mega 2560 ne doit pas dépasser 800mA (dont 80mA de consommation interne).

Donc, les leds de nos feux consommant moins de 10 mA, cela fait 160 mA maximum, répartis sur plusieurs ports. Nous sommes loin de la limite ! Ouf !

A noter que l’expression « courant entrant » (sink en anglais) signifie courant absorbé : c’est le cas quand la led est connectée au +5V par son anode (via une résistance) et à une pin de l’Arduino par sa cathode. La led s’allume quand la pin est au niveau 0V (LOW).

A l’inverse, l’expression « courant sortant » (source en anglais) signifie courant fourni : c’est le cas quand la led est connectée une pin de l’Arduino par son anode et au 0V par sa cathode (via une résistance). La led s’allume quand la pin est au niveau 5V (HIGH).

La norme DCC et les commandes d’accessoires

On trouvera dans cet article une présentation de la norme DCC, comment se présente un signal DCC sur les rails, généré par votre centrale. Mais cet article se concentre sur les commandes des locomotives avant tout.

Ici nous avons affaire aux commandes d’accessoires qui sont réalisées avec des trames DCC différentes des commandes de locos.

Le document de référence de la NMRA est RP-9.2.1-Extended packet [1] (pour ceux qui sont prêts à affronter la chose en anglais).

Nous allons maintenant tenter de vous éviter cela :-)

Les adresses correspondant aux décodeurs d’accessoires basiques (Basic Accessory) sont codées sur 9 bits d’adresses, et celles des décodeurs d’accessoires étendus (Extended Accessory) sont codées sur 11 bits d’adresse.

Ces adresses n’ont rien à voir avec les adresses des locos, du fait que les commandes DCC sont différentes. On n’a donc pas besoin de choisir des valeurs d’adresses différentes.

Curieusement, les centrales semblent utiliser les commandes d’accessoires basiques à toutes les sauces, y compris les feux, alors que ces derniers semblent devoir être commandés plutôt par des commandes étendues.

Nous allons donc nous concentrer sur les commandes basiques seulement dans l’exemple qui va suivre. Un autre article reviendra ultérieurement sur les commandes étendues.

Le format des paquets DCC pour la commande des décodeurs d’accessoires basiques

Le format d’une commande destinée à un décodeur d’accessoires est :

preambule 0 10AAAAAA 0 1AAACDDD 0 EEEEEEEE 1

L’adresse est en 2 parties AAAAAA (6 bits dans le 1er octet qui représentent les poids faibles) et AAA (3 bits dans le 2ème octet qui représentent les poids forts).
La première partie est codée simplement de 0 (000000) à 63 (111111) et la deuxième partie est codée en complément à 1 (pourquoi faire simple quand on peut faire compliqué !).
Par exemple 111 binaire vaut 000 en complément à 1 donc 0 en décimal et on a les adresses de 0 à 63 ; ensuite,
110 vaut 1 donc les adresses vont de 64 à 127 ;
101 vaut 2 donc les adresses vont de 128 à 191,
100 vaut 3 donc les adresses vont de 192 à 255,
011 vaut 4 donc les adresses vont de 256 à 319,
010 vaut 5 donc les adresses vont de 320 à 383,
001 vaut 6 donc les adresses vont de 384 à 447, et
000 vaut 7 pour les adresses de 448 à 511.

Le bit C sert à activer et désactiver un accessoire soit de façon momentanée, soit de façon permanente. L’activation ou la désactivation (si l’activation était permanente) se fait en envoyant 2 commandes, l’une avec C=1, puis une autre avec C=0, après quelques millisecondes. La durée de l’activation est déterminée par les variables de configuration CVs #515 à 518 (ce qui correspond à 4 paires de sorties).

Le décodeur peut piloter 4 paires de sorties :
Dans DDD, les DD de poids fort (à gauche) définissent quelle sortie est concernée et le dernier D de poids faible (à droite) définit l’élément dans une paire.

La combinaison de l’adresse et des bits DD de poids fort est souvent utilisée par les centrales pour simuler 4 accessoires simples comme des moteurs d’aiguilles classiques ou pour des feux ou des relais. Cette combinaison permet donc d’adresser un peu moins de 2048 accessoires (quelques unes sont réservées selon le fabricant).
Dans ce cas l’adressage indiqué dans la documentation des centrales peut prendre les formes 101/0, 101/1, 102/0, 102/1, 103/0, 103/1, 104/0, 104/1 qui correspondent toutes à l’adresse 26, paires de sorties 1, 2, 3, et 4.

On vous avait bien dit que ce n’est pas simple !

Le format des paquets DCC pour la commande des décodeurs d’accessoires étendus

Ce format est destiné à transmettre des commandes d’aspect plus particulièrement pour les décodeurs de feux ou des octets des données pour des décodeurs plus complexes. Chaque commande pilote un seul aspect à la fois d’un feu complexe.

preambule 0 10AAAAAA 0 0AAA0AA1 0 000XXXXX 0 EEEEEEEE 1

On y retrouve les 9 bits d’adresse AAAAAA et AAA comme dans la commande basique, auxquels s’ajoutent 2 bit supplémentaires en bits 1 et 2 du 2ème octet.

XXXXX concerne un seul feu. la valeur 00000 indique un Stop absolu.
Tous les autres aspects représentés par les autres valeurs de XXXXX sont déterminés par rapport à un modèle de système de signalisation.

Les commandes de Variables de Configuration des accessoires

Chaque décodeur d’accessoires contient des paramètres programmables par des commandes DCC. Son adresse en est l’exemple typique. Celle-ci est programmée dans le CV 1 (poids faibles) et le CV 9 (poids forts).

Les CVs des décodeurs d’accessoires peuvent être configurés au même titre que les décodeurs de locomotives par des commandes DCC.

Programmation des CVs d’un décodeur d’accessoire basique :

preambule 10AAAAAA 0 1AAACDDD 0 (1110CCVV 0 VVVVVVVV 0 DDDDDDDD) 0 EEEEEEEE 1

AAAAAA AAA est l’adresse comme expliqué plus haut.

DDD indique la sortie pour laquelle les CVs sont modifiés avec C=1.

Si CDDD= 0000 alors les CVs concernent le décodeur dans sa totalité (toutes les sorties).
(1110CCVV 0 VVVVVVVV 0 DDDDDDDD) est l’instruction de configuration des CVs.

Programmation des CVs d’un décodeur d’accessoires étendu :

preamble 10AAAAAA 0 0AAA0AA1 0 (1110CCVV 0 VVVVVVVV 0 DDDDDDDD) 0 EEEEEEEE 1

Notez que le bit 3 à 0 dans l’octet 2 garantit que ce paquet ne peut pas être confondu avec celui d’un accessoire basique.

La construction du décodeur

Comme indiqué au début de cet article, j’ai choisi d’utiliser un Arduino Mega 2560 qui permet de brancher les 48 Leds sans matériel extérieur (j’aurais pu utiliser un Arduino plus petit avec une carte contenant 6 x 74HC595, chacun pouvant alimenter 8 Leds, mais ça aurait fait beaucoup de câblage).
De toute façon, pour faire ce décodeur et le rendre facile à utiliser, il faut ajouter une interface DCC isolée par optocoupleur, selon ce schéma éprouvé décrit en détail à l’article Un moniteur de signaux DCC.

PNG - 61.1 kio

Ensuite, il faut ajouter des borniers à vis pour brancher les fils qui vont aux feux.
J’ai donc ajouté à mon Mega 2560 une carte d’extension imprimée à pastilles, avec la place pour les connecteurs mâles qui vont s’enficher dans les connecteurs femelles de l’Arduino :

JPEG - 975.8 kio

Sur cette carte d’extension, j’ai câblé l’interface DCC ci-dessus et un régulateur de tension pour fournir le 5V nécessaire au fonctionnement de l’Arduino :

JPEG - 637.4 kio

Puis j’ai réalisé une plaquette équipée de borniers à vis et l’ai reliée soigneusement aux Pins de l’Arduino sur la carte d’extension :

JPEG - 967.6 kio

Il ne reste plus, ensuite, qu’à installer le programme et connecter les feux.

Le programme sans ’Accessories’

Après tout, pourquoi ne pas essayer : Je suis parti de l’article de Nicolas Zin : Un décodeur d’accessoire DCC versatile basé sur Arduino.

Voici le code qui est relativement court et facile à comprendre :

Tout d’abord, on déclare les ressources nécessaires :

#include <DCC_Decoder.h>		 // la bibliothèque de Minabay
#define kDCC_INTERRUPT    3    // la pin 20 du Mega2560 reçoit les interruptions du signal DCC

#define NB_FEUX 16			// 16 feux
#define NB_LEDS 3			// 3 leds par feu

Ensuite, les adresses DCC à utiliser, dans un tableau pour permettre l’usage de boucles avec index :

// plage de codes DCC : 100 a 131

#define DCC_CODE_START 100

// DCC_codes1 = DCC_CODE_START + 2*LED_NUM
// DCC_codes2 = DCC_CODE_START + 2*LED_NUM + 1

int dcc_codes[NB_FEUX] = { 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130 };

Puis les pins connectées aux Leds et les variables pour les gérer :

// Pins des Leds des Feux {vert, jaune, rouge, allumage = LOW }

int pins[NB_FEUX][NB_LEDS] = {
		{ 11, 10, 9 },
		{ 8, 7, 6 },
		{ 5, 4, 25 },
		{ 27, 29, 31 },
		{ 28, 30, 32 },
		{ 34, 36, 38 },
		{ 40, 42, 44 },
		{ 46, 48, 50 },
		{ 13, 12, 3 },
		{ 2, 14, 15 },
		{ 16, 17, 18 },
		{ 19, 22, 23 },
		{ 24, 26, 33 },
		{ 35, 37, 39 },
		{ 41, 43, 45 },
		{ 47, 49, 51 }
};

int STATE_FEUX[NB_FEUX*2];    // NB_FEUX couples = 4 états, valeurs HIGH ou LOW
boolean VALID_FEUX[NB_FEUX];  // un feu est à traiter si true par Maj_Feux()
int NUM_FEUX;

Maintenant, le routine Maj_Feux() qui va allumer ou éteindre les Leds en fonction de l’état DCC (sur 2 variables car associé à 2 adresses DCC) qui est converti en un état "State" compris entre 0 et 3 pour coder tout cela dans un switch :

void Maj_Feux() {
  
  for (int i = 0; i < NB_FEUX; i++)  
  {
    if (VALID_FEUX[i] == true)
    {
      // conversion des 2 etats en State compris entre 0  et 3
      int State = STATE_FEUX[i*2] + STATE_FEUX[i*2+1]*2;
      // positionnement des Leds 
      switch(State) 
      {
        case 0: // rouge
        digitalWrite(pins[i][0] , HIGH);  //extinction Led verte
        digitalWrite(pins[i][1] , HIGH);  //extinction Led jaune
        digitalWrite(pins[i][2] , LOW);   //allumage Led rouge
        break;
        case 1: // jaune
        digitalWrite(pins[i][0] , HIGH);  //extinction Led verte
        digitalWrite(pins[i][1] , LOW);   //allumage Led jaune
        digitalWrite(pins[i][2] , HIGH);  //extinction Led rouge
        break;
        case 2: // jaune
        digitalWrite(pins[i][0] , HIGH);  //extinction Led verte
        digitalWrite(pins[i][1] , LOW);   //allumage Led jaune
        digitalWrite(pins[i][2] , HIGH);  //extinction Led rouge
        break;
        case 3: // vert
        digitalWrite(pins[i][0] , LOW);   //allumage Led verte
        digitalWrite(pins[i][1] , HIGH);  //extinction Led jaune
        digitalWrite(pins[i][2] , HIGH);  //extinction Led rouge
        break;
      }
      VALID_FEUX[i] = false;
    }
  }
}

Puis la routine (handler en anglais) exécutée par la librairie DCC de Minabay quand une commande est arrivée :

void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data)
{
  // Conversion de l'adresse NMRA en adresse décodeur d'accessoire
  address -= 1;
  address *= 4;
  address += 1;
  address += (data & 0x06) >> 1;
    
  int enable = (data & 0x01) ? HIGH : LOW;
    
  if ((address >= DCC_CODE_START) && (address < DCC_CODE_START + NB_FEUX*2))
  {
    VALID_FEUX[(address - DCC_CODE_START)/2] =  true;
    STATE_FEUX[address - DCC_CODE_START] = enable;
    Serial.print("@ ");
    Serial.print(address);
    Serial.print(" ");
    Serial.print(STATE_FEUX[address - DCC_CODE_START]);
    Serial.print(" feu ");
    for (int i = 0; i < NB_FEUX; i++)  
    {
      if (VALID_FEUX[i] == true)
      {
        Serial.print(i);
        Serial.print(" etat ");
        int State = STATE_FEUX[i*2] + STATE_FEUX[i*2+1]*2;
        Serial.println(State);
      }
    }
  }
}

A noter que la conversion d’adresse NMRA est une recette de cuisine qui fonctionne avec plusieurs centrales (heureusement). Les "Serial.print()" qui parsèment cette routine servent à voir ce qui se passe et peuvent être enlevés après la mise au point.

Maintenant le fameux SETUP qui initialise les variables tableaux, le port série, les modes de Pins et la bibliothèque DCC de Minabay :

void setup() {
  
  Serial.begin(115200);

  for (int i=0; i<NB_FEUX; i++)
  {
    STATE_FEUX[i*2] = LOW;  // eteint
    STATE_FEUX[i*2+1] = LOW;  // eteint
    VALID_FEUX[i] = true;
    for (int j=0; j<NB_LEDS; j++)
    {
    pinMode(pins[i][j] , OUTPUT); 
    digitalWrite(pins[i][j] , LOW);  //allumage Led
    delay(50);
    digitalWrite(pins[i][j] , HIGH);  //extinction Led
    delay(50);
    }
  }
    
  DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
//  ConfigureDecoder();
  DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
  
  Serial.println("DCC 16 Feux en route (V1)");
}

Étonnamment, la LOOP est toute petite :

void loop() {
  static unsigned long timer=0;
  
  ////////////////////////////////////////////////////////////////
  // passer le main à la bibliothèque DCC

  DCC.loop();
  
   ////////////////////////////////////////////////////////////////
  // on change l'etat des Leds toutes les 50 millisecondes

  if (millis()-timer>50) {
    Maj_Feux();
    timer=millis();
  }
}

Et ce programme fonctionne à merveille !

J’ai testé au départ avec ma petite MS2.

En respectant les branchements des Leds conformément à la table des Pins des Leds des Feux ci-dessus, et en programmant ces feux comme "Three Aspect Signal" avec Train Controleur :

JPEG - 55.8 kio

Le résultat est immédiat via une console ESU ECOs.

On remarque qu’on a choisi d’allumer le Vert quand les 2 adresses sont au Vert, le rouge quand les 2 adresses sont au rouge et le jaune quand les 2 adresses sont différentes.

Le programme peut être téléchargé à partir d’ici :

dcc16feux

Le programme avec Accessories

Ce code s’appuie sur la bibliothèque Commanders V1.23 du 31/12/2016 [2] et la bibliothèque Accessories V0.51 du 10/01/2017 [3].

Pour faire 16 feux, de trois leds chacun, avec une led allumée à la fois par un code Dcc, il nous faut passer par plusieurs étapes.

1 : Déclarer 16 accessoires multi leds avec trois leds chacun, en créant les ports nécessaires au fur et à mesure.
2 : Associer chaque led aux ports déclarés plus tôt.

On voit bien que le travail va être très répétitif, et peut être consommateur de mémoire programme... On a trois solutions pour parvenir au résultat :

1 : Force brute : les éléments sont tous déclarés et construits un par un. Avantage : c’est long, mais c’est le plus simple à coder, et le code est facile à comprendre. Inconvénient : il ne faut pas vouloir changer de méthode pour un feu... Parce qu’il faudra le refaire 16 fois !
2 : Boucles : le but à atteindre se prête très bien à l’utilisation de boucles. Il faut juste rendre configurable un maximum de choses, comme les pins utilisées ou le code Dcc associé à chaque feu. Avantage : si la méthode change pour un feu, elle est automatiquement appliquée à tous les feux. Inconvénient : la lisibilité devient passable...
3 : Version élégante : la voie royale consiste à créer une classe ’Feu’ qui va être instanciée 16 fois. La partie création d’un seul feu avec toutes ses implications est déporté dans cette classe. Avantage : le code principal est concis, simple à comprendre puisque pour un feu unique, et maintenable. Inconvénient : le côté C++ s’adresse à des connaisseurs... que vous êtes certainement devenus !

Le code pour un feu

Voyons le code pour un seul feu. Nous l’étendrons ensuite à plusieurs...
Nous allons commencer par une annexe, par la déclaration d’un bouton poussoir qui va servir à tester le fonctionnement, au cas où l’on soit sur un réseau analogique, ou que la centrale Dcc ne soit pas disponible, voire dans un cas extrême (et peu probable...) où l’on ait la flemme d’allumer ladite centrale !

On déclare d’abord tous les objets nécessaires : le poussoir, les trois ports utilisés, et le feu lui même :

ButtonsCommanderPush poussoir;

PortOnePin portVert, portJaune, portRouge;
AccessoryLightMulti feu;

Puis dans setup() :

 Commanders::begin(ReceiveEvent, LED_BUILTIN);
  Accessories::begin();

  poussoir.begin(DCCINT(15, 0), 1);

  poussoir.AddEvent(DCCINT(15, 1));
  poussoir.AddEvent(DCCINT(16, 0));

Après l’initialisation des deux bibliothèques, on passe au begin du poussoir avec un premier identifiant, et sa broche à 1. On y ajoute ensuite deux autres identifiants avec AddEvent(). Chaque appui sur ce bouton enverra un code Dcc parmi les trois, l’appui suivant le code suivant, et ainsi de suite... J’ai choisi les codes Dcc 15/0, 15/1 et 16/0 pour chaque état du feu : vert, jaune, rouge.

Déclarons ensuite les ports sur l’Arduino, un port par Led sur les broches 9, 10 et 11.

 portVert.begin(9, DIGITAL);
 portJaune.begin(10, DIGITAL);
 portRouge.begin(11, DIGITAL);

Passons au feu, un multi DELs avec trois DELs :

feu.begin(1000, 3, 0);

L’accessoire feu pourrait être commandé pour tout allumer ou tout éteindre. Comme ce n’est pas le but ici, j’ai mis un identifiant volontairement excessif de 1000 pour ne pas risquer une mise en route malencontreuse...

Puis l’affectation de chaque led sur le bon port :

  feu.beginLight(0, &portVert);   // led verte (0)
  feu.beginLight(1, &portJaune);  // led jaune (1)
  feu.beginLight(2, &portRouge);  // led rouge (2)

Tous les éléments sont en place. Il nous faut maintenant déclarer les différents états (MovingPosition dans le jargon d’Accessories), un pour chaque led allumée tandis que les autres sont éteintes.

  feu.AdjustMovingPositionsSize(3);  // trois combinaisons possibles
  feu.AddMovingPosition(DCCINT(15, 0), 0b00000001, 0); // 1
  feu.AddMovingPosition(DCCINT(15, 1), 0b00000010, 0); // 2
  feu.AddMovingPosition(DCCINT(16, 0), 0b00000100, 0); // 4

Le deuxième argument de la fonction AddMovingPosition() est un ’champ de bits’ sur un entier 8 bits qui représente la liste des leds de l’accessoire, avec 0 pour celles qui sont éteintes, 1 pour les allumées. Plusieurs notations sont possibles : désigner la troisième led par exemple peut se faire par trois moyens :

  • 4 sous forme d’entier. C’est le plus évident.
  • 0b00000100 qui représente huit bits, dont le troisième est à 1. Cette notation ne marche plus s’il y a plus de huit leds sur le feu...
  • 1<<2 qui signifie 1 décalé deux fois vers la gauche. C’est à dire 00000001 au départ, puis 00000010, et enfin 00000100.
  • 2 (on est en binaire) puissance 2, position du bit dans l’octet.

Résultat, si un code Dcc 15/0 est reçu, c’est la led verte qui est allumée, les autres leds seront éteintes. Pour 15/1, c’est la jaune qui va s’allumer, et pour 16/0, c’est la rouge. Si on voulait tout allumer par exemple avec le code Dcc 16/1, il faudrait ajouter une position Dcc avec 0b00000111 . Le dernier argument a un fonctionnement identique, mais pour faire clignoter certaines DELs. C’est pourquoi il est à zéro ici.

Le Setup est terminé. Le loop() est simple, il faut y faire figurer à la fois les Commanders et les accessoires :

void loop()
{
	Commanders::Loop();
	Accessories::Loop();
}

La fonction de passage d’événement entre les deux bibliothèques doit aussi être présente avant le setup. C’est la fonction utilisée en argument du begin de Commanders :

void ReceiveEvent(unsigned long inId, COMMANDERS_EVENT_TYPE inEventType, int inEventData)
{
  Accessories::ReceiveEvent(inId, (ACCESSORIES_EVENT_TYPE)inEventType, inEventData);
}

Le code pour plusieurs feux

J’ai choisi de coder une classe décrivant un seul feu, la troisième possibilité de codage vue plus haut donc. A noter que la classe doit être définie avant son utilisation, c’est pourquoi elle est en tête du fichier ino.

#include "Commanders.h"
#include "Accessories.h"

//------------------------------------------------------------------------------
// SignalArduino declaration

class SignalArduino : public AccessoryLightMulti
{
public:
	void begin(byte inNbLeds, int *inpPins, int inStartingDcc);
};

//------------------------------------------------------------------------------
// SignalArduino definition

static byte counter = 0;

void SignalArduino::begin(byte inNbLeds, int *inpPins, int inStartingDcc)
{
	// Je force l'appel au begin() de la classe de base AccessoryLightMulti 
	// pour faire ce qui est nécessaire pour un AccessoryLightMulti.
	this->AccessoryLightMulti::begin(1000 + (counter++), inNbLeds, 0);

	// puis j'ajoute ce qui est specifique à la classe 'SignalArduino'
	for (int led = 0; led < inNbLeds; led++)
	{
		// Le port est créé ici et utilisé tout de suite.
		// Inutile de le déclarer plus tôt ou d'en garder une trace dans une liste.
		PortOnePin *port = new PortOnePin();
		port->begin(inpPins[led], DIGITAL);
		this->beginLight(led, port);
	}

	// Table de vérité sur le lien code Dcc / leds
	//                 Led  0    1    2
	// inStartingDcc   / 0  on  off  off
	// inStartingDcc   / 1  off on   off
	// inStartingDcc+1 / 0  off off  on
	// inStartingDcc+1 / 1  off off  off

	this->AdjustMovingPositionsSize(inNbLeds);

	int dcc = inStartingDcc;
	bool etat = false;
	for (int i = 0; i < this->GetSize(); i++)
	{
		if (!etat)
		{
			this->AddMovingPosition(DCCINT(dcc, 0), 1 << i, 0);
			etat = true;
		}
		else
		{
			this->AddMovingPosition(DCCINT(dcc, 1), 1 << i, 0);
			dcc++;
			etat = false;
		}
	}

	// Last moving position used to set all off.
	this->AddMovingPosition(DCCINT(dcc, etat == true ? 1 : 0), 0, 0);
}
// End of class
//------------------------------------------------------------------------------

Cette classe est dérivée de AccessoriesLightMulti et ne comprend qu’une fonction : begin. En gros, c’est une factorisation du code de l’exemple précédent pour un seul feu. J’y ai ajouté des boucles pour permettre de choisir le nombre de DELs sans limitation.

Vient ensuite le code normal du croquis.

/* kDCC_INTERRUPT values :
Board         int.0   int.1   int.2   int.3   int.4   int.5
Uno, Ethernet   2      3
Mega2560        2      3      21      20      19      18
Leonardo        3      2      0       1       7
*/
#define kDCC_INTERRUPT            0

J’ai choisi l’interruption 0 sur un Mega2560, donc la broche 2 sera réservée pour l’entrée du signal DCC provenant des rails.

On définit ensuite nos 16 feux, utilisant chacun 3 leds et pouvant avoir 3 états. Ils occupent 48 broches du Mega2560.

#define NB_LEDS     3
#define NB_ETATS    3
#define NB_FEUX     16

int pins[][NB_LEDS] = {
        { 5, 6, 7 },
        { 8, 9, 10 },
        { 11, 12, 13 },
        { 14, 15, 16 },
        { 17, 18, 19 },
        { 20, 21, 22 },
        { 23, 24, 25 },
        { 26, 27, 28 },
        { 29, 30, 31 },
        { 32, 33, 34 },
        { 35, 36, 37 },
        { 38, 39, 40 },
        { 41, 42, 43 },
        { 44, 45, 46 },
        { 47, 48, 49 },
        { 50, 51, 52 }
};

Notez l’absence de valeur dans le premier crochet ’[]’ de la matrice ’pins’. En effet le compilateur déduira le nombre depuis la liste qui suit. Si cette matrice n’avait pas été initialisée tout de suite, il aurait fallu préciser le nombre de feux ! Cette notation est très pratique puisqu’elle permet de modifier le nombre de lignes de la matrice sans rien toucher d’autre. Accessoirement on est sûr de ne pas se tromper en confiant ce genre de boulot au compilateur ! Par contre le nombre d’entiers par ligne doit être fixé (NB_LEDS).

Puis les objets que nous utilisons dans les bibliothèques et les adresses DCC des 16 feux :

ButtonsCommanderPush poussoir;
SignalArduino* signaux[NB_FEUX];

int dcc_codes[] = { 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40};

Les instructions de la fonction setup()

Initialisation des librairies :

    Commanders::begin(ReceiveEvent, LED_BUILTIN);
    Accessories::begin();

    DccCommander.begin(0x00, 0x00, kDCC_INTERRUPT);

Initialisation du bouton de test des leds de tous les feux. ce bouton est connecté sur le port A2, la plupart des ports digitaux étant utilisés par les feux :

    poussoir.begin(0, A2);

    // Ce petit bouton va permettre de passer en revue tous les codes dcc des feux en séquence...
    for (int feu = 0; feu < NB_FEUX; feu++)
    {
      poussoir.AddEvent(DCCINT(dcc_codes[feu], 0));
      poussoir.AddEvent(DCCINT(dcc_codes[feu], 1));
      poussoir.AddEvent(DCCINT(dcc_codes[feu] + 1, 0));
    }

Initialisation des 16 feux :

    for (int feu = 0; feu < NB_FEUX; feu++)
    {
      signaux[feu] = new SignalArduino();
      signaux[feu]->begin(NB_LEDS, pins[feu], dcc_codes[feu]);
    }

La boucle loop()

Celle-ci devient très simple et se réduit à :

void loop()
{
	Commanders::loop();
	Accessories::loop();
}

Comme dans l’exemple précédent ; il faut ajouter la fonction ReceiveEvent() citée plus haut.

Ce programme est simplement une adaptation de l’exemple "Signals4x3" de la bibliothèque Accessories.

Et le code est ici :

Le programme utilisant une interface avec Accessories

Cette interface permet à des personnes ne souhaitant pas entrer dans les détails informatiques de mettre en place des signaux sur une carte Arduino MEGA. Les signaux possibles peuvent être à 2 et/ou 3 feux, ce qui est le cas le plus général sur nos petits réseaux.

Le principe est le suivant :

On génère d’abord les accessoires pour les signaux à 2 feux, puis ceux pour les signaux à 3 feux. Il peut donc y avoir mixité, mais toujours les signaux à 2 feux en premier puis les signaux à 3 feux ensuite. La première broche de la première led est la broche 3. Les signaux à 2 feux correspondent à des doublons de broches (x, x+1) et des adresses y/0 et y/1, les signaux à 3 feux correspondent à des triplets de broches (x, x+1, x+2) et des adresses y/0, y/1, y+1/0. Rien n’empêche cependant de ne mettre aucun signal à 2 feux ou à 3 feux et donc de réaliser des décodeurs pour des signaux exclusivement à deux ou trois feux.

L’avantage est sa simplicité, on doit donner :

  • Le nombre de signaux à 2 feux (zéro possible)
  • Le nombre de signaux à 3 feux (zéro possible)
  • La première adresse DCC
  • La broche de la led témoin du DCC
  • La broche du bouton

J’ai testé avec mon ECOS et cela marche très bien. Attention cependant à la limite de courant à ne pas dépasser pour toutes les broches (voir en début d’article). Une valeur de 16 feux semble raisonnable. La bibliothèque TrafficSignal est chargeable à la fin de l’article.

Le croquis ino est très simple :

  • On instancie les signaux : TrafficSignal signaux ;
  • On fait le setup : Signaux.Setup(nbsignauxa2leds,nbSignauxa3leds, code_DCC, pinLedStatus, pinBouton) ;
  • Puis on boucle : Signaux.Loop()

Exemple de programme Arduino

#include <TrafficSignal.h>  // la gestion des signaux

#define NB_FEUX_2  	8   // nombre de signaux a 2 feux
#define NB_FEUX_3  	8   // nombre de signaux a 3 feux
#define ID_DCC 	40        // adresse DCC de départ du premier feu
#define LED_STATUS 53 // la broche de la led temoin DCC
#define BUTTON_PIN A15 // la broche du bouton de test

TrafficSignal signaux;    // une instance des signaux
void setup() 
{
     signaux.Setup(NB_FEUX_2, NB_FEUX_3, ID_DCC, LED_STATUS, BUTTON_PIN);
}

void loop() 
{
     signaux.Loop();
}

Voilà les 16 signaux sont opérationnels.

Voici ce que l’on obtient avec cet exemple :

PortOnePin :
2 feux (vert, rouge) : (3, 4), (5, 6), (7, 8), (9, 10),( 11, 12), (13, 14), (15, 16), (17, 18),
3 feux (vert, rouge, jaune) : (19, 20, 21), (22, 23, 24), (25, 26, 27), (28, 29, 30), (31, 32, 33), (34, 35, 36), (37, 38, 39), (40, 41, 42)

Accessoires 2 feux (code dcc vert, code dcc rouge) :
(40/0, 40/1), (41/0, 41/1), (42/0, 42/1), (43/0, 43/1)
(44/0, 44/1), (45/0, 45/1), (46/0, 46/1), (47/0, 47/1)

Accessoires 3 feux (code dcc vert, code dcc rouge, code dcc jaune)
(48/0, 48/1, 49/0), (50/0, 50/1, 51/0), (52/0, 52/1, 53/0),
(54/0, 54/1, 55/0), (56/0, 56/1, 57/0), (58/0, 58/1, 59/0),
(60/0, 60/1, 61/0), (62/0, 62/1, 63/0)

Ou de façon plus lisible :

PNG - 45.8 kio

Voici le code complet :

Conclusions

La réalisation de décodeurs pour des signaux ferroviaires est tout à fait réalisable avec des Arduino, ce n’est que de la gestion de Led.
La première solution permet de comprendre la complexité des messages DCC, leur réception, leur décodage et les actions à entreprendre. Cependant cela impose un certain effort de programmation.
La deuxième approche basée sur Commanders et Accessories, très bien architecturée, soulage le développement mais la grande quantité de ces méthodes demande une investigation non négligeable.
Enfin la dernière approche permet rapidement de réaliser des décodeurs de signaux sans rentrer dans les détails de ces bibliothèques.
On peut donc, pour un coût très modique et rapidement, gérer 16 signaux à trois feux sur un Arduino Mega.

Cependant on peut faire quelques remarques. La première concerne l’utilisation de la bibliothèque Accessories seulement pour gérer des signaux ferroviaires est un luxe et sera beaucoup plus efficace si on l’utilise avec d’autres types d’activités comme la gestion des barrières de PN ou autres, ou cette bibliothèque montrera toute sa puissance et prendra tout son intérêt. La seconde concerne l’utilisation d’un Arduino MEGA à cause du nombre important de pins (48) utilisées pour 16 feux tricolores. Personnellement je préfère les Nano qui me permettent une meilleure répartition sur le réseau et donc d’avoir moins de câblage.

Le DCC, par sa vocation, est très efficace pour l’envoi des commandes sur le réseau et le bus CAN pour la rétro signalisation (ou la commande d’accessoires de décors). Un prochain article permettra de détailler cette approche distribuée au sein de nos réseaux !