LOCODUINO

Forum de discussion
Dépôt GIT Locoduino

vendredi 24 février 2017

34 visiteurs en ce moment

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

Comment générer un signal DCC

. Par : Dominique

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.

PNG - 17.7 ko
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 :

  1. /* ------------------- Setup Timer2. -------------------
  2.  * Configure le Timer2 de 8-Bit pour générer une interruption
  3.  * toutes les 58 ou 100 micro-secondes.
  4.  * La durée doit être chargée dans TCNT2 par la routine
  5.  * d'interruption ISR(TIMER2_OVF_vect).
  6.  */
  7.  
  8. #define TIMER_LONG 156
  9. #define TIMER_SHORT 198
  10.  
  11. void SetupTimer2()
  12. {
  13. //Timer2 Settings: Timer Prescaler /8, mode 0
  14. //Timer clock = 16MHz/8 = 2MHz ou 0,5usec
  15. TCCR2A = 0;
  16. TCCR2B = 0 << CS22 | 1 << CS21 | 0 << CS20;
  17.  
  18. //Timer2 Overflow Interrupt Enable
  19. TIMSK2 = 1 << TOIE2;
  20.  
  21. //load the timer for its first cycle
  22. TCNT2 = TIMER_SHORT;
  23. }

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 :

  1. #define DCC_PIN 4 // Port Arduino qui délivre le signal DCC
  2. #define PREAMBLE 0 // état "envoi du préambule"
  3. #define SEPARATOR 1 // état "envoi du bit start de chaque octet"
  4. #define SENDBYTE 2 // état "envoi des bits de chaque octet"
  5. unsigned char last_timer = TIMER_SHORT; // valeur du Timer en cours
  6. unsigned char flag = 0; // soit bit 1 (court) ou 0 (long)
  7. unsigned char every_second_isr = 0; // demi-impulsion : haut ou bas
  8. unsigned char state = PREAMBLE;
  9. unsigned char preamble_count = 16;
  10. unsigned char outbyte = 0;
  11. 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.

  1. // ------------------- buffer for command -------------------
  2. struct Message {
  3. unsigned char data[7]; // taille de la plus grande commande possible
  4. unsigned char len; // taille réelle de la commande
  5. } ;
  6.  
  7. #define MAXMSG 5
  8.  
  9. struct Message msg[MAXMSG] = {
  10. {{0xFF, 0, 0xFF, 0, 0, 0, 0}, 3}, // message idle
  11. {{locoAdr, 0x3F, 0, 0, 0, 0, 0}, 4}, // vitesse (128 pas) et direction (bit 7)
  12. {{locoAdr2, 0x3F, 0, 0, 0, 0, 0}, 4}, // vitesse (128 pas) et direction (bit 7)
  13. {{locoAdr, 0x80, 0, 0, 0, 0, 0}, 3}, // lumière FL : 0x80 éteinte
  14. {{locoAdr2, 0x90, 0, 0, 0, 0, 0}, 3} // lumière FL : 0x90 allumée
  15. }; // chaque message doit être complété avec les valeurs et le XOR
  16.  
  17. int msgIndex=0;
  18. 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.

  1. // ------------------- Timer2 overflow interrupt vector handler -------------------
  2. ISR(TIMER2_OVF_vect) {
  3. //Capture la valeur courante de TCTN2.
  4. //Permet de corriger le temps de latence de l'interruption.
  5. //Recharge le Timer.
  6.  
  7. // Une interruption sur 2, inversion seulement du signal (seconde moitié du bit)
  8. if (every_second_isr) {
  9. digitalWrite(DCC_PIN,1);
  10. every_second_isr = 0;
  11.  
  12. // mise à jour du Timer
  13. latency=TCNT2;
  14. TCNT2=latency+last_timer;
  15.  
  16. } else { // != passage au bit ou état suivant
  17. digitalWrite(DCC_PIN,0);
  18. every_second_isr = 1;
  19.  
  20. switch(state) {
  21. case PREAMBLE:
  22. flag=1; // bit court
  23. preamble_count--;
  24. if (preamble_count == 0) { // vers état suivant
  25. state = SEPARATOR;
  26. // message suivant
  27. msgIndex++;
  28. if (msgIndex >= MAXMSG) { msgIndex = 0; }
  29. byteIndex = 0; //index 0 du premier octet
  30. }
  31. break;
  32. case SEPARATOR:
  33. flag=0; // bit long
  34. // état suivant
  35. state = SENDBYTE;
  36. // octet suivant
  37. cbit = 0x80; // bit à envoyer le coup suivant
  38. outbyte = msg[msgIndex].data[byteIndex];
  39. break;
  40. case SENDBYTE:
  41. if (outbyte & cbit) {
  42. flag = 1; // bit court
  43. } else {
  44. flag = 0; // bit long
  45. }
  46. cbit = cbit >> 1;
  47. if (cbit == 0) { // dernier bit, vers octet suivant
  48. byteIndex++;
  49. if (byteIndex >= msg[msgIndex].len) {
  50. // fin du message
  51. state = PREAMBLE;
  52. preamble_count = 16;
  53. } else {
  54. // envoi du bit 0 séparateur avant octet suivant
  55. state = SEPARATOR ;
  56. }
  57. }
  58. break;
  59. }
  60.  
  61. if (flag) { // 1 bit court
  62. latency=TCNT2;
  63. TCNT2=latency+TIMER_SHORT;
  64. last_timer=TIMER_SHORT;
  65. } else { // 0 bit long
  66. latency=TCNT2;
  67. TCNT2=latency+TIMER_LONG;
  68. last_timer=TIMER_LONG;
  69. }
  70. }
  71. }

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 :

  1. /********************
  2.  * Centrale DCC minimum avec un potentiomètre de vitesse connecté
  3.  * sur le port analogique 0,
  4.  * un bouton poussoir connecté entre le 0V et l'entrée digitale 4
  5.  * Le signal DCC est délivré sur la Pin 9, et est capable de piloter
  6.  * un booster à base de LMD18200 directement.
  7.  ********************/
  8.  
  9. #include <DCCPacket.h>
  10. #include <DCCPacketQueue.h>
  11. #include <DCCPacketScheduler.h>
  12.  
  13. DCCPacketScheduler dps;
  14. unsigned int analog_value;
  15. char speed_byte, old_speed = 0;
  16. byte count = 0;
  17. byte prev_state = 1;
  18. byte F0 = 0;
  19.  
  20. void setup() {
  21. Serial.begin(9600);
  22. dps.setup(); // initialisation de la librairie
  23.  
  24. // Bouton sur la pin 4
  25. pinMode(4, INPUT_PULLUP);
  26. }
  27.  
  28. void loop() {
  29. // Lecture de l'état du bouton pour la commande de lumière F0
  30. byte button_state = digitalRead(4); //high == relaché; low == appuyé
  31. if(button_state && (button_state != prev_state))
  32. {
  33. // inversion de l'état
  34. F0 ^= 1;
  35. Serial.println(F0,BIN);
  36. dps.setFunctions0to4(3,DCC_SHORT_ADDRESS,F0);
  37. }
  38. prev_state = button_state;
  39.  
  40. // Potentiomètre de vitesse
  41. analog_value = analogRead(0);
  42. speed_byte = (analog_value >> 2)-127 ;
  43. // Ramène la gamme 0-1023 à +126-126, l'arrêt étant le point milieu du potentiomètre
  44. if(speed_byte != old_speed)
  45. {
  46. if(speed_byte == 0) // On évite l'arrêt brutal (e_stop) en remplaçant le 0 par 1
  47. {
  48. if(old_speed > 0) speed_byte = 1;
  49. else speed_byte = -1;
  50. }
  51. Serial.print("analog = ");
  52. Serial.println(analog_value, DEC);
  53. Serial.print("digital = ");
  54. Serial.println(speed_byte, DEC);
  55. dps.setSpeed128(3,DCC_SHORT_ADDRESS,speed_byte);
  56. old_speed = speed_byte;
  57. }
  58. // Cet appel est impératif pour permettre à la librairie de faire son travail
  59. dps.update();
  60. }

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

JPEG - 215.7 ko

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.

60 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 »

Un chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

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

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

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

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

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

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

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

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

Réalisation d’un affichage de gare ARRIVEE DEPART

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)

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)

Les derniers articles

Réalisation d’un affichage de gare ARRIVEE DEPART


Gilbert

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


Daniel

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


Jean-Luc

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


bobyAndCo

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


Pierre59

Un décodeur DCC pour 16 feux tricolores


Dominique, JPClaude, Thierry

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


bobyAndCo

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


Dominique

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


JPClaude

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


Pierre59

Les articles les plus lus

Réalisation d’un affichage de gare ARRIVEE DEPART

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

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

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

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

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

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

La rétro-signalisation sur Arduino

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

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