Lorsque j’ai réalisé mon premier réseau, il y a quelques années, devant le choix difficile et le prix d’une solution DCC du commerce pour un petit réseau, et pour éviter d’être enfermé dans une solution propriétaire limitant les évolutions, j’ai finalement opté pour une centrale à base d’Arduino que j’ai construite moi-même, à partir des exemples qu’on pouvait trouver sur le Web (Locoduino n’existait pas encore, il fallait l’inventer !).
Ayant trouvé le moyen de piloter quelques trains en même temps sur une voie unique, il m’a fallu très rapidement éviter les collisions et réguler les circulations, pour que mes petits enfants puissent profiter du réseau en toute quiétude (surtout pour moi !). Un cantonnement était nécessaire avec quelques capteurs. La programmation en C standard s’est vite avérée laborieuse même pour « synchroniser » ce petit nombre d’éléments.
Aujourd’hui je me lance dans la réalisation d’un réseau plus grand en double voie + une voie unique, avec 4 gares, un vingtaine d’aiguilles et de signaux, une quarantaine de zones, au moins 8 trains en circulation simultanée possibles, et je souhaite disposer de toutes les sécurités et automatismes nécessaires, des signaux qui fonctionnent sur le bord des voies et qui conditionnent les comportements des trains, des itinéraires, des priorités, un beau TCO, des manettes pour les enfants et surtout sans PC ni centrale du commerce (peut-être en option plus tard).
C’est un réseau de taille moyenne, mais l’approche de l’architecture matérielle et logicielle est faite avec soin. Cet article a pour but de décrire succinctement le cheminement de mes réflexions et mes choix, jusqu’à la description de l’informatique ferroviaire à base d’objets C++, possibles dans l’environnement Arduino, que Pierre m’a fait découvrir.
Cet article peut servir à « planter le décor » avant la lecture des articles de Pierre qui sont donnés dans le Post-Scriptum à la fin de cet article.
Pourquoi aurai-je autant confiance dans la réalisation d’un tel système ?
En étudiant de nombreuses réalisations possibles à travers les forums et après avoir choisi mon tracé en fonction des dimensions de mon local, j’ai commencé par la mise en place des ingrédients matériels : châssis porteur, supports des voies, pose des voies avec coupures, conception et pose les détecteurs d’occupation, à travers lesquels les coupons de voies sont alimentées (en DCC ou en analogique).
Avec la pose des moteurs d’aiguille, j’ai développé un module de commande d’aiguilles, puis un module TCO recevant les signaux des détecteurs d’occupation, puis un module de commande de traction DCC, tous les trois à base d’Arduino Mega 2560. Ces modules feront l’objet d’une description prochainement.
D’autres modules viendront s’ajouter plus tard (modules de signalisation, modules d’animations des décors, modules sonores, etc..).Tous les modules sont et seront équipés d’une carte de communication avec un bus CAN à 500kb/s.
Le bus CAN est le bus de communication que j’ai choisi pour les échanges de messages entre Arduino. Mais cet article s’applique à tous les types de bus tel l’I2C, natif dans chaque Arduino, ou d’autres types de bus pourvu qu’il existe une interface avec l’Arduino, voire aucun bus du tout si tout peut tenir dans un seul Arduino !
Grâce au bus, les fonctions de chaque module sont bien caractérisées et les interactions entre modules sont faciles à réaliser. Par exemple, pour commander les aiguilles à partir des clés du TCO, un changement d’état d’une clé est traduit en un message qui est transmis au module de commande d’aiguilles. Quand ce dernier a exécuté la commande il renvoie un message avec la position de l’aiguille. Ce message est récupéré par le TCO qui affiche la position en allumant une Led à coté de la clé.
Ce bus remplit toutes les fonctions de rétro-signalisation et de commandes d’accessoires. Ainsi, avec une interface "réseau" simple que procure le CAN, mon informatique ferroviaire peut grandir à mon rythme, sans souci de compatibilité, à commencer par l’entrée en jeu du gestionnaire du réseau.
Notons dès maintenant que l’architecture décentralisée en modules que j’ai choisie, et qui fonctionne par échange de messages, n’est pas la seule possible. De toute façon, les différentes entités qui composent votre réseau (les trains, les zones, les aiguilles, les signaux..) doivent s’échanger des messages pour que tout fonctionne harmonieusement. Nous verrons comment dans ce qui suit, le raisonnement étant indépendant de l’architecture.
Je disposais alors d’un réseau opérationnel mais en fonctionnement purement manuel !
Mon but étant de permettre à plusieurs trains de circuler en même temps, je voulais réguler la circulation avec un block système ou un BAL (block automatique lumineux) qui permette de prévenir un convoi suffisamment tôt de la présence d’un obstacle à sa marche (convoi précédent, aiguille mal positionnée, etc..) par un signal ouvert ou fermé. De plus je voulais assurer une régulation de vitesse réaliste en imposant les ralentissements (30 et 60) dans les zones à risque (zones d’aiguilles, entrées de gare, virages serrés).
J’avais donc mis en place, dans ce but, un découpage en zones permettant de réaliser un cantonnement et installé des détecteurs d’occupation. Pour profiter de l’effet "lumineux" au plus vite, j’ai relié les détecteurs d’occupation au TCO, puisque c’est lui qui pilote les Leds d’occupation sur le tableau. Ensuite il transmet un message vers le bus. Ces messages n’étaient pas encore utilisés mais ils existaient. J’envisageais également d’ajouter un module de commande de signaux pour le réalisme du réseau (aux endroits où je choisis d’en installer) et pour le contrôle des trains (signaux virtuels respectés par chaque convoi).
Cerise sur le gateau, je voulais animer le décor en fonction des circulations ou en fonction de certains contextes : allumer les phares dans les tunnels, générer des sons (crissements des roues dans les virages, annonces en gare, etc..), illuminer les maisons à la tombée de la nuit, etc…
Il restait donc à trouver comment faire fonctionner tous ces éléments avec un logiciel « chef d’orchestre » qui aurait une description précise du réseau et de ses éléments (ce que j’appelle « la modélisation du réseau »), qui serait capable de recevoir tous les événements (occupations, comportement des trains, actions utilisateurs, etc.. sous forme de messages) et qui pourrait générer toutes les commandes en conséquence (suivi des trains, commandes des trains, gestion des feux, des itinéraires, affichages, etc.. également sous forme de messages).
J’ai donc ajouté un module de "gestion de réseau » ou « gestionnaire » connecté aux autres modules via le bus.
Par exemple, si je reprend l’exemple de la commande d’aiguille ci-dessus, les messages du TCO ne sont pas directement exécutés par le contrôleur d’aiguilles, mais passent par le gestionnaire qui doit vérifier si cette commande est possible.
Imaginons qu’une aiguille soit protégée pour laisser passer un convoi prioritaire : cette aiguille ne pourra être manœuvrée qu’après le passage du convoi. C’est ce que fera le gestionnaire : si je bascule la clé de l’aiguille sur le TCO, rien ne se passe ! Dès que le convoi est passé, l’aiguille change automatiquement et la Led sur le TCO, à coté de la clé, répercute ce changement.
Bien entendu le gestionnaire pourra faire beaucoup plus : gérer les signaux, des itinéraires, réguler les trains, etc..
D’habitude, cette fonction est gérée par un logiciel sur ordinateur comme RRTC (Train Controler), JMRI, RocRail, etc.., avec la problématique de la rétro-signalisation qui doit être compatible. Ici, au contraire, je fais le pari que tout peut être géré par une carte Arduino puissante, comme la carte DUE, qui comporte en standard 2 interfaces CAN : l’une peut être affectée à la rétro-signalisation et les commandes d’appareils de voie, l’autre à l’animation du décor, si on veut.
Du coup, l’architecture matérielle complète de mon réseau se présentera comme ceci :
Il restait donc à répondre aux deux questions suivantes :
1 Comment modéliser la structure du réseau et ses éléments pour en déduire une définition informatique qui permette de suivre l’état du réseau et des circulations en temps réel ?
2 Comment programmer tout cela avec le seul IDE Arduino ?
Il y a certainement plusieurs scenarii possibles, mais voici ma vision personnelle, suivie de son implémentation.
Une autre raison m’a amené inexorablement à choisir cette architecture : je veux que les positions de mes trains (couple train + zone) soient automatiquement reconnues par le gestionnaire pour assurer le pilotage en DCC.
Or cette fonction de reconnaissance n’existe pas avec les centrales et les logiciels du commerce ! [1] Je me devais de relever ce challenge.
Vous verrez dans les prochains articles comment je m’y suis pris !
Une évidence s’impose :
La quantité d’éléments est telle qu’il m’a semblé difficile de conserver la programmation en C standard.
Il saute aux yeux qu’il y a des familles d’éléments dans le réseau qui comportent des similitudes :
plusieurs aiguilles,
plusieurs zones et cantons (un canton est une zone sans aiguille),
plusieurs signaux,
plusieurs trains,
plusieurs itinéraires,
plusieurs manettes ou cabines de conduite,
etc..
Chacune de ces familles est caractérisée par un ensemble de variables qui décrivent son état et par un ensemble de fonctions qui réalisent les actions liées aux événements qui les concernent. On peut dire que chaque élément est susceptible de recevoir un message ou un type de message (événement ou commande) et d’en envoyer à d’autres éléments, à un changement d’état ou en réponse à un message reçu.
De plus, dans chaque famille il y a des variantes : tous les éléments ont des variables et fonctions semblables, mais aussi quelques différences.
Par exemple, chaque zone est connectée à une « zone suivante » qui dépend de la position directe ou déviée d’une ou plusieurs aiguilles à traverser et du sens de circulation. La fonction qui retourne ce résultat est donc spécifique à chaque zone.
Or il existe une façon de programmer particulièrement adaptée à ce type de situation : la programmation OBJET, en C++ pour l’Arduino, qui est particulièrement recommandée pour ce type de projet.
Heureusement Locoduino nous guide sur ce sujet, grâce aux articles de Thierry :
Et grâce au Forum Locoduino, en particulier le fil Modélisation logicielle d’un réseau, avec les contributions de Jean-Luc, Denis et Pierre.
Pourquoi envisager la programmation orientée objet ?
En programmation standard (dite aussi structurée) comme le C de l’Arduino, il faut définir d’un coté les variables (globales et locales) et d’un autre coté les fonctions (sous-programmes), puis assembler tout cela dans une suite d’actions programmées dans la loop.
Définir la modélisation ou la topographie d’un réseau revient à écrire des tables d’éléments (variables et pointeurs) et des tables d’automates pour faire vivre le réseau. C’est terriblement compliqué (sauf pour de tout petits réseaux).
En C++, un objet est une structure informatique regroupant :
des variables, caractérisant l’état de l’objet,
des fonctions, caractérisant le comportement de l’objet.
Les variables s’appellent données-membres de l’objet.
Les fonctions s’appellent fonctions-membres ou encore méthodes de l’objet.
L’originalité dans la notion d’objet, c’est que variables et fonctions sont regroupées dans une même structure.
La classe est le type d’un objet.
Une variable de type classe est un objet qui contient tout ce qui lui est nécessaire pour fonctionner et n’expose que ce qui est nécessaire au monde extérieur : tout ce qui est interne à un objet peut être protégé du reste du programme ce qui garanti que les variables internes ne risquent pas d’être polluées par une erreur de programmation. De plus, les noms des variables sont bien plus faciles à gérer que s’ils devaient être déclarés en variables globales puisque ce nom n’est défini qu’une seule fois dans la définition de la classe. Le nom d’une variable ou d’une fonction de la classe de base est le même dans tous les objets de cette classe. Seul le nom de l’objet varie.
Par exemple un objet aiguille rassemble :
une variable « no » numéro qui servira à générer un message de commande vers l’aiguille réelle correspondante
une variable « état » pour indiquer si elle est en position droite (vrai) ou déviée (faux)
une fonction « devier() » pour la commander en position déviée et faire toutes les actions qui en découlent
une fonction « directer() » pour la commander en position directe et faire toutes les actions qui en découlent
une fonction « directe() » pour connaitre si sa position est directe
une fonction « deviee() » pour connaitre si sa position est déviée
Par exemple un objet zone rassemble (cette liste n’est pas limitative) :
une variable « no » numéro qui servira à générer un message de commande ou une requête relative à cette zone
une variable « état » pour indiquer s’il est libre (vrai) ou occupé (faux),
une variable « train » pour indiquer par quel train et dans quelle direction
une variable « signal » correspondant au signal à la fin de la zone
une fonction « suivantePaire » qui donnera la zone suivante paire en fonction des éventuelles aiguilles à traverser
une fonction « suivanteImpaire » qui donnera la zone suivante impaire (dans l’autre sens) en fonction des éventuelles aiguilles à traverser
une fonction « occuper » qui commandera à l’objet de réaliser toutes les actions en cas d’occupation comme, par exemple, allumer la led d’occupation sur le TCO et modifier l’état des signaux en aval de la zone (selon le sens de circulation)
une fonction « libérer » qui commandera à l’objet de réaliser toutes les actions en cas de libération
une fonction « libre » qui peut être appelée pour savoir si son état est libre ou occupé
En partant seulement de ces 2 exemples d’objets, on peut déjà comprendre comment va fonctionner le réseau :
à partir de clés d’aiguilles sur le TCO, on peut commander les aiguilles : un message de demande de changement de l’aiguille J généré par le TCO est décodé par le gestionnaire et entraîne l’appel de la fonction « directer » ou « dévier » de l’aiguille J concernée. Cette fonction va commander l’aiguille en envoyant un message au contrôleur d’aiguille qui retournera ensuite un message de position permettant d’allumer une led correspondant à sa position sur le TCO.
quand un train pénètre dans une zone N à la sortie de laquelle se trouve l’aiguille J, un détecteur d’occupation génère un message vers le gestionnaire qui entraîne l’appel de la fonction « occuper » de la zone concernée. Cette fonction va rechercher si le train peut continuer ou non à rouler en appelant la fonction « suivante paire » ou « suivante impaire » (selon le sens de marche) pour connaitre si la zone suivante est accessible en fonction de la position de l’aiguille J et de l’état de cette zone suivante. Le gestionnaire en déduit le signal à appliquer à la marche du train. Ce signal est envoyé par message au module de traction, pour le train concerné, et aux signaux éventuels.
Il est évident que la conduite des trains est aussi soumise au gestionnaire : un ordre de conduite décidé par un conducteur sur sa manette (démarrage, vitesse, direction), ne sera exécuté par le module de traction qu’après validation par le gestionnaire. Par exemple, si un ralentissement 30 s’impose dans une courbe serrée ou une sortie de gare traversant plusieurs aiguilles en position déviée, le train respectera cette consigne même si le pilote pousse la manette de vitesse à fond !
C’est ce principe qui permet de réaliser la sécurité sur le réseau.
Cet exemple est évidemment très simplifié : Dans la réalité il y a de nombreux types d’objets (trains, itinéraires, signaux, etc..) qui seront tous « autonomes » mais capables de coopérer entre eux à chaque événement humain ou événement de circulation.
J’ai surtout découvert une chose essentielle : la déclaration et la description des objets « zone » et « aiguille » suffit à décrire la topographie du réseau. Les autres objets la complètent et l’ensemble constitue bien la modélisation du réseau !
Pour bien s’imprégner de la programmation objet, il faut rappeler les 3 principes qui doivent s’appliquer :
L’encapsulation
Il s’agit de définir une structure qui regroupe (ou encapsule) les données et les fonctions qui les utilisent. Une telle structure est appelée classe.
Une classe est donc une définition de structure dont le nom est un nouveau type.
La déclaration d’une variable de type classe est une instance de cette classe : elle est appelée objet.
Les classes sont composées de données membres (que l’on peut comparer aux champs des enregistrements de la programmation structurée) et de fonctions membres, qui définissent les opérations à réaliser sur les données membres.
C’est ce que nous avons esquissé ci-dessus en regroupant les variables et les fonctions appartenant à une même classe.
Nous verrons la description précise des classes Aiguille, Zone, Signal, Train, Itinéraire, etc.. dans les articles de Pierre.
L’héritage
Les classes peuvent être définies et utilisées de manière autonome, chaque classe constituant un ensemble homogène indépendant de toutes les autres classes.
Cependant, il peut être très utile qu’une classe soit construite à partir d’une autre classe, en conservant les propriétés de la classe d’origine et en ajoutant de nouvelles. Ce processus est possible et s’appelle héritage ou dérivation. Il conduit à la notion de hiérarchie de classe.
L’héritage (ou dérivation) est un mécanisme qui permet de construire des classes à partir d’autres classes, en définissant une nouvelle classe, appelée classe dérivée, comme une extension d’une classe existante, appelée classe de base. La dérivation permet à une classe dérivée d’hériter des propriétés d’une classe de base, c’est-à-dire des données et fonctions membres.
Ainsi, il est possible de compléter des classes, en rajoutant des données ou des fonctions membres, et/ou de les personnaliser, en redéfinissant des données (valeurs) ou des fonctions membres.
Pour les zones, par exemple, cela se traduit par une définition des zones suivantes paires et impaires qui diffère selon les zones et les aiguilles qui les occupent : cette définition qui nécessite donc l’héritage, nous conduira à la modélisation du réseau réel au moment de la déclaration des objets, instances de ces classes dérivées.
Le polymorphisme
Des fonctions et des méthodes de même nom peuvent avoir des comportements différents ou effectuer des opérations sur des données de types différents. L’on distingue 2 types de polymorphisme, la surcharge et la redéfinition.
La surcharge est une possibilité offerte par certains langages de programmation, et notamment le C++, qui permet de choisir entre différentes implémentations d’une même fonction ou méthode selon le nombre et le type des arguments fournis.
La notion de polymorphisme est très liée à celle d’héritage. Grâce à la redéfinition, il est possible de redéfinir une méthode dans des classes héritant d’une classe de base. Par ce mécanisme, une classe qui hérite des méthodes d’une classe de base peut modifier le comportement de certaines méthodes héritées pour être adaptées aux besoins de la classe fille.
Ces notions vous paraissent un peu trop théoriques ? Nous verrons dans l’article Un gestionnaire en C++ pour votre réseau comment ces notions s’appliquent concrètement à la définition et au fonctionnement du réseau.
Nous vous invitons ensuite à regarder l’exemple de mise en oeuvre de la modélisation de mon réseau en suivant le fil du forum.
Attention au départ du train !
[1] Sauf, peut-être avec l’aide de décodeurs et détecteurs d’occupation compatibles "RailCom", ce qui est beaucoup plus cher !!!
Bravo à tous les deux pour cet article passionnant : c’est du très lourd !
Je suis pour ma part bien convaincu qu’un seul Arduino pour gérer tout un réseau est insuffisant. Le concept d’en utiliser plusieurs et de les faire communiquer permet une mise au point plus facile et permet de rajouter d’autres "cartes fonction" comme le démontre cet article.
Je vais m’empresser de lire la suite et j’aurais certainement des questions. Mais avant de les poser, je dois avoir une connaissance globale du sujet. A plus.
Excusez mon intrusion, j’ai compris le principe énoncé-ci dessus mais étant débutant je voudrais commencer par piloter 2 trains ensemble su le même réseau et donc commencer par affecter deux adresses différentes à mes deux locos : et je ne trouve pas sur Locoduino !
Pouvez-vous me guider ?
Merci
Vous pouvez construire votre centrale DCC pour piloter 2 trains en partant des nombreux articles que vous trouverez en tapant « centrale DCC » dans la recherche en haut à droite de la page d’acceuil de Locoduino.
Le choix ne manque pas ! Votre question ressemble plus à “ comment commander la centrale pour 2 trains » . Il faut choisir l’interface utilisateur qui vous convient : je répondrai plus en détail ensuite.
Maintenant si votre question concerne la programmation des adresses des locos, avec DCCpp ou DCC++, vous pouvez le faire.
Dominique,
j’ai bien lu tous vos articles mais je ne trouve pas de réponse a deux questions !
J’ai une carte Arduino Uno que j’ai relié comme expliqué à un booster LMD 18200, j’ai équipé une loco avec un décodeur LokPilot V4.0 et le tout fonctionne sur mon réseau Fleischmann en "N" (sauf que le point arrêt est difficile à trouver).
Je voudrais faire circuler sur le même réseau une deuxième loco avec un deuxième décodeur LokPilot V4.0 qui a la même adresse par défaut (3) ;
Est-ce possible ? si oui ou puis-je trouver un exemple pour faire circuler deux trains en même temps ? (j’ai essayé de nommer deux loco dans le programme mais ça plante !)
Comment changer l’adresse du deuxième décodeur (j’ai passé plusieurs nuit sur les modes d’emploi mais je n’ai pas trouvé !!!
Merci
Difficile de vous répondre si je ne connais pas les détails de la centrale DCC++ faite d’après un article de Locoduino et lequel, pour savoir ce que vous pouvez faire, notamment programmer l’adresse des locos.
Deux locos DCC ayant la même adresse ne peuvent pas être commandées individuellement. Il faudrait qu’elles aient des adresses différentes pour avoir des comportements différents. C’est le principe du DCC, chaque loco à sa propre adresse.
Bonjour,
Voici le sketch que j’utilise (ci-dessous) comment ajouter une deuxième loco ? :
de plus, j’ai bien compris qu’il faut deux adresses différentes, ma question est : comment changer l’adresse d’une loco sachant que les décodeurs sont livrés avec la même adresse ?
Merci encore de vos réponses
/********************
* Creates a minimum DCC command station from a potentiometer connected to analog pin 0,
* and a button connected to ground on one end and digital pin 4 on the other end. See this link
* http://www.arduino.cc/en/Tutorial/A...
* The DCC waveform is output on Pin 9, and is suitable for connection to an LMD18200-based booster directly,
* or to a single-ended-to-differential driver, to connect with most other kinds of boosters.
* The Differential DCC waveform is output on Pins 9 and 10.
********************/
//handle reading throttle
analog_value = analogRead(0) ;
speed_byte = (analog_value >> 2)-127 ; //divide by four to take a 0-1023 range number and make it 1-126 range.
if(speed_byte != old_speed)
if(speed_byte == 0) //this would be treated as an e-stop !
if(old_speed > 0) speed_byte = 1 ;
else speed_byte = -1 ;
Bien évidement... je n’utilise que cela.
Ouvrir DecoderPro3
Deux cas : c’est un nouvelle loco (qui a, en usine, le CV1=3)
Dans le tableau des locos (le premier écran qui arrive) on crée une fiche "faire Nouvelle en haut à gauche" et l’on indique le CV que l’on veut (c’est vrai pour tous les CV)
Autre cas : si la loco est déjà répertoriée il y a un un tableau avec tous les CV que l’on peut modifier à sa guise
Le principe est simple : chaque loco a une fiche que l’on peut modifier.
On peut véifier si cela marche en actionnant le régulateur (bas à droite sur la fiche)