LOCODUINO

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

mercredi 24 octobre 2018

9 visiteurs en ce moment

Realisation d’un va-et-vient automatique et réaliste

. Par : Dominique

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 ko

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 ko

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 ko

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 ko

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 ko

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 ko

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 ko

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 ko

- 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 !

  1.  
  2. /*
  3. Affectation des Pins du NANO sur NanoVV perso
  4. Pin 2 <-> ENC1 encodeur pin 1
  5. Pin 3 <-> PWM (enable, HIGH = marche, LOW = stop) pour LMD18200
  6. Pin 4 <-> ENC2 encodeur pin 2
  7. Pin 5 <-> detecteur Gare 1
  8. Pin 6 <-> detecteur zone A
  9. Pin 7 <-> detecteur zone B
  10. Pin 8 <-> detecteur zone C
  11. Pin 9 <-> detecteur Gare 2
  12. Pin 10 <-> DIR (DCC = OC1B output) pour LMD18200
  13. Pin 11 <-> Led Rouge ERREUR
  14. Pin 12 <-> non utilisée
  15. Pin 13 <-> non utilisée
  16. Pin A0 <-> Mesure de courant du Mac471
  17. Pin A1 <-> bouton OK (encodeur)
  18. Pin A2 <-> inter marche/arret
  19. Pin A3 <-> inter lumière
  20. Pin A4 <-> SDA LCD
  21. Pin A5 <-> SLC LCD
  22. Pin A6 iibre
  23. Pin A7 libre
  24. */
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.

  1.  
  2. //#define DEBUG
  3.  
  4. const char Version[16] = "DCCpp_VV_200118"; // 15 caractères + \0
  5. const int EEPVERSION = 300;
  6.  
  7. #include <DCCpp.h>
  8. #include "Automate.h"
  9. #include <EEPROM.h>
  10. #include <Wire.h>
  11. #include <LiquidCrystal_I2C.h>
  12. #include <Encoder.h>
  13. #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 :

  1.  
  2. struct ConfigEEprom {
  3. unsigned int vers; // version logiciel
  4. unsigned long dist; // = DISTANCE_MODULE en cm
  5. int arret; // = ARRETENGARE en secondes
  6. int adresse; // = 3; ADRESSE DCC par DEFAUT
  7. };
  8. // Une seule variable locale
  9. 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 :

  1.  
  2. 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 :

  1.  
  2. Encoder myEnc(2,4);
  3. 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.

  1.  
  2. #define MOTOR_SHIELD_TYPE 0 // LMD18200
  3. #define COMM_INTERFACE 0
  4. #define LMD_DIR 10 // DCC_MAIN - DIR LMD18200
  5. #define LMD_PWM 3 // DCC_ENABLE = PWM LMD18200
  6. #define Max471 A0 // current sensor
  7. #define gDCCStepsNumber 128 // mode DCC 128
  8.  
  9. 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 !

  1.  
  2. DCCpp::begin();
  3. 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 :

  1.  
  2. #define BOK A1 // bouton OK
  3. #define STARTSTOP A2 //inter Marche / Arret
  4. #define LUMIERE A3 //inter FEUX
  5. #define PINGARE1 5 //capteur input
  6. #define PINZONEA 6 //capteur input
  7. #define PINZONEB 7 //capteur input
  8. #define PINZONEC 8 //capteur input
  9. #define PINGARE2 9 //capteur input
  10. #define LEDROUGE 11 //led rouge output
  11. #define ENC1 2 //encoder input
  12. #define ENC2 4 //encoder input
  13.  
  14. #define ZONE_SAMPLE_TIME 100 // ms
  15. #define DEBOUNCE_SAMPLE_TIME 100 // ms
  16. #define AUTOMATE_TIMEOUT 120000 // 2 minutes
  17. #define DISTANCE_MODULE 120 // cm
  18. #define VITESSE_MIN 10 //cran
  19.  
  20. #define ARRETENGARE 50 // x0,1s soit 5 secondes
  21. #define ACCELERATION 5 // incrément de cran de vitesse
  22. #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 :

  1.  
  2. Train mTrain(3); //create train adresse 3
  3. ZoneMonitor mzoneMonitor(PINGARE1, PINZONEA, PINZONEB, PINZONEC, PINGARE2); // create monitor for zone detectors
  4. 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.

  1. struct Train{
  2. int adresse_dcc; // pour la commande DCC
  3. bool sens1vers2; // true si gare 1-> gare 2
  4. bool sens; // pour la commande DCC
  5. bool lumiere; // pour la commande DCC
  6. bool troplent; // true si Vmin atteinte pendant RALENTI
  7. int zone; // la zone ou il est
  8. int cran_vitesse; // cran DCC en cours
  9. int cran_consigne; // cran DCC à la consigne
  10. int consigne; // en km/H au potar et LCD
  11. int vitesseVraie; // en km/h
  12. int acceleration; // en crans
  13. int deceleration1; // en crans vers gare 1
  14. int deceleration2; // en crans vers gare 2
  15. Train(int);
  16. void roule(); // fonction pour avancer/reculer
  17. void stoppe(); // fonction pour s'arreter
  18. void feux(bool); // commande des lumieres
  19. void eteint();
  20. void allume();};
  21.  
  22. Train::Train(int a) {
  23. adresse_dcc = a;
  24. sens = true;
  25. cran_vitesse = 30;
  26. lumiere = false;
  27. acceleration = ACCELERATION;
  28. deceleration1 = DECELERATION;
  29. deceleration2 = DECELERATION;
  30. cran_consigne = 60;
  31. }
  32.  
  33. void Train::roule() { // t1 adresse_dcc vitesse
  34. DCCpp::setSpeedMain(1, adresse_dcc, 128, cran_vitesse, sens);
  35. }
  36.  
  37. void Train::feux(bool l) {
  38. if (l) {
  39. gLocoFunctions.activate(0);
  40. } else {
  41. gLocoFunctions.inactivate(0);
  42. }
  43. DCCpp::setFunctionsMain(1, adresse_dcc, gLocoFunctions);
  44. }
  45.  
  46. void Train::stoppe() {
  47. cran_vitesse = 0;
  48. roule();
  49. }
  50.  
  51. void Train::eteint() {
  52. lumiere=false;
  53. feux(lumiere);
  54. }
  55.  
  56. void Train::allume() {
  57. lumiere=true;
  58. feux(lumiere);
  59. }

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.

  1.  
  2. enum { // nom des états
  3. INIT=0,
  4. EXPLORE=1,
  5. ARRET=2,
  6. ACCELERE=3,
  7. PALIER=4,
  8. RALENTI=5
  9. };
  10.  
  11. int gETAT = 0; // etat de l'automate
  12.  
  13. enum {
  14. ZGARE1=0,
  15. ZONEA=1,
  16. ZONEB=2,
  17. ZONEC=3,
  18. ZGARE2=4
  19. };
  20.  
  21. const char * nomZone[ZGARE2+1] = { // noms zones sur LCD
  22. "Gar1 ",
  23. "ZonA ",
  24. "ZonB ",
  25. "ZonC ",
  26. "Gar2 "
  27. };
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.

  1.  
  2. struct Automate{
  3. int auto_etat; // INIT, EXPLORE, ARRET, ACCELERE, PALIER, RALENTI
  4. unsigned long arretengare; // decompteur temps d'arret en gare en ms
  5. unsigned long timeout, timeseconde; // timecheck
  6. unsigned long timeLR; // tempo clignotement Led Rouge
  7. bool auto_seconde; // tempo seconde écoulée
  8. bool auto_ERR; // erreur automate
  9. bool LRclignote; // true = Led Rouge clignotante
  10. // fonctions
  11. Automate(int);
  12. void runauto();
  13. };
  14.  
  15. Automate::Automate(int ae) {
  16. auto_etat = INIT;
  17. timeseconde=millis();
  18. timeLR=millis();
  19. arretengare = ARRETENGARE; // tempo d'arret en gare configurable
  20. auto_ERR = false;
  21. LRclignote = false;
  22. }
  23.  
  24. void Automate::runauto() {
  25. if (millis()-timeLR > 500) { // clignotement LED ROUGE
  26. if (LRclignote) { // clignotement LED ROUGE
  27. digitalWrite(LEDROUGE, !digitalRead(LEDROUGE));
  28. timeLR = millis();
  29. }
  30. }
  31. if (millis()-timeseconde > 1000) { // base de temps seconde
  32. auto_seconde = true;
  33. }
  34. if (millis()-timeout > AUTOMATE_TIMEOUT) { // time-out
  35. auto_ERR = true;
  36. }
  37. }
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 :

  1.  
  2. struct ZoneMonitor {
  3. unsigned long zoneTime, zoneTimeA, zoneTimeAG1, zoneTimeCG2, zoneTimeC;
  4. int pin1, pinA, pinB, pinC, pin2;
  5. byte occupation;
  6. ZoneMonitor(int, int, int, int, int);
  7. void Init();
  8. bool checkZoneTime();
  9. byte checkZone();
  10. };
  11.  
  12. ZoneMonitor::ZoneMonitor(int pin1, int pina, int pinb, int pinc,int pin2){
  13. this->pin1=pin1;
  14. this->pinA=pina;
  15. this->pinB=pinb;
  16. this->pinC=pinc;
  17. this->pin2=pin2;
  18. occupation = 0;
  19. } // ZoneMonitor::ZoneMonitor
  20.  
  21. void ZoneMonitor::Init() {
  22. pinMode(pin1,INPUT_PULLUP);
  23. pinMode(pinA,INPUT_PULLUP);
  24. pinMode(pinB,INPUT_PULLUP);
  25. pinMode(pinC,INPUT_PULLUP);
  26. pinMode(pin2,INPUT_PULLUP);
  27. bitWrite(occupation, ZGARE1, !digitalRead(pin1)); // LOW = occupé
  28. bitWrite(occupation, ZONEA, !digitalRead(pinA)); // LOW = occupé
  29. bitWrite(occupation, ZONEB, !digitalRead(pinB)); // LOW = occupé
  30. bitWrite(occupation, ZONEC, !digitalRead(pinC)); // LOW = occupé
  31. bitWrite(occupation, ZGARE2, !digitalRead(pin2)); // LOW = occupé
  32. zoneTime = millis();
  33. }
  34.  
  35. bool ZoneMonitor::checkZoneTime() {
  36. if (millis()-zoneTime < ZONE_SAMPLE_TIME) // test préiodique
  37. return(false);
  38. zoneTime = millis();
  39. return(true);
  40. } // ZoneMonitor::checkTime
  41.  
  42. byte ZoneMonitor::checkZone() { // détection de changement d'état
  43. bitWrite(occupation, ZGARE1, !digitalRead(pin1)); // LOW = occupé -> un bit 1
  44. bitWrite(occupation, ZONEA, !digitalRead(pinA));
  45. bitWrite(occupation, ZONEB, !digitalRead(pinB));
  46. bitWrite(occupation, ZONEC, !digitalRead(pinC));
  47. bitWrite(occupation, ZGARE2, !digitalRead(pin2));
  48. return(occupation);
  49. }
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().

  1.  
  2. ///////////////////////////////////////////////////////////////////////
  3. // FONCTION DE CONFIGURATION APPELÉE UNIQUEMENT AU DÉMARRAGE EN MAINTENANT LE BOUTON APPUYÉ
  4. ///////////////////////////////////////////////////////////////////////
  5.  
  6. void afficheLCD(int val) {
  7. lcd.setCursor(8,1);
  8. if (val < 100) lcd.print(" ");
  9. if (val < 10) lcd.print(" ");
  10. lcd.print(val);
  11. }
  12.  
  13. int configure(int val) {
  14. afficheLCD(val);
  15. do {
  16. long newPosition = myEnc.read()/4;
  17. if (newPosition != oldPosition) {
  18. if ( newPosition > oldPosition) val++; else val--;
  19. oldPosition = newPosition;
  20. afficheLCD(val);
  21. }
  22. } while (digitalRead(BOK) == true); // attente appui sur bouton
  23. while (digitalRead(BOK) == false) {delay(100);} // attente relachement bouton
  24. return(val);
  25. }
  26.  
  27. // Configuration de la distance de la zone de mesure de vitesse, de la durée d'arret en gare
  28. // et de l'adresse DCC par défaut en cas de non reconaissance de l'adresse
  29.  
  30. void configuration() {
  31. int valeur;
  32.  
  33. EEPROM.get(0, eeConfig); // lecture record eeConfig en EEPROM
  34. #ifdef DEBUG
  35. Serial.println(eeConfig.vers);
  36. Serial.println(eeConfig.dist);
  37. Serial.println(eeConfig.arret);
  38. Serial.println(eeConfig.adresse);
  39. #endif
  40. if (eeConfig.vers == 0xFFFF) { // si l'EEPROM est vierge, on écrit les valeurs par défaut
  41. eeConfig.dist = DISTANCE_MODULE;
  42. eeConfig.arret = ARRETENGARE;
  43. eeConfig.adresse = 3;
  44. eeConfig.vers = EEPVERSION;
  45. EEPROM.put(0, eeConfig);
  46. }
  47. if (digitalRead(BOK) == false) { // mode configuration en appuyant sur BOK au démarrage
  48. lcd.clear();
  49. lcd.setCursor(0,1);
  50. lcd.print("v"); // affichage version "v300"
  51. if (eeConfig.vers < 100) lcd.print(" ");
  52. if (eeConfig.vers < 10) lcd.print(" ");
  53. lcd.print(eeConfig.vers);
  54. ///////////////////////// configuration distance zone C ou D
  55. lcd.setCursor(0,0);
  56. lcd.print(F("Distance zone "));
  57. while (digitalRead(BOK) == false) {delay(100);} // attente relachement bouton
  58. valeur = eeConfig.dist; // valeur initiale
  59. if (valeur > 600) valeur = 600;
  60. eeConfig.dist = configure(valeur);
  61. ///////////////////////// configuration durée arret en gare
  62. lcd.setCursor(0,0);
  63. lcd.print(F("Duree arret gare"));
  64. valeur = eeConfig.arret;
  65. if (valeur > 600) valeur = 600;
  66. eeConfig.arret = configure(valeur);
  67. ///////////////////////// configuration adresse DCC par défaut
  68. lcd.setCursor(0,0);
  69. lcd.print(F("Adresse DCC "));
  70. valeur = eeConfig.adresse;
  71. if (valeur > 500) valeur = 500;
  72. eeConfig.adresse = configure(valeur);
  73. ////////////////////////
  74. lcd.setCursor(0,0);
  75. lcd.print(F("Enregistrement.."));
  76. EEPROM.put(0, eeConfig);
  77. delay(1000);
  78. }
  79. }
Les variables globales

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

  1. unsigned long gCurrentSampleTime;
  2. unsigned long gCurrentNulTime;
  3. int gCurrent;
  4. const int CurrentMax = 400;
  5.  
  6. unsigned long compteurVitesse;
  7. bool gMesureVitesse=false;
  8. bool gEtatFeux=false; // pas de FEUX
  9. bool gEtatStartStop=false; // en ARRET
  10. int gconsigne=60;
  11. 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 ko
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.

  1.  
  2. void lcdloco() {
  3. lcd.setCursor(8,0);
  4. if (mTrain.cran_vitesse < 100) lcd.print(" ");
  5. if (mTrain.cran_vitesse < 10) lcd.print(" ");
  6. lcd.print(mTrain.cran_vitesse); // cran
  7. if (mTrain.sens1vers2) {
  8. lcd.print(" >> ");
  9. } else {
  10. lcd.print(" << ");
  11. }
  12. if (mTrain.lumiere) {
  13. lcd.print("*");
  14. } else {
  15. lcd.print(" ");
  16. }
  17. lcd.setCursor(0,1);
  18. lcd.print(nomZone[mTrain.zone]);
  19. //lcd.setCursor(5,1);
  20. if (gconsigne < 100) lcd.print(" ");
  21. if (gconsigne < 10) lcd.print(" ");
  22. lcd.print(gconsigne);
  23. }
  24.  
  25. void lcdETAT(char E) { // lcdETAT('a');
  26. lcd.setCursor(4,0);
  27. lcd.print(E); // error char ? a T
  28. }
  29.  
  30. void lcdERR(char E) { // lcdERR('+');
  31. lcd.setCursor(7,0);
  32. lcd.print(E); // error char - +
  33. }
  34.  
  35. void lcdCR(int n) { // compte à rebours
  36. lcd.setCursor(4,0);
  37. if (n < 100) lcd.print(" ");
  38. if (n < 10) lcd.print(" ");
  39. if (n == 0) lcd.print(" "); else lcd.print(n);
  40. }

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.

  1.  
  2. void MiseEnRoute() {
  3. start_DCC();
  4. int reponse = DCCpp::identifyLocoIdMain();
  5. #ifdef DEBUG
  6. Serial.print(F("adresse="));Serial.print(reponse);
  7. #endif
  8. lcd.clear();
  9. lcd.setCursor(0,0);
  10. lcd.print(reponse);
  11. if (reponse == -1) {
  12. #ifdef DEBUG
  13. Serial.println(F(" >>> erreur !"));
  14. #endif
  15. lcdETAT('?');
  16. mAutomate.LRclignote=true; // clignotement Led Rouge dans mAutomate.run()
  17. mTrain.adresse_dcc = eeConfig.adresse;
  18. delay(500);
  19. } else {
  20. #ifdef DEBUG
  21. Serial.println(F(" valide :-)"));
  22. #endif
  23. mAutomate.LRclignote=false;
  24. mTrain.adresse_dcc = reponse;
  25. }
  26. mTrain.cran_vitesse = 30; // vitesse 30, avant jusqu' à une gare
  27. mTrain.sens = true;
  28. mTrain.roule();
  29. }
Deux petites fonctions de démarrage et d’arrêt du DCC

Ces 2 fonctions mettent en ou hors service le DCC.

  1.  
  2. void start_DCC() {
  3. digitalWrite(DCC_ENABLE,HIGH);
  4. digitalWrite(LEDROUGE, HIGH); // eteint led rouge
  5. lcd.setCursor(0,1);lcd.print("DCC on ");
  6. }
  7.  
  8. void stop_DCC() {
  9. digitalWrite(DCC_ENABLE,LOW);
  10. digitalWrite(LEDROUGE, LOW); // allume led rouge
  11. lcd.setCursor(0,1);lcd.print("DCC off ");
  12. delay(1000);
  13. }

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.

  1.  
  2. void setup(){
  3.  
  4. #ifdef DEBUG
  5. Serial.begin(115200); // configure serial interface
  6. //while (!Serial) ; // wait for Arduino Serial Monitor
  7. Serial.flush();
  8. Serial.println(Version);
  9. #endif
  10.  
  11. lcd.init(); // initialize the lcd
  12. lcd.clear();
  13. lcd.backlight();
  14. lcd.setCursor(0,0);
  15. lcd.print(Version);
  16.  
  17. lcd.setCursor(0,1);
  18. lcd.print("Demarre DCC "); // 12
  19. DCCpp::begin();
  20. DCCpp::beginMain(UNDEFINED_PIN, LMD_DIR, LMD_PWM, Max471); // Dc: Dir, Pwm, current sensor
  21.  
  22. pinMode(STARTSTOP, INPUT_PULLUP);
  23. pinMode(LEDROUGE, OUTPUT);
  24. digitalWrite(LEDROUGE, HIGH);
  25. pinMode(LUMIERE, INPUT_PULLUP);
  26. pinMode(BOK, INPUT_PULLUP);
  27.  
  28. pinMode(PINGARE1, INPUT_PULLUP);
  29. pinMode(PINZONEA, INPUT_PULLUP);
  30. pinMode(PINZONEB, INPUT_PULLUP);
  31. pinMode(PINZONEC, INPUT_PULLUP);
  32. pinMode(PINGARE2, INPUT_PULLUP);
  33.  
  34. oldPosition = myEnc.read()/4;
  35.  
  36. delay(500);
  37. digitalWrite(LEDROUGE, LOW);
  38.  
  39. // Lecture de la configuration en EEPROM
  40. configuration(); // Configuration EEPROM lecture et ecriture si BOK appuyé
  41.  
  42. mzoneMonitor.Init(); // init zoneMonitor
  43.  
  44. // lecture de l'état de l'inter A/M
  45.  
  46. gEtatStartStop = !digitalRead(STARTSTOP); // start = LOW donc faut inverser
  47. if (gEtatStartStop) { // position MARCHE, automate en attente de synchro en gare
  48. MiseEnRoute(); // DCC on et roule vitesse = 30
  49. mAutomate.auto_etat = EXPLORE; // init automate : exploration ligne
  50. } else { // position ARRET
  51. mAutomate.auto_etat = INIT; // init automate : en attente découverte et exploration ligne
  52. mTrain.adresse_dcc = eeConfig.adresse;
  53. mTrain.cran_vitesse = 0; // vitesse = 0
  54. mTrain.sens = true;
  55. lcd.clear();
  56. }
  57. gconsigne = 60; // valeur initial de la consigne de vitesse et de l'encoder
  58. mTrain.consigne = gconsigne;
  59. mTrain.cran_consigne = gconsigne;
  60.  
  61. // lecture de l'état de l'inter LUMIERE
  62.  
  63. gEtatFeux = !digitalRead(LUMIERE);
  64. if (gEtatFeux) { // true = allumé
  65. mTrain.allume(); // lumiere ON
  66. } else { // false = eteint
  67. mTrain.eteint(); // lumiere OFF
  68. }
  69. lcd.setCursor(0,0);
  70. lcd.print(mTrain.adresse_dcc);// ligne 0 : adresse
  71. lcd.print(" "); // effacement du "1" en cas d'erreur
  72. lcdloco(); // ligne 1 : vitesse, sens, lumiere et consigne
  73.  
  74. // initialisation des états de l'automate
  75. mAutomate.auto_ERR = false;
  76. mAutomate.timeout = millis(); // armement de la tempo "timeout"
  77. mzoneMonitor.zoneTime = millis();
  78. if (mAutomate.auto_etat == EXPLORE) mTrain.roule();
  79.  
  80. } // 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.

  1.  
  2. void loop() {
  3.  
  4. DCCpp::loop();
  5.  
  6. /////// autres tâches ci-dessous
  7.  
  8. } // 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" :-)).

  1.  
  2. // affichage courant = la valeur maxi pendant 500 ms
  3. if ((millis() - gCurrentSampleTime) > 500) {
  4. gCurrentSampleTime = millis();
  5. lcd.setCursor(13,1);lcd.print(gCurrent*5);lcd.print(' '); // en mA réels
  6. gCurrent = 0;
  7. }
  8. int iCurrent = analogRead(A0);
  9. if (iCurrent > gCurrent) {
  10. gCurrent = iCurrent;
  11. if (gCurrent > CurrentMax) { // 400 * 5 = 2000 mA
  12. stop_DCC();
  13. }
  14. }
  15. if ((iCurrent == 0) && (mAutomate.auto_etat > 2)) { // perte de consommation
  16. if ((millis() - gCurrentNulTime) > NULCURRENT_TIME) {
  17. mAutomate.auto_etat=INIT; // RAZ automate
  18. mAutomate.LRclignote=true; // led rouge clignote
  19. mTrain.cran_vitesse = 0; // vitesse = 0
  20. mTrain.lumiere=false;
  21. delay(2000);
  22. stop_DCC(); // arrêt DCC
  23. lcd.setCursor(10,1);lcd.print("Faucon");
  24. // panne de courant : faire STOP puis START pour redémarrer
  25. }
  26. } else {
  27. gCurrentNulTime = millis(); // relance tempo
  28. }
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.

  1.  
  2. if (mzoneMonitor.checkZoneTime()) { // test occupations toutes les 0,1s
  3. byte loccupation = mzoneMonitor.checkZone(); // détection seulement des changements de type occupation
  4. byte test = loccupation ^ gOccupation; // un bit 1 marque chaque différence
  5. if (test != 0) { // on n'en traite qu'une seule à la fois
  6. gOccupation = loccupation; // sauve valeur de gOccupation
  7. test = test & loccupation; // valeur du changement détecté (soit 1 soit 0)
  8. switch (test) { // on ne traite qu'une nouvelle occupation
  9. case 1: //ZGARE1
  10. _doGare1();
  11. break;
  12. case 2: //ZONEA
  13. _doZoneA();
  14. break;
  15. case 4: //ZONEB
  16. _doZoneB();
  17. break;
  18. case 8: //ZONEC
  19. _doZoneC();
  20. break;
  21. case 16: //ZGARE2
  22. _doGare2();
  23. break;
  24. }
  25. }
  26. } // 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).

  1.  
  2. //////// évolution des états temporels de l'automate /////////////
  3.  
  4. if (mAutomate.auto_seconde) { // TOP seconde écoulée !
  5. mAutomate.auto_seconde=false;
  6. mAutomate.timeseconde = millis(); // réarmement de la tempo "seconde"
  7. lcdETAT('a');
  8. lcd.print(mAutomate.auto_etat); // a+ N° etat automate
  9. switch (mAutomate.auto_etat) { // phases automate indépendantes des tests d'occupation
  10. case INIT:
  11. // inactif
  12. break;
  13. case EXPLORE:
  14. // roule vers gare // attente détection zone ralentissement
  15. break;
  16. case ARRET: // ATTENTE EN GARE et départ si temps écoulé
  17. mAutomate.arretengare--; // decrementé 1 fois par seconde
  18. lcdCR(mAutomate.arretengare); // affichage du temps restant
  19. if (mAutomate.arretengare == 0) { // fin de la tempo
  20. mTrain.cran_vitesse = 5; // demarrage du train cran 5
  21. mTrain.roule();
  22. if (gEtatFeux) mTrain.allume();
  23. mAutomate.auto_etat=ACCELERE; // etat : accélération
  24. }
  25. break;
  26. case ACCELERE : // ACCELERATION du train
  27. mTrain.cran_vitesse+=mTrain.acceleration;
  28. if (mTrain.cran_vitesse > mTrain.cran_consigne){ // atteinte de la consigne de vitesse
  29. mTrain.cran_vitesse = mTrain.cran_consigne;
  30. mAutomate.auto_etat=PALIER; // etat : palier à vitesse constante
  31. }
  32. mTrain.roule();
  33. break;
  34. case PALIER : // ROULE a vitesse contante
  35. if (mTrain.cran_vitesse != mTrain.cran_consigne) {
  36. mTrain.cran_vitesse = mTrain.cran_consigne;
  37. mTrain.roule(); // attente détection zone ralentissement
  38. }
  39. break;
  40. case RALENTI : // RALENTISSEMENT du train
  41. if (mTrain.sens1vers2) {
  42. mTrain.cran_vitesse-=mTrain.deceleration2;// Vers gare 2
  43. } else {
  44. mTrain.cran_vitesse-=mTrain.deceleration1;// Vers gare 1
  45. }
  46. if (mTrain.cran_vitesse < VITESSE_MIN) { // atteinte de la consigne minimale avant Gare
  47. mTrain.cran_vitesse = VITESSE_MIN;
  48. if (mTrain.troplent==false) {
  49. mTrain.troplent=true;
  50. if (mTrain.sens1vers2) {
  51. mzoneMonitor.zoneTimeCG2 = millis(); // comptage du temps à vitesse MIN de la zone à la gare 2
  52. } else {
  53. mzoneMonitor.zoneTimeAG1 = millis(); // comptage du temps à vitesse MIN de la zone A à la gare 1
  54. }
  55. }
  56. }
  57. mTrain.roule();
  58. break;
  59. } // switch mAutomate
  60. lcdloco();
  61. } // toutes les secondes
  62.  
  63. ///////////////// Gestion des temporisations de l'automate //////////////////
  64.  
  65. mAutomate.runauto(); // fait les choses liées au temps
  66.  
  67. if (mAutomate.auto_ERR) { // TIMEOUT : défaut de détection de zone
  68. lcdETAT('T');
  69. lcd.print(mAutomate.auto_etat); // TX ERR.
  70. mAutomate.LRclignote=true; // clignotement Led Rouge dans mAutomate.run()
  71. lcdloco();
  72. mAutomate.auto_ERR=false;
  73. mAutomate.timeout = millis(); // rearmement de la tempo "timeout"
  74. }

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 :

  1.  
  2. long newPosition = myEnc.read()/4;
  3. if (newPosition != oldPosition) {
  4. if ( newPosition > oldPosition) gconsigne++; else gconsigne--;
  5. oldPosition = newPosition;
  6. if (gconsigne < 0) gconsigne=0;
  7. if (gconsigne > 180) gconsigne=180;
  8. mTrain.consigne = gconsigne;
  9. }
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).

  1.  
  2. if (gEtatStartStop == digitalRead(STARTSTOP)) { // changement d'etat
  3. gEtatStartStop = !digitalRead(STARTSTOP); // toujours inverser
  4. #ifdef DEBUG
  5. Serial.print(F("SS="));Serial.println(gEtatStartStop);
  6. #endif
  7. if (gEtatStartStop) { // start
  8. MiseEnRoute();
  9. mzoneMonitor.zoneTime = millis();
  10. mAutomate.auto_etat=EXPLORE; // init automate : attente arrivée en gare a vitesse constante
  11. mAutomate.auto_ERR = false;
  12. mAutomate.timeout = millis(); // armement de la tempo "timeout"
  13. } else { // stop
  14. mAutomate.auto_etat=INIT; // RAZ automate
  15. mAutomate.LRclignote=false; // au cas où elle clignote
  16. mTrain.cran_vitesse = 0; // vitesse = 0
  17. mTrain.lumiere=false;
  18. mTrain.feux(false);
  19. mTrain.roule();
  20. delay(2000);
  21. stop_DCC(); // arrêt DCC
  22. }
  23. lcdloco(); // affichage lcd
  24. }
L’interrupteur LUMIERE

Elle invoque les utilitaires du train.

  1.  
  2. if (gEtatFeux == digitalRead(LUMIERE)) {
  3. gEtatFeux = !digitalRead(LUMIERE);
  4. if (gEtatFeux) {
  5. mTrain.allume(); // lumiere ON
  6. } else {
  7. mTrain.eteint(); // lumiere OFF
  8. }
  9. lcdloco(); // mise a jour affichage
  10. }

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.

  1.  
  2. void _doGare1() {
  3. mTrain.zone = ZGARE1;
  4. //mAutomate.auto_position = ZGARE1;
  5. mAutomate.auto_ERR=false;
  6. mAutomate.timeout = millis(); // rearmement de la tempo "timeout"
  7. if (mAutomate.auto_etat == RALENTI) { // arrivée en mode RALENTI
  8. mzoneMonitor.zoneTimeA = (millis() - mzoneMonitor.zoneTimeA)/1000; //durée ZONEA en secondes
  9. // si la vitesse est supérieure à la Vitesse MIN, on augmente mTrain.deceleration1
  10. if (mTrain.cran_vitesse > (VITESSE_MIN)) { // le train rentre en gare trop vite
  11. mTrain.deceleration1 = mTrain.deceleration1 + ((mTrain.cran_vitesse - VITESSE_MIN)/mzoneMonitor.zoneTimeA );
  12. lcdERR('+');
  13. }
  14. if (mTrain.troplent) { // trop lent donc diminuer la deceleration1
  15. mzoneMonitor.zoneTimeAG1 = millis() - mzoneMonitor.zoneTimeAG1; //en millisecondes
  16. if (mzoneMonitor.zoneTimeAG1 > 2000) { // il faut ralentir moins vite
  17. mTrain.deceleration1--;
  18. if (mTrain.deceleration1 < 1) mTrain.deceleration1 = 1;
  19. lcdERR('-');
  20. }
  21. } // fin regulation
  22. mTrain.stoppe(); // arrêt
  23. //mTrain.eteint(); // extinction des feux
  24. mAutomate.arretengare=eeConfig.arret;
  25. mAutomate.auto_etat=ARRET; // etat = decomptage pendant l'arrêt en gare
  26. mTrain.sens1vers2 = true; // vers la gare 2
  27. mTrain.sens = !mTrain.sens; // inversion du sens DCC
  28. } // fin RALENTI
  29. lcdloco();
  30. if (mAutomate.auto_etat == EXPLORE) { // Arrivée en mode EXPLORE
  31. mTrain.stoppe(); // arrêt
  32. mAutomate.arretengare=eeConfig.arret; // tempo d'arret en gare en secondes
  33. mAutomate.auto_etat=ARRET; // etat = decomptage pendant l'arrêt en gare
  34. mTrain.sens1vers2 = true; // vers le gare 2
  35. mTrain.sens = !mTrain.sens; // inversion du sens DCC
  36. }
  37. }
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.

  1.  
  2. void _doZoneA() {
  3. mTrain.zone = ZONEA;
  4. // calcul de Vitesse seulement dans le sens G2 vers G1 ---1 cm/s <-> 5,76 km/h
  5. if (gMesureVitesse && !mTrain.sens1vers2) { // vers Gare 1
  6. compteurVitesse=millis()-compteurVitesse;
  7. unsigned long VKM = (eeConfig.dist*5760)/compteurVitesse;
  8. mTrain.vitesseVraie=(int)(VKM);
  9. lcd.setCursor(9,1);
  10. if (mTrain.vitesseVraie < 100) lcd.print(" ");
  11. if (mTrain.vitesseVraie < 10) lcd.print(" ");
  12. lcd.print(mTrain.vitesseVraie);
  13. gMesureVitesse=false;
  14. // ajustement de la vitesse mTrain.cran_consigne
  15. if ((mTrain.vitesseVraie >0) && (mTrain.vitesseVraie <160)) {
  16. if ((mTrain.vitesseVraie > mTrain.consigne + 2) || (mTrain.vitesseVraie < mTrain.consigne - 2)) {
  17. mTrain.cran_consigne = mTrain.cran_consigne - ((mTrain.vitesseVraie-mTrain.consigne)/2);
  18. if (mTrain.cran_consigne < VITESSE_MIN) mTrain.cran_consigne = VITESSE_MIN;
  19. }
  20. }
  21. }
  22. // ralentissement si automate = PALIER et seulement dans le sens 2 vers 1
  23. if (!mTrain.sens1vers2 && (mAutomate.auto_etat == PALIER)) {
  24. mAutomate.auto_etat=RALENTI; //etat = ralentissement
  25. mTrain.troplent=false;
  26. mAutomate.auto_seconde=false;
  27. mAutomate.timeseconde = millis(); // armement de la tempo "seconde"
  28. mzoneMonitor.zoneTimeA = millis(); // comptage de la traversée de la zone A
  29. }
  30. lcdERR(' ');
  31. }
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.

  1.  
  2. void _doZoneB() {
  3. mTrain.zone = ZONEB;
  4. //mAutomate.auto_position = ZONEB;
  5. compteurVitesse=millis(); // lancement compteur de vitesse
  6. gMesureVitesse=true;
  7. }
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.

  1.  
  2. void _doZoneC() {
  3. mTrain.zone = ZONEC;
  4. //mAutomate.auto_position = ZONEC;
  5. // calcul Vitesse seulement dans le sens 1 vers 2 ----1 cm/s <-> 5,76 km/h
  6. if (gMesureVitesse && mTrain.sens1vers2) {
  7. compteurVitesse=millis()-compteurVitesse;
  8. unsigned long VKM = (eeConfig.dist*5760)/compteurVitesse;
  9. mTrain.vitesseVraie=(int)(VKM);
  10. lcd.setCursor(9,1);
  11. if (mTrain.vitesseVraie < 100) lcd.print(" ");
  12. if (mTrain.vitesseVraie < 10) lcd.print(" ");
  13. lcd.print(mTrain.vitesseVraie);
  14. gMesureVitesse=false;
  15. // ajustement de la vitesse mTrain.cran_consigne
  16. if ((mTrain.vitesseVraie >0) && (mTrain.vitesseVraie <160)) {
  17. if ((mTrain.vitesseVraie > mTrain.consigne + 2) || (mTrain.vitesseVraie < mTrain.consigne - 2)) {
  18. mTrain.cran_consigne = mTrain.cran_consigne - ((mTrain.vitesseVraie-mTrain.consigne)/2);
  19. if (mTrain.cran_consigne < VITESSE_MIN) mTrain.cran_consigne = VITESSE_MIN;
  20. }
  21. }
  22. }
  23. // ralentissement si automate = PALIER dans le sens 1 vers 2
  24. if (mTrain.sens1vers2 && (mAutomate.auto_etat == PALIER)) {
  25. mAutomate.auto_etat=RALENTI; // etat = ralentissement
  26. mTrain.troplent=false;
  27. mAutomate.auto_seconde=false;
  28. mAutomate.timeseconde = millis(); // armement de la tempo "seconde"
  29. mzoneMonitor.zoneTimeC = millis(); // comptage de la traversée de la zone C
  30. }
  31. lcdERR(' ');
  32. }
La fonction _doGare2()

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

  1.  
  2. void _doGare2() {
  3. mTrain.zone = ZGARE2;
  4. //mAutomate.auto_position = ZGARE2;
  5. mAutomate.auto_ERR=false;
  6. mAutomate.timeout = millis(); // rearmement de la tempo "timeout"
  7. if (mAutomate.auto_etat == RALENTI) { // Mode RALENTI
  8. // si la vitesse est supérieure à la Vitesse MIN, on augmente mTrain.deceleration2
  9. mzoneMonitor.zoneTimeC = (millis() - mzoneMonitor.zoneTimeC)/1000; //en secondes
  10. if (mTrain.cran_vitesse > (VITESSE_MIN)) { // le train rentre en gare trop vite
  11. mTrain.deceleration2 = mTrain.deceleration2 + ((mTrain.cran_vitesse - VITESSE_MIN)/++mzoneMonitor.zoneTimeC);
  12. lcdERR('+');
  13. }
  14. if (mTrain.troplent) { // sinon trop lent vite donc diminuer la deceleration2
  15. mzoneMonitor.zoneTimeCG2 = millis() - mzoneMonitor.zoneTimeCG2; //en millisecondes
  16. if (mzoneMonitor.zoneTimeCG2 > 2000) {
  17. mTrain.deceleration2--;
  18. if (mTrain.deceleration2 < 1) mTrain.deceleration2 = 1;
  19. lcdERR('-');
  20. }
  21. } // fin regulation
  22. mTrain.stoppe(); // arrêt
  23. mAutomate.arretengare=eeConfig.arret; // tempo d'arret en gare en secondes
  24. mAutomate.auto_etat=ARRET; // etat = decomptage pendant l'arrêt en gare
  25. mTrain.sens1vers2 = false; // vers le gare 1
  26. mTrain.sens = !mTrain.sens; // inversion du sens DCC
  27. } // fin RALENTI
  28. if (mAutomate.auto_etat == EXPLORE) { // Mode EXPLORE
  29. mTrain.stoppe(); // arrêt
  30. mAutomate.arretengare=eeConfig.arret; // tempo d'arret en gare en secondes
  31. mAutomate.auto_etat=ARRET; // etat = decomptage pendant l'arrêt en gare
  32. mTrain.sens1vers2 = false; // vers le gare 1
  33. mTrain.sens = !mTrain.sens; // inversion du sens DCC
  34. }
  35. lcdloco();
  36. }

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 ko
S’il arrive en gare après un long moment à vitesse minimale, le lcd affiche un signe "-"
JPEG - 92.5 ko
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.

6 Messages

Réagissez à « Realisation d’un va-et-vient automatique et réaliste »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Projets »

Un chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

L’Arduino et le système de commande numérique DCC

Un décodeur d’accessoire DCC versatile basé sur Arduino

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Calcul de la vitesse d’un train miniature avec l’Arduino

La génèse d’un réseau 100% Arduino

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

La rétro-signalisation sur Arduino

Décodeur pour aiguillage à solénoïdes sur Arduino

Un décodeur DCC pour les signaux à deux ou trois feux sur Arduino NANO/UNO

Etude d’un passage à niveau universel

Réalisation pratique d’un système de mesure de vitesse à l’échelle N

Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation

Un décodeur DCC pour 16 feux tricolores

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"

Réalisation d’un affichage de gare ARRIVEE DEPART

Ménage à trois (Ordinateur, Arduino, réseau)

Realisation d’un va-et-vient automatique et réaliste

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

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

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

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

SGDD : Système de Gestion DD (1)

SGDD : Système de Gestion DD (2)

SGDD : Système de Gestion DD (3)

La PWM : Qu’est-ce que c’est ? (1)

La PWM : Qu’est-ce que c’est ? (2)

La PWM : Qu’est ce que c’est ? (3)

La PWM : Qu’est ce que c’est ? (4)

Mise en oeuvre du Bus CAN entre modules Arduino (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Un gestionnaire en C++ pour votre réseau (1)

Un gestionnaire en C++ pour votre réseau (2)

Un gestionnaire en C++ pour votre réseau (3)

Un gestionnaire en C++ pour votre réseau (4)

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

Contrôleur à télécommande infrarouge pour centrale DCC++

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

Les derniers articles

Realisation d’un va-et-vient automatique et réaliste


Dominique

Ménage à trois (Ordinateur, Arduino, réseau)


Christian

La PWM : Qu’est ce que c’est ? (4)


Jean-Luc

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"


Christian

Réalisation d’un affichage de gare ARRIVEE DEPART


Gilbert

Contrôleur à télécommande infrarouge pour centrale DCC++


Daniel

La PWM : Qu’est ce que c’est ? (3)


Jean-Luc

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)


bobyAndCo

Un gestionnaire en C++ pour votre réseau (4)


Pierre59

Un décodeur DCC pour 16 feux tricolores


Dominique, JPClaude, Thierry

Les articles les plus lus

La PWM : Qu’est-ce que c’est ? (1)

Feux tricolores

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (3)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

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

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

Réalisation d’un affichage de gare ARRIVEE DEPART

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"

La rétro-signalisation sur Arduino