Détection RailCom© avec ESP32 (ou Arduino)

. Par : bobyAndCo, catplus. URL : https://www.locoduino.org/spip.php?article334

La détection des locomotives et des convois sur nos réseaux est essentielle dès que nous souhaitons introduire un peu d’automatisation, en particulier pour ce qui concerne la sécurité. Les méthodes de détection classiques fonctionnent généralement bien pour signaler la présence (ou non) d’une locomotive, mais peu sont capables de nous renseigner sur l’adresse DCC de la locomotive.

Dans cet article, nous présentons un système de détection et d’identification de trains qui utilise le système Railcom©. Il permet de convertir les données envoyées par les locomotives en informations exploitables par n’importe quel microcontrôleur, comme l’Arduino ou l’ESP32.

Mais tout d’abord, qu’est-ce que Railcom©.

Railcom© est un système de détection de locomotive sur un canton qui permet de connaître l’adresse DCC attribuée au décodeur. Au-delà de nous informer de l’occupation de la zone, Railcom© est probablement le seul, ou du moins le plus répandu des systèmes de détection qui soit capable également de nous fournir l’adresse.

Il s’agit d’une technologie propriétaire développée par Lenz mais ouverte à d’autres constructeurs qui ne fonctionne qu’en DCC et nécessite des décodeurs de locomotives "compatibles". Au sujet des droits, le brevet lié à RailCom© (EP1380326), déposé et valable dans l’UE et aux États-Unis peut être utilisé librement depuis juin 2017. Cela ne s’applique pas à RailComPlus© (RailCom+©), qui a été développé conjointement par ESU et Lenz, mais nous n’utilisons ici que la technologie Railcom©.

Railcom© fait l’objet d’un « draft » du NMRA qui est à ce jour le document public le plus complet :

https://www.nmra.org/sites/default/files/standards/sandrp/DCC/S/S-9.3.2_2012_12_10.pdf

PNG - 11.7 kio
Source : Paco’s Official Web Site

Railcom© nécessite que les trames DCC envoyées sur le réseau contiennent un « cutout », c’est-à-dire une suspension de l’alimentation d’environ 460 µs pendant laquelle le décodeur transmet un court message (généralement deux ou quatre octets) incluant son adresse. Ce cutout est réalisable assez simplement avec une centrale DCC en DIY.

Par exemple, avec un LMD18200, on envoie un état HIGH sur la broche Brake.

JPEG - 472.3 kio

Le signal qui est alors retourné par la locomotive durant ce cutout est très faible, quelques mA. Il nécessite donc une électronique appropriée pour l’amplifier. C’est le rôle du décodeur que nous vous proposons.

JPEG - 110.6 kio
Carte de détection du signal Railcom©

Et pour pouvoir en tirer parti, il faut « traiter » ce signal car il est codé de façon assez complexe. C’est l’objet du programme proposé qui délivre, in fine, l’adresse de la locomotive.

En somme, bien que Railcom© présente les contraintes citées précédemment, il reste à ce jour le système d’identification le plus répandu et le plus performant. Plusieurs des plus importants fabricants de décodeurs ont incorporé cette technologie dans leurs décodeurs : ESU, Zimo, Uhlenbrock et depuis peu on commence à voir des locomotives Trix équipées de Railcom©.

Mais le but de cet article n’est cependant pas de vous expliquer en détail le fonctionnement de Railcom© qui fait l’objet d’une littérature très abondante sur le net.

Nous allons donc parler tour à tour de la carte de décodage et de l’application que nous avons développée permettant d’obtenir le numéro de locomotive. Et comme il n’existe pas à notre connaissance de centrales DCC en DIY permettant de générer de cutout, nous vous proposerons une centrale DCC++ sur une base ESP32 que nous avons développé pour obtenir cette possibilité.

Enfin, pour ceux dont les décodeurs DCC ne sont pas compatibles Railcom©, nous vous présenterons des petites cartes électroniques de ESU et de LENZ permettant d’envoyer l’adresse de la locomotive pendant le cutout.

La carte de détection :

JPEG - 211.1 kio
Détecteurs Railcom© en attente d’être posés sur le réseau

En fait, on devrait plutôt dire les cartes car vous aurez besoin d’autant de cartes que le nombre de cantons pour lesquels vous souhaitez faire de la détection pour connaître l’adresse de la locomotive.

JPEG - 96.9 kio
Schéma d’implantation de principe

Le détecteur se monte en série entre l’alimentation en provenance de la centrale et les rails du canton. Les deux câbles d’entrée doivent donc être reliés à DCC In et la sortie vers les rails est raccordée sur DCC Out. Il faut bien sûr respecter la polarité sur toutes les cartes. Notez également que les deux rails doivent être isolés entre chaque canton.

Sur le bornier triple, il faut relier la masse commune avec le microcontrôleur et l’alimentation VCC. Cette alimentation doit avoir la même tension que celle du microcontrôleur, soit 5V pour un Arduino ou 3,3 V pour un ESP32. Ceci pour assurer que la tension du signal série délivrée soit compatible avec la tension de fonctionnement du microcontrôleur.

Le signal produit par le détecteur est un classique signal série 8N1 soit 8 bits de données, pas de parité de bit et 1 bit de stop. La vitesse de transmission est de 250 Kbps.

Ce signal peut donc être envoyé sur la broche RX de n’importe quel Arduino ou ESP. En pratique, il sera préférable d’utiliser des cartes possédant plusieurs entrées RX comme un Mega ou un ESP32. Avec un débit de 250 Kbs, il est peu probable que des bibliothèques comme SoftwareSerial supportent de lire plusieurs ports série sur des cartes de faible puissance comme le Uno ou le Nano.

https://www.arduino.cc/reference/en/language/functions/communication/serial/
https://docs.arduino.cc/learn/built-in-libraries/software-serial

Pour peu que vous ayez choisi le bon débit (250 Kbps), la fonction Serial.read() vous permettra d’afficher très simplement les caractères reçus sur le moniteur série de l’IDE Arduino par exemple.

Voici le schéma de la carte :

JPEG - 66.3 kio

la liste des composants :

JPEG - 129.8 kio

et les fichiers Gerber :

Télécharger les fichiers Gerber du détecteur Railcom

Les programmes pour décoder :

Lire les bits transmis par le décodeur n’est pas très compliqué. Néanmoins, à ce stade, on est loin encore de pouvoir obtenir l’adresse de notre locomotive. Le décodage des messages consiste en une succession d’opérations assez complexes. Sans entrer dans des détails, on peut tout de même signaler que le signal est dit 4/8 encoding. Cela veut dire que, pour s’assurer d’une transmission correcte, chaque trame envoyée doit avoir 4 bits à 1 et 4 bits à zéro.

Nous vous proposons ci-après trois programmes différents selon ce que vous souhaitez faire. Ces programmes sont adaptés à ESP32 mais doivent pouvoir être portés sur Arduino sans difficulté majeure.

Le programme railcom_detector_ESP32

Ce programme a un intérêt plutôt théorique puisqu’il se contente d’afficher l’adresse de la locomotive dans le moniteur série. Mais il servira de base pour tester puis adapter vos propres développements.

Télécharger le programme "railcom_detector_ESP32"

Voici ce qu’il faut principalement savoir sur ce code :

#ifndef ARDUINO_ARCH_ESP32
#error "Select an ESP32 board"
#endif

Empêche le fonctionnement sur une autre carte qu’un ESP32

#include <RingBuf.h> // https://github.com/Locoduino/RingBuffer
#define NB_ADDRESS_TO_COMPARE 100 // Nombre de valeurs à comparer pour obtenir l'adresse de la loco
RingBuf<uint16_t, NB_ADDRESS_TO_COMPARE> buffer;

Le programme compare l’adresse décodée avec, ici, les 100 dernières adresses lues. Ce qui limite les éventuelles lectures défectueuses. Pour celà, il utilise la bibliothèque Locoduino RingBuf disponible sur le github :

https://github.com/Locoduino/RingBuffer

const byte railComRX = 14; // GPIO14 connecté à  RailCom Detector RX
const byte railComTX = 17; // GPIO17 non utilisée mais doit être déclarée

Vous pouvez choisir la broche RX pour la réception des données série. Ici, la broche 14 est sélectionnée. La sortie TX n’est pas utile ici mais doit cependant être renseignée.

TaskHandle_t ReceiveData;                         
TaskHandle_t ParseData; 

Le programme repose sur la gestion de tâches en parallèle disponible avec freeRTOS. Il va lancer ces deux tâches dans le setup. Ce n’est pas l’objet de cet article de traiter de la gestion de tâches mais ce point pourra être abordé dans le fil sur le forum pour ceux que cela intéresserait.

void setup()
{
  Serial.begin(115200);
...
  Serial1.begin(250000, SERIAL_8N1, railComRX, railComTX); // Port série pour la réception des données (250k bauds)

Dans le setup, on va ouvrir une connexion Serial "classique" pour l’affichage dans la fenêtre moniteur de l’IDE à 115 200 bps.

On va également établir une seconde connexion sur le port Serial1 à 250 000 bps, 8 bits de données, aucun ( N ) bit de parité et un 1 bit d’arrêt sur les broches railComRX, railComTX déclarées au début du programme.

  uint16_t x = 0;
  for (uint8_t i = 0; i < NB_ADDRESS_TO_COMPARE; i++) // On place des zéros dans le buffer de comparaison
    buffer.push(x);
  xQueue = xQueueCreate(QUEUE_SIZE, sizeof(uint8_t)); // Création de la file pour les échanges de data entre les 2 tâches
  xTaskCreatePinnedToCore(receiveData, "ReceiveData", 2048, NULL, 10, NULL, 0); // Création de la tâches pour la réception
  xTaskCreatePinnedToCore(parseData, "ParseData", 2048, NULL, 10, NULL, 1); // Création de la tâches pour le traitement

  while (true)
    ;
}

void loop(){}

On crée une "queue" pour les échanges de données entre les 2 tâches. Puis on lance les deux tâches "receiveData" et "parseData".

La fonction xTaskCreatePinnedToCore nous permet de lancer l’un des traitements sur le cœur 0 et le second traitement sur le cœur 1 ce qui optimise le potentiel de l’ESP32.

Pour minimiser les erreurs de transmission, la norme NMRA prévoit que les données envoyées par les décodeurs sont codées selon un principe dit 4/8 encoding. Cela veut dire que chaque octet transmis doit toujours avoir 4 bits à 0 et 4 bits à 1. Dans le cas contraire, il doit être ignoré.

Selon ce principe, seules 70 combinaisons de bits sont possibles. Le tableau ci-dessous, publié par le NMRA, donne la valeur qui doit être retenue pour chaque combinaison de bits.

NMRA Table 2 : 4/8 Encoding
NMRA Table 2 : 4/8 Encoding

Par exemple 0x0B (ou 11 en décimal) pour l’octet envoyé de valeur binaire 10010110.

Pour le programme, ces différents bits sont stockés dans un tableau decodeArray[]

const byte decodeArray[] = {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 64, 255, 255, 255, 255, 255, 255, 255, 51, 255, 255, 255, 52,
                        255, 53, 54, 255, 255, 255, 255, 255, 255, 255, 255, 58, 255, 255, 255, 59, 255, 60, 55, 255, 255, 255, 255, 63, 255, 61, 56, 255, 255, 62,
                        57, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 36, 255, 255, 255, 35, 255, 34, 33, 255, 255, 255, 255, 31, 255, 30, 32, 255,
                        255, 29, 28, 255, 27, 255, 255, 255, 255, 255, 255, 25, 255, 24, 26, 255, 255, 23, 22, 255, 21, 255, 255, 255, 255, 37, 20, 255, 19, 255, 255,
                        255, 50, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 14, 255, 13, 12, 255, 255, 255, 255, 10, 255,
                        9, 11, 255, 255, 8, 7, 255, 6, 255, 255, 255, 255, 255, 255, 4, 255, 3, 5, 255, 255, 2, 1, 255, 0, 255, 255, 255, 255, 15, 16, 255, 17, 255, 255, 255,
                        18, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 43, 48, 255, 255, 42, 47, 255, 49, 255, 255, 255, 255, 41, 46, 255, 45, 255, 255,
                        255, 44, 255, 255, 255, 255, 255, 255, 255, 255, 66, 40, 255, 39, 255, 255, 255, 38, 255, 255, 255, 255, 255, 255, 255, 65, 255, 255, 255, 255,
                        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};

Dans la fonction parseDatas();, on va chercher dans ce tableau la valeur de l’octet reçu.

C’est la fonction lambda placée dans la variable check_4_8_code qui permet de trouver dans le tableau la valeur de l’octet reçu. Si inByte est trouvée dans le tableau, la fonction retourne true et la variable inByte prend la valeur de decodeArray[inByte], la valeur décodée de l’octet !

 auto check_4_8_code = [&]() -> bool
  {
    if(decodeArray[inByte] < 255) // Si la valeur de inByte est une valeur "valide"
    {
      inByte = decodeArray[inByte];
      return true;
    }
    return false;
  };

Le programme railcom_display

Ce programme permet d’afficher l’adresse de la locomotive sur un afficheur 4 X 7 segments. Il reprend l’essentiel du programme précédent.

Ca peut être intéressant pour afficher rapidement l’adresse de la locomotive en la posant simplement sur les rails.

Téléchargement des fichiers Gerber "Railcom_display"
Télécharger le programme "Railcom_display"

Le programme railcom_detector_inClass

Ce dernier programme vous intéresse si vous souhaitez récupérer facilement et simplement l’adresse de la locomotive pour pouvoir l’utiliser par exemple dans un programme plus global ou l’envoyer sur un bus de communication, CAN, S88 ou Loconet, ou encore en TCP.

Cette classe Railcom va se comporter comme une bibliothèque qui retourne l’adresse DCC en invoquant simplement une méthode.

Il vous suffira dans votre programme principal, d’inclure le fichier d’entête « Railcom.h », de spécifier les broches de réception du signal série, ici la broche 33. Notez que la broche 17 est déclarée mais ne sera pas utilisée car il n’y a aucune communication série du microcontrôleur vers le détecteur.

Enfin, vous créez une instance de la classe Railcom qui peut avoir pour nom railcom et vous passerez en paramètres RAILCOM_RX et RAILCOM_TX.

C’est absolument tout pour les paramétrages.

#include "Railcom.h" // Inclure le fichier d’entête

#define RAILCOM_RX                    GPIO_NUM_33
#define RAILCOM_TX                    GPIO_NUM_17

Railcom railcom(RAILCOM_TX, RAILCOM_RX);

Il est possible de créer jusqu’à trois instances ce qui veut dire que trois lecteurs Railcom différents peuvent-être reliés à un même ESP32.

Voici un exemple :

Railcom railcom_0(GPIO_NUM_3, GPIO_NUM_1);   // Instance de la classe Railcom
Railcom railcom_1(GPIO_NUM_16, GPIO_NUM_17); // Instance de la classe Railcom
Railcom railcom_2(GPIO_NUM_13, GPIO_NUM_14); // Instance de la classe Railcom

Vous penserez alors à régler le débit de Serial à 250 000 bauds :
Serial.begin (250000);

Dans votre programme principal vous pourrez à tout moment connaître l’adresse de la loco en appelant la méthode : Railcom::address();

Par exemple, uint16_t addressLoco = railcom.address(); où la variable de type uint16_t (ou unsigned int) va recevoir l’adresse de la loco.

Il s’agit d’une variable sur 2 octets (16 bits) capable de recevoir une adresse longue.

Lorsqu’il n’y a pas de locomotive détectée, l’adresse retournée est 0 (zéro).

Pour plusieurs instances, l’affichage nécessitera de modifier le code comme ceci :

  Serial.printf("Adresse loco 0 = %d\n", railcom_0.address());
  Serial.printf("Adresse loco 1 = %d\n", railcom_1.address());
  Serial.printf("Adresse loco 2 = %d\n", railcom_2.address());

Voici un exemple qui montre que dans le fichier principal (.ino) le code est très simple d’utilisation :

/*
   Programme de lecture, de decodage et d'affichage des messages Railcom ©
   qui retourne l'adresse d'un decodeur (adresse courte ou longue) sur le port serie (115200 bauds)

   Fonctionne exclusivement sur ESP32
   © christophe bobille - locoduino.org

   lib_deps = locoduino/RingBuffer@^1.0.3 / https://github.com/Locoduino/RingBuffer

*/

#ifndef ARDUINO_ARCH_ESP32
#error "Select an ESP32 board"
#endif

#include <Arduino.h>
#include "Railcom.h"

#define VERSION "v 1.0"
#define PROJECT "Railcom Detector ESP32 (freeRTOS in class)"

#define RAILCOM_RX                    GPIO_NUM_33  // Broche recevant le RX du detecteur Railcom
#define RAILCOM_TX                    GPIO_NUM_17  // Broche non utilisee mais declaree 

Railcom railcom(RAILCOM_RX, RAILCOM_TX); // Instance de la classe Railcom

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


void loop ()
{
  // Affiche toutes les secondes dans le moniteur serie l'adresse de la locomotive
  Serial.printf("Adresse loco = %d\n", railcom.address());
  delay(1000);
}

Téléchargement du code complet pour Railcom_detector_inClass

Télécharger le programme "railcom_detector_inClass"

Une centrale DCC qui répond au protocole de messagerie de DCC++ et génère le cutout.

A notre connaissance, il n’existe aucune centrale DCC en DIY capable de générer le cutout. Cela à souvent été évoqué pour DCC-Ex mais ne semble pas implanté à ce jour. Aussi, nous vous proposons une centrale sur une base d’ESP32 qui peut être reliée à un logiciel de pilotage par le port USB ou en WiFi comme la plupart des centrales. Elle utilise le protocole de messagerie de DCC++, cela veut dire qu’elle est totalement compatible avec des logiciels comme JMRI par exemple.

Vous n’avez pas besoin de matériel spécifique pour réaliser cette centrale hormis un ESP32, un LMD18200, un capteur pour la mesure du courant et des câbles de liaison.

La carte que nous vous présentons ici est une carte universelle conçue pour recevoir des projets ESP32 « génériques ». La communication CAN y est implantée en utilisant un MCP2562 mais cela est optionnel.

La configuration de l’application est assez simple regroupée dans le fichier Config.h

Ligne 16, pour utiliser la centrale avec le port série, on placera 0 comme valeur pour COMM_INTERFACE

// Define communication interface : 0 = Serial Port, 1 = Wifi
#define COMM_INTERFACE 0 // Select mode

Pour l’utilisation en WiFi, on choisira 1

// Define communication interface : 0 = Serial Port, 1 = Wifi
#define COMM_INTERFACE 1 // Select mode

Il faudra alors régler les paramètres de connexion WiFi selon les spécificités de votre installation domestique. Le SSID et le mot de passe d’accès au réseau WiFi et peut-être l’adresse IP qui peut éventuellement être du type 192.168.0.200 comme l’adresse de la « Gateway » qui peut-être 192.168.1.1 mais aussi 192.168.0.1 ou encore 192.168.1.254 !!!

Par contre, vous n’aurez pas besoin de modifier le port réglé sur 2560.

Cette centrale est aussi conçue pour recevoir des commandes par le bus CAN, si vous souhaitez activer ce bus, il vous faut « décommenter » la ligne 36 :

/* ----- CAN ----------------------*/
#define CAN_INTERFACE
#define CAN_RX GPIO_NUM_22
#define CAN_TX GPIO_NUM_23
#define CAN_BITRATE 1000UL * 1000UL // 1 Mb/s

Pour la carte moteur, il est nécessaire que celle-ci dispose de la fonction BRAKE qui, quand elle est activée, met les deux rails en court-circuit, condition pour générer le Railcom. La carte LMD18200 que l’on ne présente plus sur Locoduino réalise cela parfaitement avec une entrée spécifique.

Du point de vue des câblages, la PWM sera reliée à la broche 12 de l’ESP32, la DIR à la broche 13 et le BRAKE à la broche 14.

Pour la mesure du courant et l’arrêt en cas de court-circuit, l’entrée se fera sur la broche 35.

Cela correspond aux lignes 7 à 10 :

#define PIN_PWM GPIO_NUM_12   // ENABLE (PWM)
#define PIN_DIR GPIO_NUM_13   // SIGNAL (DIR)
#define PIN_BRAKE GPIO_NUM_14 // CUTOUT (BRAKE)
#define CURRENT_MONITOR_PIN_MAIN GPIO_NUM_36 // Mesure de courant

Dans notre montage, nous utilisons un MAX471, il nous en restait en stock…

Comme vous pouvez le constater sur cette vidéo, cette centrale est simple à réaliser. Il suffit d’un LMD18200 câblé comme indiqué ci-dessus et d’un composant pour la mesure de courant comme le MAX471 qui est celui que j’utilise dans ce montage. La broche de signal du module étant reliée à la broche 35 de l’ESP32.

La carte LaBox qui est en cours de développement par quelques membres de Locoduino convient parfaitement et intègre sur le même PCB tous les composants, LMD18200, lecture de courant, ESP32, régulateur de tension...

LaBox de Locoduino
La carte LaBox dans une version "minimaliste"

Le programme DCC qui génére le cutout nécessaire pour Railcom® est téléchargeable sur le GitHub de Locoduino

Et si mes décodeurs (certains de mes décodeurs) n’ont pas l’option RailCom© ?

Bonne nouvelle, TRIX qui était l’un de acteurs majeurs à ne pas implanter Railcom©, produit maintenant des modèles compatibles. Tous ne sont pas concernés mais c’est par exemple le cas pour la dernière 241 A 004 EST ! Je ne vous cache pas le plaisir que j’ai eu de lire la première fois au travers de Railcom© l’adresse de la 241 A 004 EST.

Pour les autres modèles et autres marques, ESU comme LENZ proposent dans ce cas une carte électronique de très petit format que l’on place (en général) sous la locomotive, que l’on achète en général par 5 unités pour environ 60 à 80€ (soit 12 à 15€ l’unité).

Chez LENZ, le nom est LRC100 et la référence LE15105
Chez ESU, la référence est 54680

Ce ne sont pas des produits courants (souvent en rupture de stock) mais en cherchant un peu sur le net on arrive à en trouver.

La carte Lenz dispose d’un fil rouge et d’un fil noir, la carte ESU dispose en plus de deux fils marrons qui sont à dessouder.

Pour éviter les risques de court-circuit, il est conseillé de recouvrir la carte d’une gaine thermo rétractable.

La carte se programme en parallèle du décodeur de la locomotive à laquelle elle sera associée. Ainsi, quand vous envoyer une commande de modification/confirmation d’adresse au décodeur, la carte est automatiquement mise à jour.

Ne pas programmer la carte avec un décodeur qui serait lui-même déjà équipé de Railcom© au risque de créer des dommages irréversibles au décodeur.

Avec votre centrale ou avec un logiciel comme JMRI dans notre exemple, entrez l’adresse souhaitée pour la locomotive qui sera la même copiée dans la carte.

Nous avons ici choisi l’adresse longue 4251.

Avant de faire le montage dans la locomotive, vous pouvez tester « à vide » que tout fonctionne bien.

La carte est ensuite installée dans la locomotive ou sous le châssis, ou encore dans un wagon suiveur (tender).

Vous devez simplement relier les fils d’alimentation au courant des rails en soudant par exemple chacun des fils aux patins frotteurs de prise de courant.

Une fois tout terminé, une dernière vérification ne sera pas inutile.

Pour conclure :

Voici donc une proposition assez globale pour implanter la détection Railcom© sur votre réseau.

Elle est sans doute incomplète et c’est la raison pour laquelle nous avons ouvert un fil sur le forum pour permettre les échanges, corrections, propositions et ajouts. N’hésitez surtout pas à intervenir.
https://forum.locoduino.org/index.php?topic=1606