Voir les contributions

Cette section vous permet de consulter les contributions (messages, sujets et fichiers joints) d'un utilisateur. Vous ne pourrez voir que les contributions des zones auxquelles vous avez accès.


Messages - Marc-Henri

Pages: 1 [2] 3 4 ... 12
16
Présentez vous ! / Re : Présentation de Julaye
« le: novembre 03, 2021, 08:13:01 am »
@Dominique,

Quelques recherches sur le web m'ont permis de voir que void main n'est pas standard, même si certains compilateurs l'acceptent. C'était le cas de la version d'avr-gcc utilisée lors du développement de ce projet, mais cela ne passe plus avec la version actuelle.

Il faut donc bien spécifier int comme type de retour. Je pense que c'est dû au fait qu'un programme en C écrit pour un ordinateur autre qu'un système embarqué doit retourner une valeur à l'issue de son exécution (0: tout s'est bien passé, autre valeur: erreur). Cela ne s'applique évidemment pas à nos programmes constitués d'une boucle principale ne se terminant pas, mais c'est exigé par la plupart des compilateurs.

Meilleures salutations.

17
Présentez vous ! / Re : Présentation de Julaye
« le: novembre 02, 2021, 11:56:23 am »
Bonjour Julaye,

À mon tour je te souhaite la bienvenue parmi nous.

@chris_bzg je choisi toujours l'outil et l'architecture la plus adaptée à ce que je souhaite programmer. Les objets sont un outil possible mais pas toujours le plus efficient. Pour l'embarqué sur du matériel comme le Nano, j'aime bien le C (de Ritchie), les machines à état fini et les co-routines.

Les machines à états finis sont très présentes dans les systèmes embarqués. Je les ai utilisées dans la plupart de mes développements pour le modélisme ferroviaire. Au début j'ai réalisé des projets directement en C sur des Attiny2313 et Atmega8, plus récemment je suis passé à l'Arduino pour plus de puissance et utiliser C++. Mon réseau hivernal est contrôlé par plusieurs classes C++ implémentant chacune une machine à états finis. Voir https://forum.locoduino.org/index.php?topic=763.msg13315#new.

Mais les coroutines sont intéressantes lorsque le traitement est majoritairement séquentiel. Une implantation très pratique des coroutines sont les protothreads dont la documentation se trouve ici: http://dunkels.com/adam/pt/. C'est simple à mettre en oeuvre et ne requiert que quelques includes, car implanté sous forme de macros du préprocesseur.
L'exemple ci-après est la gestion en C sur Attiny2313 d'un PN dont les barrières sont actionnées par des servo moteurs. Il y a 3 protothreads: 2 pour faire clignoter les signaux sur les 2 PN, 1 pour actionner les barrières de l'un des PN, l'autre n'en ayant pas.

Au plaisir d'échanger sur ces sujets.
Meilleures salutations.

//
//   PassageNiveau.c
//
//   Programme de contrôle du passage à niveau.
//
//   2010.06.12   MHP   Création.
//   2011.06.14   MHP   Premiers tests concluants de commande du servo-moteur.
//   2011.06.20   MHP   Utilisation des protothreads PC (Adam Dunkels, adam@sics.se).
//   2011.06.22   MHP   Premier essai des prothothreads AVR.
//   2011.06.24   MHP   Correction des timings ville et prise en compte du transistor de sortie (inverser PWM).
//   2011.07.06   MHP   Configuration des entrées/sorties définitive!
//   2011.07.07   MHP   Réglage des délais pour la ville.
//   2011.09.10   MHP   Raccourcissement de la période et attente avant de redécter le train au PN ville.
//   2011.12.20   MHP   Suppression de l'anti-rebond pour la détection des trains et ralentissement des mouvements
//            des barrières.
//   2012.04.01   MHP   Suppression du délai entre 2 détections PN ville car nouveaux circuits de détection unidirectionnels.
//
//   Directives de compilation: PC ou AVR.
//

// Includes standard: types, IO.
#ifdef PC
#include   <stdint.h>
#include   <stdio.h>
#endif

#ifdef AVR
#include <inttypes.h>
#include <avr/io.h>
#endif


// Protothreads.
#include "pt.h"

static struct pt ptGererSignalVille, ptGererBarrieresVille, ptGererSignalGare;


// Timers.
typedef uint16_t   tTimer;

static tTimer   tmrBaisserBarrieresVille, tmrBarrieresVille, tmrClignSignalVille, tmrMvtBarrieresVille;
static tTimer   tmrGareA, tmrGareB, tmrClignSignalGare;

// Tous les délais sont exprimés nombres de périodes de PERIODE millisecondes.
// Période en ms.
#define   PERIODE   10

// La durée durant laquelle le signal fonctionne est exprimée en nombre d'itérations
// plutôt qu'à l'aide d'un timer.
#define DELAI_CLIGN_VILLE   (300/PERIODE)

#define   DUREE_SIGNAL_VILLE   (14000/PERIODE)
#define   NB_ITER_SIGNAL_VILLE   (DUREE_SIGNAL_VILLE/(2*DELAI_CLIGN_VILLE)+1)
#define   DELAI_BAISSER_BARRIERES_VILLE   (400/PERIODE)

// La durée durant laquelle le signal fonctionne est exprimée en nombre d'itérations
// plutôt qu'à l'aide d'un timer.
#define DELAI_CLIGN_GARE   (300/PERIODE)
#define DUREE_SIGNAL_GARE   (8000/PERIODE)
#define NB_ITER_SIGNAL_GARE   (DUREE_SIGNAL_GARE/(2*DELAI_CLIGN_GARE))

// Le délai lorsque les barrières sont à nouveau levées est plus grand que le délai entre
// le début du signal de ville et la descente des barrières.
#define   DELAI_BARRIERES_VILLE   (DUREE_SIGNAL_VILLE - 5*DELAI_BAISSER_BARRIERES_VILLE)
#define   DELAI_SERVO      (60/PERIODE)


// Constantes communes PC et AVR


// Spécifique AVR.
#ifdef AVR

#define F_CPU 1000000UL
#include <util/delay.h>

// Configuration des ports
#define INPUT_VILLE   PD1
#define   INPUT_GARE_A   PD2
#define   INPUT_GARE_B   PD3
#define INPUT_ATTENTE_A   PD4
#define   INPUT_ATTENTE_B   PD5

#define   OUTPUT_SERVO   PB3
#define OUTPUT_SIGNAL_VILLE   PB1
#define   OUTPUT_SIGNAL_GARE   PB0


#define CONFIG_DDRB   (_BV(OUTPUT_SERVO)|_BV(OUTPUT_SIGNAL_VILLE)|_BV(OUTPUT_SIGNAL_GARE))

#define PASSAGE_VILLE   (!(PIND & _BV(INPUT_VILLE)))
#define PASSAGE_GARE_A   (!(PIND & _BV(INPUT_GARE_A)))
#define PASSAGE_GARE_B   (!(PIND & _BV(INPUT_GARE_B)))

#define   CONFIG_PULLUPD   (_BV(INPUT_VILLE)|_BV(INPUT_GARE_A)|_BV(INPUT_GARE_B)|_BV(INPUT_ATTENTE_A)|_BV(INPUT_ATTENTE_B))
#define CONFIG_DDRD   ~CONFIG_PULLUPD

#define   ATTENTE_A   (!(PIND & _BV(INPUT_ATTENTE_A)))
#define   ATTENTE_B   (!(PIND & _BV(INPUT_ATTENTE_B)))

#define   SetClignVille   (PORTB |= _BV(OUTPUT_SIGNAL_VILLE))
#define ClrClignVille   (PORTB &= ~_BV(OUTPUT_SIGNAL_VILLE))

#define   SetClignGare   (PORTB |= _BV(OUTPUT_SIGNAL_GARE))
#define   ClrClignGare   (PORTB &= ~_BV(OUTPUT_SIGNAL_GARE))
#endif

#ifdef PC
// Pour simuler le passage des trains.
static uint8_t   PASSAGE_VILLE, PASSAGE_GARE_A, PASSAGE_GARE_B, ATTENTE_A, ATTENTE_B;
#endif

#define   MIN_SERVO      1000
#define   MAX_SERVO      1900
#define   PAS_SERVO      20

#ifdef AVR

#define CONFIG_TCCR1A   (_BV(COM1A1)|_BV(WGM11))

// Pour le test, prescaler sur 1024.
#if 0
#define CONFIG_TCCR1B   (_BV(CS12)|_BV(CS10)|_BV(WGM12)|_BV(WGM13))
#endif
// Fonctionnement normal, prescaler sur 1.
#define CONFIG_TCCR1B   (_BV(CS10)|_BV(WGM12)|_BV(WGM13))

#define   PERIODE_SERVO      20000
#endif


static PT_THREAD(GererSignalGare(struct pt *pt))
{

   // On doit pouvoir traiter ces événements quelque soit l'endroit où
   // on se trouve dans le protothread.
   if (ATTENTE_A && (tmrGareA > 0))
   {
#ifdef PC
      printf("Train en attente gare A\n");
#endif
      tmrGareA = DUREE_SIGNAL_GARE;
   } // if

   if (ATTENTE_B && (tmrGareB > 0))
   {
#ifdef PC
      printf("Train en attente gare B\n");
#endif
      tmrGareB = DUREE_SIGNAL_GARE;
   } // if

   PT_BEGIN(pt);

   while (1)
   {
      PT_WAIT_UNTIL(pt, PASSAGE_GARE_A || PASSAGE_GARE_B);

      if (PASSAGE_GARE_A && (tmrGareA == 0))
      {
#ifdef PC
         printf("Passage gare A\n");
#endif
         tmrGareA = DUREE_SIGNAL_GARE;
      } // if

      if (PASSAGE_GARE_B && (tmrGareB == 0))
      {
#ifdef PC
         printf("Passage gare B\n");
#endif
         tmrGareB = DUREE_SIGNAL_GARE;
      } // if

      while ((tmrGareA > 0) || (tmrGareB > 0))
      {
#ifdef PC
         printf("Signal gare ON\n");
#endif
#ifdef AVR
         SetClignGare;
#endif

         tmrClignSignalGare = DELAI_CLIGN_GARE;
         PT_WAIT_UNTIL(pt, tmrClignSignalGare == 0);

#ifdef PC
         printf("Signal gare OFF\n");
#endif
#ifdef AVR
         ClrClignGare;
#endif
         tmrClignSignalGare = DELAI_CLIGN_GARE;
         PT_WAIT_UNTIL(pt, tmrClignSignalGare == 0);
      } // while
   } // while

   PT_END(pt);
} // GererSignalGare


static PT_THREAD(GererSignalVille(struct pt *pt))
{
static uint16_t   nIter;

   PT_BEGIN(pt);

   while (1)
   {
      PT_WAIT_UNTIL(pt, PASSAGE_VILLE);

      for (nIter = 0; nIter < NB_ITER_SIGNAL_VILLE; nIter++)
      {
#ifdef PC
         printf("Signal ville ON\n");
#endif
#ifdef AVR
         SetClignVille;
#endif
         tmrClignSignalVille = DELAI_CLIGN_VILLE;
         PT_WAIT_UNTIL(pt, tmrClignSignalVille == 0);

#ifdef PC
         printf("Signal ville OFF\n");
#endif
#ifdef AVR
         ClrClignVille;
#endif
         tmrClignSignalVille = DELAI_CLIGN_VILLE;
         PT_WAIT_UNTIL(pt, tmrClignSignalVille == 0);
      } // for

#ifdef PC
      printf("Fin signal ville\n");
#endif
   } // while

   PT_END(pt);
} // GererSignalVille


static PT_THREAD(GererBarrieresVille(struct pt *pt))
{
static uint16_t   nLen;   // Longueur de l'impulsion des servos.


   PT_BEGIN(pt);

   while (1)
   {
      PT_WAIT_UNTIL(pt, PASSAGE_VILLE);

#ifdef PC
      printf("Attendre pour baisser les barrières\n");
#endif

      tmrBaisserBarrieresVille = DELAI_BAISSER_BARRIERES_VILLE;
      PT_WAIT_UNTIL(pt, tmrBaisserBarrieresVille == 0);

#ifdef PC
      printf("Début baisser les barrières\n");
#endif
      tmrBarrieresVille = DELAI_BARRIERES_VILLE;

      nLen = MIN_SERVO;
      while (nLen <= MAX_SERVO)
      {
#ifdef PC
         printf("Baisser barrières, servo: %0d\n", nLen);
#endif
#ifdef AVR
         OCR1A = nLen;
#endif
         nLen += PAS_SERVO;

         tmrMvtBarrieresVille = DELAI_SERVO;
         PT_WAIT_UNTIL(pt, tmrMvtBarrieresVille == 0);
      } // while

#ifdef PC
      printf("Barrières baissées\n");
#endif
      PT_WAIT_UNTIL(pt, tmrBarrieresVille == 0);

      nLen -= PAS_SERVO;
      while (nLen >= MIN_SERVO)
      {
#ifdef PC
         printf("Lever barrières, servo: %0d\n", nLen);
#endif
#ifdef AVR
         OCR1A = nLen;
#endif
         nLen -= PAS_SERVO;

         tmrMvtBarrieresVille = DELAI_SERVO;
         PT_WAIT_UNTIL(pt, tmrMvtBarrieresVille == 0);
      } // while

#ifdef PC
      printf("Barrières levées\n");
#endif
   } // while

   PT_END(pt);
} // GererBarrieresVille


void main(void)
{
   // Initialiser les protothreads.
   PT_INIT(&ptGererSignalVille);
   PT_INIT(&ptGererBarrieresVille);
   PT_INIT(&ptGererSignalGare);

   // Configuration spécifique AVR.
#ifdef AVR
   DDRD   = CONFIG_DDRD;
   PORTD   = CONFIG_PULLUPD;
   DDRB   = CONFIG_DDRB;

   ICR1 = PERIODE_SERVO;
   OCR1A = MIN_SERVO;   
   TCCR1A = CONFIG_TCCR1A;
   TCCR1B = CONFIG_TCCR1B;
#endif

#ifdef PC
uint16_t   nIter;

   //PASSAGE_VILLE = 1;
   PASSAGE_GARE_A = 1;

   for (nIter = 0; nIter <= 1000; nIter++)
   {
      printf("Temps: %0000d\n", nIter * PERIODE);

      if (nIter == 2)
         PASSAGE_GARE_A = 0;

      if (nIter == 40)
      {
         printf("ATTENTE_A = 1\n");
         ATTENTE_A = 1;
      }
      if (nIter > 45)
         ATTENTE_A = 0;


      if (nIter == 50)
      {
         printf("PASSAGE_GARE = 1\n");
         PASSAGE_GARE_B = 1;
      }
      if (nIter > 50)
         PASSAGE_GARE_B = 0;
#endif

#ifdef AVR
   while (1)
   {
#endif
      // Mettre à jour tous les timers.
      if (tmrBaisserBarrieresVille > 0)   tmrBaisserBarrieresVille--;
      if (tmrBarrieresVille > 0)      tmrBarrieresVille--;
      if (tmrClignSignalVille > 0)      tmrClignSignalVille--;
      if (tmrMvtBarrieresVille > 0)      tmrMvtBarrieresVille--;
      if (tmrGareA > 0)         tmrGareA--;
      if (tmrGareB > 0)         tmrGareB--;
      if (tmrClignSignalGare > 0)      tmrClignSignalGare--;

      // Traiter les événements.
      GererSignalVille(&ptGererSignalVille);
      GererBarrieresVille(&ptGererBarrieresVille);
      GererSignalGare(&ptGererSignalGare);

#ifdef AVR
      _delay_ms(PERIODE);
#endif
   } // while, for
} // main


18
Débuter / Re : canton de commutation entre 2 boosters
« le: octobre 29, 2021, 11:55:45 am »
Bonjour à tous,

Je me demande si un tel montage est vraiment nécessaire.

Si un réseau comporte plusieurs boosters, ils sont probablement reliés à la même centrale. De ce fait, les signaux DCC issus de ces boosters doivent être en phase et il ne devrait y avoir aucun problème lorsqu'une loco franchit la limite entre les 2 sections alimentées chacune par un booster.

Comme mentionné par plusieurs sites web parlant de DCC, il est impératif d'isoler les 2 rails entre les sections alimentées par des boosters séparés. Il faut aussi s'assurer que les fils ne sont pas croisés afin d'éviter tout cour-circuit.

Bonne fin de semaine et meilleures salutations.

19
Discussions ouvertes / Re : Re : Locoduino, ILS et aimants
« le: octobre 04, 2021, 01:42:46 pm »
...avec les barrières IR, comment es-tu sûr de ne pas avoir perdu un wagon en cours de route et avoir complètement libéré le canton ?...

Pour gérer correctement ce point, l'ILS et un aimant sous le dernier wagon sont le plus adaptés. Il faut bien entendu que le logiciel gère les 2 impulsions.

La barrière IR ne permet pas cette fonction car il n'y a aucun moyen de distinguer le dernier wagon des autres. Pour compter les wagons, il faudrait un moyen de savoir combien de wagons doit comporter le train en cours de détection et donc avoir un moyen d'identifier les trains.

Cela dit, les avantages de la barrière IR en terme de fiabilité me semblent l'emporter sur l'impossibilité de vérifier la présence de tous les wagons.

20
Discussions ouvertes / Re : Locoduino, ILS et aimants
« le: octobre 04, 2021, 08:37:01 am »
Bonjour à tous,

Sur mon réseau hivernal, j'ai une ligne de bus animée par le système Magnorail. Initialement, j'utilisais des ILS pour détecter le passage du bus aux stations et déclencher la temporisation d'arrêt.

Ce n'était pas optimal, j'avais dû ajouter un aimant plus gros que ceux qui tiennent le bus et il empêchait le bon fonctionnement. J'ai finalement choisi des barrières infrarouge en me basant sur cet article du site éditorial Locoduino: https://www.locoduino.org/spip.php?article40. Le fonctionnement est impeccable.

J'ai adapté le schéma comme suit:



Les résistances sont prévues pour une alimentation Vcc de 12 V. Une led fonctionnant à 2 mA dans la base du 2ème transistor permet de positionner la led IR et la photodiode. Lorsqu'elles sont bien en face l'une de l'autre, cette led est éteinte. Elle s'allume en cas d'interruption du faisceau. Le collecteur du transistor de sortie attaque l'entrée trigger d'un monostable à 555 utilisé pour la temporisation. La résistance de tirage (pull-up) est déjà présente dans le montage monostable, d'où son absence ici. Elle n'est pas non plus nécessaire si le collecteur de ce transistor est relié à une entrée Arduino, pour autant qu'elle soit configurée en INPUT_PULLUP.

Moyennant une petite machine à états dans le logiciel, la détection infrarouge est capable de détecter tout le train. Selon la hauteur où la barrière est positionnée, le signal produit par la barrière sera plus ou moins intermittent. Il faut filtrer les cas où le faisceau est rétabli, par exemple entre 2 wagons et considérer que le train a complètement passé lorsque le faisceau est à nouveau rétabli "pendant un certain temps".  ;)

Bon début de semaine.

21
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.


22
Vos projets / Re : Re : Re : Boite de mesure de vitesse Arduino
« le: avril 01, 2021, 06:56:22 pm »
...Par ici nous somme plutôt dans l'OpenHardware et l'OpenSource....

Bonjour,

Je suis tout à fait d'accord avec ce point de vue. J'ajouterais que l'esprit de Locoduino inclut certainement aussi le partage de connaissances.

La démarche autour de ce projet me surprend. L'auteur commence par poser beaucoup de questions, jusqu'à demander si quelqu'un pourrait écrire le programme.
Ensuite il protège le concept élaboré avec l'aide des membres du forum. Par ailleurs, les conditions auxquelles on peut obtenir le système ne sont pas claires.

Bon week-end de Pâques.

23
Vos projets / Re : Gestion ligne à crémaillère
« le: février 28, 2021, 08:59:02 pm »
Bonsoir,

Oui, les locos sont bien des modèles Fleischmann, tout comme la voie.

L'écartement est normal, rare pour la crémaillère qui est le plus souvent en voie étroite. Le seul exemple de voie normale à crémaillère que je connais est la ligne du Rigi en Suisse centrale.

Bon début de semaine.

24
Vos projets / Gestion ligne à crémaillère
« le: février 26, 2021, 11:00:49 am »
Bonjour à tous,

Mon réseau époque III-IV comporte une ligne à crémaillère complètement indépendante du reste du réseau. Cette ancienne photo datant de la construction du réseau montre l'ensemble de la ligne à crémaillère: départ à gauche de la gare principale, montée, grande courbe à droite et arrivée devant le chalet qui sera un restaurant de montagne.



Les 2 extrémités sont alimentées au travers de diodes afin d'assurer l'arrêt du train et son départ dans le sens montée, respectivement descente. Un temporisateur inverse périodiquement le sens de l'alimentation traction. La zone de pleine voie a des diodes dont le rôle est de réduire la tension en descente.

En montée, la situation est:



En descente, la situation est:



Le fonctionnement obtenu n'est pas satisfaisant, les locos Fleischmann ayant la fâcheuse tendance à s'emballer à la descente. Je viens donc de démarrer un projet basé sur un Attiny45 dont les caractéristiques seront:
  • Réutilisation de l'alimentation traction actuelle basée sur un LM317.
  • Conserver la possibilité d'exploiter manuellement la ligne.
  • Réduire les modifications de câblage de la ligne autant que possible. Probablement que les diodes de pleine voie seront supprimées.
  • Alimentation PWM ou PFM (pulse frequency modulation).
  • Vitesses différentes en montée et en descente avec si possible des phases d'accélération / déccélération.

La 1ère fonction sera réalisée en court-circuitant la résistance R2 du montage à LM317, on réduit la tension de sortie à la tension de référence de 1.25 V. Le schéma ci-après, tiré de la datasheet du LM317 (où les diodes de protection ne sont pas dessinées) montre le principe. Un transistor commandé par un signal logique gère cela.



Les alimentations traction de mon réseau sont regroupées dans un module de 4 alimentations trouvé en occasion sur Internet. La ligne à crémaillère est alimentée par celle de gauche, le potentiomètre linéaire joue le rôle de la résistance R2, un transistor le court-circuitera. En exploitation automatique, il suffira de régler le potentiomètre au maximum. On conserve ainsi la possibilité d'exploiter manuellement. Un bénéfice de ce montage est que le LM317 a une protection contre la surcharge qui s'active en cas de courant supérieur à 1.5 A.



J'ai déjà fait quelques essais qui me confortent dans ce choix pour la PWM. Le LM317 suit bien, mais on reste à des fréquences basses de l'ordre de 100 Hz.

Bonne fin de semaine.

25
Merci à tous pour vos voeux et pour vos compliments à propos de mon réseau.

Bonne fin de semaine et meilleures salutations.

26
Trucs & astuces / Re : Techniques de mise au point
« le: janvier 22, 2021, 11:05:13 pm »
Bonsoir à tous,

Voici une nouvelle technique de mise au point, l'envoi d'une trame de bits sur oscilloscope. J'ai un oscilloscope analogique de presque 40 ans d'âge sans possibilité de mémorisation et je me suis demandé comment l'utiliser pour afficher des séquences binaires pour la mise au point.

L'image ci-après montre le résultat obtenu, pour le moment avec un programme de test basé sur une instruction _delay_us, à remplacer par une routine d'interruption pilotée par un timer pour un timing plus précis. La résolution horizontale est de 50 us / division, la résolution verticale est de 2 V / division. La valeur affichée est: 11010000.



Le principe est de commencer par une impulsion à 5 V, ensuite les 1 sont représentés par 3 V et les 0 par 0 V.
L'impulsion de 5 V permet de voir où le nombre affiché commence.
Le trigger de l'oscilloscope est réglé pour se déclencher entre 3V et 5V, si on le règle plus bas, les bits représentés par 3 et 0 V se mélangent. Ainsi le trigger ne se déclenche qu'au début de l'affichage de la valeur.

Il suffit d'une broche du microcontrôleur et 2 résistances: 2.2 K entre la sortie et 5 V,  3.3 K entre la sortie et la masse.

En C, sans librairie Arduino, j'ai défini la broche reliée à l'oscilloscope.
#define OSCILLO _BV(PB1)

Arduino
#define OSCILLO  13

Affichage de l'impulsion initiale
Configurer la broche en sortie.
Écrire un 1.

DDRB |= OSCILLO;
PORTB |= OSCILLO;

Arduino
pinMode (OSCILLO, OUTPUT);
digitalWrite (OSCILLO, HIGH);

Afficher un 0
Configurer la broche en sortie.
Ẽcrire un 0.

DDRB |= OSCILLO;
PORTB &= ~OSCILLO;

Arduino
pinMode (OSCILLO, OUTPUT);
digitalWrite (OSCILLO, LOW);

Afficher un 1
Broche en entrée, sans résistance pullup interne.
Le diviseur de tension constitué par les résistances maintient la broche à 3.3 / 5.5 de 5 V, soit 3 V.

DDRB &= ~OSCILLO;

Arduino
pinMode (OSCILLO, INPUT);

Cette technique est peut-être moins nécessaire avec un Arduino où les serial.print permettent de tracer l'exécution, mais sur les petits Attiny elle peut s'avérer utile.

Bonne fin de semaine à tous.

27
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.

28
Bonjour à tous,

Merci pour le partage de cette solution de détection.

Je réfléchis à l'amélioration du fonctionnement de la ligne à crémaillère de mon 1er réseau N. Elle fonctionne actuellement à l'aide de diodes anti-retour aux 2 extrémités et un 555 en astable qui commande un relais d'inversion de sens (source: le site ptitrain.com, j'y vais j'y retourne). Obtenir des vitesses acceptables en montée et en descente est très difficile avec ce système car c'est le même réglage de l'alimentation traction analogique pour les 2 sens de marche.

Le nouveau système que je prévois devrait avoir les spécifications suivantes:
  • Montage à microcontrôleur, sinon je n'en parlerais pas ici.  :)
  • Alimentation traction PWM avec des consignes différente pour la montée et la descente.
  • 2 détections du train à chaque extrémité afin de gérer accélération / décélération et arrêt.

La détection optique me semble la meilleure option pour ce projet. Comme il s'agira d'un système complètement indépendant dédié à la voie à crémaillère, je pense relier les diviseurs de tension constitués des phototransistors et résistances, directement sur les entrées ADC du microcontrôleur. Enfin, l'éclairage du réseau étant assez uniforme, un seul phototransistor de référence devrait suffire. Cela me ferait donc 5 entrées analogiques, la référence et les 2 * 2 détecteurs, un Atmega8 au-moins est donc nécessaire, il dispose de 6 entrées sur le multiplexeur en amont du convertisseur AD.

Je démarrerai probablement un fil spécifique à ce projet lorsque mes réflexions seront plus abouties.

Bonne journée et meilleures salutations.
Marc-Henri

29
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.

30
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.

Pages: 1 [2] 3 4 ... 12