Réalisation d’un va-et-vient automatique et réaliste

. Par : Dominique. URL : https://www.locoduino.org/spip.php?article232

On peut penser qu’un va-et-vient sur une voie unique sur laquelle un train circule seul dans un sens puis dans l’autre, est un projet trop simple !
Mais si on y regarde de près, il y a déjà eu sur Locoduino les articles Comment piloter trains et accessoires en DCC avec un Arduino (3) et Comment piloter trains et accessoires en DCC avec un Arduino (4) qui étaient consacrés à ce sujet pour illustrer la fabrication et l’utilisation du DCC.

Deux ans se sont écoulés depuis ces articles et l’expérience de 3 réalisations personnelles de ce va-et-vient m’amène tout naturellement à vous proposer une nouvelle version modernisée avec les dernières techniques (DCCpp en l’occurrence), très réaliste pour qui regarde circuler un bel autorail, et complètement automatique pour permettre aux présentateurs de réseau dans les salons, de discuter avec les visiteurs sans craindre un accident ferroviaire.

De surcroit, cette réalisation en Do-It-Yourself reste d’un coût très faible (environ 30€), elle constitue une mini-centrale DCC moderne, une première expérience instructive pour débuter tout en restant évolutive. Une version analogique (PWM) est possible avec le même matériel (avec une simple variante logicielle).

Enfin, ce petit réseau, du type métro à 2 gares (voire 3 avec une voie d’évitement) trouvera facilement sa place même dans un ensemble plus grand. C’est la raison de son existence dans mon club et chez moi.

Est-ce un projet pour débutant ou confirmé ?

Dans un autre article Comment réussir son projet Arduino, on vous rappelle les bonnes règles pour réussir un projet. Celui-ci sera, j’espère, un exemple concret de mise en oeuvre de ces règles :

  • Le cahier des charges est complet
  • la réalisation est découpée en parties simples
  • la mise au point comprend l’intégration des différentes parties, les contraintes de temps et de mémoire et le déverminage ( "debugging" en anglais courant).
    Cet article vous paraîtra peut-être un peu long, mais c’est pour être le plus formateur possible.

Tout d’abord le cahier des charges

Ma première démarche est de regarder ce qui se fait dans le commerce : J’y trouve des systèmes pour une alimentation analogique à des prix variant de 20 à 80€. Ils utilisent tous un découpage en 3 zones. Dans le plus simples, les zones d’extrémité sont séparées de la zone centrale par une diode de sorte que le train s’arrête d’un coté car la diode isole la zone. C’est un arrêt brutal par perte d’alimentation. Il faut inverser le sens du courant pour repartir à fond la caisse vers l’autre extrémité où se produira aussi un arrêt brutal. Ce n’est pas cela que je veux !

En montant au dela de 40€, on trouve du démarrage et du ralentissement progressif, toujours en analogique, tels que ce kit à 39,20 € [1] et ce kit à 78,95 € [2]

En regardant les notices de montage et d’emploi, j’ai trouvé des limitations gênantes comme la nécessité d’avoir des zones de ralentissement de même longueur de chaque coté, ou la distance de freinage qui dépend de la vitesse en palier, ou des réglages qui ne peuvent pas être mémorisés. Mais si ces solutions peuvent convenir pour de l’analogique, nous avons des trains et autorails dans les clubs qui sont en DCC.

Le meilleur produit que j’ai trouvé en DCC, est réalisé par Paul Trouvé d’IntégraL [3]. Il contient sa propre centrale DCC comme la réalisation décrite dans cet article et coûte un peu plus de 100 €, mais c’est une réalisation industrielle testée et garantie qui vous économise l’effort de construction. Le seul inconvénient que je lui ai trouvé est qu’il reprogramme l’adresse DCC.

Donc finalement le choix de faire soi-même un petit système intelligent, réaliste et automatique à base d’Arduino, qui inclut la centrale DCC s’est imposé facilement compte-tenu des éléments déjà décrits dans Locoduino. La facture du matériel va tourner entre 30 et 50 € selon les sources et les stocks de chacun. Avec la liberté totale de définir ce que l’on veut.

De plus, c’est un système que j’espère assez facile à comprendre et à réaliser par les débutants (avec un peu d’attention et d’expérience qui peut s’acquérir en lisant les articles de Locoduino). Il devrait aussi les encourager à aller plus loin ensuite, fort de cette expérience, sachant qu’une évolution des fonctions se limitera le plus souvent à des variantes logicielles.

Comme d’habitude sur Locoduino, cette description repose sur une réalisation et une mise au point effective (3 fois en ce qui me concerne ce qui garantit la justesse de cette description) dont voici la dernière version sur cette photo :

La réalisation matérielle

Il s’agit d’un montage sur plaque d’essai et assemblage de modules sur une plaque de plexiglass avec un câblage par soudures, coté Arduino pour avoir une excellente fiabilité. Une prise DB15 relie le montage au réseau (alimentation, rails et détecteurs de consommation). La face avant est très sophistiquée pour permettre un minimum de réglages et suivre le déroulement des différentes phases de l’automate qui est programmé dans l’Arduino, ce qui facilitera beaucoup la mise au point.

Mais revenons au cahier des charges, qui repose sur l’expérience des précédentes réalisations. Voilà ce que j’ai choisi de réaliser :

  • Le système fonctionne en DCC avec n’importe quelle locomotive, train court ou autorail sans connaitre ni changer son adresse : c’est important pour les clubs, chaque membre pouvant poser sa propre loco sans modification : Le système va trouver son adresse automatiquement et ne nécessiter aucun étalonnage des machines.
  • Le DCC permet de commander la vitesse mais aussi les fonctions de la loco comme les lumières et les sons. On pourra même ultérieurement poser 2 trains sur le va-et-vient si on ajoute une voie d’évitement avec 2 aiguilles avec talonnages pour permettre les croisements. Mais c’est une évolution future qui sera décrite plus tard.
  • A la mise en route, après reconnaissance automatique de l’adresse DCC, le train doit avancer automatiquement en marche avant à vitesse réduite jusqu’à l’une des deux gares, sans intervention humaine. Le système connait sa position et sait dans quelle sens est orientée sa marche avant.
  • Tout doit être automatique ensuite : L’attente en gare, l’accélération, le palier à vitesse constante, selon une consigne de vitesse donnée en km/h à l’échelle, le ralentissement, l’arrêt en gare opposée pile poil à quai, le changement de sens, etc..
  • Il n’y a aucun réglage sauf la consigne de vitesse en km/h à l’échelle avec un bouton rotatif genre potentiomètre. Cette vitesse sera respectée par le train après quelques aller et retour, quel que soit le type de décodeur dans la loco et son étalonnage éventuel. Il y a donc une mesure de vitesse et des asservissements de vitesse à la fois pour le palier à vitesse constante et pour les accélérations et ralentissements.
  • En DCC, il faut que ça marche avec tous les types de décodeurs et sans autre centrale (l’article publié dans LocoRevue de Janvier 2017 montre que la moitié des centrales et des décodeurs ne sont pas compatibles avec le système de ralentissement basé sur une dissymétrie du signal DCC, technique que je n’utilise donc pas ici).
  • Le système doit pouvoir être utilisé sur des modules scéniques sans boucle de retour en coulisse (3 modules minimum donc les modules extrêmes comportent une zone d’arrêt).
  • Il ne doit pas se limiter au DCC et permettre aussi l’analogique PWM. Le fonctionnement doit être aussi beau et souple qu’en DCC (sans la commande des lumières ou des sons des locos évidemment que l’on pourrait faire autrement). Ce sera l’objet d’une réalisation future et d’un nouvel article.
  • Il doit être facile à construire par les modélistes de Locoduino qui seraient prêts à se lancer dans l’aventure du DCC [4].
  • La mise au point et le suivi du fonctionnent est facilitée par l’utilisation d’un petit afficheur LCD de 2 lignes de 16 caractères qui affichent toutes les données de conduite et les états du système.
  • On profitera de la présence de cet écran LCD pour utiliser un encodeur rotatif à la place d’un potentiomètre moins fiable, moins précis et aussi cher. Il permettra de faire aussi quelques réglages comme la distance de la zone de mesure de vitesse, la durée d’arrêt en gare et l’adresse DCC par défaut en cas d’échec de la reconnaissance automatique.

Pour fonctionner de cette façon, il faut définir 2 zones de gare (chacune étant une zone arrêt immédiat en bout de quai), 2 zones latérales qui sont tantôt en accélération tantôt en ralentissement et une zone centrale (ou plus, selon la complexité de votre réseau) sur laquelle il y a une mesure de vitesse. Sur chaque zone il faut placer des détecteurs de consommation compatibles DCC (et aussi PWM si nécessaire) qui alimentent les rails à partir de ce système va et vient et retournent un signal d’occupation.

A chaque changement de zone, un détecteur de consommation (ou d’occupation) va générer un événement (un signal qui passe de HIGH à LOW) qui sera détecté par une pin de l’Arduino et le logiciel réagira en exécutant une action (un bout de programme correspondant à ce qu’il doit faire lorsque cet événement arrive). Durant la traversée des zones, il n’y a pas d’événement "détecteur" et là, c’est le temps système de l’Arduino qui agira sur la vitesse (accélération et ralentissement), par exemple toutes les secondes.

Je vais expliquer tout cela en détail ;)

Voici une vidéo d’un ralentissement avec arrêt en gare réaliste :

Schéma de la voie, des zones et des détecteurs

JPEG - 108.7 kio

On voit sur ce schéma 5 zones : gare 1, zone A, zone B, zone C, gare 2.
Un des 2 rails est coupé à la frontière de chaque zone.

JPEG - 128.1 kio

Les 5 zones sont donc alimentées séparément par l’intermédiaire d’un détecteur de consommation (ligne rouge). Lorsque le train se trouve sur une zone, le courant qui traverse le détecteur génère un signal pour l’Arduino (ligne verte).

JPEG - 186.8 kio

Le signal de la zone de gare 1 déclenche un arrêt immédiat du train, puis un temps d’attente, puis un redémarrage du train en sens inverse. Pour être réaliste, l’arrivée en gare doit se faire à toute petite vitesse donc le train doit ralentir avant d’arriver en gare. Après le temps d’attente, il redémarre à petite vitesse et accélère ensuite progressivement.

Le signal dans la zone A ou la zone C dépend du sens de circulation : vers la gare adjacente, le train doit ralentir. Dans l’autre sens il doit accélérer. Le train est détecté exactement de la même façon, qu’il entre dans cette zone par la gauche ou par la droite, donc il faut quelques variables pour décrire la position et le sens du train. Dès que le train arrive dans l’une des 2 gares après la mise en route automatique, sa position et son sens sont connus et ensuite il suffit de le suivre et le commander correctement.

L’enjeu de ce projet est de faire en sorte que le train arrive dans la zone de gare avec une vitesse très faible (une vitesse minimale que j’ai choisie égale au cran 5 DCC sur 128) correspondant à celle d’un train qui roule devant un quai de gare. Le ralentissement qui commence à l’entrée à l’autre bout de la zone adjacente doit être ajusté pour que cette vitesse minimale soit atteinte un peu avant l’entrée en gare. Ceci doit être automatique, quelque soit la vitesse du train en palier et la longueur de la zone de ralentissement : un peu d’intelligence artificielle sera mise en oeuvre pour ce faire (le mot est à la mode mais rassurez-vous, c’est plus simple que cela en a l’air) !!

Dans l’autre sens c’est une simple accélération qui se déroulera dans la zone d’accélération jusqu’à atteindre une vitesse de palier correspondant à la consigne en km/h choisie avec le bouton et affichée sur l’écran. Là aussi, l’enjeu est que le train roule bien à cette vitesse effective qui n’a rien à voir avec le cran DCC de la commande de vitesse (j’ai une seule loco dont la vitesse au cran 60 correspond à peu près à 60 km/h à l’échelle N, toutes les autres vont beaucoup plus vite ou plus lentement, c’est aléatoire !).

Il y a donc un système de mesure de la vitesse réelle du train qui repose simplement sur la mesure du temps de traversée (à vitesse constante) de la zone centrale B. On mesure sa longueur en cm qui est entrée dans la configuration en EEPROM de l’Arduino et on obtient la vitesse par un calcul simple.

Quel détecteur de consommation choisir ?

Pour le DCC seulement, j’ai construit mes propres détecteurs sur la base de ce schéma :

PNG - 173.4 kio

Les branchements aux rails se font sur les bornes 2 et 3 (flèches vertes) et la centrale sur les bornes 1 et 2 (flèches rouges) du connecteur K3.

Il est décrit sur le Forum, ici :

Ce type de détecteur fournit un signal très propre car la détection est amplifiée par un transistor qui charge un condensateur avant l’optocoupleur. Mais il est limité au DCC car il ne détecte qu’une alternance du signal. Pour le PWM il vaut utiliser ce schéma :

JPEG - 12.7 kio

Il est décrit aussi sur le Forum, ici :

Il contient un optocoupleur pour courant alternatif donc détecte les 2 alternances et, en particulier, les 2 polarités du signal PWM donc les 2 sens de circulation. Mais il nécessite un peu de filtrage par logiciel. Nous expliquerons cela plus loin.

Les composants nécessaires

JPEG - 451.9 kio

Nous allons assembler :

  • Un Arduino Nano (ou un Mini, un Uno)
  • Un module LMD18200
  • Un module Max471 pour la mesure d’intensité
  • Un LCD 2 lignes de 16 caractères avec interface I2C (présenté à l’envers sur cette figure pour montrer le circuit d’interface I2C)
  • Un encodeur rotatif
  • 2 inverseurs
  • quelques diodes Led et quelques résistances
  • Un module d’alimentation 5V

Il faut prévoir également une alimentation 12 à 15 V environ en N, 15 à 18V en HO pour l’alimentation générale du montage avec 1 à 2 A environ, soit 30VA.

Quel Arduino ?

Vu le faible nombre de fils à connecter, un simple Nano ou un Uno suffira.

On trouvera ici un schéma très détaillé du brochage du Nano
Cet Arduino aura à assurer les tâches suivantes :

  • Produire le courant d’alimentation DCC pour les rails avec les composants additionnels classiques représentés sur ce schéma (le pont en H LMD18200, le détecteur de courant Max 471 et un régulateur 5V pour réduire l’alimentation générale de 15V commune à l’ensemble du montage, à 5V pour l’Arduino) :
La centrale Arduino

On y voit également l’afficheur LCD relié au bus I2C de l’Arduino.

On a ici le coeur d’une centrale DCC qui peut faire beaucoup de choses.

Le système contient en plus des liaisons aux détecteurs de consommation et quelques composants pour la configuration : régler la durée d’arrêt, la longueur de la zone B centrale et l’adresse DCC par défaut qui sera choisie par le système au cas où l’adresse de la loco n’est pas reconnue (ça arrive parfois, à cause de mauvais contacts ou de décodeur exotique).

A part quelques diodes électroluminescentes (j’utilise une DEL rouge pour indiquer un état d’erreur) et 2 interrupteurs ou inverseurs (pour un arrêt-marche et une commande de lumière), qui sont bien connus de tout le monde, j’utilise un bouton du genre "potentiomètre" pour définir des valeurs numériques.

Tout le monde connait le potentiomètre présenté dans l’article 61.
Après de nombreuses réalisations avec des vrais potentiomètres, j’ai décidé de m’en passer car il ne sont pas très stables : la valeur change même sans intervention manuelle, varie avec la température, etc..

J’ai préféré utiliser un encodeur quadratique présenté dans l’article 82 qui a l’avantage de la stabilité et donne une valeur indépendante d’une résistance. Il ne compte que des crans en + ou en -. De plus, il contient un bouton poussoir intégré que j’utilise pour valider un choix de valeur.

Le montage des éléments du système

Chacun fera comme il préfère.

On peut placer côte à côte des éléments et les maintenir par des vis sur une planchette en bois. Le Nano peut-être monté sur un support avec borniers à vis ce qui permet de relier les éléments sans soudure. Moi, j’ai préférer souder des fils sur l’Arduino pour une meilleure fiabilité. Mais il faut réussir les soudures !

Les connexions externes de cette centrale peuvent être regroupées sur une prise DB15 à 15 points sur laquelle on trouvera :

  • l’entrée 15V (+ et -)
  • la sortie DCC (2 fils)
  • les 5 capteurs avec leur masse commune (10 paires torsadées dont les Gnd sont regroupés 2 à 2 pour économiser les connecteurs de la DB15)

En ce qui concerne la face avant, j’ai utilisé un morceau de pexiglass blanc, dans lequel j’ai fait une découpe rectangulaire pour l’afficheur Lcd et de simples trous cylindriques pour l’encodeur, les interrupteurs/inverseurs et les Leds.

Le câblage de la face avant se fait en fils volants entre les composants. J’ai utilisé des morceaux de nappes multibrins pour relier la face avant avec le coeur de la centrale, ce qui évite aux fils de s’emmêler et ce type de nappe est très robuste.

Il faut tout particulièrement soigner la qualité des soudures.

La rangée de leds sous l’écran n’est pas nécessaire mais elle permet de vérifier les occupations de zone : les leds d’occupation sont reliées directement aux détecteurs à travers une résistance d’1kΩ et ne consomment aucune broche de l’Arduino.

Les 2 leds tête-bêche jaunes protégées par une résistance de 1,5KΩ sont reliées directement à le sortie DCC et permettent de voir quand il est activé.

Les leds marche-arrêt et lumière sont aussi optionnelles.

Vue coté câblage :

Coté câblage

Vue de face :

Vue de face

Le branchement des détecteurs

Le schéma suivant permet est simple :

JPEG - 204.2 kio

Les sorties "collecteur ouvert" des optocoupleurs des détecteurs sont reliées par une paire torsadée à l’Arduino. La masse (Gnd) et le 5V sont communs aux détecteurs, de sorte que 6 fils suffisent au minimum.
Les leds sur la face avant sont protégées par une résistance unique puisqu’un seul détecteur est actif à la fois (sauf au passage d’une zone à l’autre, mais un court instant).

Le principe de fonctionnement

Il faut se reporter au cahier des charges en début de cet article pour bien comprendre ce que le montage doit faire. Tout tourne autour de la détection des zones et d’une base de temps. Cela s’appelle un Automate.

L’automate est composé de 2 parties :
1) une détection spatiale avec des détecteurs de consommation qui déterminent des zones : gare 1, zone A, zone B, zone C, gare 2. Les gares sont juste des zones d’arrêt immédiat qui déterminent l’arrêt, la tempo d’arrêt et le sens du départ suivant vers l’autre gare. Depuis la gare 1, il y a une accélération puis un palier à vitesse constante jusqu’à la zone C qui déclenche un ralentit jusqu’à la gare 2. Idem en sens inverse avec ralentissement à partir de le zone A. La traversée de la zone B permet de mesurer la vitesse (constante) qui doit être égale à une consigne en km/h (la loco n’est pas étalonnée, il faut donc un asservissement sur la vitesse de consigne). Les zones A et C ne sont pas forcément égales mais la loco doit adapter son ralentissement à chaque longueur : c’est automatique. La vitesse en palier est réglable par un encodeur rotatif qui règle la consigne en km/h à l’échelle. La loco doit se réguler automatiquement sur cette vitesse à partir de la mesure de vitesse. Ça se régule tout seul en plusieurs aller et retour !

2) il y a en plus une base de temps de période 1 seconde qui régit les accélérations et ralentissements ainsi que les arrêts en gare.

Les détecteurs de consommation régissent les états de l’automate et les actions de la base de temps en fonction de variables d’état, c’est à dire qui décrivent l’état courant du système : un événement détecteur provoque un changement d’état. De même l’écoulement d’une seconde provoque aussi une action ou un changement d’état.

L’automate pilote du DCC par des ordres de type « roule, vitesse, sens, lumière ».

ORGANIGRAMME

Voilà un résumé de l’automate : il y a 6 phases possibles auxquelles j’ai donné des noms dans un Enum : INIT, EXPLORE, ARRET, ACCELERE, PALIER et RALENTI.

JPEG - 146.6 kio
  • INIT : uniquement au démarrage du système, la machine doit être posée ailleurs que sur les zones de gare pour permettre de découvrir l’adresse DCC et de trouver une gare en marche avant dans la phase EXPLORE.
  • EXPLORE : faire rouler en marche avant (vitesse 30) jusqu’à détecter l’arrivée sur gare 1 ou gare 2. On aura alors un arrêt immédiat de la machine (voire un arrêt sur time-out si le machine ne bouge pas suite à un mauvais contact).
  • ARRET : initialiser le trajet (gare 1->2 ou gare 2->1 selon le point de départ et marche arrière/avant) et démarrer une tempo d’arrêt en gare puis l’attente de fin de tempo d’attente en gare.
  • ACCELERE : accélération en marche arrière/avant jusqu’à vitesse de consigne. Initialement le cran de consigne est égal à la valeur lue avec le bouton, puis le calcul de correction se réalisera à chaque passage en zone B, par un incrément linéaire avec périodicité constante (le but est d’arriver à la vitesse de consigne avant la zone centrale).
  • PALIER : la vitesse en palier doit être égale à la consigne fixée par l’encodeur, jusqu’àu détecteur de zone A ou C. A l’entrée dans la zone B il y a armement d’une tempo de mesure du temps de traversée de la zone B, puis calcul de vitesse réelle en km/h à l’ échelle N (à adapter pour le HO ou une autre échelle).
    RALENTI : décélération jusqu’à une vitesse minimale (vitesse réaliste avant arrêt à quai) ou détecteur gare2 ou gare1, par décélération linéaire avec décrément variable et calcul de correction du décrément :
    - Si la vitesse min est atteinte avant la gare : mesure du temps jusqu’à la gare et correction de la décélération (diminution du décrément).
    - Si la gare est atteinte avant la vitesse min : correction de la décélération (augmentation du décrément).
  • ARRET : initialiser le sens du trajet et démarrer une tempo d’arrêt en gare

Puis ACCELERE, PALIER, RALENTI, ARRET, ...

Le logiciel

Le logiciel est composé, comme tous les logiciels Arduino,

  • d’une partie "déclarations" où sont définies les connexions aux broches de l’Arduino, les bibliothèques utilisées, les structures, objets, énumérations, constantes et variables globales, ainsi que des fonctions utilitaires.
  • de la fonction setup() qui réalise les initialisations
  • de la boucle infinie loop() qui déroule le fonctionnement du système sous forme de tâches successives qui se répètent.

L’enchainement des tâches de la loop() et leur temps d’exécution est donc très important. Il faut bien comprendre que ces tâches ne doivent pas bloquer le processeur et que leur effet va influer sur d’autres tâches.

Etant donné le grand nombre de fonctions qui composent ce logiciel, je vais décomposer sa description en expliquant chaque fonction séparément, en regroupant les parties déclaratives, du setup et de la loop, pour bien comprendre ce qu’elle fait exactement. Le code complet est donné en fin d’article pour vous éviter de devoir tout reconstruire à partir des morceaux de programme présentés.

Il est bien évident qu’il faudra adapter ce code à votre matériel si votre réalisation diffère de la mienne.

Les affectations des Pins du Nano

C’est juste un commentaire bien pratique que j’inscris toujours dans un programme Arduino pour me remémorer les connexions des éléments autour de l’Arduino. Si je dois faire de la maintenance plus tard, c’est une mini-documentation écrite au bon endroit !

 
/*
Affectation des Pins du NANO sur NanoVV perso
Pin 2   <-> ENC1 encodeur pin 1
Pin 3   <-> PWM (enable, HIGH = marche, LOW = stop) pour LMD18200
Pin 4   <-> ENC2 encodeur pin 2
Pin 5   <-> detecteur Gare 1
Pin 6   <-> detecteur zone A
Pin 7   <-> detecteur zone B
Pin 8   <-> detecteur zone C
Pin 9   <-> detecteur Gare 2
Pin 10  <-> DIR (DCC = OC1B output) pour LMD18200
Pin 11  <-> Led Rouge ERREUR
Pin 12  <-> non utilisée 
Pin 13  <-> non utilisée
Pin A0  <-> Mesure de courant du Mac471
Pin A1  <-> bouton OK (encodeur)
Pin A2  <-> inter marche/arret
Pin A3  <-> inter lumière
Pin A4  <-> SDA LCD
Pin A5  <-> SLC LCD
Pin A6  iibre
Pin A7  libre
*/
Les bibliothèques utilisées et un numéro de version

Le numéro de version est indispensable pour s’y retrouver en cas d’évolutions.
Il est doublé ici avec un numéro (300) et un texte (DCCpp_VV_200118) qui regroupe l’emploi de DCCpp pour un Va-et-Vient et la date de compilation du logiciel. Dans les futures versions il faudra penser à incrémenter le numéro de version (301, 302, ...) et mettre à jour la date.

L’étiquette DEBUG sert à compiler une version qui affiche quelques informations sur l’écran du terminal, dont le numéro de version. On se sert du Debug en décommentant cette ligne.

 
//#define DEBUG

const char Version[16] =  "DCCpp_VV_200118"; // 15 caractères + \0
const int EEPVERSION = 300;

#include <DCCpp.h> 
#include "Automate.h"
#include <EEPROM.h>
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>  
#include "Configuration.h"

Ici les bibliothèques utilisées sont DCCpp, EEPROM, Wire, LiquidCrystal_I2C et Encoder.
Automate.h et Configuration.h sont des fichiers qui se trouvent dans le même dossier que le programme DCCpp_VV_300. Il apparaissent dans 2 onglets à coté du programme. C’est juste une manière de présenter le programme dans plusieurs fenêtres plus courtes au lieu d’une seule fenêtre trop longue. On gagne du temps !

La gestion de l’EEPROM : une structure et des fonctions.

L’EEPROM intégrée va servir à stocker quelques paramètres dépendant de votre réseau et de vos souhaits de circulation :

 
struct ConfigEEprom {
  unsigned int vers;              // version logiciel
  unsigned long dist;             // = DISTANCE_MODULE en cm
  int arret;                      // = ARRETENGARE  en secondes
  int adresse;                    // = 3; ADRESSE DCC par DEFAUT
};
// Une seule variable locale
ConfigEEprom eeConfig;

Cette variable se décompose donc en 4 parties :
eeConfig.vers contient la valeur 300 (EEPVERSION citée plus haut)
eeConfig.dist contient la distance en cm de la zone B pour faire les calculs de vitesse.
eeConfig.arret contient la durée d’arrêt en gare
eeConfig.adresse contient l’adresse DCC utilisée en cas d’échec de la reconnaissance automatique : ça arrive en cas de mauvais contact de la loco sur les rails ou avec certains décodeurs bas de gamme (rares).

L’écran LCD

J’ai choisi une modèle avec interface I2C qui n’utilise que 2 broches du Nano (SDA et SCL) sur lesquelles peuvent se raccorder plusieurs périphériques I2C.
Le modèle avec 2 lignes de 16 caractères s’avère suffisant pour ce projet.
Si vous préférez un modèle plus grand, il faudra revoir les coordonnées d’affichage sur cet écran.

Pour l’utiliser il faut déclarer une variable globale lcd en précisant en argument l’adresse I2C et les nombres de caractères par ligne et de ligne :

LiquidCrystal_I2C lcd(0x3F,16,2);  // address 0x3F pour un ecran de 16 car. sur 2 lignes

L’encodeur

L’encodeur rotatif est déclaré aussi avec une variable globale myEnc (j’ai repris par copier-coller l’exemple fourni avec la bibliothèque) dans laquelle sont fournis les broches de l’Arduino utilisées :

 
Encoder myEnc(2,4);             
long oldPosition  = -999;       // encoder

Si les valeurs de l’encodeur augmentent ou diminuent à l’envers par rapport au sens de rotation, il suffit d’inverser ces 2 arguments pour le faire tourner à l’endroit !
La variable oldPosition sert à comparer la valeur lue à un instant avec la valeur lue précédemment.

On lit la position de l’encodeur avec l’instruction
long newPosition = myEnc.read()/4;
La division par 4 est nécessaire car chaque cran de l’encodeur correspond à 4 unités en plus ou en moins, du fait de la structure interne de l’encodeur (voir l’article 82)

La bibliothèque DCCpp

Elle est appelée dans #include <DCCpp.h>. Ensuite il faut déclarer les options de DCCpp (voir sa documentation intégrée dans la bibliothèque).
Pour agir sur les fonctions de la loco, il faut déclarer une variable structurée gLocoFunctions de type FunctionsState.

 
#define MOTOR_SHIELD_TYPE   0      // LMD18200 
#define COMM_INTERFACE   0
#define LMD_DIR 10                 // DCC_MAIN -  DIR LMD18200
#define LMD_PWM 3                  // DCC_ENABLE = PWM LMD18200
#define Max471  A0                 // current sensor
#define gDCCStepsNumber 128        // mode DCC 128

FunctionsState gLocoFunctions;     // Current functions of the loco
Le démarrage de DCCpp dans setup()

Juste 2 lignes sont nécessaires : on ne peut pas faire plus simple !

 
  DCCpp::begin();
  DCCpp::beginMain(UNDEFINED_PIN, LMD_DIR, LMD_PWM, Max471);
Les définitions des pins et les constantes

Il faut maintenant déclarer les broches utilisées de l’Arduino et les constantes utilisées par le programme :

 
#define  BOK                A1    // bouton OK
#define  STARTSTOP          A2    //inter Marche / Arret
#define  LUMIERE            A3    //inter FEUX
#define  PINGARE1           5     //capteur input
#define  PINZONEA           6     //capteur input
#define  PINZONEB           7     //capteur input
#define  PINZONEC           8     //capteur input
#define  PINGARE2           9     //capteur input
#define  LEDROUGE           11    //led rouge output
#define  ENC1               2     //encoder input
#define  ENC2               4     //encoder input

#define  ZONE_SAMPLE_TIME     100       // ms
#define  DEBOUNCE_SAMPLE_TIME 100       // ms
#define  AUTOMATE_TIMEOUT     120000    // 2 minutes
#define  DISTANCE_MODULE      120       // cm
#define  VITESSE_MIN          10        //cran

#define  ARRETENGARE        50    // x0,1s soit 5 secondes
#define  ACCELERATION       5     // incrément de cran de vitesse
#define  DECELERATION       5     // décrement de cran de vitesse

Définitions de l’Automate

Pour réaliser cet automatisme j’utilise 3 structures :

  • une structure Train qui contient les variables et les fonctions décrivant les comportements du train
  • une structure ZoneMonitor qui contient les définitions, les variables et les fonctions des 5 zones Gare 1, A, B, C et Gare 2 qui forment la partie liée à l’espace.
  • une structure Automate qui contient les variables et les fonctions de la partie liée au temps.
Les définitions et déclarations de la structure Train et des automates (temps et zones)

Je déclare donc 3 variables globales mTrain, mzoneMonitor et mAutomate qui sont des structures :

 
Train mTrain(3);                   //create train adresse 3
ZoneMonitor mzoneMonitor(PINGARE1, PINZONEA, PINZONEB, PINZONEC, PINGARE2); // create monitor for zone detectors
Automate mAutomate(0);             // creation initialisation automate

Voyons le détail de ces structures :

Les définitions et fonctions de commande du train

La structure Train regroupe tout ce qui est nécessaire pour décrire un train.
Ensuite il y a son constructeur (l’initialisation de la structure), puis le code des fonctions roule, stoppe, feux, allume et éteint.

struct Train{
  int adresse_dcc;            // pour la commande DCC
  bool sens1vers2;            // true si gare 1-> gare 2
  bool sens;                  // pour la commande DCC
  bool lumiere;               // pour la commande DCC
  bool troplent;              // true si Vmin atteinte pendant RALENTI
  int zone;                   // la zone ou il est
  int cran_vitesse;           // cran DCC en cours
  int cran_consigne;          // cran DCC à la consigne
  int consigne;               // en km/H au potar et LCD
  int vitesseVraie;           // en km/h
  int acceleration;           // en crans
  int deceleration1;          // en crans vers gare 1
  int deceleration2;          // en crans vers gare 2
  Train(int);
  void roule();               // fonction pour avancer/reculer
  void stoppe();              // fonction pour s'arreter
  void feux(bool);            // commande des lumieres
  void eteint();
  void allume();};

Train::Train(int a) {
  adresse_dcc = a;
  sens = true;
  cran_vitesse = 30;
  lumiere = false;
  acceleration = ACCELERATION;
  deceleration1 = DECELERATION;
  deceleration2 = DECELERATION;
  cran_consigne = 60;
}

void Train::roule() { // t1 adresse_dcc vitesse
  DCCpp::setSpeedMain(1, adresse_dcc, 128, cran_vitesse, sens);
}

void Train::feux(bool l) {
  if (l) {
    gLocoFunctions.activate(0);
  } else {
    gLocoFunctions.inactivate(0);
  }
  DCCpp::setFunctionsMain(1, adresse_dcc, gLocoFunctions);
}

void Train::stoppe() {
  cran_vitesse = 0;
  roule();    
}

void Train::eteint() {
  lumiere=false;
  feux(lumiere);
}

void Train::allume() {
  lumiere=true;
  feux(lumiere);
}

Ce sont dans ces fonctions roule() et feux() et seulement ici, que l’on trouve les appels des fonctions de DCCpp pour commander la loco en DCC.

Les énumérations

Nous trouvons ici les énumérations des phases de l’automate, des zones de voie, ainsi que les noms des zones qui serviront à l’affichage.

 
enum {              // nom des états
  INIT=0,
  EXPLORE=1,
  ARRET=2,
  ACCELERE=3,
  PALIER=4,
  RALENTI=5
};

int gETAT = 0;      // etat de l'automate

enum {
  ZGARE1=0,
  ZONEA=1,
  ZONEB=2,
  ZONEC=3,
  ZGARE2=4
};

const char * nomZone[ZGARE2+1]  = {     // noms zones sur LCD
  "Gar1 ",
  "ZonA ",
  "ZonB ",
  "ZonC ",
  "Gar2 "
};
Les définitions de l’automate

On y trouve les variables décrivant l’automate : son état (parmi six valeurs), un certain nombre de durées en millisecondes et deux fonctions : le constructeur et la base temps d’une seconde, avec en plus une fonction de clignotement de la led rouge.

 
struct Automate{
  int auto_etat;                  // INIT, EXPLORE, ARRET, ACCELERE, PALIER, RALENTI
  unsigned long arretengare;      // decompteur temps d'arret en gare en ms
  unsigned long timeout, timeseconde; // timecheck
  unsigned long timeLR;           // tempo clignotement Led Rouge
  bool auto_seconde;              // tempo seconde écoulée
  bool auto_ERR;                  // erreur automate
  bool LRclignote;                // true = Led Rouge clignotante
  // fonctions
  Automate(int);
  void runauto();
};

Automate::Automate(int ae) {
  auto_etat = INIT;
  timeseconde=millis();
  timeLR=millis();
  arretengare = ARRETENGARE;     // tempo d'arret en gare configurable
  auto_ERR = false;
  LRclignote = false;
}

void Automate::runauto() {
  if (millis()-timeLR > 500) {  // clignotement LED ROUGE
    if (LRclignote) {  // clignotement LED ROUGE
      digitalWrite(LEDROUGE, !digitalRead(LEDROUGE));
      timeLR = millis();
    }
  } 
  if (millis()-timeseconde > 1000) { // base de temps seconde
    auto_seconde = true;
  }
  if (millis()-timeout > AUTOMATE_TIMEOUT) { // time-out
    auto_ERR = true;
  }
}
Les définitions des zones

Pour les zones, on a besoin de durées dans certaines zones (que nous verrons plus loin), des broches affectées aux détecteurs de consommation, d’un indicateur d’occupation, chaque zone occupant un bit de la variable. On trouve ensuite le constructeur, une fonction d’initialisation à appeler dans le setup(), une fonction de test du temps pour réaliser les détections de manière récurrente, mais pas à toutes les loop() et la fonction de test des occupations qui va mettre à jour la variable occupation :

 
struct ZoneMonitor {
  unsigned long zoneTime, zoneTimeA, zoneTimeAG1, zoneTimeCG2, zoneTimeC;
  int pin1, pinA, pinB, pinC, pin2;
  byte occupation;
  ZoneMonitor(int, int, int, int, int);
  void Init();
  bool checkZoneTime();
  byte checkZone();
};

ZoneMonitor::ZoneMonitor(int pin1, int pina, int pinb, int pinc,int pin2){
    this->pin1=pin1;
    this->pinA=pina;
    this->pinB=pinb;
    this->pinC=pinc;
    this->pin2=pin2;
    occupation = 0;
} // ZoneMonitor::ZoneMonitor

void ZoneMonitor::Init() {
    pinMode(pin1,INPUT_PULLUP);
    pinMode(pinA,INPUT_PULLUP);
    pinMode(pinB,INPUT_PULLUP);
    pinMode(pinC,INPUT_PULLUP);
    pinMode(pin2,INPUT_PULLUP);
    bitWrite(occupation, ZGARE1, !digitalRead(pin1)); // LOW = occupé
    bitWrite(occupation, ZONEA, !digitalRead(pinA)); // LOW = occupé
    bitWrite(occupation, ZONEB, !digitalRead(pinB)); // LOW = occupé
    bitWrite(occupation, ZONEC, !digitalRead(pinC)); // LOW = occupé
    bitWrite(occupation, ZGARE2, !digitalRead(pin2)); // LOW = occupé    
    zoneTime = millis();  
}

bool ZoneMonitor::checkZoneTime() {
  if (millis()-zoneTime < ZONE_SAMPLE_TIME)    // test préiodique
    return(false);
  zoneTime = millis();   
  return(true);  
} // ZoneMonitor::checkTime

byte ZoneMonitor::checkZone() {      // détection de changement d'état
  bitWrite(occupation, ZGARE1, !digitalRead(pin1)); // LOW = occupé -> un bit 1
  bitWrite(occupation, ZONEA, !digitalRead(pinA));
  bitWrite(occupation, ZONEB, !digitalRead(pinB));
  bitWrite(occupation, ZONEC, !digitalRead(pinC));
  bitWrite(occupation, ZGARE2, !digitalRead(pin2));
  return(occupation);
}
Les fonctions de configuration

Ces fonctions sont destinées à être appelées très rarement, voire une seule fois ; pour mettre à jour une petite zone de l’EEPROM avec ces valeurs :

  • la version du logiciel,
  • la longueur de la zone B
  • le temps d’arrêt en gare
  • l’adresse DCC par défaut

Ce sous-ensemble du programme est placé dans l’onglet "Configuration.h" pour alléger la fenêtre principale du programme.

On y trouve d’abord un utilitaire d’affichage qui sert à formater l’affichage d’une valeur sur 1, 2 ou 3 chiffres.
Puis un autre utilitaire qui sert à lire la position de l’encodeur.
Ensuite la fonction configuration() qui sera appelée dans le setup().
Cette fonction attend une succession d’appuis sur le bouton de l’encodeur et de rotations de l’encodeur pour renseigner les 3 valeurs (hors version logicielle) et les enregistrer en EEPROM.
Bien évidemment ; pendant l’exécution de ce code, l’Arduino ne peut rien faire d’autre à cause des boucles do-while. Elle est donc utilisable seulement une seule fois au démarrage de setup().

 
///////////////////////////////////////////////////////////////////////
// FONCTION DE CONFIGURATION APPELÉE UNIQUEMENT AU DÉMARRAGE EN MAINTENANT LE BOUTON APPUYÉ
///////////////////////////////////////////////////////////////////////

void afficheLCD(int val) {
    lcd.setCursor(8,1);
    if (val < 100) lcd.print(" ");
    if (val < 10) lcd.print(" ");
    lcd.print(val);
}

int configure(int val) {
  afficheLCD(val);
  do {
  long newPosition = myEnc.read()/4;
    if (newPosition != oldPosition) {  
      if ( newPosition > oldPosition) val++;  else  val--;
      oldPosition = newPosition; 
      afficheLCD(val);
    }
  } while (digitalRead(BOK) == true);    // attente appui sur bouton
  while (digitalRead(BOK) == false) {delay(100);} // attente relachement bouton
  return(val);
}

// Configuration de la distance de la zone de mesure de vitesse, de la durée d'arret en gare 
// et de l'adresse DCC par défaut en cas de non reconaissance de l'adresse

void configuration() {
  int valeur;
  
  EEPROM.get(0, eeConfig);              // lecture record eeConfig en EEPROM
  #ifdef DEBUG
  Serial.println(eeConfig.vers);
  Serial.println(eeConfig.dist);
  Serial.println(eeConfig.arret);
  Serial.println(eeConfig.adresse);
  #endif
  if (eeConfig.vers == 0xFFFF) {        // si l'EEPROM est vierge, on écrit les valeurs par défaut
    eeConfig.dist = DISTANCE_MODULE;
    eeConfig.arret = ARRETENGARE;
    eeConfig.adresse = 3;
    eeConfig.vers = EEPVERSION;
    EEPROM.put(0, eeConfig);
  }
  if (digitalRead(BOK) == false) {      // mode configuration en appuyant sur BOK au démarrage
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("v");                     // affichage version "v300"
    if (eeConfig.vers < 100) lcd.print(" ");
    if (eeConfig.vers < 10) lcd.print(" ");
    lcd.print(eeConfig.vers);
    ///////////////////////// configuration distance zone C ou D 
    lcd.setCursor(0,0); 
    lcd.print(F("Distance zone "));
    while (digitalRead(BOK) == false) {delay(100);} // attente relachement bouton
    valeur = eeConfig.dist;             // valeur initiale
    if (valeur > 600) valeur = 600;
    eeConfig.dist = configure(valeur);
    ///////////////////////// configuration durée arret en gare   
    lcd.setCursor(0,0);
    lcd.print(F("Duree arret gare"));
    valeur = eeConfig.arret;
    if (valeur > 600) valeur = 600;
    eeConfig.arret = configure(valeur);
    ///////////////////////// configuration adresse DCC par défaut   
    lcd.setCursor(0,0);
    lcd.print(F("Adresse DCC     "));
    valeur = eeConfig.adresse;
    if (valeur > 500) valeur = 500;
    eeConfig.adresse = configure(valeur);
    ////////////////////////
    lcd.setCursor(0,0);
    lcd.print(F("Enregistrement.."));
    EEPROM.put(0, eeConfig);  
    delay(1000);    
  }
}
Les variables globales

Pour le fonctionnement du programme, il s’avère nécessaire de déclarer quelqu’autres variables globales :

unsigned long gCurrentSampleTime;
unsigned long gCurrentNulTime;
int gCurrent;
const int CurrentMax = 400;

unsigned long compteurVitesse;
bool gMesureVitesse=false;
bool gEtatFeux=false;          // pas de FEUX
bool gEtatStartStop=false;    // en ARRET
int  gconsigne=60; 
byte gOccupation;

Les premières concernent la mesure de courant, sa récurrence (gCurrentSampleTime), sa disparition (gCurrentNulTime), sa valeur et le seuil de détection d’un court-circuit.

Les suivantes concernent le calcul de vitesse, les états des inverseurs de feux et Start/Stop, la consigne de vitesse (gConsigne), l’état des occupations (gOccupation).

Des fonctions d’affichages sur LCD

Pour l’affichage LCD, il est très pratique d’écrire une fonction d’affichage des paramètre courants, ainsi que quelques indicateurs d’erreur eventuelle :

Sur cette image, la 1ère ligne affiche, de gauche à droite :

  • l’adresse DCC
  • l’etat de l’automate "a" suivi du numéro d’état
  • le cran de la vitesse de consigne
  • le sens et la lumière
    JPEG - 90.7 kio

    Sur la 2ème ligne, de gauche à droite :

  • le nom de la zone traversée
  • la consigne de vitesse en km/h
  • la vitesse réelle mesurée au passage dans la zone B
  • l’intensité consommée par la machine en mA.
 
void lcdloco() {
  lcd.setCursor(8,0);
  if (mTrain.cran_vitesse < 100) lcd.print(" ");
  if (mTrain.cran_vitesse < 10) lcd.print(" ");
  lcd.print(mTrain.cran_vitesse); // cran
  if (mTrain.sens1vers2) {
    lcd.print(" >> ");
  } else {
    lcd.print(" << ");
  }
  if (mTrain.lumiere) {
    lcd.print("*");
  } else {
    lcd.print(" ");
  }
  lcd.setCursor(0,1);
  lcd.print(nomZone[mTrain.zone]);
  //lcd.setCursor(5,1);
  if (gconsigne < 100) lcd.print(" ");
  if (gconsigne < 10) lcd.print(" ");
  lcd.print(gconsigne);
}

void lcdETAT(char E) { // lcdETAT('a');
  lcd.setCursor(4,0);
  lcd.print(E);     // error char ? a T
}

void lcdERR(char E) { // lcdERR('+');
  lcd.setCursor(7,0);
  lcd.print(E);     // error char - +
}

void lcdCR(int n) { // compte à rebours
  lcd.setCursor(4,0);
  if (n < 100) lcd.print(" ");
  if (n < 10) lcd.print(" ");
  if (n == 0) lcd.print(" "); else lcd.print(n);
}

Maintenant voici quelques fonctions utilitaires :

La fonction de mise en route du train au démarrage

Cette fonction est appelée une fois au démarrage dans l’état INIT. C’est là que l’adresse DCC est recherchée par une seule fonction de DCCpp, identifyLocoIdMain() qui va réaliser plusieurs lectures de CVs :

  • tester le bit 5 du CV 29 qui indique si c’est une adresse courte ou longue,
  • lire le CV 1 si c’est une adresse courte,
  • lire des CVs 17 et 18 si c’est une adresse longue.
    En cas d’échec la valeur est récupérée depuis l’EEPROM.
    Ensuite la fonction met le train en marche, au cran 30, en marche avant.
 
void MiseEnRoute() {
  start_DCC();
  int reponse = DCCpp::identifyLocoIdMain();
  #ifdef DEBUG
  Serial.print(F("adresse="));Serial.print(reponse);
  #endif
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(reponse);
  if (reponse == -1) {
    #ifdef DEBUG
    Serial.println(F(" >>> erreur !")); 
    #endif
    lcdETAT('?');
    mAutomate.LRclignote=true;      // clignotement Led Rouge dans mAutomate.run()
    mTrain.adresse_dcc = eeConfig.adresse;  
    delay(500);
  } else {
    #ifdef DEBUG
    Serial.println(F(" valide :-)"));
    #endif
    mAutomate.LRclignote=false;
    mTrain.adresse_dcc = reponse;
  }
  mTrain.cran_vitesse = 30;                  // vitesse 30, avant jusqu' à une gare
  mTrain.sens = true;
  mTrain.roule();
}
Deux petites fonctions de démarrage et d’arrêt du DCC

Ces 2 fonctions mettent en ou hors service le DCC.

 
void start_DCC() {
  digitalWrite(DCC_ENABLE,HIGH);
  digitalWrite(LEDROUGE, HIGH);             // eteint led rouge
  lcd.setCursor(0,1);lcd.print("DCC on          ");
}

void stop_DCC() {
  digitalWrite(DCC_ENABLE,LOW);
  digitalWrite(LEDROUGE, LOW);              // allume led rouge
  lcd.setCursor(0,1);lcd.print("DCC off         ");
  delay(1000);
}

Le bibliothèque DCCpp permet aussi de faire la même chose avec les fonctions DCCpp::powerOn() et DCCpp::powerOff().
J’ai préférer agir directement sur la pinDCC_ENABLE ce qui est plus rapide.

Maintenant nous avons tous les éléments pour écrire la fonction setup().

La fonction SETUP

Elle comprend évidemment l’initialisation :

  • du port série (pour le debug),
  • du LCD,
  • de DCCpp,
  • des broches affectées aux leds, inters et détecteurs de zones,
  • des paramètres en EEPROM avec configuration ou non,
  • des variables globales et états de l’automate.
 
void setup(){  

  #ifdef DEBUG
  Serial.begin(115200);           // configure serial interface
  //while (!Serial) ;             // wait for Arduino Serial Monitor
  Serial.flush();
  Serial.println(Version);
  #endif

  lcd.init();                      // initialize the lcd 
  lcd.clear();
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print(Version);
  
  lcd.setCursor(0,1);
  lcd.print("Demarre DCC ");      // 12
  DCCpp::begin();
  DCCpp::beginMain(UNDEFINED_PIN, LMD_DIR, LMD_PWM, Max471);        // Dc: Dir, Pwm, current sensor

  pinMode(STARTSTOP, INPUT_PULLUP);
  pinMode(LEDROUGE, OUTPUT);
  digitalWrite(LEDROUGE, HIGH);
  pinMode(LUMIERE, INPUT_PULLUP);
  pinMode(BOK, INPUT_PULLUP);

  pinMode(PINGARE1, INPUT_PULLUP);
  pinMode(PINZONEA, INPUT_PULLUP);
  pinMode(PINZONEB, INPUT_PULLUP);
  pinMode(PINZONEC, INPUT_PULLUP);
  pinMode(PINGARE2, INPUT_PULLUP);
  
  oldPosition = myEnc.read()/4;
  
  delay(500);
  digitalWrite(LEDROUGE, LOW);

  // Lecture de la configuration en EEPROM
  configuration();          // Configuration EEPROM lecture et ecriture si BOK appuyé

  mzoneMonitor.Init();      // init zoneMonitor

  // lecture de l'état de l'inter A/M

  gEtatStartStop = !digitalRead(STARTSTOP); // start = LOW donc faut inverser
  if (gEtatStartStop) {         // position MARCHE, automate en attente de synchro en gare
    MiseEnRoute();              // DCC on et roule vitesse = 30
    mAutomate.auto_etat = EXPLORE; // init automate : exploration ligne 
  } else {                      // position ARRET
    mAutomate.auto_etat = INIT; // init automate : en attente découverte et exploration ligne    
    mTrain.adresse_dcc = eeConfig.adresse;
    mTrain.cran_vitesse = 0;    // vitesse = 0
    mTrain.sens = true;
    lcd.clear();
  }
  gconsigne = 60;             // valeur initial de la consigne de vitesse et de l'encoder
  mTrain.consigne = gconsigne;
  mTrain.cran_consigne = gconsigne;

  // lecture de l'état de l'inter LUMIERE
  
  gEtatFeux = !digitalRead(LUMIERE);
  if (gEtatFeux) {            // true = allumé
    mTrain.allume();          // lumiere ON
  } else {                    // false = eteint
    mTrain.eteint();          // lumiere OFF
  }
  lcd.setCursor(0,0);
  lcd.print(mTrain.adresse_dcc);// ligne 0 : adresse
  lcd.print(" ");               // effacement  du "1" en cas d'erreur
  lcdloco();                    // ligne 1 : vitesse, sens, lumiere et consigne
  
  // initialisation des états de l'automate
  mAutomate.auto_ERR = false;
  mAutomate.timeout = millis();          // armement de la tempo "timeout"
  mzoneMonitor.zoneTime = millis();
  if (mAutomate.auto_etat == EXPLORE) mTrain.roule();

} // setup

Voici maintenant le coeur du programme

La fonction loop

Elle donne la main à DCCpp via sa fonction loop() pour qu’il réalise les mises à jour internes nécessaires. Il y en a très peu dans notre cas.

 
void loop() {
  
  DCCpp::loop();

  /////// autres tâches ci-dessous 

} // loop
Gestion du courant DCC

La mesure du courant sert avant tout à détecter les court-circuits, pour arrêter le DCC si c’est le cas.
La mesure est affichée sur le LCD.
Une surveillance de l’alimentation est faite aussi pour détecter un éventuel faux contact (plus de courant) et alerter l’opérateur (message "Faucon", pour ne pas dire "faux contact" :-)).

 
 // affichage courant = la valeur maxi pendant 500 ms
  if ((millis() - gCurrentSampleTime) > 500) {
    gCurrentSampleTime = millis();
    lcd.setCursor(13,1);lcd.print(gCurrent*5);lcd.print(' '); // en mA réels
    gCurrent = 0;
  }
  int iCurrent = analogRead(A0);
  if (iCurrent > gCurrent) {
    gCurrent = iCurrent;
    if (gCurrent > CurrentMax) { // 400 * 5 = 2000 mA
    stop_DCC();
    } 
  }
  if ((iCurrent == 0) && (mAutomate.auto_etat > 2)) { // perte de consommation 
    if ((millis() - gCurrentNulTime) > NULCURRENT_TIME) {
      mAutomate.auto_etat=INIT;           // RAZ automate
      mAutomate.LRclignote=true;          // led rouge clignote
      mTrain.cran_vitesse = 0;            // vitesse = 0
      mTrain.lumiere=false;
      delay(2000);
      stop_DCC();                         // arrêt DCC
      lcd.setCursor(10,1);lcd.print("Faucon");
      // panne de courant : faire STOP puis START pour redémarrer
    }   
  } else {
    gCurrentNulTime = millis();           // relance tempo
  }
Détection de événements de zones

Viennent maintenant les tests d’occupation : à chaque changement le programme appelle un traitement spécifique correspondant à la nouvelle zone détectée.
Nous verrons ces traitements un peu plus loin. Ce sont les fonctions les plus importantes du programme.

 
  if (mzoneMonitor.checkZoneTime()) {             // test occupations toutes les 0,1s
    byte loccupation = mzoneMonitor.checkZone(); // détection seulement des changements de type occupation
    byte test = loccupation ^ gOccupation;        // un bit 1 marque chaque différence
    if (test != 0) {                              // on n'en traite qu'une seule à la fois
      gOccupation = loccupation;                  // sauve valeur de gOccupation
      test = test & loccupation;                  // valeur du changement détecté (soit 1 soit 0)
      switch (test) {                             // on ne traite qu'une nouvelle occupation
        case 1:      //ZGARE1
        _doGare1();
        break;
        case 2:      //ZONEA
        _doZoneA();
        break;
        case 4:      //ZONEB
        _doZoneB();
        break;
        case 8:      //ZONEC
        _doZoneC();
        break;
        case 16:     //ZGARE2
        _doGare2();
        break;
      }
    }
  }   //   fin de mzoneMonitor.checkZoneTime()
Gestion du temps (tâche lancée toutes les secondes)

Toutes les secondes l’automate fait quelque chose (ou rien) en fonction de son état.

  • soit attendre la fin de l’arrêt en gare puis démarrer doucement au cran 5
  • soit accélérer à raison de +5 crans par seconde
  • soit ralentir en fonction d’une valeur de décélération calculée.
  • selon les états des compteurs de temps additionnels sont mis en oeuvre (voir les commentaires).
 
  //////// évolution des états temporels de l'automate /////////////
    
  if (mAutomate.auto_seconde) {                   // TOP seconde écoulée !
    mAutomate.auto_seconde=false;
    mAutomate.timeseconde = millis();             // réarmement de la tempo "seconde"
    lcdETAT('a');
    lcd.print(mAutomate.auto_etat);               // a+ N° etat automate
    switch (mAutomate.auto_etat) {                // phases automate indépendantes des tests d'occupation
      case INIT:   
      // inactif   
      break;
      case EXPLORE:   
      // roule vers gare                          // attente détection zone ralentissement
      break;
      case ARRET:                                 // ATTENTE EN GARE et départ si temps écoulé
      mAutomate.arretengare--;                    // decrementé 1 fois par seconde
      lcdCR(mAutomate.arretengare);               // affichage du temps restant
      if (mAutomate.arretengare == 0) {           // fin de la tempo
        mTrain.cran_vitesse = 5;                  // demarrage du train cran 5
        mTrain.roule();
        if (gEtatFeux) mTrain.allume();
        mAutomate.auto_etat=ACCELERE;             // etat : accélération
      }
      break;
      case ACCELERE :                             // ACCELERATION du train
      mTrain.cran_vitesse+=mTrain.acceleration;             
      if (mTrain.cran_vitesse > mTrain.cran_consigne){ // atteinte de la consigne de vitesse
        mTrain.cran_vitesse = mTrain.cran_consigne;
        mAutomate.auto_etat=PALIER;               // etat : palier à vitesse constante
      }
      mTrain.roule();
      break;  
      case PALIER :                               // ROULE a vitesse contante
      if (mTrain.cran_vitesse != mTrain.cran_consigne) { 
        mTrain.cran_vitesse = mTrain.cran_consigne; 
        mTrain.roule();                           // attente détection zone ralentissement
      }
      break;
      case RALENTI :                              // RALENTISSEMENT du train
      if (mTrain.sens1vers2) {
        mTrain.cran_vitesse-=mTrain.deceleration2;// Vers gare 2
      } else {
        mTrain.cran_vitesse-=mTrain.deceleration1;// Vers gare 1
      }
      if (mTrain.cran_vitesse < VITESSE_MIN) {    // atteinte de la consigne minimale avant Gare
        mTrain.cran_vitesse = VITESSE_MIN;
        if (mTrain.troplent==false) {
          mTrain.troplent=true;
          if (mTrain.sens1vers2) {
            mzoneMonitor.zoneTimeCG2 = millis();    // comptage du temps à vitesse MIN de la zone  à la gare 2
          } else {
            mzoneMonitor.zoneTimeAG1 = millis();    // comptage du temps à vitesse MIN de la zone A à la gare 1
          } 
        }
      }
      mTrain.roule();
      break;    
    }  // switch mAutomate       
    lcdloco();
  }  // toutes les secondes

  ///////////////// Gestion des temporisations de l'automate //////////////////
  
  mAutomate.runauto();                            // fait les choses liées au temps 
  
  if (mAutomate.auto_ERR) {                       // TIMEOUT : défaut de détection de zone
    lcdETAT('T');
    lcd.print(mAutomate.auto_etat);               // TX ERR.
    mAutomate.LRclignote=true;                    // clignotement Led Rouge dans mAutomate.run()
    lcdloco();
    mAutomate.auto_ERR=false;
    mAutomate.timeout = millis();                 // rearmement de la tempo "timeout"
  }

Maintenant nous allons voir les commandes manuelles (l’encodeur et les 2 inters)

Gestion de l’encodeur pour la consigne de vitesse

Elle est très simple et récupère une nouvelle valeur de gconsigne :

 
  long newPosition = myEnc.read()/4;
    if (newPosition != oldPosition) {  
      if ( newPosition > oldPosition) gconsigne++;  else  gconsigne--;
      oldPosition = newPosition; 
      if (gconsigne < 0) gconsigne=0;
      if (gconsigne > 180) gconsigne=180;
      mTrain.consigne = gconsigne;
    }
L’interrupteur MARCHE / ARRET

En le mettant en position Arrêt, on stoppe le train et on arrête le DCC.
En le mettant en position Marche, on recommence tout le processus d’initialisation depuis le début (on aurait pu déclencher un reset du processeur mais cette pratique ne me semble pas recommandable).

 
  if (gEtatStartStop == digitalRead(STARTSTOP)) {   // changement d'etat
    gEtatStartStop = !digitalRead(STARTSTOP); // toujours inverser
    #ifdef DEBUG
    Serial.print(F("SS="));Serial.println(gEtatStartStop);
    #endif
    if (gEtatStartStop) {                  // start
      MiseEnRoute(); 
      mzoneMonitor.zoneTime = millis();
      mAutomate.auto_etat=EXPLORE;        // init automate : attente arrivée en gare a vitesse constante    
      mAutomate.auto_ERR = false;
      mAutomate.timeout = millis();       // armement de la tempo "timeout"
    } else {        // stop
      mAutomate.auto_etat=INIT;           // RAZ automate
      mAutomate.LRclignote=false;         // au cas où elle clignote
      mTrain.cran_vitesse = 0;            // vitesse = 0
      mTrain.lumiere=false;
      mTrain.feux(false);
      mTrain.roule();
      delay(2000);
      stop_DCC();                         // arrêt DCC
    }
    lcdloco();                          // affichage lcd
  }
L’interrupteur LUMIERE

Elle invoque les utilitaires du train.

 
  if (gEtatFeux == digitalRead(LUMIERE)) {
    gEtatFeux = !digitalRead(LUMIERE);
    if (gEtatFeux) { 
      mTrain.allume();   // lumiere ON
    } else {
      mTrain.eteint();   // lumiere OFF
    }
    lcdloco();           // mise a jour affichage
  }

Nous sommes arrivé à la fin de la fonction loop().

Suivent maintenant les fonctions appelées lorsque qu’un événement d’occupation intervient lors d’un changement de zone : c’est là que ça se passe !

La fonction _doGare1()

Cette fonction fait ce qu’il y a à faire lorsque le train arrive sur un zone d’arrêt en gare :

  • démarrer la tempo d’arrêt en gare,
  • tester la vitesse à l’arrivée en mode RALENTI : soit elle est trop grande (supérieure à la vitesse minimale), alors il faut augmenter le décrément de vitesse pour le prochain ralenti, soit elle est trop faible et il faut le diminuer.
  • le train est arrêté et le sens est inversé, prêt à partir quand la tempo d’arrêt en gare sera écoulée.
 
void _doGare1() {
  mTrain.zone = ZGARE1;
  //mAutomate.auto_position = ZGARE1;
  mAutomate.auto_ERR=false;
  mAutomate.timeout = millis();                 // rearmement de la tempo "timeout"
  if (mAutomate.auto_etat == RALENTI) {         // arrivée en mode RALENTI
    mzoneMonitor.zoneTimeA = (millis() - mzoneMonitor.zoneTimeA)/1000; //durée ZONEA en secondes
    // si la vitesse est supérieure à la Vitesse MIN, on augmente mTrain.deceleration1
    if (mTrain.cran_vitesse > (VITESSE_MIN))  { // le train rentre en gare trop vite             
      mTrain.deceleration1 = mTrain.deceleration1 + ((mTrain.cran_vitesse - VITESSE_MIN)/mzoneMonitor.zoneTimeA );
      lcdERR('+');
    }
    if (mTrain.troplent) {                                    // trop lent donc diminuer la deceleration1
      mzoneMonitor.zoneTimeAG1 = millis() - mzoneMonitor.zoneTimeAG1; //en millisecondes
      if (mzoneMonitor.zoneTimeAG1 > 2000) { // il faut ralentir moins vite
        mTrain.deceleration1--;
        if (mTrain.deceleration1 < 1) mTrain.deceleration1 = 1;
        lcdERR('-');
      }
    } // fin regulation
    mTrain.stoppe();                          // arrêt
    //mTrain.eteint();                        // extinction des feux
    mAutomate.arretengare=eeConfig.arret;
    mAutomate.auto_etat=ARRET;                // etat = decomptage pendant l'arrêt en gare
    mTrain.sens1vers2 = true;                 // vers la gare 2
    mTrain.sens = !mTrain.sens;               // inversion du sens DCC
  } // fin RALENTI
  lcdloco();  
  if (mAutomate.auto_etat == EXPLORE) {     // Arrivée en mode EXPLORE 
    mTrain.stoppe();                        // arrêt
    mAutomate.arretengare=eeConfig.arret;   // tempo d'arret en gare en secondes
    mAutomate.auto_etat=ARRET;              // etat = decomptage pendant l'arrêt en gare
    mTrain.sens1vers2 = true;               // vers le gare 2
    mTrain.sens = !mTrain.sens;             // inversion du sens DCC
  }  
}
La fonction _doZoneA()

En arrivant dans cette zone ; soit le train vient de la zone B, alors il faut calculer la vitesse en palier et commencer le ralenti, soit le train vient de la gare et il n’y a rien à faire (l’accélération est gérée par l’automate à la seconde).

En cas d’écart entre la vitesse mesurée et la consigne, une correction est calculée : le cran de vitesse est augmenté/diminué de la moitié de la différence. Il faut 3-4 tours pour observer la convergence de la vitesse vers la consigne.

Evidemment la régulation de vitesse en palier agit sur les calculs de ralenti qui s’adaptent ensuite automatiquement.

 
void _doZoneA() {
  mTrain.zone = ZONEA;
  // calcul de Vitesse seulement dans le sens G2 vers G1 ---1 cm/s <-> 5,76 km/h 
  if (gMesureVitesse && !mTrain.sens1vers2) {  // vers Gare 1
    compteurVitesse=millis()-compteurVitesse;
    unsigned long VKM = (eeConfig.dist*5760)/compteurVitesse;
    mTrain.vitesseVraie=(int)(VKM);
    lcd.setCursor(9,1);
    if (mTrain.vitesseVraie < 100) lcd.print(" ");
    if (mTrain.vitesseVraie < 10) lcd.print(" ");
    lcd.print(mTrain.vitesseVraie);
    gMesureVitesse=false;
    // ajustement de la vitesse mTrain.cran_consigne
    if ((mTrain.vitesseVraie >0) && (mTrain.vitesseVraie <160)) {
      if ((mTrain.vitesseVraie > mTrain.consigne + 2) || (mTrain.vitesseVraie < mTrain.consigne - 2)) {
        mTrain.cran_consigne = mTrain.cran_consigne - ((mTrain.vitesseVraie-mTrain.consigne)/2);
        if (mTrain.cran_consigne < VITESSE_MIN) mTrain.cran_consigne = VITESSE_MIN;
      }
    }
  }
  // ralentissement si automate = PALIER et seulement dans le sens 2 vers 1         
  if (!mTrain.sens1vers2 && (mAutomate.auto_etat == PALIER)) {  
    mAutomate.auto_etat=RALENTI;            //etat = ralentissement
    mTrain.troplent=false;
    mAutomate.auto_seconde=false;
    mAutomate.timeseconde = millis();       // armement de la tempo "seconde"
    mzoneMonitor.zoneTimeA = millis();      // comptage de la traversée de la zone A
  }
  lcdERR(' ');    
}
La fonction _doZoneB()

Cette fonction ne fait rien d’autre que de lancer le compteur du temps de traversée de la zone B en vue du calcul de vitesse dans les zones A et C.

 
void _doZoneB() {
  mTrain.zone = ZONEB;
  //mAutomate.auto_position = ZONEB;
  compteurVitesse=millis();                 // lancement compteur de vitesse
  gMesureVitesse=true;   
}
La fonction _doZoneC()

Cette fonction est symétrique de celle de la zone A

  • calcul de la vitesse dans la zone B,
  • correction du cran de consigne de vitesse,
  • mise en ralentissement.

On remarquera que les variables de ralentissement ne sont pas les même pour les zones A et C : Ces ralentissements sont complètement indépendants. C’est important car les distances des zones A et C peuvent être très différentes, donc les ralentissements aussi.

 
void _doZoneC() {
  mTrain.zone = ZONEC;
  //mAutomate.auto_position = ZONEC;
  // calcul Vitesse seulement dans le sens 1 vers 2 ----1 cm/s <-> 5,76 km/h 
  if (gMesureVitesse && mTrain.sens1vers2) {
    compteurVitesse=millis()-compteurVitesse;
    unsigned long VKM = (eeConfig.dist*5760)/compteurVitesse;
    mTrain.vitesseVraie=(int)(VKM);
    lcd.setCursor(9,1);
    if (mTrain.vitesseVraie < 100) lcd.print(" ");
    if (mTrain.vitesseVraie < 10) lcd.print(" ");
    lcd.print(mTrain.vitesseVraie);
    gMesureVitesse=false;
    // ajustement de la vitesse mTrain.cran_consigne
    if ((mTrain.vitesseVraie >0) && (mTrain.vitesseVraie <160)) {
      if ((mTrain.vitesseVraie > mTrain.consigne + 2) || (mTrain.vitesseVraie < mTrain.consigne - 2)) {
        mTrain.cran_consigne = mTrain.cran_consigne - ((mTrain.vitesseVraie-mTrain.consigne)/2);
        if (mTrain.cran_consigne < VITESSE_MIN) mTrain.cran_consigne = VITESSE_MIN;
      }
    }
  }       
  // ralentissement si automate = PALIER dans le sens 1 vers 2   
  if (mTrain.sens1vers2 && (mAutomate.auto_etat == PALIER)) {  
    mAutomate.auto_etat=RALENTI;          // etat = ralentissement
    mTrain.troplent=false;
    mAutomate.auto_seconde=false;
    mAutomate.timeseconde = millis();     // armement de la tempo "seconde"
    mzoneMonitor.zoneTimeC = millis();    // comptage de la traversée de la zone C
  }
  lcdERR(' ');          
}
La fonction _doGare2()

Cette fonction est symétrique de celle de la gare 1.

 
void _doGare2() {
  mTrain.zone = ZGARE2;  
  //mAutomate.auto_position = ZGARE2;
  mAutomate.auto_ERR=false;
  mAutomate.timeout = millis();             // rearmement de la tempo "timeout"
  if (mAutomate.auto_etat == RALENTI) {     // Mode RALENTI
    // si la vitesse est supérieure à la Vitesse MIN, on augmente mTrain.deceleration2
    mzoneMonitor.zoneTimeC = (millis() - mzoneMonitor.zoneTimeC)/1000; //en secondes
    if (mTrain.cran_vitesse > (VITESSE_MIN)) { // le train rentre en gare trop vite              
      mTrain.deceleration2 = mTrain.deceleration2 + ((mTrain.cran_vitesse - VITESSE_MIN)/++mzoneMonitor.zoneTimeC);
      lcdERR('+');
    }
    if (mTrain.troplent) {                     // sinon trop lent vite donc diminuer la deceleration2
      mzoneMonitor.zoneTimeCG2 = millis() - mzoneMonitor.zoneTimeCG2; //en millisecondes
      if (mzoneMonitor.zoneTimeCG2 > 2000) {
        mTrain.deceleration2--;
        if (mTrain.deceleration2 < 1) mTrain.deceleration2 = 1;
        lcdERR('-');
      }
    } // fin regulation    
    mTrain.stoppe();                        // arrêt
    mAutomate.arretengare=eeConfig.arret;   // tempo d'arret en gare en secondes
    mAutomate.auto_etat=ARRET;              // etat = decomptage pendant l'arrêt en gare
    mTrain.sens1vers2 = false;              // vers le gare 1
    mTrain.sens = !mTrain.sens;             // inversion du sens DCC
  } // fin RALENTI
  if (mAutomate.auto_etat == EXPLORE) {     // Mode EXPLORE
    mTrain.stoppe();                        // arrêt
    mAutomate.arretengare=eeConfig.arret;   // tempo d'arret en gare en secondes
    mAutomate.auto_etat=ARRET;              // etat = decomptage pendant l'arrêt en gare
    mTrain.sens1vers2 = false;              // vers le gare 1
    mTrain.sens = !mTrain.sens;             // inversion du sens DCC
  }
  lcdloco();  
}

L’assemblage de ces fonctions

Le programme complet peut être téléchargé ici :

Le programme complet du va et vient

La bibliothèque LiquidCrystal_I2C que j’ai utilisée est ici :

Bibliothèque LiquidCrystal_I2C

Une petite remarque concernant les bibliothèques :
Vous devez savoir que les bibliothèques sont susceptibles d’évoluer dans le temps. Les mises à jour se font habituellement à partir du menu de gestion des bibliothèques. Parfois, les nouvelles versions ne sont pas exactement compatibles avec celle qui a été utilisée par l’auteur de l’article. C’est le cas de LiquidCrystal_I2C que je fournis ici pour éviter aux débutants de buter sur un obstable. Les locoduinautes confirmés savent qu’il faut parfois adapter le sketch à la nouvelle version de bibliothèque en lisant le fichier .h.

La mise au point

Même si vous reproduisez exactement ce projet chez vous et même si vous ne modifiez pas le programme, il se pourrait qu’il ne démarre pas du premier coup.

Voici une liste de vérifications et de tests à faire :

1) Les composants utilisés ne sont pas forcément les mêmes et ceux que vous avez approvisionnés ont un branchement peut-être différent : refaites votre propre schéma et vérifiez votre montage plusieurs fois plutôt qu’une seule.

2) Je vous conseille d’intégrer les éléments les uns après les autres, par exemple :

  • d’abord l’afficheur LCD (vérifiez son adresse avec le programme de test d’adresse I2C qui se trouve dans les exemples de la bibliothèque "i2cdetect"
  • puis les leds et les inters, pour déterminer le sens des inverseurs (si ce n’est pas LOW, c’est HIGH). N’oubliez pas les résistances en série avec les leds.
  • puis l’encodeur : lisez attentivement les conseils présentés dans les exemples. J’ai encore récemment utilisé un potentiomètre classique à piste graphite et c’est la galère pour stabiliser et filtrer la lecture de la valeur à coté de ce qu’on peut faire avec un encodeur.
  • une fois que vous maitrisez ces composants d’entrée-sortie, vous pouvez intégrer DCCpp

A chaque fois, créez un nouveau programme qui contient juste ce qu’il faut pour bien comprendre ce qui se passe.

Vous pouvez parsemer votre code de Serial.println(votrevariable) pour voir évoluer votre programme.

3) Je vous conseille de tester DCCpp séparément avec les exemples fournis avec la bibliothèque. Si votre pont en H n’est pas du même type que le mien, relisez la documentation de DCCpp. Vous trouverez la fonction qui correspond exactement à votre configuration matérielle, par exemple :
DCCpp::beginMainMotorShield();

4) Testez la reconnaissance d’adresse DCC avec plusieurs locomotives et vérifiez qu’elles démarrent bien.

5) Tester le bon fonctionnement des détecteurs d’occupation, d’abord au multimètre sur les sorties des modules, puis en regardant la variable gOccupation avec un Serial.println(gOccupation);

6) Mesurez la longueur de la zone B et entrez sa valeur en centimètres (pour 1 mètre, il faut entrer "100") dans la configuration en EEPROM. Pour cela, faites un reset de l’Arduino en maintenant enfoncé le bouton de l’encodeur. Vous pouvez, dans la foulée, entrer l’adresse DCC par défaut et la durée d’arrêt en gare qui vous conviendrait le mieux (pour la mise au point j’ai gardé une valeur courte).

7) Si tout se passe bien jusque là, observez le comportement du train : il faut qu’il fasse plusieurs aller-retour pour se stabiliser. Obervez les valeurs affichées sur l’écran LCD :
S’il arrive trop vite en gare le lcd affiche un signe "+"

JPEG - 89.6 kio

S’il arrive en gare après un long moment à vitesse minimale, le lcd affiche un signe "-"

JPEG - 92.5 kio

Le ralenti sera meilleur le coup suivant !

8) Vous pouvez changer la consigne de vitesse en cours de route, le train s’y adaptera automatquement.

9) Bien-entendu, quand vous serez à l’aise avec ce programme, libre à vous de modifier d’autres constantes dans le programme.

10) Si des erreurs de compilation surviennent à cause d’une bibliothèque, relisez ce qui est écrit un peu plus haut au sujet de LiquidCrystal_I2C.

Bon, ce sera un bel effort si vous réalisez ce projet avec succès et je vous souhaite du bon plaisir !!

En tout cas, vous disposerez d’une centrale DCC complète avec une face avant opérationnelle que vous pourrez modifier pour réaliser d’autres fonctions de votre choix. Cette centrale peut toujours être commandée par le port série qui est resté libre, via l’USB ou le Wifi si vous ajoutez un circuit ESP12 ou 8266 agissant comme point d’accès wifi.

Nous verrons prochainement comment ajouter le mode PWM pour commander des locomotives analogiques, mais ce sera pour plus tard quand j’aurais bien testé cette fonction.

[3M. Trouvé n’a apparemment pas de site internet mais voici son adresse email : polux89@wanadoo.fr

[4avec un minimum de pratique de l’IDE Arduino, de l’assemblage de montages électroniques et de la mise au point.