LOCODUINO

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

mardi 19 mars 2024

Visiteurs connectés : 34

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

Comment générer un signal DCC

.
Par : Dominique

DIFFICULTÉ :

Dans cet article, on va aborder la programmation de l’Arduino pour produire un signal DCC de 2 manières différentes.

La première méthode oblige à considérer le fonctionnement du Timer et des interruptions, ainsi que les routines d’interruption. C’est une application pratique des articles généraux publiés dans ce site, notamment « Les interruptions (1) » et « Les Timers (I) » et suivants. Elle a pour but de permettre de comprendre les tripes de la technologie, pour ceux qui le souhaitent.

La deuxième méthode fait appel à une bibliothèque toute faite qui permet de cacher ces tripes et aussi de proposer plus de fonctions. Ce sera certainement celle que vous choisirez pour votre projet !

<

Une première méthode pour produire un signal DCC simple avec Arduino

On a dit précédemment, voir « L’Arduino et le système de commande numérique DCC » que l’envoi des commandes DCC impose de faire travailler notre Arduino en tâche de fond, sous interruption. On va donc utiliser un des timers existants.

  • Le Timer0 est utilisée par les fonctions delay(), millis(), entre autres.
  • Le Timer1 est utilisée par la librairie Servo et est le plus précis (16 bits).
  • La Timer2 est utilisée par la fonction Tone().

On va, dans cet exemple, se servir de Timer2 et, par conséquent, on devra se passer de la production des sons avec Tone() car notre exemple va modifier les valeurs de temps pour satisfaire la norme DCC et donc dérégler la fonction Tone() ;

Le jeu va consister à programmer le timer pour qu’il envoie une interruption à chaque fois que le signal DCC doit changer. Pour les 0, ceci correspond à 50µs et pour les 1, à 29µs. Nous n’allons pas entrer dans les détails de cette programmation car elle fera l’objet d’un article séparé. Tout d’abord, le timer est programmé de manière à ce qu’il s’incrémente toutes les 0,5µs, on appelle ceci un tick du timer. Ensuite, le déclenchement d’interruption sur débordement est validé. L’explication de ce qu’est un débordement est dans l’article « Types, constantes et variables. Il est enfin initialisé à une valeur telle qu’il faut 100 ticks (TIMER_LONG dans le programme qui suit), pour un 0 et 58 ticks (TIMER_SHORT) pour un 1 pour déborder.

Utilisation de l’interruption du timer pour compter la demi période du signal DCC.
Le timer est chargé avec une valeur telle que le temps, en nombre de ticks du timer, avant de déborder est égal au temps désiré. Le tick est programmé à 0,5µs. Pour 50µs, cela représente 100 ticks. Le timer déborde lorsqu’il repasse à 0. La valeur à charger pour 100 ticks est donc égale à 256 - 100 = 156. Pour 29µs, cela représente 58 ticks. La valeur à charger pour 58 ticks est donc égale à 256 - 58 = 198.

Cela correspond à l’initialisation suivante :

/* ------------------- Setup Timer2. ------------------- 
 * Configure le Timer2 de 8-Bit pour générer une interruption
 * toutes les 58 ou 100 micro-secondes.
 * La durée doit être chargée dans TCNT2 par la routine
 * d'interruption ISR(TIMER2_OVF_vect).
 */
 
#define TIMER_LONG  156
#define TIMER_SHORT 198
 
void SetupTimer2()
{
  //Timer2 Settings: Timer Prescaler /8, mode 0
  //Timer clock = 16MHz/8 = 2MHz ou 0,5usec
  TCCR2A = 0;
  TCCR2B = 0 << CS22 | 1 << CS21 | 0 << CS20; 

  //Timer2 Overflow Interrupt Enable   
  TIMSK2 = 1 << TOIE2;

  //load the timer for its first cycle
  TCNT2 = TIMER_SHORT; 
}

Cette fonction est appelée une fois seulement dans la fonction setup().
Grâce à elle, la routine d’interruption ISR(TIMER2_OVF_vect) que l’on verra plus loin, sera appelée automatiquement à chaque fois qu’une transition 0 vers 1 ou 1 vers 0 est nécessaire.

Auparavant on devra déclarer quelques constantes et variables globales qui sont utilisées par cette routine d’interruption :

#define DCC_PIN    4 // Port Arduino qui délivre le signal DCC
#define PREAMBLE   0 // état "envoi du préambule"
#define SEPARATOR  1 // état "envoi du bit start de chaque octet"
#define SENDBYTE   2 // état "envoi des bits de chaque octet"
unsigned char last_timer = TIMER_SHORT; // valeur du Timer en cours   
unsigned char flag = 0;                 // soit bit 1 (court) ou 0 (long)
unsigned char every_second_isr = 0;     // demi-impulsion : haut ou bas
unsigned char state = PREAMBLE;
unsigned char preamble_count = 16;
unsigned char outbyte = 0;
unsigned char cbit = 0x80;

ainsi qu’une zone en mémoire qui contient les commandes DCC à envoyer les unes après les autres, en recommençant au début lorsque la dernière est émise.

// -------------------  buffer for command ------------------- 
struct Message {
  unsigned char data[7]; // taille de la plus grande commande possible
  unsigned char len;     // taille réelle de la commande
} ;

#define MAXMSG  5

struct Message msg[MAXMSG] = { 
  {{0xFF, 0, 0xFF, 0, 0, 0, 0}, 3},      // message idle
  {{locoAdr, 0x3F,  0, 0, 0, 0, 0}, 4},  // vitesse (128 pas) et direction (bit 7) 
  {{locoAdr2, 0x3F,  0, 0, 0, 0, 0}, 4}, // vitesse (128 pas) et direction (bit 7) 
  {{locoAdr, 0x80,  0, 0, 0, 0, 0}, 3},  // lumière FL : 0x80 éteinte
  {{locoAdr2, 0x90,  0, 0, 0, 0, 0}, 3}  // lumière FL : 0x90 allumée
}; // chaque message doit être complété avec les valeurs et le XOR
                                
int msgIndex=0;  
int byteIndex=0;

La routine d’interruption

Voici maintenant la routine d’interruption qui effectue l’envoi de chaque bit d’un message DCC et recharge le timer avec la valeur correspondant au délai avant la prochaine transition du signal DCC. Il est nécessaire de fournir quelque explication sur ce rechargement. Entre le moment où l’interruption est déclenchée et le moment ou le timer est rechargé, le temps continue de s’écouler et le timer de s’incrémenter. Par conséquent si le timer était rechargé avec la valeur brute, le prochain délai serait trop important. Il est donc nécessaire d’ajuster cette valeur en lui ajoutant la valeur du timer au moment du rechargement. C’est le rôle de la variable latency qui reçoit valeur du timer et est ajoutée à la valeur brute.

// ------------------- Timer2 overflow interrupt vector handler ------------------- 
ISR(TIMER2_OVF_vect) {
  //Capture la valeur courante de TCTN2. 
  //Permet de corriger le temps de latence de l'interruption.
  //Recharge le Timer.  
  
  // Une interruption sur 2, inversion seulement du signal (seconde moitié du bit)
  if (every_second_isr)  {
     digitalWrite(DCC_PIN,1);
     every_second_isr = 0;    
     
     // mise à jour du Timer
     latency=TCNT2;
     TCNT2=latency+last_timer; 
     
  }  else  {  // != passage au bit ou état suivant
     digitalWrite(DCC_PIN,0);
     every_second_isr = 1; 
     
     switch(state)  {
       case PREAMBLE:
           flag=1; // bit court
           preamble_count--;
           if (preamble_count == 0)  {  // vers état suivant
              state = SEPARATOR;
              // message suivant
              msgIndex++;
              if (msgIndex >= MAXMSG)  {  msgIndex = 0; }  
              byteIndex = 0; //index 0 du premier octet
           }
           break;
        case SEPARATOR:
           flag=0; // bit long
           // état suivant
           state = SENDBYTE;
           // octet suivant
           cbit = 0x80;  // bit à envoyer le coup suivant         
           outbyte = msg[msgIndex].data[byteIndex];
           break;
        case SENDBYTE:
           if (outbyte & cbit)  { 
              flag = 1;  // bit court
           }  else  {
              flag = 0;  // bit long
           }
           cbit = cbit >> 1;
           if (cbit == 0)  {  // dernier bit, vers octet suivant
              byteIndex++;
              if (byteIndex >= msg[msgIndex].len)  {
                 // fin du message
                 state = PREAMBLE;
                 preamble_count = 16;
              }  else  {
                 // envoi du bit 0 séparateur avant octet suivant
                 state = SEPARATOR ;
              }
           }
           break;
     }   
 
     if (flag)  {  // 1 bit court
        latency=TCNT2;
        TCNT2=latency+TIMER_SHORT;
        last_timer=TIMER_SHORT;
     }  else  {   // 0 bit long
        latency=TCNT2;
        TCNT2=latency+TIMER_LONG; 
        last_timer=TIMER_LONG;
     }  
  }
}

Génération de la trame DCC

D’abord, chaque bit comprend une demi-période à l’état bas (0) et une demi-periode à l’état haut (1). A la fin de cette demi-période, on doit passer au bit suivant en parcourant la zone mémoire de message, octet par octet (chargé dans la variable outbyte = msg[msgIndex].data[byteIndex]).

Pour organiser cette promenade, on utilise un automate avec switch (state) et 3 états possibles pour state :

  • PREAMBLE : tant qu’on ne tombe pas sur un 0, on envoie des 1
  • SEPARATOR : on envoie le 0 qui marque le début de l’octet suivant
  • SENDBYTE : on envoie les bits de chaque octet du message

A la fin d’un message on passe au suivant. A la fin du dernier, on recommence au premier.

Les 5 messages dans l’exemple sont :

  • Un message IDLE ;
  • 2 commandes de vitesse ;
  • 2 commandes de lumière.

donc pour piloter 2 locomotives.

Cette méthode a l’avantage d’être assez simple, mais elle a l’inconvénient de nécessiter la connaissance au bit près des commandes DCC.

J’ai vite trouvé cette méthode extrêmement fastidieuse et j’ai cherché une autre méthode.

Je suis finalement tombé sur le site de Railstar [1] qui propose une librairie disponible librement en Open Source.

La librairie CmdrArduino

Il s’agit maintenant d’une librairie, ce qui signifie qu’il n’est plus nécessaire de copier/coller du code dans votre application mais seulement d’appeler des fonctions, Nous verrons cela dans un prochain article.

Lorsque la librairie est installée, il suffit d’aller dans le menu Fichier/Exemples/CmdrArduino et de choisir l’exemple CmdrArduino_minimum :

/********************
 * Centrale DCC minimum avec un potentiomètre de vitesse connecté
 * sur le port analogique 0,
 * un bouton poussoir connecté entre le 0V et l'entrée digitale 4
 * Le signal DCC est délivré sur la Pin 9, et est capable de piloter
 * un booster à base de LMD18200 directement.
 ********************/
 
#include <DCCPacket.h>
#include <DCCPacketQueue.h>
#include <DCCPacketScheduler.h>
 
DCCPacketScheduler dps;
unsigned int analog_value;
char speed_byte, old_speed = 0;
byte count = 0;
byte prev_state = 1;
byte F0 = 0;
 
void setup() {
  Serial.begin(9600);
  dps.setup();  // initialisation de la librairie
 
  // Bouton sur la pin 4
  pinMode(4, INPUT_PULLUP);
}
 
void loop() {
  // Lecture de l'état du bouton pour la commande de lumière F0
  byte button_state = digitalRead(4); //high == relaché; low == appuyé
  if(button_state && (button_state != prev_state))
  {
    // inversion de l'état
    F0 ^= 1;
    Serial.println(F0,BIN);
    dps.setFunctions0to4(3,DCC_SHORT_ADDRESS,F0);
  }
  prev_state = button_state;

  // Potentiomètre de vitesse
  analog_value = analogRead(0);
  speed_byte = (analog_value >> 2)-127 ; 
  // Ramène la gamme 0-1023 à +126-126, l'arrêt étant le point milieu du potentiomètre
  if(speed_byte != old_speed)
  {
    if(speed_byte == 0) // On évite l'arrêt brutal (e_stop) en remplaçant le 0 par 1
    {
      if(old_speed > 0) speed_byte = 1;
      else speed_byte = -1;
    }
    Serial.print("analog = ");
    Serial.println(analog_value, DEC);
    Serial.print("digital = ");
    Serial.println(speed_byte, DEC);
    dps.setSpeed128(3,DCC_SHORT_ADDRESS,speed_byte);
    old_speed = speed_byte;
  }
  // Cet appel est impératif pour permettre à la librairie de faire son travail
  dps.update();
}

Explications :

On réalise le montage suivant avec :

  • Un Arduino Uno
  • Un module LMD18200
  • Un potentiomètre de 10K
  • Un bouton poussoir
  • Une alimentation 12 volts

On supposera que l’Arduino est alimenté via le câble USB relié à l’ordinateur

Ensuite on installe la librairie CmdrArduino, on charge le programme ci-dessus et cela doit marcher du premier coup, si la loco placée sur les rails est bien configurée avec l’adresse DCC 3.

Il faut placer le potentiomètre au milieu avant d’alimenter le circuit.
La loco avance quand on pousse le potentiomètre d’un coté, et recule de l’autre coté.
Un appui sur le bouton allume les phares de la loco. Un autre appui les éteint.

Bien entendu cet exemple est le plus simple qu’il soit possible de réaliser. Il démontre vite que nous devons aller plus loin. Par exemple :

  • Il serait plus pratique d’avoir un inverseur de Direction et utiliser toute l’excursion du potentiomètre pour la vitesse.
  • On voudrait piloter plusieurs locomotives.
  • On voudrait commander d’autres fonctions de la machine et des accessoires.
  • Etc..

Rassurez-vous, on va y arriver !

Tout d’abord nous allons explorer la librairie CmdrArduino, les fonctions qu’elle propose. Ce sera l’objet de l’article suivant.

Puis nous reviendrons sur la construction d’une centrale plus complète pour conclure cette série d’articles.

74 Messages

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

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 »

LaBox, Une Centrale DCC polyvalente et abordable (1)

LaBox, Une Centrale DCC polyvalente et abordable (2)

LaBox, Une Centrale DCC polyvalente et abordable (3)

SGDD : Système de Gestion DD (1)

SGDD : Système de Gestion DD (2)

SGDD : Système de Gestion DD (3)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

La carte Satellite V1 (1)

La carte Satellite V1 (2)

La carte Satellite V1 (3)

La carte Satellite V1 (4)

La carte Satellite V1 (5)

Chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

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

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

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

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

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

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

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

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

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

Etude d’un passage à niveau multivoies

La rétro-signalisation sur Arduino

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

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

Etude d’un passage à niveau universel

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

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

Un décodeur DCC pour 16 feux tricolores

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

Réalisation d’un affichage de gare ARRIVEE DEPART

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

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

Souris et centrale sans fil

Communications entre JMRI et Arduino

Annonces en gare avec la RFID

Une croix de pharmacie animée avec Arduino UNO

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

Passage à niveau géré par Arduino (1)

Passage à niveau géré par Arduino (2)

Passage à niveau géré par Arduino (3)

Passage à niveau géré par Arduino (4)

Passage à niveau géré par Arduino (5)

Une manette simple et autonome pour LaBox

Éclairer le réseau (1)

Éclairer le réseau (2)

Block Automatique Lumineux à 8 cantons analogiques

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

Éclairer le réseau (3)

Éclairer le réseau (4)

Éclairer le réseau (5)

JMRI pour Ma première centrale DCC

Rocrail pour Ma première centrale DCC

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

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

Banc de test pour les décodeurs DCC

Ma première manette pour les aiguillages DCC

Mon premier décodeur pour les aiguillages DCC

Boitier 3D pour la station DCC minimale

Va-et-vient pour deux trains

Un programme pour régler facilement les servos moteurs avec un ESP32

Affichage publicitaire avec Arduino (1)

Affichage publicitaire avec Arduino (2)

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

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

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

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

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

Les derniers articles

LaBox, Une Centrale DCC polyvalente et abordable (3)


Thierry

LaBox, Une Centrale DCC polyvalente et abordable (1)


Thierry

LaBox, Une Centrale DCC polyvalente et abordable (2)


Dominique, msport, Thierry

Un programme pour régler facilement les servos moteurs avec un ESP32


bobyAndCo

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


utpeca

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


utpeca

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


utpeca

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


utpeca

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


utpeca

Affichage publicitaire avec Arduino (2)


catplus, Christian

Les articles les plus lus

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

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

La rétro-signalisation sur Arduino

Mon premier décodeur pour les aiguillages DCC

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

Chenillard de DEL

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

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

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

LaBox, Une Centrale DCC polyvalente et abordable (1)