LOCODUINO

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

mardi 19 mars 2024

Visiteurs connectés : 29

Un moniteur de signaux DCC

Comment espionner les commandes émises par la centrale DCC

.
Par : Dominique

DIFFICULTÉ :

Dans l’article « L’Arduino et le système de commande numérique DCC », nous avons vu à quoi ressemble la norme DCC.

Dans l’article « Comment piloter trains et accessoires en DCC avec un Arduino (2) », nous avons étudié la bibliothèque CmdrArduino qui se charge de générer les signaux DCC pour nous en tâche de fond.

Dans cet article nous allons utiliser une autre bibliothèque pour visualiser sur notre ordinateur les trames DCC qui sont envoyées aux rails, pour vérifier le bon fonctionnement de notre projet.

<

Cette bibliothèque est celle de Minabay qui est aussi utilisée dans l’article Un décodeur d’accessoires universel (1).

Mais cette fois-ci, la bibliothèque va observer le signal DCC et afficher les paquets vus toutes les 2 secondes.

La trace ci-dessous correspond à ce que ma centrale "va-et-vient" envoie en mode automatique. On peut voir l’adresse 4 de la loco (premier octet 00000100) et les 2 commandes :

  • la lumière (fonction groupe 1) : 10010000 (allumée)
  • la vitesse : advanced operation instruction 00111111 + octet de vitesse (bits 0..6) et direction (bit 7)

Le dernier octet est le code de contrôle qui ne nous intéresse pas ici.

J’ai ajouté la signification des paquets à droite de la liste.

Premières constatations :

  • On compte bien entre 130 et 140 paquets par seconde comme calculé dans la Présentation de la norme DCC. Ici on a 272 paquets en 2 secondes.
  • Environ 90% des paquets sont des Idle 11111111 00000000 11111111
  • La bibliothèque répète bien automatiquement les paquets de vitesse et lumière, environ 4 fois par seconde

Réalisation matérielle du moniteur

J’ai utilisé un simple Nano complété par un petit circuit d’interface entre les rails (tension +/- 15 volts, insupportable pour l’Arduino) et la broche 2 programmée en entrée digitale sous interruption.

Ce circuit est celui du site Minabay : j’y ai ajouté un condensateur de 270 pF (une valeur inférieure, jusqu’à 30-50 pF peut convenir également).

Voici la liste des composants nécessaires :

  • un Arduino (j’ai choisi un Nano mais n’importe lequel doit convenir)
  • 2 résistances de 10kΩ
  • 1 résistance de 1kΩ
  • 1 Diode 1N4148
  • 1 condensateur de 270 picoFarad
  • 1 Opto-coupleur 6N137 (il en faut un rapide quand même)
  • 1 plaque d’essai pour assembler les composants

Plutôt que d’utiliser un Uno avec son bornier femelle, j’ai préféré le Nano sans bornier. J’ai inséré des barrettes à souder dans les trous à la place des barrettes à picots habituels : de cette façon les fils à raccorder au Nano sont soudés sur les barrettes ce qui procure un meilleur contact qu’un ensemble de prises mâle et femelle.

De ce fait, les barrettes du Nano ne sont soudées qu’aux 4 coins ce qui permet de récupérer l’Arduino si le projet n’est plus utilisé plus tard.

A l’arrière de la plaque d’essai, le nombre de soudures est minimal et on ne prend pas de risque da court-circuits.

Le circuit terminé ne m’a pris qu’une petite heure. Le connecteur 4 points permet de sortir une liaison I2C vers un afficheur LCD 4 lignes de 20 caractères qui devrait me permettre de me passer du PC/Mac, à condition d’ajouter une petite alimentation 5V. Pour la liaison I2C, ne pas oublier les 2 resistances de rappel au 5V, de 4,7kΩ chacune sur SDA et SDL respectivement.

Le logiciel qui suit ne tient pas compte de cette possibilité.

La mise en boite est un simple sandwich entre 2 planchettes de contreplaqué.

Le logiciel

Après installation de la bibliothèque, on ouvre simplement l’exemple DCC_Monitor qui se trouve dans le dossier DCC_Decoder.

// DCC_Monitor
// Based on DCC_Decoder Library Version 4
// Dump of DCC packets every 2 seconds
// Arduino Pin 2 is the DCC input. It drives interrupt 0.

#include <DCC_Decoder.h>

////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
//
// Defines and structures
//
#define kDCC_INTERRUPT    0

typedef struct
{
    int count;
    byte validBytes;
    byte data[6];
} DCCPacket;

////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
//
// The dcc decoder object and global data
//
int gPacketCount = 0;
int gIdlePacketCount = 0;
int gLongestPreamble = 0;

DCCPacket gPackets[25];

static unsigned long lastMillis = millis();
    
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
//
// Packet handlers
//

// ALL packets are sent to the RawPacket handler. Returning true indicates
// that packet was handled. DCC library starts watching for 
// next preamble. Returning false and library continue parsing packet
// and finds another handler to call.
boolean RawPacket_Handler(byte byteCount, byte* packetBytes)
{
    // Bump global packet count
    ++gPacketCount;
    
    int thisPreamble = DCC.LastPreambleBitCount();
    if( thisPreamble > gLongestPreamble )
    {
        gLongestPreamble = thisPreamble;
    }
    
    // Walk table and look for a matching packet
    for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
    {
        if( gPackets[i].validBytes )
        {
            // Not an empty slot. Does this slot match this packet?
            // If so, bump count.
            if( gPackets[i].validBytes==byteCount )
            {
                char isPacket = true;
                for( int j=0; j<byteCount; j++)
                {
                    if( gPackets[i].data[j] != packetBytes[j] )
                    {
                        isPacket = false;
                        break;
                    } 
                }
                if( isPacket )
                {
                   gPackets[i].count++;
                   return false;
                }
            }
        }else{
                // Empty slot, just copy over data
            gPackets[i].count++;
            gPackets[i].validBytes = byteCount;
            for( int j=0; j<byteCount; j++)
            {
                gPackets[i].data[j] = packetBytes[j];
            }
            return false;
        }
    }    
    
    return false;
}

// Idle packets are sent here (unless handled in rawpacket handler). 
void IdlePacket_Handler(byte byteCount, byte* packetBytes)
{
    ++gIdlePacketCount;
}

////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
//
// Setup
//
void setup() 
{ 
    Serial.begin(9600);
    
    DCC.SetRawPacketHandler(RawPacket_Handler);   
    DCC.SetIdlePacketHandler(IdlePacket_Handler);
            
    DCC.SetupMonitor( kDCC_INTERRUPT );   
}

////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
void DumpAndResetTable()
{
    char buffer60Bytes[60];
    
    Serial.print("Total Packet Count: ");
    Serial.println(gPacketCount, DEC);
    
    Serial.print("Idle Packet Count:  ");
    Serial.println(gIdlePacketCount, DEC);
        
    Serial.print("Longest Preamble:  ");
    Serial.println(gLongestPreamble, DEC);
    
    Serial.println("Count    Packet_Data");
    for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
    {
        if( gPackets[i].validBytes > 0 )
        {
            Serial.print(gPackets[i].count, DEC);
            if( gPackets[i].count < 10 )
            {
                Serial.print("        ");
            }else{
                if( gPackets[i].count < 100 )
                {
                    Serial.print("       ");
                }else{
                    Serial.print("      ");
                }
            }
            Serial.println(
            	DCC.MakePacketString(
                    buffer60Bytes,
            	    gPackets[i].validBytes,
            	    &gPackets[i].data[0]
                )
            );
        }
        gPackets[i].validBytes = 0;
        gPackets[i].count = 0;
    }
    Serial.println("============================================");
    
    gPacketCount = 0;
    gIdlePacketCount = 0;
    gLongestPreamble = 0;
}

////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
//
// Main loop
//
void loop()
{
    DCC.loop();
    
    if( millis()-lastMillis > 1000 )
    {
        DumpAndResetTable();
        lastMillis = millis();
    }
}

Une fois le programme compilé et installé dans l’Arduino, on relie l’interface aux rails, puis on lance le moniteur de l’environnement Arduino ou tout autre terminal de son choix.

J’ai testé le programme sur différents Arduino, y compris le Due.

Version LCD I2C

On trouve facilement, pour 10€ environ, des afficheurs 4 lignes de 20 caractères avec une interface I2C.

J’ai donc modifié l’exemple de Minabay ci-dessus pour obtenir un affichage décodé selon les besoins de mon projet (centrale va-et-vient).

Toutes les 2 secondes, l’afficheur présente :

  • Sur la ligne 1 : le nombre de paquets IDLE et le nombre de paquets de données valides ;
  • Sur les lignes suivantes : le nombre de chaque type de paquet de données et leur signification.

Par exemple :
1@4>3 signifie 1 paquet d’adresse DCC 4, direction avant et vitesse 3
4@4>5 signifie 4 paquet d’adresse DCC 4, direction avant et vitesse 5
15@4L0 signifie 15 paquets d’adresse DCC 4, lumière éteinte
4@4>7 signifie 4 paquet d’adresse DCC 4, direction avant et vitesse 7

On voit bien l’accélération de 3 à 11 crans DCC pendant l’interval de 2 secondes.

Le logiciel de cette version est ici :

/*  DCC_Monitor for LCD 4x20

  Based on DCC_Decoder Library Version 4 from http://www.mynabay.com/arduino
  Display DCC packets counts every 2 seconds (IDLE should be around 50%)
  Arduino Pin 2 is the DCC input. It drives interrupt 0.
  Arduino Pin A4 is SDA, Pin A5 is SLC (even on Nano !).
  To connect an LCD 20x4 display via I2C

  Don't forget I2C pull-up resistors (4,7K each) connected to + 5V
  Compilation on Arduino 1.5.x necessitate a minor change in the DCC_Decoder library:
  Replace "prog_char*" type by "char*"
  
  Display 1st line : Idle paquets count, valid data paquets count
  Display next lines : for each data type :
  - paquet count, DCC address, 
    - speed paquet : direction (< or >), speed value (0..127)
    - light function paquet (FL) : L0 (off) or L1 (on)

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation.
  
  Copyright (c) 2014 Dominique Bultez. */

#define VERSION "1.0b3 Oct 14"
#define D_USB // comment this line to cancel display to the computer's terminal

#include <DCC_Decoder.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 20 chars and 4 line display

////////////////////////////////////////////////////////////////////////////////////////
//
// Defines and structures
//
#define kDCC_INTERRUPT            0

typedef struct
{
    int count;
    byte validBytes;
    byte data[6];
} DCCPacket;

////////////////////////////////////////////////////////////////////////////////////////
//
// The dcc decoder object and global data
//
int gPacketCount = 0;
int gIdlePacketCount = 0;
int gLongestPreamble = 0;

DCCPacket gPackets[25];

static unsigned long lastMillis = millis();
    
////////////////////////////////////////////////////////////////////////////////////////
//
// Packet handlers
//

// ALL packets are sent to the RawPacket handler. Returning true indicates
// that packet was handled. DCC library starts watching for 
// next preamble. Returning false and library continue parsing packet
// and finds another handler to call.
boolean RawPacket_Handler(byte byteCount, byte* packetBytes)
{
        // Bump global packet count
    ++gPacketCount;
    
    int thisPreamble = DCC.LastPreambleBitCount();
    if( thisPreamble > gLongestPreamble )
    {
        gLongestPreamble = thisPreamble;
    }
    
        // Walk table and look for a matching packet
    for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
    {
        if( gPackets[i].validBytes )
        {
                // Not an empty slot. Does this slot match this packet? If so, bump count.
            if( gPackets[i].validBytes==byteCount )
            {
                char isPacket = true;
                for( int j=0; j<byteCount; j++)
                {
                    if( gPackets[i].data[j] != packetBytes[j] )
                    {
                        isPacket = false;
                        break;
                    } 
                }
                if( isPacket )
                {
                   gPackets[i].count++;
                   return false;
                }
            }
        }else{
                // Empty slot, just copy over data
            gPackets[i].count++;
            gPackets[i].validBytes = byteCount;
            for( int j=0; j<byteCount; j++)
            {
                gPackets[i].data[j] = packetBytes[j];
            }
            return false;
        }
    }    
    
    return false;
}

// Idle packets are sent here (unless handled in rawpacket handler). 
void IdlePacket_Handler(byte byteCount, byte* packetBytes)
{
    ++gIdlePacketCount;
}

////////////////////////////////////////////////////////////////////////////////////////
//
// Setup
//
void setup() 
{ 
   Serial.begin(115200);
   lcd.init();                      // initialize the lcd 
   lcd.backlight();

   lcd.print("DCC monitor Version ");
   lcd.print(VERSION);
   delay(1000);
   //lcd.clear();
   
   DCC.SetRawPacketHandler(RawPacket_Handler);   
   DCC.SetIdlePacketHandler(IdlePacket_Handler);
            
   DCC.SetupMonitor( kDCC_INTERRUPT );   
}

////////////////////////////////////////////////////////////////////////////////////////
void DumpAndResetTable()
{
    char buffer60Bytes[60];
    
    #ifdef D_USB
    Serial.print("Total Packet Count: ");
    Serial.println(gPacketCount, DEC);
    #endif
    
    lcd.clear();
    //lcd.setCursor(0, 0);    // 1e ligne
    lcd.print("Idle:");
    lcd.print(gIdlePacketCount, DEC);

    #ifdef D_USB
    Serial.print("Idle Packet Count:  ");
    Serial.println(gIdlePacketCount, DEC);
    #endif
    
    lcd.print(" Data:");
    lcd.print(gPacketCount - gIdlePacketCount, DEC);
    lcd.print("  ");    
    
    #ifdef D_USB
    Serial.print("Longest Preamble:  ");
    Serial.println(gLongestPreamble, DEC);    
    Serial.println("Count    Packet_Data");
    #endif
    
    lcd.setCursor(0, 1);    // 2e ligne
    int j=0;
    for( int i=0; i<(int)(sizeof(gPackets)/sizeof(gPackets[0])); ++i )
    {
        if( gPackets[i].validBytes > 0 )
        {
            if (gPackets[i].count != gIdlePacketCount)
            {
              #ifdef D_USB
              Serial.print(gPackets[i].count, DEC);                                
              if( gPackets[i].count < 10 )
              {
                Serial.print("        ");
              }else{
                if( gPackets[i].count < 100 )
                {
                    Serial.print("       ");
                }else{
                    Serial.print("      ");
                }
              }
              Serial.println(
                  DCC.MakePacketString(
                      buffer60Bytes,
                      gPackets[i].validBytes,
                      &gPackets[i].data[0]
                  )
              );
              #endif
              
              if (gPackets[i].data[0] != 255)
              {
                lcd.print(gPackets[i].count, DEC);
                lcd.print("@");
                lcd.print(gPackets[i].data[0], DEC);
                switch (gPackets[i].data[1]) 
                {
                  case 0x3F:  // Advanced Operation Instruction : speed & direction
                  if (gPackets[i].data[2] > 127)
                  {
                    lcd.print(">");
                    lcd.print(gPackets[i].data[2] - 128);
                  }else{
                    lcd.print("<");
                    lcd.print(gPackets[i].data[2]);
                  }
                  break;
                  case 0x90:  // Fonction Group One FL on
                  lcd.print("L1");
                  break;
                  case 0x80:  // Fonction Group One FL off
                  lcd.print("L0");
                  break;
                  default:
                  lcd.print(gPackets[i].data[1], HEX);
                  break;                  
                }
                j++;
                if (j>5) j=0;
                switch (j)
                {
                  case 0:
                  lcd.setCursor(0, 1);    // 2e ligne
                  break;
                  case 1:
                  lcd.setCursor(10, 1);    // 2e ligne
                  break;
                  case 2:
                  lcd.setCursor(0, 2);    // 2e ligne
                  break;
                  case 3:
                  lcd.setCursor(10, 2);    // 2e ligne
                  break;
                  case 4:
                  lcd.setCursor(0, 3);    // 2e ligne
                  break;
                  case 5:
                  lcd.setCursor(10, 3);    // 2e ligne
                  break;
                }
              }
            }
        }
        gPackets[i].validBytes = 0;
        gPackets[i].count = 0;
    }
    #ifdef D_USB
    Serial.println("============================================");
    #endif
    
    gPacketCount = 0;
    gIdlePacketCount = 0;
    gLongestPreamble = 0;
}

////////////////////////////////////////////////////////////////////////////////////////
//
// Main loop
//
void loop()
{
    DCC.loop();
    
    if( millis()-lastMillis > 2000 )
    {
        DumpAndResetTable();
        lastMillis = millis();
    }
}

Le code peut être téléchargé ici :

La bibliothèque peut être téléchargée ici :

Dernièrement j’ai ajouté un circuit d’alimentation à partir des rails : il contient un pont redresseur reliée au connecteur des rails et suivi d’une capacité de 22uF, puis d’un régulateur 7805 et d’une capacité de 100uF. La sortie est reliée directement au 5V de l’Arduino.

62 Messages

Réagissez à « Un moniteur de signaux DCC »

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)

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

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

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

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

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)

Mon premier décodeur pour les aiguillages DCC

La rétro-signalisation sur Arduino

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)