Auteur Sujet: Réseau hivernal en N et machines d'états  (Lu 9378 fois)

Marc-Henri

  • Full Member
  • ***
  • Messages: 159
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Réseau hivernal en N et machines d'états
« Réponse #15 le: janvier 13, 2020, 08:42:29 am »
Bonjour Dominique,

Voici déjà quelques réponses rapides:
  • C'est effectivement par dissymétrie du signal DCC à l'aide de diodes et relais que les locos sont commandées (cf les schémas en début de ce post.
  • Il n'y a actuellement aucun lien entre l'Arduino et la centrale DCC, une ancienne Trix MS1.
  • Le système Magnorail du car postal n'est pas non plus commandé par l'Arduino, c'est un simple monostable déclenché les aimants de la chaîne via un ILS qui rend le signal assymétrique pour le décodeur fixe du Magnorail. Mais on pourrait très bien relier Magnorail à l'Arduino et imaginer des scénarios où le train attend le car ou vice-versa.
  • Oui pour une publication sur le site Locoduino, mais plutôt d'ici quelques semaines, j'ai encore pas mal d'activités privées à terminer d'ici là.

Profite bien du beau temps, bon début de semaine et meilleures salutations.
Marc-Henri

Marc-Henri

  • Full Member
  • ***
  • Messages: 159
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Réseau hivernal en N et machines d'états
« Réponse #16 le: novembre 27, 2020, 11:47:54 am »
Bonjour à tous,

J'ai profité d'un coup de soleil éclairant le réseau pour tourner cette vidéo. La neige est tombée presque partout, il reste à peaufiner les détails, placer les personnages et quelques véhicules routiers immobiles.



Bonne fin de semaine à tous.
« Modifié: novembre 27, 2020, 11:57:35 am par Dominique »

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 2454
  • 100% Arduino et N
    • Voir le profil
Re : Réseau hivernal en N et machines d'états
« Réponse #17 le: novembre 27, 2020, 11:59:01 am »
Merci Marc-Henri,

Je me suis permis de modifier l'url de ta video pour qu'elle apparaisse : enlever le "youtu.be" et trouver l'url  avec "youtube.com". J'avoue que je ne sais pas pourquoi (faute de recherche).

C'est vraiment très joli, quel décor superbe  ;D
La vitesse de l'autocar me semble parfaite mais celle des trains me semble un peu trop rapide par rapport à la réalité.

J'aime beaucoup les automatismes et merci pour cet exemple qui donne peut-être une idée de ce qu'on pourrait faire avec la centrale ESP32 /wifi / can (désolé je fais la pub).

La suisse est belle !

Bien amicalement
Dominique
« Modifié: novembre 27, 2020, 12:08:18 pm par Dominique »
Cordialement,
Dominique

Marc-Henri

  • Full Member
  • ***
  • Messages: 159
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Réseau hivernal en N et machines d'états
« Réponse #18 le: novembre 28, 2020, 10:55:08 pm »
Merci beaucoup Dominique pour tes compliments et pour la correction de l'URL YouTube.

C'est vrai qu'en faisant circuler les trains plus lentement, c'est mieux. C'est simple à faire, il suffit de réduire la vitesse sur la centrale. Cela ne pose aucun problème à l'Arduino qui gère cela car il "attend" les trains dans les zones de détection avant de prendre la prochaine décision.

Il faudra que je prenne du temps pour me familiariser avec l'ESP32 !

Bon dimanche.

Marc-Henri

  • Full Member
  • ***
  • Messages: 159
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Réseau hivernal en N et machines d'états
« Réponse #19 le: janvier 02, 2021, 05:01:07 pm »
Bonjour à tous,

Je vous souhaite mes meilleurs voeux pour une année 2021 moins perturbée que 2020, de belles réalisations en modélisme et Arduino, mais surtout une bonne santé.

J'ai terminé mon réseau hivernal, voici une vidéo montrant le tout dernier état.



Le dernier article de mon blog montre des photos du réseau: https://savignyexpress.wordpress.com/2021/01/02/reseau-hivernal-suite-et-fin/

Je reprendrai la description du programme en C++ qui gère la circulation des trains.

Bon début d'année et meilleures salutations.

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 2454
  • 100% Arduino et N
    • Voir le profil
Re : Réseau hivernal en N et machines d'états
« Réponse #20 le: janvier 02, 2021, 05:22:36 pm »
Bonjour Marc-Henri,

Mes meilleurs voeux également pour une nouvelle année enfin normale, on le mérite bien vu les efforts qu'on a fait pour rester confinés à souder, programmer, etc.. seuls dans notre atelier !

Ton réseau est splendide : le décor, les automatismes et la vidéo sont parfaits : il y a plein d'enseignements dans ce beau projet.

Amicalement
Dominique
Cordialement,
Dominique

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 668
    • Voir le profil
Re : Réseau hivernal en N et machines d'états
« Réponse #21 le: janvier 02, 2021, 06:18:13 pm »
Je plussoie, très joli réseau, et une sympathique ambiance bucolique hivernale ! Meilleurs voeux pour cette nouvelle année, et plein de projets modélistiques et Arduinesques !

Marc-Henri

  • Full Member
  • ***
  • Messages: 159
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Réseau hivernal en N et machines d'états
« Réponse #22 le: janvier 22, 2021, 11:17:24 pm »
Merci à tous pour vos voeux et pour vos compliments à propos de mon réseau.

Bonne fin de semaine et meilleures salutations.

Marc-Henri

  • Full Member
  • ***
  • Messages: 159
    • Voir le profil
    • Modélisme ferroviaire & électronique
Re : Réseau hivernal en N et machines d'états
« Réponse #23 le: mai 20, 2021, 02:26:00 pm »
Bonjour à tous,

Après une très longue pause, je reprends la description du logiciel de mon réseau hivernal.

Classe Affichage
Comme le montre la vidéo de 2019, le réseau comporte un affichage LCD en face avant destiné à afficher l'état de la gare cachée ainsi que le mode de fonctionnement du réseau: manuel, semi-automatique, automatique.

Par exemple, voies vides, mode automatique:


Un train dans chaque sens sur chaque voie, mode manuel:


La classe Affichage sera utilisée par la classe GareCachee. Le header est le suivant:

//
//      Affichage.h
//
//      Gestion de l'affichage LCD.
//
//      2019.02.19      Création.

#ifndef AFFICHAGE_H
#define AFFICHAGE_H

#include <LiquidCrystal_I2C.h>
#include <stdint.h>

#include "Hardware.h"
#include "Tache.h"


// États de la gare cachée.
#define E_INCONNU       0
#define E_LIBRE         1

// Entrée par la gauche et sortie par la droite.
#define E_ENTREE_G      2
#define E_ARRIVE_G      3
#define E_ARRETE_D      4
#define E_SORTIE_D_DEM  5
#define E_AIG_SORT_D    6
#define E_SORTANT_D1    7
#define E_SORTANT_D2    8

// Entrée par la droite et sortie par la gauche.
#define E_ENTREE_D      9
#define E_ARRIVE_D      10
#define E_ARRETE_G      11
#define E_SORTIE_G_DEM  12
#define E_AIG_SORT_G    13
#define E_SORTANT_G1    14
#define E_SORTANT_G2    15

#define E_TRAVERS       16

#define NB_VOIES_GC        2
#define GC1     0
#define GC2     1

#define NB_AIG_GC       2
#define A4      0
#define A5      1

class Affichage: public Tache
{
public:

        Affichage ();
        void setup ();
        void loop ();

        void afficherVoieGC (uint8_t nxVoie, uint8_t exEtat);
        void afficherAigGC (uint8_t nxAig, uint8_t exEtat);
        void afficherMode (uint8_t nxMode);

        void afficherTempo (const char acxLcd0[], const char acxLcd1[], uint32_t nxDuree);
private:
        uint8_t aEtatVoies[NB_VOIES_GC];
        uint8_t aEtatAig[NB_AIG_GC];

        uint8_t eEtat;

        uint32_t tDebut;
        uint32_t nDureeTempo;

        uint8_t nMode;  // Mode de fonctionnement.

        char acLcd [LCD_LIGNES][LCD_CHARS];

        LiquidCrystal_I2C lcd;

        void afficherGC (void);

        void rafraichir (void);
};

#endif

Comme l'affichage doit tenir compte de l'état de la gare cachée, il m'a semblé naturel que les états possibles de la gare cachée soient définis ici.

À l'instar des autres classes du programme, celle-ci hérite de la classe Tache, ce qui permet l'appel automatique de ses méthodes virtuelles setup et loop. Les autres méthodes publiques affichent l'état des voies de la gare cachée, l'état des aiguilles, le mode de fonctionnement, ainsi que des messages temporaires.

Dans la partie privée, on remarquera la déclaration d'une instance de la classe LiquidCrystal_I2C. La classe fonctionnerait aussi très bien avec un affichage LCD connecté à 4 ou 8 broches de l'Arduino. J'ai fait le choix d'une liaison I2C par un câble blindé car elle est insensible aux perturbations causées par les moteurs d'aiguilles à solénoïde.

Le fichier .cpp de la classe est:

#include <LiquidCrystal.h>

//
//      Affichage.cpp
//
//      2019.02.19      Création.

#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <stdint.h>

#include "Affichage.h"
#include "Aiguilles.h"
#include "Boutons.h"
#include "Hardware.h"


const uint8_t wagonChar[] = {
        0b00000,
        0b11111,
        0b10101,
        0b11111,
        0b11111,
        0b01010,
        0b00000,
        0b00000
};
const uint8_t locoDChar[] = {
        0b00000,
        0b11000,
        0b11010,
        0b11111,
        0b11111,
        0b01010,
        0b00000,
        0b00000
};
const uint8_t locoGChar[] = {
        0b00000,
        0b00011,
        0b01011,
        0b11111,
        0b11111,
        0b01010,
        0b00000,
        0b00000
};
const uint8_t A4devieChar[] = {
        0b00000,
        0b00000,
        0b00000,
        0b10000,
        0b01000,
        0b00100,
        0b00010,
        0b00001         
};
const uint8_t A5devieChar[] = {
        0b00000,
        0b00000,
        0b00000,
        0b00001,
        0b00010,
        0b00100,
        0b01000,
        0b10000                               
};
const uint8_t jonctA4Char[] = {
        0b10000,
        0b01000,
        0b00100,
        0b00011,
        0b00000,
        0b00000,
        0b00000,
        0b00000                       
};
const uint8_t jonctA5Char[] = {
        0b00001,
        0b00010,
        0b00100,
        0b11000,
        0b00000,
        0b00000,
        0b00000,
        0b00000               
};

#define C_WAGON 0
#define C_LOCO_D 1
#define C_LOCO_G 2
#define C_A4_DEV        3
#define C_A5_DEV        4
#define C_JCT_A4        5
#define C_JCT_A5        6

#define C_FLEC_G        0b01111111
#define C_FLEC_D        0b01111110

// Pour centrer l'affichage de la gare cachée.
#define POS_INIT        2
#define POS_A4          (POS_INIT+1)
#define POS_TRAIN       (POS_INIT+4)
#define LONG_TRAIN      4
#define POS_A5          (POS_TRAIN+LONG_TRAIN+2)
#define POS_JCT_A4      (POS_A4+1)
#define POS_JCT_A5      (POS_A5-1)

#define PERIODE 20


// États possibles de l'affichage.

#define E_GARE_CACHEE           0
#define E_GARE_CACHEE_NOUV      1
#define E_MSG_TEMPO             2


#define DUREE_MSG_DEMARRAGE     7000

//Affichage::Affichage () : lcd (LiquidCrystal (LCD_RS, LCD_RW, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7)) {}
Affichage::Affichage () : lcd (LiquidCrystal_I2C (0x27, LCD_CHARS, LCD_LIGNES)) {}


void Affichage::setup ()
{
        for (uint8_t nVoie = 0; nVoie < NB_VOIES_GC; nVoie++)
        {
                aEtatVoies[nVoie] = E_INCONNU;
        } // for

        for (uint8_t nAig = 0; nAig < NB_AIG_GC; nAig++)
        {
                aEtatAig[nAig] = NORMAL;
        } // for

        for (uint8_t nLig = 0; nLig < LCD_LIGNES; nLig++)
        {
                for (uint8_t nChar = 0; nChar < LCD_CHARS; nChar++)
                {
                        acLcd [nLig][nChar] = ' ';
                } // for
        } // for
       
        lcd.init ();
        lcd.backlight ();
        lcd.clear ();

        lcd.createChar (C_WAGON, (uint8_t *)wagonChar);
        lcd.createChar (C_LOCO_D, (uint8_t *)locoDChar);
        lcd.createChar (C_LOCO_G, (uint8_t *)locoGChar);
        lcd.createChar (C_A4_DEV, (uint8_t *)A4devieChar);
        lcd.createChar (C_A5_DEV, (uint8_t *)A5devieChar);
        lcd.createChar (C_JCT_A4, (uint8_t *)jonctA4Char);               
        lcd.createChar (C_JCT_A5, (uint8_t *)jonctA5Char);

        eEtat = E_GARE_CACHEE_NOUV;
        afficherTempo (RESEAU_HIVERNAL, VERSION, DUREE_MSG_DEMARRAGE);
} // Affichage::setup


#define BLANCS_1ERE     2
#define TRAITS_1ERE     12
#define BLANCS_2EME     4
#define TRAITS_2EME     6


void Affichage::loop ()
{
static uint8_t ePrev = 255;

        #ifdef DEBUG_LCD
        if (eEtat != ePrev)
        {
                Serial.print (millis (), DEC);
                Serial.print (" - entrée loop - ");
                Serial.println (eEtat, DEC);
        }
        ePrev = eEtat;
        #endif
       
        switch (eEtat)
        {
                case E_MSG_TEMPO:
                        if ((millis () - tDebut) > nDureeTempo)
                        {
                                eEtat = E_GARE_CACHEE_NOUV;

                                #ifdef DEBUG_LCD
                                Serial.print (millis (), DEC);
                                Serial.println (" - loop - E_GARE_CACHEE_NOUV");
                                #endif
                        } // if
                break;

                case E_GARE_CACHEE_NOUV:
                        afficherGC ();
                        eEtat = E_GARE_CACHEE;             
                        tDebut = millis ();

                        #ifdef DEBUG_LCD
                        Serial.print (millis (), DEC);
                        Serial.println (" - loop - E_GARE_CACHEE");
                        #endif                       
                break;

                case E_GARE_CACHEE:
                        if ((millis () - tDebut) > PERIODE)
                        {       
                                tDebut = millis ();
                                rafraichir ();
                        } // if
                break;
        } // switch
} // Affichage::loop


void Affichage::afficherVoieGC (uint8_t nxVoie, uint8_t exEtat)
{
        aEtatVoies[nxVoie] = exEtat;

        #ifdef DEBUG_LCD
        Serial.print (millis (), DEC);
        Serial.print (" - entrée afficherVoieGC: ");
        Serial.println (eEtat, DEC);
        #endif
       
        if (eEtat != E_MSG_TEMPO)
        {
                eEtat = E_GARE_CACHEE_NOUV;

                #ifdef DEBUG_LCD
                Serial.print (millis (), DEC);
                Serial.println (" - afficherVoieGC - E_GARE_CACHEE_NOUV");
                #endif                                       
        } // if
} // Affichage::afficherVoieGC


void Affichage::afficherAigGC (uint8_t nxAig, uint8_t exEtat)
{
        aEtatAig[nxAig] = exEtat;       
        if (eEtat != E_MSG_TEMPO)
        {
                eEtat = E_GARE_CACHEE_NOUV;

                #ifdef DEBUG_LCD
                Serial.print (millis (), DEC);
                Serial.println (" - afficherAigGC - E_GARE_CACHEE_NOUV");
                #endif                                       
        } // if               
} // Affichage::afficherAigGC


void Affichage::afficherMode (uint8_t nxMode)
{
        nMode = nxMode;
        if (eEtat != E_MSG_TEMPO)
        {
                eEtat = E_GARE_CACHEE_NOUV;       

                #ifdef DEBUG_LCD
                Serial.print (millis (), DEC);
                Serial.println (" - afficherMode - E_GARE_CACHEE_NOUV");
                #endif                                       
        } // if               
} // Affichage::afficherMode



void Affichage::afficherTempo (const char acxLcd0[], const char acxLcd1[], uint32_t nxDuree)
{
uint8_t nChar;

        #ifdef DEBUG_LCD
        Serial.print (millis (), DEC);
        Serial.print (" - E_MSG_TEMPO: ");
        Serial.print (acxLcd0);
        Serial.print (" / ");
        Serial.println (acxLcd1);
        #endif
       
        if ((eEtat == E_GARE_CACHEE) || (eEtat == E_GARE_CACHEE_NOUV))
                eEtat = E_MSG_TEMPO;

        nDureeTempo = nxDuree;
        tDebut = millis ();

        for (nChar = 0; nChar < LCD_CHARS; nChar++)
        {
                acLcd[0][nChar] = ' ';
                acLcd[1][nChar] = ' ';               
        } // for

        for (nChar = 0; nChar < (strlen (acxLcd0) <= LCD_CHARS ? strlen (acxLcd0) : LCD_CHARS); nChar++)
        {
                acLcd[0][nChar] = acxLcd0[nChar];
        } // for
               
        for (nChar = 0; nChar < (strlen (acxLcd1) <= LCD_CHARS ? strlen (acxLcd1) : LCD_CHARS); nChar++)
        {
                acLcd[1][nChar] = acxLcd1[nChar];
        } // for

        rafraichir ();
} // Affichage::afficherTempo


void Affichage::afficherGC (void)
{
uint8_t nLig, nChar, n;
       
        // 1ère ligne: caractères fixes.                       
        nLig = 0;
        nChar = 0;
        for (n = 0; n < BLANCS_1ERE; n++)
        {
                acLcd [nLig][nChar++] = ' ';
        } // for

        for (n = 0; n < TRAITS_1ERE; n++)
        {
                acLcd [nLig][nChar++] = '-';
        } // for

        for (n = 0; n < BLANCS_1ERE; n++)
        {
                acLcd [nLig][nChar++] = ' ';
        } // for

        // 2ème ligne: caractères fixes.
        nLig = 1;
        nChar = 0;
        for (n = 0; n < BLANCS_2EME; n++)
        {
                acLcd [nLig][nChar++] = ' ';
        } // for

        acLcd [nLig][nChar++] = C_JCT_A4;

        for (n = 0; n < TRAITS_2EME; n++)
        {
                acLcd [nLig][nChar++] = '-';
        } // for

        acLcd [nLig][nChar++] = C_JCT_A5;

        for (n = 0; n < BLANCS_2EME; n++)
        {
                acLcd [nLig][nChar++] = ' ';
        } // for

        // L'état des aiguilles.
        if (aEtatAig[A4] == NORMAL)
        {
                acLcd [0][POS_A4] = '-';
        }
        else
        {
                acLcd [0][POS_A4] = C_A4_DEV;
        } // if

        if (aEtatAig[A5] == NORMAL)
        {
                acLcd [0][POS_A5] = '-';
        }
        else
        {
                acLcd [0][POS_A5] = C_A5_DEV;
        } // if

        // Puis l'état d'occupation des voies.
        for (uint8_t nVoie = 0; nVoie < NB_VOIES_GC; nVoie++)
        {       
                nLig = nVoie;
                nChar = POS_TRAIN;

                switch (aEtatVoies[nVoie])
                {
                        case E_INCONNU:
                                acLcd [nLig][nChar++] = '?';
                                acLcd [nLig][nChar++] = '?';
                                acLcd [nLig][nChar++] = '?';
                                acLcd [nLig][nChar++] = '?';                                       
                        break;
                               
                        case E_LIBRE:
                                acLcd [nLig][nChar++] = '-';
                                acLcd [nLig][nChar++] = '-';
                                acLcd [nLig][nChar++] = '-';
                                acLcd [nLig][nChar++] = '-';
                        break;

                        case E_ARRETE_G:
                                acLcd [nLig][nChar++] = C_LOCO_G;
                                acLcd [nLig][nChar++] = C_WAGON;
                                acLcd [nLig][nChar++] = C_WAGON;
                                acLcd [nLig][nChar++] = C_WAGON;
                        break;

                        case E_ARRETE_D:
                                acLcd [nLig][nChar++] = C_WAGON;
                                acLcd [nLig][nChar++] = C_WAGON;
                                acLcd [nLig][nChar++] = C_WAGON;
                                acLcd [nLig][nChar++] = C_LOCO_D;
                        break;                               

                        case E_TRAVERS:
                                acLcd [nLig][nChar++] = C_FLEC_G;
                                acLcd [nLig][nChar++] = '-';     
                                acLcd [nLig][nChar++] = '-';
                                acLcd [nLig][nChar++] = C_FLEC_D;
                        break;                                       
                } // switch
        } // for

        // Terminer par le mode de fonctionnement.
        switch (nMode)
        {
                case MODE_MANUEL: acLcd[1][0] = 'M'; break;
                case MODE_SEMI:   acLcd[1][0] = 'S'; break;               
                case MODE_AUTO:   acLcd[1][0] = 'A'; break;
        } // switch

        rafraichir ();
} // Affichage::afficherGC


void Affichage::rafraichir (void)
{
        for (uint8_t nLig = 0; nLig < LCD_LIGNES; nLig++)
        {
                lcd.setCursor (0, nLig);

                for (uint8_t nChar = 0; nChar < LCD_CHARS; nChar++)
                {
                        lcd.write (acLcd [nLig][nChar]);               
                } // for
        } // for       
} // Affichage::rafraichir

Au début de ce fichier, des tableaux d'octets contiennent les caractères personnalisables utilisés pour afficher les trains et les positions des aiguilles. Une seule version du wagon est suffisante, mais il faut des versions gauche et droite de la loco pour distinguer le sens de marche des trains. Les octets en binaire définissent les bits à allumer pour chaque ligne, on retrouve bien les caractères affichés sur les 2 exemples ci-dessus. Il aurait été possible de déclarer ces caractères en mémoire programme plutôt qu'en RAM à l'aide de la directive PROGMEM, mais il me reste encore bien assez de RAM !
La création de ces caractères se fait dans la méthode setup.

Les données à afficher sont préparées dans les tableaux aEtatVoies, aEtatAig et acLcd. C'est ensuite la méthode privée rafraichir qui effectue l'envoi à l'affichage LCD.

La classe comporte elle-même 3 états:
  • E_GARE_CACHEE: état stable après mise à jour de l'affichage.
  • E_GARE_CACHEE_NOUV: un nouvel état doit être affiché. C'est par exemple le cas si on a appelé l'une des 3 méthodes publiques: afficherVoiesGC, afficherAigGC, etc.
  • E_GARE_CACHEE_TEMPO: affichage d'un message temporaire.

Portez-vous bien et meilleures salutations.

« Modifié: mai 20, 2021, 02:39:01 pm par Marc-Henri »