Auteur Sujet: DCC++ BaseStation  (Lu 21444 fois)

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #30 le: août 19, 2016, 12:56:04 pm »
Bonjour à tous,

Nous avons 3 fils qui parlent de DCC++ :
Je propose que le coeur des discussions sur DCC++ se situe dans CE fil et que les 2 autres restent consacrés à ses applications.

Ce qui me gène aussi c'est que ce fil soit dans le sujet "bibliothèques". Je préfèrerai le voir dans la discussion générale soit dans "Bus DCC" soit en sujet à part entière car il présente pas mal de potentiel (j'y consacrerai une réflexion bientôt). Est-ce possible ?
« Modifié: août 20, 2016, 08:04:09 pm par Dominique »

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #31 le: août 20, 2016, 07:58:52 pm »
Présentation de DCC++

Le projet DCC++ a pour auteur un américain : Gregg E. Berman, qui y a travaillé depuis 2013 et jusqu'en 2016, avec la livraison de sa dernière version sur GitHub : https://github.com/DccPlusPlus/BaseStation.

C'est un très bon programmeur C++ à qui je rend hommage pour cet excellent projet (s'il lit ce fil, ce qui ne manquera pas d'arriver).

Ce projet comprend deux parties :
- une centrale DCC dite "BaseStation" qui se charge de la génération du courant de traction selon la norme NMRA DCC. Cette centrale est construite sur la base d'un Arduino UNO ou d'un MEGA, augmenté d'une carte "Motor Shield" pour la partie puissance.
- une application "Processing" dite "Controller" qui se charge du pilotage de la "BaseStation" soit via l'interface USB entre le PC et l'Arduino, soit via l'interface TCP/IP (ethernet ou wifi sur Mega seulement).



Présentée comme cela, la BaseStation semble ne pas pouvoir se passer du Controller, comme une centrale SPROG a besoin d'un logiciel comme JMRI ou RocRail.
En réalité, d'une part, c'est vrai et il est possible de piloter la BaseStation par JMRI à partir de la version 4.1.16 ou RocRail (récent avec la bibliothèque dccpp) qui contiennent donc une interface spécifique pour DCC++. D'autre part ce n'est pas vrai, la centrale peut fonctionner de façon autonome à condition d'en modifier la programmation, ce à quoi je m'emploie obstinément.

Car Gregg a réalisé un logiciel Open Source admirable en C++ avec force commentaires et explications, et un découpage des fonctions en plusieurs onglets qui rendent la compréhension du logiciel beaucoup plus facile.

Je n'ai pas pu résisté à la tentation et je vais avoir bientôt plusieurs versions personnelles en test pour mon réseau et celui du club, que je décrirai bientôt sur Locoduino (j'en imagine qui salivent déjà ….)

Pour ceux qui voudraient se documenter sur le Web, en langue américaine, voici les liens utiles qui vous diront tout :


Il faut reconnaitre que Gregg à voulu particulièrement respecter la norme NMRA pour que sa centrale fournisse les meilleures caractéristiques :
  • adressage des locos sur 2 ou 4 octets (de 1 à 10293)
  • vitesses sur 128 crans
  • conduite simultanée de plusieurs locos (un dizaine)
  • commande des fonctions des locos (F0 à F28)
  • gestion des accessoires d'adresse 0 à 2048
  • programmation sur la voie principale, avec l'adresse DCC et sans confirmation de la loco (écriture de CVs et de bits dans les CVs)
  • programmation sur la voie de programmation sans adresse DCC mais avec confirmation de la (seule) loco (lecture et écriture de CVs et de bits dans les CVs).

La centrale peut donc se connecter à la voie principale ET à la voie de programmation. C'est une configuration qui ressemble assez bien à la SPROG (sans vouloir les comparer).

J'ai rapidement découvert qu'on pouvait ajouter d'autres fonctions facilement comme expliqué dans le fil http://forum.locoduino.org/index.php?topic=46.msg1853#msg1853 : DCC++ est plein de promesses  :P

Dans la suite de cet exposé, je vais détailler d'abord le logiciel de la BaseStation en montrant comment on pourrait tirer partie des différents mécanismes qui le composent.

Il s'agit du sketch DCpp_Uno.

Ce sketch est accompagné de 17 onglets, certains toujours nécessaires, d'autres optionnels (ce qui ne veut pas dire que si on les enlève, ça va encore compiler, mais l'adaptation est facile à faire) :

D'abord les onglets nécessaires :
  • DCCpp_Uno.h contient les choix de configuration matériels principaux : le type d'Arduino (Uno ou Mega, -> mais en changeant quelques mots-clé, on peut faire tourner le logiciel aussi sur un Nano ou un Pro Mini s'ils utilisent tous un ATMega328 à 16Mhz, comme le démontre le fil http://forum.locoduino.org/index.php?topic=203), le type de motor shield (Arduino ou Polulu, -> qui peut facilement être détourné vers un LMD18200 par exemple), et le type d'interface de communication (Série/USB ou Ethernet), le  numéro de version (1.2.1+) et le mode diagnostic qui affiche les registres (non activé par défaut) qu'on va détailler plus loin.
  • Comm.h qui définit 3 types de cartes Ethernet si le choix "Ethernet" est retenu. On n'est plus très loin de la Z21  :o :o
  • Config.h qui contient les réglages de la configuration choisie parmi celles possibles : le motor shield, le nombre maximum de registres (dont on va parler plus loin car c'est l'élément clé de DCC++), l'interface de communication, l'adresse IP, le port et l'adresse Mac (si Ethernet). Il est doc possible de connecter la BaseStation en Wifi avec une carte Ethernet Wifi si on le souhaite (je ne l'ai pas testé).
  • PacketRegister.h (les déclarations) et .cpp (le code) : c'est lui qui gère les contenus binaires des registres qui sont transmis bit à bit, répétitivement vers le circuit de puissance (motor shield) pour générer le signal DCC
  • SerialCommand.h (les déclarations) et .cpp (le code) : c'est lui qui décode les commandes reçues sous forme de texte soit par la liaison série, soit par Ethernet. On trouvera la liste de ces commandes là : https://github.com/DccPlusPlus/BaseStation/wiki/Commands-for-DCCpp-BaseStation
  • CurrentMonitor.h (les déclarations) et .cpp (le code) : c'est lui qui gère la mesure de courant pour éviter les court-circuits, bien-sûr, mais surtout pour récupérer les réponses du décodeur en cours de programmation. C'est une fonction que je n'avais pas encore trouvée toute faite et ça tombe bien  :-*

Ensuite les onglets dont on peut se passer éventuellement :
  • Accessories.h (les déclarations) et .cpp (le code) qui gère les commandes des accessoires
  • EEStore.h (les déclarations) et .cpp (le code) qui gère la sauvegarde des paramètres en EEPROM
  • Outputs.h (les déclarations) et .cpp (le code) qui pilote des sorties (commandes d'aiguille) de l'Arduino
  • Sensor.h (les déclarations) et .cpp (le code) qui gère des entrées (rétrosignalisation) de l'Arduino

Je fais une petite pause pour vous laisser digérer cette intro et je détaillerai ensuite :
  • la logique de génération du signal DCC (sur les voies principales et de programmation), avec la notion de Registres qui ressemble fortement à mon premier exemple de générateur DCC dans l'article http://www.locoduino.org/spip.php?article17, c'est le moment de le relire, ça aidera à comprendre.
  • les méthodes qui permettent de programmer les registres, donc de faire toutes les fonctions précitées.

A suivre ...
« Modifié: octobre 27, 2016, 12:32:59 pm par Dominique »

Thierry

  • Global Moderator
  • Sr. Member
  • *****
  • Messages: 387
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #32 le: août 21, 2016, 10:23:02 am »
Tout ça est très clair. Hâte de lire la suite !

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #33 le: août 21, 2016, 06:43:16 pm »
La génération du signal DCC

C'est là où réside l'intérêt de ce logiciel :

Comme je l'ai indiqué auparavant, DCC++ utilise des tableaux d'octets dits "Packet" organisés en "Register" qui sont gérés dans une liste "RegisterList". Ceci est décrit dans l'onglet PacketRegister.h. Les définitions de base sont :

struct Packet{
  byte buf[10];
  byte nBits;
}; // Packet

struct Register{
  Packet packet[2];
  Packet *activePacket;
  Packet *updatePacket;
  void initPackets();
}; // Register

struct RegisterList{ 
  int maxNumRegs;
  Register *reg;
  Register **regMap;
  Register *currentReg;
  Register *maxLoadedReg;
  Register *nextReg;
  Packet  *tempPacket;
  byte currentBit;
  byte nRepeat;
  int *speedTable;
  int addressDccToDiscover;      // ajout Dominique
  static byte idlePacket[];
  static byte resetPacket[];
  static byte bitMask[];
  RegisterList(int);
  void loadPacket(int, byte *, int, int, int=0) volatile;
  void setThrottle(char *) volatile;
  void setFunction(char *) volatile; 
  void setAccessory(char *) volatile;
  void writeTextPacket(char *) volatile;
  void readCV(char *) volatile;
  void readCV_Main(char *s) volatile;      // ajout Dominique
  void writeCVByte(char *) volatile;
  void writeCVBit(char *) volatile;
  void writeCVByteMain(char *) volatile;
  void writeCVBitMain(char *s) volatile; 
  void printPacket(int, byte *, int, int) volatile;
};

Au passage, on voit dans RegisterList les méthodes disponibles dans CPP++, ainsi que celle que j'ai osé ajouter. Mais nous y reviendrons plus loin.

Il peut y avoir 12 registres dans un Uno et probablement jusqu'à 50 dans un Mega. En tout cas c'est réglable dans la configuration.
Le registre N°0 est réservé pour les commandes à envoyer une seule fois (paquets Idle et Reset, commandes de programmation, commandes de fonctions, commandes d'accessoires). Les registres N° 1 au maximum sont réservés chacun à une machine sur le réseau. Il ne doit pas y avoir plusieurs registres affectés à une même machine, on verra pourquoi bientôt.

Il y a un jeu de registres pour la voie principale et un autre, plus petit pour la voie de programmation car elles peuvent être commandées simultanément.

L'ensemble de ces Registres constitue donc un grand tableau de bits 0 et de bits 1 où chaque ligne est une trame DCC conforme à la norme NMRA et où chaque bit se traduit par une alternance de 200 microsecondes pour un bit ZERO ou 116 microsecondes pour un bit UN.

On se reportera à l'article "L’Arduino et le système de commande numérique DCC" http://www.locoduino.org/spip.php?article14 pour se remémorer le principe de fonctionnement du bus DCC.

Pour produire le signal DCC automatiquement il faut une routine sous interruption pilotée par les 2 compteurs OCR1A (pour les 2 alternances du bit) et OCR1B (pour chaque alternance, donc la moitié de la durée du bit) de l'ATMega.  Il est évident que cette mécanique dépend du processeur utilisé et DCC++ ne s'applique dans son état actuel qu'au UNO et au MEGA. On peut le modifier légèrement pour qu'il fonctionne sur NANO et MINI car ils utilisent le même processeur que le UNO, un ATMega328.

Les 2 routines d'interruption, l'une pour la voie principale et l'autre pour la voie de programmation sont déclarées dans le programme principal DCCpp_Uno

ISR(TIMER1_COMPB_vect){              // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
  DCC_SIGNAL(mainRegs,1)
}

#ifdef ARDUINO_AVR_UNO      // Configuration for UNO

ISR(TIMER0_COMPB_vect){              // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track
  DCC_SIGNAL(progRegs,0)
}

#else      // Configuration for MEGA

ISR(TIMER3_COMPB_vect){              // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track
  DCC_SIGNAL(progRegs,3)
}

#endif

Cette écriture simplifiée vient du fait que Gregg utilise une "Macro" qui évite d'écrite 3 fois à peu prês la même chose. J'avoue que je me suis gratté la tête en tombant là dessus, mais Thierry et Jean-Luc m'ont éclairé à temps.

Dans mon adaptation à un prochain va et vient, j'ai limité le fonctionnement à la seule voie de programmation et à un ATMega328 (Uno ou Nano ou Mini)

Dans le code qui suit, j'ai "dé-macro-isé" la routine pour la voie principale :


ISR(TIMER1_COMPB_vect){              // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
  //DCC_SIGNAL(mainRegs,1)
  if(mainRegs.currentBit==mainRegs.currentReg->activePacket->nBits){  /* IF no more bits in this DCC Packet */
    mainRegs.currentBit=0;                                            /*   reset current bit pointer and determine which Register and Packet to process next--- */   
    if(mainRegs.nRepeat>0 && mainRegs.currentReg==mainRegs.reg){      /*   IF current Register is first Register AND should be repeated */
      mainRegs.nRepeat--;                                             /*     decrement repeat count; result is this same Packet will be repeated */
    } else if(mainRegs.nextReg!=NULL){                                /*   ELSE IF another Register has been updated */
      mainRegs.currentReg=mainRegs.nextReg;                           /*     update currentReg to nextReg */
      mainRegs.nextReg=NULL;                                          /*     reset nextReg to NULL */
      mainRegs.tempPacket=mainRegs.currentReg->activePacket;          /*     flip active and update Packets */         
      mainRegs.currentReg->activePacket=mainRegs.currentReg->updatePacket;
      mainRegs.currentReg->updatePacket=mainRegs.tempPacket;         
    } else{                                                           /*   ELSE simply move to next Register */
      if(mainRegs.currentReg==mainRegs.maxLoadedReg)                  /*     BUT IF this is last Register loaded */
        mainRegs.currentReg=mainRegs.reg;                             /*       first reset currentReg to base Register, THEN */
      mainRegs.currentReg++;                                          /*     increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */
    }                                                                 /*   END-ELSE */
  }                                                                   /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */
                                                                     
  if(mainRegs.currentReg->activePacket->buf[mainRegs.currentBit/8] & mainRegs.bitMask[mainRegs.currentBit%8]){     /* IF bit is a ONE */
    OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1;                          /*   set OCRA for timer N to full cycle duration of DCC ONE bit */
    OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1;                          /*   set OCRB for timer N to half cycle duration of DCC ONE but */
  } else{                                                             /* ELSE it is a ZERO */
    OCR1A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER1;                         /*   set OCRA for timer N to full cycle duration of DCC ZERO bit */
    OCR1B=DCC_ZERO_BIT_PULSE_DURATION_TIMER1;                         /*   set OCRB for timer N to half cycle duration of DCC ZERO bit */
  }                                                                   /* END-ELSE */ 
                                                                       
  mainRegs.currentBit++;                                              /* point to next bit in current Packet */ 
}

Rien qu'avec cette routine d'interruption, et après l'initialisation des variables qui doit être faite dans le Setup(), notre BaseStation est capable d'emblée de produire le signal d'alimentation DCC des voies.

Il reste à comprendre maintenant comment les Registres sont programmés (octets et bits), c'est à dire comment BaseStation peut envoyer des commandes DCC sur les rails.

Pour ce faire, il y a une méthode de RegisterList qui est très importante et qu'il ne faut absolument pas modifier :

// LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE)
// CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS
// BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS)

void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile {
 
  nReg=nReg%((maxNumRegs+1));         // force nReg to be between 0 and maxNumRegs, inclusive

  while(nextReg!=NULL);               // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed
 
  if(regMap[nReg]==NULL)              // first time this Register Number has been called
   regMap[nReg]=maxLoadedReg+1;       // set Register Pointer for this Register Number to next available Register
 
  Register *r=regMap[nReg];           // set Register to be updated
  Packet *p=r->updatePacket;          // set Packet in the Register to be updated
  byte *buf=p->buf;                   // set byte buffer in the Packet to be updated
         
  b[nBytes]=b[0];                     // copy first byte into what will become the checksum byte 
  for(int i=1;i<nBytes;i++)           // XOR remaining bytes into checksum byte
    b[nBytes]^=b[i];
  nBytes++;                           // increment number of bytes in packet to include checksum byte
     
  buf[0]=0xFF;                        // first 8 bytes of 22-byte preamble
  buf[1]=0xFF;                        // second 8 bytes of 22-byte preamble
  buf[2]=0xFC + bitRead(b[0],7);      // last 6 bytes of 22-byte preamble + data start bit + b[0], bit 7
  buf[3]=b[0]<<1;                     // b[0], bits 6-0 + data start bit
  buf[4]=b[1];                        // b[1], all bits
  buf[5]=b[2]>>1;                     // b[2], bits 7-1
  buf[6]=b[2]<<7;                     // b[2], bit 0
 
  if(nBytes==3){
    p->nBits=49;
  } else{
    buf[6]+=b[3]>>2;                  // b[3], bits 7-2
    buf[7]=b[3]<<6;                   // b[3], bit 1-0
    if(nBytes==4){
      p->nBits=58;
    } else{
      buf[7]+=b[4]>>3;                // b[4], bits 7-3
      buf[8]=b[4]<<5;                 // b[4], bits 2-0
      if(nBytes==5){
        p->nBits=67;
      } else{
        buf[8]+=b[5]>>4;              // b[5], bits 7-4
        buf[9]=b[5]<<4;               // b[5], bits 3-0
        p->nBits=76;
      } // >5 bytes
    } // >4 bytes
  } // >3 bytes
 
  nextReg=r;
  this->nRepeat=nRepeat;
  maxLoadedReg=max(maxLoadedReg,nextReg);
 
  if(printFlag && SHOW_PACKETS)       // for debugging purposes
    printPacket(nReg,b,nBytes,nRepeat); 

} // RegisterList::loadPacket

C'est loadPacket qui va positionner les octets et les bits de chaque commande DCC dans un des registres.

LoadPacket contient aussi un mécanisme très important : une sorte de synchronisation avec la routine d'interruption ISR (pendant laquelle le programme principal est mis en attente) qui empêche la modification d'un registre tant que la fois précédente d'appel de loadPacket n'a pas complètement terminé sont travail et que la routine ISR n'a pas envoyé le paquet DCC sur les rails.

C'est la ligne suivant qui fait cela :

while(nextReg!=NULL);               // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed
Cette astuce permet de simplifier considérablement l'écriture du programme dans les niveaux plus hauts en faisant abstraction des contraintes précitées.

Par exemple, dans une autre fonction de RegisterList, on trouve :

loadPacket(0,resetPacket,2,3);          // NMRA recommends starting with 3 reset packets
loadPacket(0,bRead,3,5);                // NMRA recommends 5 verify packets
loadPacket(0,resetPacket,2,1);          // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
On est certain, grâce à ce mécanisme, que les paquets seront bien transmis intégralement sur les rails dans l'ordre indiqué. C'est génial, moi j'aime !

Avant de conclure sur cette partie, voici les autres fonctions présentes dans RegisterList. Leurs arguments sont contenus dans un tableau de caractères :

  void setThrottle(char *); // commande de vitesse (0..126) et directions (1=avant, 0=arriere)
  void setFunction(char *); // commande de fonction loco F0..F28
  void setAccessory(char *); // commande d'accessoire (0..2048)
  void writeTextPacket(char *); //envoi d'un paquet libre
  void readCV(char *); // lecture d'un CV sur voie de programation
  void readCV_Main(char *s); // lecture d'un CV sur voie principale (ajout perso de Dominique)
  void writeCVByte(char *); // écriture d'un octet entier de CV sur voie de programation
  void writeCVBit(char *); // écriture d'un bit de CV sur voie de programation
  void writeCVByteMain(char *); // écriture d'un octet entier de CV sur voie principale (sans réponse du décodeur)
  void writeCVBitMain(char *s);  // écriture d'un bit de CV sur voie principale (sans réponse du décodeur)

Si vous comparez avec le source original de BaseStation, vous trouverez que la fonction readCV_Main(char *s) n'existe pas. C'est un exemple de modification qui est facile à faire. Dans cet exemple, j'ai simplement adressé la commande aux registres de la voie principale et j'ai pris les mesures de courant sur la voie principale (en lieu et place de la voie de programmation). Il va de soi que cette fonction n'a d'intérêt que si une seule loco est présente sur la voie principale.

Dans la suite de cette série, nous regarderons la couche supérieure, c'est à dire l'interface entre RegisterList et la communication série ou ethernet.
Dans la série suivante nous regarderons les autres fonctions accessibles à la communication, qui sont les commandes des ports d'entrée et de sortie restant de l'Arduino, ce qui permet de commander des aiguilles directement sans passer par les commandes DCC d'accessoires et un peu de rétrosignalisation (détections de zones principalement).

A suivre …
« Modifié: août 23, 2016, 04:13:50 pm par Dominique »

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #34 le: août 21, 2016, 10:37:08 pm »
L'interface avec le Contrôleur par liaison série ou ethernet

Nous avons vu dans l'épisode précédent comment la BaseStation fabrique ses paquets DCC à partir de commandes sous forme textuelle qui sont passées à des fonctions de l'objet RegisterList (qui n'est pas une classe mais une structure, mais bon..).

Les onglets SerialCommand.h et SerialCommand.cpp vont se charger d'assurer l'interface entre le canal de communication (série ou ethernet) et les fonctions de RegisterList.

SerialCommand.h déclare une structure SerialCommand :

struct SerialCommand{
  static char commandString[MAX_COMMAND_LENGTH+1];
  static volatile RegisterList *mRegs, *pRegs;
  static CurrentMonitor *mMonitor;
  static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *);
  static void parse(char *);
  static void process();
}; // SerialCommand

On y trouve pas mal de choses importantes :

  • un tableau de caractères commandString: c'est la commande
  • deux RegisterList, l'une mRegs pour la voie principale et l'autre pRegs pour la voie de programmation. C'est ce qui permet d'appeler simplement les fonctions de RegisterList vues précédemment
  • une structure CurrentMonitor qui permet d'avoir une mesure de courant
  • une fonction parse qui se chargera du décodage des commandString et qui va donc appeler les fonctions de RegisterList
  • une fonction process qui va lire l'interface série ou ethernet et va passer la commande reçue à la fonction parse

Le code d'exécution se trouve donc dans l'onglet SerialCommand.cpp :

La fonction process ne fait pas grand chose d'autre que d'enregistrer les caractères compris entre "<" et ">" et de passer le résultat à parse.

La fonction parse analyse le 1er caractère de la commande pour envoyer le reste des caractères (sans cette 1ère lettre) vers la bonne fonction de RegisterList

"t" pour setThrottle (commande DCC)
"f" pour setFunction (commande DCC)
"a" pour setAccessory (commande DCC)
"T" pour une commande d'aiguille directe (commande non DCC)
"Z" pour une commande d'une pin de sortie de l'Arduino  (commande non DCC)
"S" pour lire l'état d'un capteur relié à une pin d'entrée de l'Arduino  (commande non DCC)
"Q" pour lire l'état de tous les capteurs à la fois  (commande non DCC)
"w" pour programmer un CV sur la voie principale en écriture seule sans vérification (commande DCC)
"b" pour programmer un bit particulier d'un CV sur la voie principale en écriture seule sans vérification (commande DCC)
"W" pour programmer un CV sur la voie de programmation avec vérification donc réponse du décodeur (commande DCC)
"B" pour programmer un bit particulier d'un CV sur la voie de programmation avec vérification donc réponse du décodeur (commande DCC)
"R" pour lire un CV sur la voie de programmation avec réponse du décodeur évidemment (commande DCC)
"1" pour appliquer le signal DCC sur les rails (power ON)
"0" pour couper le signal DCC sur les rails (power OFF)
"c" pour lire la valeur du courant sur la voie principale  (commande non DCC)
"s" pour envoyer sur la liaison un état du système  (commande non DCC)
"E" pour sauvegarder les valeurs des aiguilles et capteurs en EEPROM  (commande non DCC)
"e" pour effacer les valeurs enregistrées dans l'EEPROM  (commande non DCC)

A cela s'ajoute quelques utilitaires comme :
"M" pour envoyer un paquet DCC de 2 à 5 octets sur la voie principale (commande DCC)
"P" pour envoyer un paquet DCC de 2 à 5 octets sur la voie de programmation (commande DCC)

Si on s'arrête un instant pour regarder la loop() dans DCCp_Uno, on y lit :

///////////////////////////////////////////////////////////////////////////////
// MAIN ARDUINO LOOP
///////////////////////////////////////////////////////////////////////////////

void loop(){
 
  SerialCommand::process();              // check for, and process, and new serial commands
 
  if(CurrentMonitor::checkTime()){      // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks
    mainMonitor.check();
    progMonitor.check();
  }

  Sensor::check();    // check sensors for activate/de-activate
 
} // loop

Ce qui veut dire que le programme BaseStation ne fait rien d'autre que de traiter les commandes ci-dessus venues de la liaison série ou ethernet, ainsi que de surveiller le courant en cas de court-circuit et de tester les capteurs, ce qui se traduit par l'envoi de messages vers la liaison série ou ethernet.

Il ne fait donc rien de lui-même, mais cela nous ouvre la voie vers des réalisations personnelles intéressantes et sophistiquées, avec une telle belle boite à outils.

Mais revenons à la fonction parse :

On constate qu'il y a deux familles de fonctions :
  • Celles qui sont conformes à la norme DCC NMRA : t, f, a, w, b, W, B, R, M et P
  • Celles qui sont spécifiques à BaseStation et ne concernent pas la norme DCC : T, Z, S, Q, 1, 0, c, s, E, e

Les fonctions conformes à la norme DCC sont réalisées par RegisterList comme on l'a vu précédemment. Mais RegisterList ne s'occupe que des fonctions propres au DCC puisqu'il se charge de la gestion des paquets DCC envoyés aux rails.

Alors, pour les autres fonctions, il existe des onglets spécifiques qui traitent des fonctions non DCC :

Les onglets Accessories.h et Accessories.cpp s'occupent des aiguilles en commande directe par l'Arduino (et non en DCC via un décodeur d'accessoire, ce que BaseStation supporte aussi via la fonction setAccessory). Elles gèrent les structures :

struct TurnoutData {
  byte tStatus;
  byte subAddress;
  int id;
  int address; 
};

struct Turnout{
  static Turnout *firstTurnout;
  int num;
  struct TurnoutData data;
  Turnout *nextTurnout;
  void activate(int s);
  static void parse(char *c);
  static Turnout* get(int);
  static void remove(int);
  static void load();
  static void store();
  static Turnout *create(int, int, int, int=0);
  static void show(int=0);
}; // Turnout

On y retrouve la fonction parse qui fait le lien avec la fonction parse de SerialCommand, pour permettre au contrôleur de commander les aiguilles de cette façon.

Les onglets Outputs.h et Outputs.cpp s'occupent des pins disponibles de l'Arduino qui peuvent être programmées en sortie. Elles gère les structures :

struct OutputData {
  byte oStatus;
  int id;
  byte pin;
  byte iFlag;
};

struct Output{
  static Output *firstOutput;
  int num;
  struct OutputData data;
  Output *nextOutput;
  void activate(int s);
  static void parse(char *c);
  static Output* get(int);
  static void remove(int);
  static void load();
  static void store();
  static Output *create(int, int, int, int=0);
  static void show(int=0);
}; // Output

On y retrouve aussi la fonction parse qui fait le lien avec la fonction parse de SerialCommand, pour permettre au contrôleur de commander les pins de sortie de l'Arduino.

Les onglets Sensor.h et Sensor.cpp s'occupent des pins disponibles de l'Arduino qui peuvent être programmées en entrée pour lire des états de capteurs. Elles gèrent les structures :

struct SensorData {
  int snum;
  byte pin;
  byte pullUp;
};

struct Sensor{
  static Sensor *firstSensor;
  SensorData data;
  boolean active;
  float signal;
  Sensor *nextSensor;
  static void load();
  static void store();
  static Sensor *create(int, int, int, int=0);
  static Sensor* get(int); 
  static void remove(int); 
  static void show();
  static void status();
  static void parse(char *c);
  static void check();   
}; // Sensor

On y retrouve encore la fonction parse qui fait le lien avec la fonction parse de SerialCommand, pour permettre au contrôleur de lire les états des capteurs de rétrosignalisation.

De plus, la fonction check est scrutée par la loop de façon à informer le contrôleur au plus vite des changements d'état des capteurs.

Bien entendu je ne vais pas rentrer dans l'analyse des fonctions de ces 3 dernières familles, mais une série de conclusions d'imposent :

1) BaseStation est un logiciel très complet rédigé de façon propre et claire donc fiable (bien que je n'ai pas tout testé, loin s'en faut, mais cela fleure bon).

2) Sa modularité en fait un logiciel adaptable : on peut enlever et ajouter des fonctions, on peut réaliser certaines fonctions différemment, mais il convient de respecter le noyau principal qui est le moteur DCC.

3) BaseStation est principalement destiné à être piloté par un logiciel de contrôle sur PC. Gregg a écrit Controleur en Processing (version récente) : on peut l'apprécier ou pas. D'autres ont réalisé les adaptations dans JMRI (dernières versions seulement).
N'ayant pas pu installer de version récente dans mon MacBook, j'ai réalisé une petite manette perso que l'on peut voir en document joint.

4) Mais rien n'empêche d'ajouter d'autres fonctions, notamment un automate de gestion de circulation, une gestion de Bal, une gestion de signaux, etc..
Les modèles de programmation des onglets peuvent être une bonne base pour ces extensions. Un bel automate peut aussi trouver sa place dans la loop ou appelé depuis la loop.

Je vous invite maintenant à partager vos impressions et expériences sur DCC++.




bobyAndCo

  • Full Member
  • ***
  • Messages: 191
  • HO avec DCC++
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #35 le: août 22, 2016, 01:34:23 pm »
Bonjour à tous,

Je me suis moi même intéressé au DCC++ que j’ai testé avec succès. Ce programme est d’une simplicité d’utilisation déconcertante. J’ai bien dit d’utilisation car quand on regarde le code, c’est "chiadé", propre. Nul doute que Gregg Berman possède un don pour la programmation.

Mais je crois qu’il est moins doué pour le graphisme et l’esthétisme si j’en juge par son controller (DCCpp_Controller) ! Cela a déjà été précisé ailleurs mais il est important de rappeler que DCC++ peut fonctionner indépendamment de DCC++ Controller. Il y a là un côté génial car vous pouvez utiliser pratiquement tout sortes de moyens pour envoyer des ordres à votre Arduino sur laquelle vous avez recopié tel quel le code de DCC++ BasrStation et ça marche !

Vous pouvez utiliser JMRI ou RocRail par exemple, mais vous pouvez aussi (facilement) créer votre propre "controller".

A la base, c’est assez déconcertant, après avoir relié tout le hardware et alimenté l’ensemble, puis entré dans le moniteur série de l’IDE d’Arduino : <1> puis <t 1 3 10 1> de voir que la locomotive adresse 3 démarre bien à la vitesse 10 en avant. Entrez maintenant <t 1 3 10 0> et elle va partir en arrière toujours à la vitesse 10. Plus vite maintenant toujours en arrière <t 1 3 80 0>  à la vitesse 80. Voir aussi le premier post de Tanguy : http://forum.locoduino.org/index.php?topic=203.0

Tout a été fait pour que l’utilisation soit la plus simple possible. Certes, taper des <t 1 3 10 1> <t 1 3 10 0> ou autres <t 1 3 10 0> dans la fenêtre série pour piloter son réseau est un peu fastidieux mais beaucoup plus réaliste avec un petit programme simple qui saurait envoyer ces séquences d’ordres au port série ou à un shield Ethernet que vous auriez implanté sur votre Arduino Mega ! Eh oui, DCC++ ne peut communiquer par Ethernet qu’avec un Mega, avec son code actuel, il faut le préciser.

J’utilise l’Ethernet sur tout mon réseau et on m'a déjà demandé plusieurs fois d'expliciter la chose. Se servir de l’Ethernet n’est pas aussi compliqué qu’on le pense sans doute. Tout d’abord, on utilise son « bon vieux » réseau domestique, Free, Bouygues ou Orange et on peut concevoir de piloter son réseau avec son navigateur web ou son smartphone. A voir les vidéo sur le net, ça semble être le grand phantasme des amateurs de N ou de HO. Et pourquoi pas tous les smarthones de la famille car Ethernet permet le raccordement de terminaux multiples contrairement au port série. Mais cela peut être aussi un pilotage principal sur l’ordinateur, les commandes d'accessoires dont les aiguilles sur tablettes et pour le fun, une loco sur smarphone.

Voici un lien sur mon controller actuel : http://89.225.199.180/train_HO/locomotives/controller_demo.html

Cette version n’utilise pas encore DCC++ mais je suis en train de m’y consacrer.

Alors, je vous propose de rédiger un sujet sur la mise en œuvre de cette technologie et sur la réalisation des applications web à développer. Pour ce faire, j’aimerai que vous puissiez me remonter vos différentes attentes et questions sur le sujet afin que je puisse y répondre au mieux. A cet effet, j’ai ouvert un nouveau sujet dans la rubrique Modélisation, Architecture logicielles et matérielles : http://forum.locoduino.org/index.php?board=13.0

Je vous propose de nous retrouver sur ce tropic et merci par avances de vos contributions.

Bien à vous.

Christophe

« Modifié: août 23, 2016, 04:22:04 pm par Dominique »

DDEFF

  • Sr. Member
  • ****
  • Messages: 432
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #36 le: août 22, 2016, 09:04:26 pm »
Finalement, avec DCC++, j'avais bien déniché une pépite... ;D ;D (premières vidéos il y a un an, quand même)

Bien content que Dominique et d'autres (dont Christophe et Tanguy) décortiquent la partie DCC. D'autant que je n'ai pas de locos DCC  :(

Je développe en ce moment une V3 de mon TCO qui sera en deux programmes :

Un éditeur graphique de TCO
Un gestionnaire de réseau en Processing.
La partie graphique avance bien et je corrige tous les bugs qui trainaient. Je veux faire un article qui fonctionne parfaitement. Et, des fois, c'est long  ::)

Développez bien, on se rejoindra à la fin.

Denis

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #37 le: août 23, 2016, 05:30:57 pm »
La détection de courant de DCC++

Un des gros avantages de DCC++ par rapport aux autres logiciels connus qui génèrent du DCC est sa mesure de courant qui fonctionne bien sur divers matériels (shield Arduino Motor, ou Polulu ou LMD18200).

La mesure de courant se fait par la lecture d'un port analogique à chaque tour de loop() et l'intérêt de cette mesure est qu'elle intègre une moyenne glissante ou "lissage exponentiel" avec les dernières mesures, c'est une sorte de filtre passe-bas : on n'est pas tous capable d'inventer cela, donc profitons en  ;)

La formule utilisée est c_t = coeff x apin + c_t-1 x (1-coeff) où
- c_t est la valeur de courant calculée au tour t
- c_t-1 est la valeur de courant calculée au tour t-1 (précédent)
- apin est la valeur analogique lue sur la pin analogique utilisée pour la mesure
- coeff est le coefficient de pondération qui est ici de 1%

La valeur lue sur le port apin est donc réduite au 1/100 ème de sa valeur et ajoutée à 99/100 du résultat précédent (initialisé à 0 au départ). La valeur calculée converge donc progressivement vers une valeur moyenne du courant, avec un grande stabilité car les mesures affectent peu le résultat.

On trouve le code suivant dans l'onglet CurrentMonitor.cpp

void CurrentMonitor::check(){
#define  CURRENT_SAMPLE_SMOOTHING   0.01

  current=analogRead(pin)*CURRENT_SAMPLE_SMOOTHING+current*(1.0-CURRENT_SAMPLE_SMOOTHING);        // compute new exponentially-smoothed current
  if(current>CURRENT_SAMPLE_MAX && digitalRead(SIGNAL_ENABLE_PIN_PROG)==HIGH){                    // current overload and Prog Signal is on (or could have checked Main Signal, since both are always on or off together)
    digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW);                                                     // disable both Motor Shield Channels
    digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW);                                                     // regardless of which caused current overload
    INTERFACE.print(msg);                                                                         // print corresponding error message
  }   
} // CurrentMonitor::check 

Ce code, appelé dans la loop permet de couper l'alimentation en cas de dépassement de seuil (ici 300), c'est à dire en cas de court-circuit.

La mesure de courant pour les commandes de programmation

C'est l'application la plus interessante de cette mesure de courant lissée.

Nous avons tous remarqué que lors de la programmation du décodeur d'une loco (on commence toujours par son adresse DCC), celle-ci se met à bouger  :-[
Cela vient du fait que le décodeur va provoquer des impulsions de consommation de courant sur les rails en activant le moteur pendant une fraction de seconde. Il répète plusieurs cycles de "consommation" pour transmettre une information qui est un bit 0 ou un bit 1.

Pour récupérer cette information, notre logiciel BaseStation va d'abord mesurer une valeur de repos du courant "base" c'est à dire quand la loco est au repos : ce courant n'est pas nul car le décodeur en consomme toujours un peu.
Puis quand la loco va transmettre ses "bits" le logiciel va mesurer la consommation en faisant une moyenne glissante pendant la durée du bit, puis comparer le résultat avec la valeur de repos. Si le résultat dépasse la valeur de repos d'un seuil ACK_SAMPLE_THRESHOLD alors le bit est un "1", sinon c'est un "0"

Ce mécanisme se répète 8 fois pour récupérer les 8 bits de l'octet de réponse de la loco.

Les constantes utilisées dans cette mesure se trouvent dans l'onglet PacketRegister.h :

// Define constants used for reading CVs from the Programming Track

#define  ACK_BASE_COUNT            100      // number of analogRead samples to take before each CV verify to establish a baseline current
#define  ACK_SAMPLE_COUNT          500      // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent
#define  ACK_SAMPLE_SMOOTHING      0.2      // exponential smoothing to use in processing the analogRead samples after a CV verify (bit or byte) has been sent
#define  ACK_SAMPLE_THRESHOLD       30      // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT

On constate que les paramètres de lissage ne sont pas tout à fait les même que pour la mesure dans la loop. Cela vient du fait que les mesures dans la loop se font en fonction du temps d’exécution de la loop toute entière, alors que dans la réponse lors d'une programmation, ce sont seulement quelques instructions qui s’exécutent.

Et le code de récupération de chaque bit se trouve dans l'onglet PacketRegister.cpp, à l'intérieur de chaque fonction de programmation avec réponse, comme readCV, writeCVByte, writeCVBit sur la voie de programmation :

    c=0;
    d=0;
    base=0;

    for(int j=0;j<ACK_BASE_COUNT;j++)
      base+=analogRead(CURRENT_MONITOR_PIN_PROG);
    base/=ACK_BASE_COUNT;

    bRead[2]=0xE8+i; 

    loadPacket(0,resetPacket,2,3);          // NMRA recommends starting with 3 reset packets
    loadPacket(0,bRead,3,5);                // NMRA recommends 5 verfy packets
    loadPacket(0,resetPacket,2,1);          // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)

    for(int j=0;j<ACK_SAMPLE_COUNT;j++){
      c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
      if(c>ACK_SAMPLE_THRESHOLD)
        d=1;
    }


Il va de soi que ce code ne doit pas être modifié car il fonctionne bien : nous avons testé ce code sur plusieurs plateformes à base d'Uno, Nano et probablement Mega.

BaseStation confirme donc son universalité en permettant de commander la voie principale, et aussi la voie de programmation avec un compatibilité NMRA bien étudiée.

L'interface "textuelle" par le port série/usb ou par ethernet va permettre de développer diverses interfaces, en concurrence avec celle de Gregg, mais en s'appuyant sur la qualité de BaseStation. J'en suis convaincu.

J'espère que la présentation de cette partie du logiciel BaseStation ne vous a pas semblé trop ardue  ::)

« Modifié: mai 04, 2017, 05:09:55 pm par Thierry »

bobyAndCo

  • Full Member
  • ***
  • Messages: 191
  • HO avec DCC++
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #38 le: août 23, 2016, 06:39:17 pm »
Alors là je dis chapeau parce que ça veut dire (si je ne trompe pas) que l'on doit pouvoir récupérer l'ensemble des cv's et leur valeur dès que l'on pose la loco sur la voie de programmation. On peut donc renseigner la base de données du controller automatiquement.

Ca devrait donc aussi vouloir dire que si la loco possède une adresse déjà attribuée (et stockée dans la base de données controller), il est possible de lui en affecter automatiquement une autre qui est libre.

Je suis certain que ce que tu nous soumets ouvre sur bien d'autres potentialités.

DDEFF

  • Sr. Member
  • ****
  • Messages: 432
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #39 le: août 23, 2016, 06:53:10 pm »
"Hardue", avec un "h", c'est voulu ? Moi, j'aime bien... ;D

A côté, la partie Processing est plus light, même si ces fonctionnalités sont étendues : on peut tout tester.

Mais côté Arduino, on est très loin de "cMDRarduino". Que de progrès.

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #40 le: août 23, 2016, 07:15:54 pm »
Merci pour la détection d'erreur que j'ai quand même corrigée   :-[

J'étais en train de terminer quand toute la famille est rentrée et j'ai du être distrait !

DDEFF

  • Sr. Member
  • ****
  • Messages: 432
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #41 le: août 23, 2016, 07:26:39 pm »
Moi, je l'aurais laissée, mais avec des guillemets. C'est marrant.

Il ne faut pas focaliser sur ce détail. Le principal c'est l'énorme boulot que tu as abattu en décortiquant ce magnifique programme.
C'est ça qui est important et rafraichissant.

Bravo et merci.

Amicalement
Denis

Thierry

  • Global Moderator
  • Sr. Member
  • *****
  • Messages: 387
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #42 le: août 23, 2016, 08:58:37 pm »
Oui, c'est un gros boulot d'analyse et de compréhension qui va nous faciliter la vie à tous, en tout cas à ceux qui vont utiliser Dcc++ et dont je fais partie (ferais serait plus juste...) .

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1175
  • 100% Arduino et N
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #43 le: août 23, 2016, 09:00:51 pm »
En fait j'avais envie de faire un article sur CmdrArduino mais l'inspiration tardait à venir. Et puis DCC++ est arrivé et tout est devenu évident parce que ce logiciel est bien foutu  8).

Christophe va bientôt nous éclairer sur Ethernet et ça vaudra le coup de faire un article complet dans la zone éditoriale à partir de toutes ces contributions.

Amicalement
dominique

bobyAndCo

  • Full Member
  • ***
  • Messages: 191
  • HO avec DCC++
    • Voir le profil
Re : DCC++ BaseStation
« Réponse #44 le: août 29, 2016, 05:51:58 pm »
Bonsoir à tous,

Je suis en train de tester la lecture des valeurs de CV's < R CV CALLBACKNUM CALLBACKSUB > mais impossible d'obtenir d'autre réponse que :  -1 étant "vérification impossible". Exemple : <r12|13|1 -1>.

Returns: **< r CALLBACKNUM|CALLBACKSUB|CV VALUE>
CV VALUE is a number from 0-255 as read from the requested CV, or -1 if read could not be verified.

Je suis sur Mega avec carte Pololu, j'ai fait le test en port série et en Ethernet. La voie de programmation est bien alimentée, diodes de la carte allumée et la loco a bien le comportement typique de la programmation (léger mouvement + phares allumés). J'ai ainsi testé dans une boucle les 200 premier CV's de deux locos avec des valeurs (non prises en compte par DCC++ de 12 et 13, puis 100 et 200). Exemple : <R 1 12 13> et c'est à chaque fois la même réponse.

Merci de votre aide, et bien cordialement

Christophe