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 :
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
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.
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).
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 :
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.
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 :
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
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.
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) :
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 :
Vue de face :
Le branchement des détecteurs
Le schéma suivant permet est simple :
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.
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.
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 !
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 :
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 :
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
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.
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.
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 :
La bibliothèque LiquidCrystal_I2C que j’ai utilisée est ici :
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 "+"
S’il arrive en gare après un long moment à vitesse minimale, le lcd affiche un signe "-"
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.
Je suis très intéressé (bien que tout débutant Arduino, et principalement "lecteur" pour l’instant) pour mon futur petit réseau, et j’ai une question / proposition :
> Dans une version ultérieure, sera-t-il possible de prévoir un "arrêt en gare intermédiaire" ?
> un point d’arrêt (à temporiser) entre les 2 gares d’extrémités, à une "distance" non symétrique et adaptable à chaque réseau...
Ce qui ajouterait encore du "réalisme" à cette animation (et ajouterait également encore un "+" à cette réalisation !)
Oui j’y pense notamment avec une gare intermédiaire comportant une voie d’évitement pour permettre le croisement de 2 trains en sens inverse. Evidemment toutes les distances et les durées seront paramètrables.
Mais un peu de patience SVP !
Méfiez-vous : c’est un projet qui peut être difficile pour un débutant. Une expérience de la mise au point est nécessaire et un environnement PC (ou Mac c’est mieux) stable et maitrisé est indispensable.
Bonjour, bien que débutant en Arduino, je me suis lancé dans la réalisation de ce va-et-vient. Une question : dans votre document la photo de la façade comporte 6 zones G1, Z0, Z1, Z2, Z3, et G2, alors que l’application n’en comporte que 5.Un peu déconcertant, mais je suppose que cela représente une évolution.
Merci pour votre travail et une éventuelle réponse.
Bonjour et merci pour votre intérêt pour ce projet.
Vous êtes perspicace ! Mais c’est vrai parce que j’ai fait plusieurs réalisations et la photo diffère un peu du texte parce que c’est la plus belle que j’ai.
J’en attaque une 3ème plus simple, sans les leds (le texte sur le lcd suffit).
Cela prouve qu’il est facile de faire des variantes.
Dominique
Bonjour.
Tout d’abord grand bravo pour cette belle réalisation et ses explications.
Etant primo débutant en Arduino, je voudrais vous demander s’il serait possible de ne faire fonctionner le système que pour une seule adresse de loco moyennant quelques modifications de votre programme.
Pourquoi ?
J’envisage de commander une loco "allège" qui ne devrait que pousser un train en côte et arrivée au sommet, redescendre jusqu’à sa voie "parking".
Chaque départ de l’allège étant commandé manuellement.
Merci d’avance pour votre réponse.
GM
Un grand merci pour ces compliments qui font plaisir (c’est vrai, on se décarcasse pour votre plus grand plaisir !).
Je ne comprend pas le "une seule adresse de loco" car ce va et vient ne fonctionne déjà que pour une seule loco dans cette version : soit un choix par configuration, soit par détection d’adresse automatique de la loco présente sur les rails.
Pour la suite de la question, je comprend un mix entre automatique et manuel, mais il faut expliquer plus amplement ce que vous avez en tête .
J’ouvre un sujet sur le forum (rubrique "Les réseaux" où vous pouvez décrire vos variantes de mon va et vient avec des images !!!
Merci pour votre réponse rapide, Dominique !
Je m’explique.
Pourquoi "Une seule adresse de loco" - "mix entre automatique et manuel" ?
Mon cahier des charges impose qu’une loco dite "allège", quitte sur commande manuelle (initialistion du va-et-vient) sa voie de garage pour venir se placer derrière une rame pour l’aider à gravir une rampe (il n’y a pas d’accrochage).
L’ensemble étant arrivé au sommet de la rampe, la loco d’allège s’arrête puis redescend "haut le pied" sur la même voie jusqu’à sa voie de garage et y reste.
Fin du cycle va-et-vient.
La loco d’allège ne sera sollicitée que pour des rames lourdes, certains trains plus légers pouvant gravir seuls la rampe, d’où le principe de départ manuel du va-et-vient : seule commande "manuelle" le reste étant automatique tel que décrit dans votre programme.
Les rames lourdes ont à peu près la même vitesse, donc l’allège aura une vitesse très légèrement supérieure à celles-ci.
Mais alors questions :
Sachant, qu’en l’état, votre programme reconnait n’importe quelle adresse de loco, il faudrait faire la différence entre l’adresse de l’allège et celle de la loco tirant la rame sans quoi le va-et-vient s’appliquerait aussi à cette dernière ?
Les fins de parcours de l’allège (ralentissement et arrêt en montée) ne devraient-elles pas être signalées par des reeds (ils) en lieu et place de détecteurs de courant sachant que la motrice de tête ne doit pas être prise en compte ?
Pour la fin de cycle, remisage de l’allège sur sa voie de garage, la détection de courant resterait d’application puisqu’y étant seule.
Je ne peux encore mettre d’images parce que cette partie du réseau n’est pas finalisée, que ceci n’est encore qu’à l’état de projet et à l’étude de faisabilité.
(les essais se feront sur une simple portion de voie avant de passer à la réalisation sur le réseau).
C’est un peu long, j’ose espérer ne pas avoir été trop confus et/ou compliqué ... Lol !
Merci de votre compréhension.
G.
Je vous remercie de copier toutes vos questions sur le Forum ici : Forum Va et Vient, afin de donner plus d’explications sur votre projet qui semble très interessant et des réponses détaillées, avec images (schémas sommaires, pas forcément des photos) pour illustrer : c’est nécessaire et je vous en remercie d’avance.
Cordialement
Dominique
Bonjour.
Je souhaite me monter une petite maquette afin d’utiliser une Arduino, est ce que ce montage pourrai fonctionner avec des locomotives anciennes et non "numériques" ? Merci.
Non pour le moment mais la génération d’un signal analogique (PWM) à la place du signal digital (DCC) est possible, j’y réfléchi (lentement).
cordialement
dominique
Bonjour. Je souhaite faire sur mon réseau à crémaillère un va et vient automatique entre 2 trains opposés qui se croiseront à mi parcours par une voie d’évitement, avec arrêt temporaire( gare ). Je souhaite que chaque arrêt et chaque départ soit progressif. Avez vous une solution à mon problème ? merci .Cordialement. MARC
Et bien Marc, ça tombe bien ! Dans cet article je décris comment faire des démarrages et des arrêts progressifs, d’où le mot “realiste” dans le titre. Est-ce que les explications et les éléments de logiciels donnés peuvent vous convenir ?
Je suis très intéressé par cette réalisation de va et vient automatique, mais j’aimerais pouvoir y ajouter des gares intermédiaires. Si j’ai bien saisi le processus, je vais alors devoir créer de nouvelles zones d’accélération, de vitesse palier et de décélération entre chaque gare intermédiaire ?
Je réfléchis aussi à l’activation de fonctions selon présence dans une gare : l’idée serait, via un décodeur sonore, d’annoncer le bon arrêt selon la gare.
Je pense dans un 1er temps tenter avec 3 ou 4 gares (en fait, c’est pour un tramway)
D’avance, merci pour vos avis, suggestions, propositions...
Je me lance progressivement avec Arduino et les Attiny, mais je suis loin d’être un expert !
Votre projet est tout à fait possible en partant de cet article. Oui il faudra multiplier les phases d’arrêt, accélération, ralentissement et surtout les détecteurs. Un Nano/Uno ne suffira peut-être pas. Une re-écriture plus “objet” du logiciel sera sans doute nécessaire. Cela devient un peu ambitieux pour un débutant. Mais vous êtes en progression. Procédez par étapes et tout ira bien.
Je pense que vous devriez construire votre projet par étapes en commençant par un va-et-vient simple (pas réaliste du tout) avec les deux stations d’extrémité.
En le complétant au fur et à mesure par les stations intermédiaires.
Et enfin en ajoutant la progressivité des arrêts démarrages.
Pour ma part, je trouve que l’écriture "objet" rend l’écriture et la lecture des programmes trop abstraites pour un usage amateur mais que l’utilisation des fonctions permet de structurer correctement son application.
Vous pouvez également regarder cet article sur le forum (il a un peu dérivé) :
Bonjour Dominique et bravo pour votre travail et votre partage.
J’ai essayé de reproduire ce va et vient mais l’afficheur n’indique rien avec le programme installé et ses 3 onglets alors qu’il fonctionne bien avec un test de sketch. Qu’en pensez vous ?
Bonjour, merci pour votre travail très riche en informations.je viens de redémarrer le réseau analogique de mon fils malheureusement disparu que j’avais construit il y a quelques années (réseau en tiroir).je voudrais créer un vas et viens automatique et réaliste du même type mais avec des capteurs infrarouge.serait il envisageable.ce réseau analogique sera utilisé pour mes petits enfants merci pour eux.j’oubliait la chose la plus importante je débute dans le monde Arduino et du modélisme.merci
Amicalement Alain