Une débutante dans le décor - Ep13 - Que la lumière jaillisse!

Bonsoir,

Edit du 29.10.2021 v02 pour prendre compte remarques de @Thias et @sdel95, pour un code plus lisible et compréhensible pour un débutant.

Ce tutoriel est un prolongement des épisodes Une débutante dans le décor - Ep6 - Quelques automatismes (Arduino Nano)), Une débutante dans le décor - Ep8 - Un servo pour l'Arduino et Une débutante dans le décor - Ep9 - Paparazzi, comment réaliser des automatismes en mode “lego” basé sur la plateforme Arduino Nano.

Objectif : pas de fer à souder, pas de câblage compliqué, pas de résistance/condo/… à rajouter et bien sur un prix mini mini. Si tu sais brancher un M84, tu sais utiliser un Arduino à la sauce Julaye :wink: !

Pour agrémenter le bâtiment administratif d’Obourg, j’ai décidé d’embarquer un Arduino Nano pour proposer des automatismes sur l’éclairage des différents bureaux et couloirs du bâtiment.

Cet épisode sera en deux parties principales.

La première partie va mettre en place une séquence d’allumage d’une lampe avec un tableau de configuration. Dans l’exercice proposé, nous allons allumer cette led en simulant l’allumage d’un néon, que nous compléterons avec des glitchs aléatoires, un néon en pas très bon état donc.

La deuxième partie va mettre en place un automate permettant de gérer jusqu’à 6 éclairages différents en proposant des séquences d’allumage et extinction pour chaque pièce, comme en domotique lorsque l’on veut simuler une présence dans une maison inoccupée …

Pour aujourd’hui, je vous propose la première partie. C’est parti !


Première partie : un néon sinon rien !

L’Arduino Nano possède 6 sorties digitales que nous pouvons utiliser en mode PWM pour commander autant de lampes, une lampe pouvant être constituée de plusieurs leds. Attention néanmoins à ne pas dépasser 40 mA par sortie, le total ne devant excéder 200 mA au risque de détruire le Nano.

Les sorties utilisables sont D3, D5, D6, D9, D10 et D11. Le mode PWM est utile pour commander des servos moteurs mais permet aussi de faire varier l’intensité lumineuse de la led :slight_smile:. Nous avions abordé le mode PWM dans Une débutante dans le décor - Ep8 - Un servo pour l'Arduino.

Le schéma de branchement de l’Arduino Nano est minimalistique :

  • alimentation par le connecteur USB ou par GND / +12V
  • led branchée entre GND et D3 (+ de la led)

Je n’ai pas mis de résistance avec la led pour les essais, la sortie est limité à 5V et le PWM va envoyer un signal moyen très inférieur à ce que peut supporter la led.

Reste à écrire le programme que nous allons dérouler pas à pas


On charge la bibliothèque standard de l’Arduino et on définit une constante debug que l’on met à 0 en temps normal mais que l’on pourra mettre à 1 pour obtenir des traces sur la console connectée sur le port série.

#include <Arduino.h>

// mettre à 1 pour un debug dans la console série, 2 pour full debug
const int debug = 1;

Une constante stocke le port utilisé, ici D3 d’autres constantes pour jouer sur l’intensité de l’éclairage avec des valeurs différentes sur le PWM

// configuration sortie D3 pour connecter la led
int ledPin = 3;

// l'intensité maximum de chaque sortie PWM 
const int PWM_FOR_LED = 12;

// ON ou OFF ou autre valeur (fading)
const int LIGHT_ON = PWM_FOR_LED;
const int LIGHT_FAD2 = PWM_FOR_LED/2;
const int LIGHT_FAD4 = PWM_FOR_LED/4;
const int LIGHT_OFF = 0;

On crée un tableau formé par des couples dont la première valeur contient un délai en millisecondes et la deuxième valeur indique l’intensité de l’allumage pendant le délai spécifié.

A noter que vous pouvez ainsi vous créer tout un tas d’animation lumineuses, le tableau ci-après simule un néon qui s’allume.

// blink
const struct blink {
  int duration;
  int intensity;
} blinkOn[] = {
    10, LIGHT_ON,   20, LIGHT_OFF,
    20, LIGHT_ON,  240, LIGHT_OFF,
    20, LIGHT_ON,   40, LIGHT_OFF,
    20, LIGHT_ON,  100, LIGHT_OFF,
    20, LIGHT_ON,   20, LIGHT_OFF, 
    20, LIGHT_ON,  260, LIGHT_OFF,
    80, LIGHT_ON,   20, LIGHT_OFF, 
   240, LIGHT_ON,   60, LIGHT_OFF,
   160, LIGHT_ON,   20, LIGHT_OFF,
   240, LIGHT_ON,   20, LIGHT_OFF,
  1000, LIGHT_FAD2, 20, LIGHT_OFF,
    20, LIGHT_ON,   40, LIGHT_OFF,
   100, LIGHT_ON,   20, LIGHT_OFF,
  2740, LIGHT_ON,  340, LIGHT_FAD4,
   860, LIGHT_FAD2, 20, LIGHT_OFF,
  1400, LIGHT_ON,   20, LIGHT_OFF,
    60, LIGHT_ON,   20, LIGHT_OFF
}; 

Les deux variables qui suivent permettent de se déplacer dans le tableau précédent et de connaitre le nombre d’éléments du tableau.

// etat de clignotement (position dans le tableau précédent)
int gStateBlink = 0;

// nombre de transitions
const int maxStateBlink = sizeof(blinkOn)/sizeof(blink);

Comme à mon habitude, nous allons mettre en place un petit automate à états finis (Finite State Machine) avec trois états : lumière éteinte, lumière allumée et lumière en allumage.

La variable gStateRunning contient l’état courant de l’automate.

// stateLight
const int state_OFF   = 0;
const int state_ON    = 1;
const int state_PWRUP = 2;
int gStateRunning = state_OFF;

L’automate est simple. Lorsque l’Arduino démarre, il est dans l’état state_OFF puis il passe dans l’état state_PWRUP qui va simuler l’allumage du néon et enfin il sera dans l’état State_ON d’allumage permanent.

Pour agrémenter un peu cette première partie, j’ai rajouté un tirage aléatoire qui génère un glitch toutes les 10 secondes environ. Le glitch consiste à remettre l’automate dans l’état state_PWRUP en commençant à une séquence précise dans le tableau blinkOn …


Enfin la variable gDelay est utilisée pour prendre en compte le délai d’allumage ou d’extinction de la led, conformément aux informations de la séquence extraite du tableau blinkOn.

// delay avant une transition de la machine à état
int gDelay = 0;

Commence ensuite le programme proprement dit, avec la fonction de démarrage de l’Arduino, executée une seule fois à la mise sous tension de la carte.

void setup() {
  // code de démarrage executé une seule fois à la mise sous tension de la carte
  
  // ouvre le port série (console de l'outil) avec la vitesse 57600 bauds
  // attention que le paramètre sur la console soit bien 57600 !
  Serial.begin(57600);

  // Ports digitaux programmables (PWM) : D3, D5, D6, D9, D10 et D11
  pinMode(ledPin,OUTPUT);
 
  // Annonce la version
  Serial.println("Neon v02 20211029 - (c) Julie Dumortier - Licence GPL");
}

Séquence assez classique qui consiste à paramétrer le port série à la vitesse de 57600 bauds pour une éventuelle sortie si debug est défini à une valeur différente de 0.

Ensuite la fonction paramètre la broche D3 en sortie et imprime sur la console la version du programme. A noter que le code que je vous propose est comme à mon habitude sous licence GPL, vous en faites ce que vous voulez, sauf du business :slight_smile:.


Le reste du programme est constitué de trois fonctions : lightOn(), lightOff() et powerUp() qui correspondent aux trois états state_ON, state_OFF et state_PWRUP de notre machine à état fini.

Le programme principal est une boucle sans fin qui ne s’arrête que lorsque vous coupez l’alimentation de l’Arduino. Ce programme consiste à appeler une des trois fonctions précédentes en fonction de l’état de l’automate, qui je vous le rappelle se trouve stocké dans la variable stateRunning.

void loop() {

  switch (gStateRunning) {
    
    case state_OFF : lightOff(); break;
    
    case state_ON : lightOn(); break;
    
    case state_PWRUP : powerUp(); break;
    
   } // end switch

  delay(1);
}

Vous remarquerez le délai d’une milliseconde à chaque itération dans la boucle, c’est ce qui est aligné avec la variable gDelay dont l’unité est justement la milliseconde !.

Nous reste à étudier chacune des trois fonctions lightOn(), lightOff() et powerUp() .

lightOff() est appelée au démarrage de la carte, elle permet d’initialiser l’automate et de s’assurer que la led est bien éteinte. Puis l’état de l’automate passe en state_PWRUP.

void lightOff()
{
  if (debug) Serial.println("state_OFF");

  // eteint la led (au cas où)
  analogWrite(ledPin,0); 

  // passe en mode démarrage
  gStateRunning = state_PWRUP;
  if (debug) Serial.println("stateRunning <-- PWRUP");

  // prépare la séquence
  gStateBlink = 0;
  gDelay = 0;
}

La fonction powerUp() est appelée tant que l’automate est dans l’état state_PWRUP qui correspond à la séquence d’allumage de notre lampe.

A chaque fois que l’on entre dans cette fonction, on va regarder si le délai est écoulé en testant la variable gDelay. Si le délai est écoulé, on avance dans le tableau de séquence blinkOn en incrémentant l’état de clignotement gSateBlink, c’est à dire que l’on se déplace dans ce tableau blinkOn.

Lorsque l’on arrive à la fin du tableau, c’est que la séquence de démarrage est terminée et on passe l’automate dans l’état state_ON.

C’est la fonction analogWrite() proposé par l’Arduino qui active la sortie D3.

void powerUp()
{
    int lightOn;

    // si le délai est échu, démarre une nouvelle séquence
    if (gDelay<=0) 
    { 
        if (debug) {
          Serial.print("state_PWRUP - blink sequence ");
          Serial.print(gStateBlink);
        }
        
        lightOn = blinkOn[gStateBlink].intensity;
        analogWrite(ledPin,lightOn);
        
        if (debug) {
          Serial.print(" intensity: ");
          Serial.print(lightOn);
          Serial.print(" delay:");

        }
        
        gDelay = blinkOn[gStateBlink].duration;
        if (debug) Serial.println(gDelay);

        gStateBlink += 1;
        if (gStateBlink>=maxStateBlink) { 
          gStateBlink = 0;
          gStateRunning = state_ON;
          if (debug) Serial.println("state_ON");
        }
  }
  if (debug>1) Serial.print(".");
  gDelay = gDelay - 1;
}

Dernière fonction, lightOn() allume la lampe de façon définitive avec la commande analogWrite().

Mais j’ai ajouté un tirage aléatoire pour relancer l’automate sur la séquence de démarrage en position 22 du tableau, ce qui permet de faire un petit glitch de temps en temps :slight_smile:.

void lightOn()
{
      int alea;
      
      analogWrite(ledPin,PWM_FOR_LED);

      // fait un tirage aléatoire et refait un glitch à partir de la séquence 22
      // ajuster la valeur random en fonction de la fréquence d'apparition (en ms)
      alea = random(0,10000);
      if (alea < 1) {
        if (debug) Serial.print("Glitch alea:");
        if (debug) Serial.println(alea);
        gStateRunning = state_PWRUP;
        gStateBlink = 22;
        gDelay = 0;
      }
}

Ceci met fin à cette première partie que je referme avec une petite vidéo qui montre la séquence de démarrage suivi de quelques glitchs. Désolée pour le bruit de fond, j’ai mon ex qui est bruyant quand il se mouche :roll_eyes:

Et le programme complet ci-après :

// Tutoriel Neon 
// (c) Julie Dumortier, 2021
// licence GPL
//
// Evolutions
// v0.1 première version sur plateforme de démonstration
// v0.2 polish pour un code plus lisible et prendre en compte quelques remarques (Thias, Stéphane)
//
// branchez une LED entre GND et D3
// et appréciez l'allumage du néon à la mise sous tension de l'Arduino
// 
// pensez à protéger votre led avec une résistance
//  utilisez le calculateur de résistance svp  https://www.digikey.fr/fr/resources/conversion-calculators/conversion-calculator-led-series-resistor
//  pas plus de 40 mA par sortie, pas plus de 200 mA au total, au risque de griller une sortie et/ou l'Arduino Nano
//
//  l'intensité est réglable sur les sorties D3, D5, D6, D9, D10 et D11 (cf pwmPin)
//
// Retrouvez ce tutoriel sur le lien :https://forum.3rails.fr/t/une-debutante-dans-le-decor-ep13-que-la-lumiere-jaillisse/20889

#include <Arduino.h>

// mettre à 1 pour un debug dans la console série, 2 pour full debug
const int debug = 1;

// configuration sortie D3 pour connecter la led
int ledPin = 3;

// l'intensité maximum de chaque sortie PWM 
const int PWM_FOR_LED = 12;

// ON ou OFF ou autre valeur (fading)
const int LIGHT_ON = PWM_FOR_LED;
const int LIGHT_FAD2 = PWM_FOR_LED/2;
const int LIGHT_FAD4 = PWM_FOR_LED/4;
const int LIGHT_OFF = 0;

// blink
const struct blink {
  int duration;
  int intensity;
} blinkOn[] = {
    10, LIGHT_ON,   20, LIGHT_OFF,
    20, LIGHT_ON,  240, LIGHT_OFF,
    20, LIGHT_ON,   40, LIGHT_OFF,
    20, LIGHT_ON,  100, LIGHT_OFF,
    20, LIGHT_ON,   20, LIGHT_OFF, 
    20, LIGHT_ON,  260, LIGHT_OFF,
    80, LIGHT_ON,   20, LIGHT_OFF, 
   240, LIGHT_ON,   60, LIGHT_OFF,
   160, LIGHT_ON,   20, LIGHT_OFF,
   240, LIGHT_ON,   20, LIGHT_OFF,
  1000, LIGHT_FAD2, 20, LIGHT_OFF,
    20, LIGHT_ON,   40, LIGHT_OFF,
   100, LIGHT_ON,   20, LIGHT_OFF,
  2740, LIGHT_ON,  340, LIGHT_FAD4,
   860, LIGHT_FAD2, 20, LIGHT_OFF,
  1400, LIGHT_ON,   20, LIGHT_OFF,
    60, LIGHT_ON,   20, LIGHT_OFF
}; 

// nombre de transitions
const int maxStateBlink = sizeof(blinkOn)/sizeof(blink);

// etat de clignotement (position dans le tableau précédent)
int gStateBlink = 0;

// stateLight
const int state_OFF   = 0;
const int state_ON    = 1;
const int state_PWRUP = 2;
int gStateRunning = state_OFF;

// delay avant une transition de la machine à état
int gDelay = 0;

// ---------------------------------------------------------------------
// --- setup()
// ---------------------------------------------------------------------

void setup() {
  // code de démarrage executé une seule fois à la mise sous tension de la carte
  
  // ouvre le port série (console de l'outil) avec la vitesse 57600 bauds
  // attention que le paramètre sur la console soit bien 57600 !
  Serial.begin(57600);

  // Ports digitaux programmables (PWM) : D3, D5, D6, D9, D10 et D11
  pinMode(ledPin,OUTPUT);
 
  // Annonce la version
  Serial.println("Neon v02 20211029 - (c) Julie Dumortier - Licence GPL");
}

// ---------------------------------------------------------------------
// ligthOff()
//
// éteint le néon
// ---------------------------------------------------------------------

void lightOff()
{
  if (debug) Serial.println("state_OFF");

  // eteint la led (au cas où)
  analogWrite(ledPin,0); 

  // passe en mode démarrage
  gStateRunning = state_PWRUP;
  if (debug) Serial.println("stateRunning <-- PWRUP");

  // prépare la séquence
  gStateBlink = 0;
  gDelay = 0;
}

// ---------------------------------------------------------------------
// ligthOn()
//
// allume le néon en permanent
// ---------------------------------------------------------------------

void lightOn()
{
      int alea;
      
      analogWrite(ledPin,PWM_FOR_LED);

      // fait un tirage aléatoire et refait un glitch à partir de la séquence 22
      // ajuster la valeur random en fonction de la fréquence d'apparition (en ms)
      alea = random(0,10000);
      if (alea < 1) {
        if (debug) Serial.print("Glitch alea:");
        if (debug) Serial.println(alea);
        gStateRunning = state_PWRUP;
        gStateBlink = 22;
        gDelay = 0;
      }
}

// ---------------------------------------------------------------------
// powerUp()
//
// gère une séquence d'allumage du néon
// ---------------------------------------------------------------------

void powerUp()
{
    int lightOn;

    // si le délai est échu, démarre une nouvelle séquence
    if (gDelay<=0) 
    { 
        if (debug) {
          Serial.print("state_PWRUP - blink sequence ");
          Serial.print(gStateBlink);
        }
        
        lightOn = blinkOn[gStateBlink].intensity;
        analogWrite(ledPin,lightOn);
        
        if (debug) {
          Serial.print(" intensity: ");
          Serial.print(lightOn);
          Serial.print(" delay:");

        }
        
        gDelay = blinkOn[gStateBlink].duration;
        if (debug) Serial.println(gDelay);

        gStateBlink += 1;
        if (gStateBlink>=maxStateBlink) { 
          gStateBlink = 0;
          gStateRunning = state_ON;
          if (debug) Serial.println("state_ON");
        }
  }
  if (debug>1) Serial.print(".");
  gDelay = gDelay - 1;
}

// ---------------------------------------------------------------------
// loop()
//
// L'automate néon 
// ---------------------------------------------------------------------

void loop() {

  switch (gStateRunning) {
    
    case state_OFF : lightOff(); break;
    
    case state_ON : lightOn(); break;
    
    case state_PWRUP : powerUp(); break;
    
   } // end switch

  delay(1);
}

Seconde partie : la gestion d’une scène

Objectif du projet : Le programme Lumières est une tentative pour proposer aux adeptes du modélisme un automate de gestion des éclairages d’un bâtiment, d’une scène, ou plus généralement d’un ensemble d’éclairages, qui soit facile d’accès pour un non initié à la programmation d’un Arduino, et fortement paramétrable pour couvrir les besoins les plus courants.

L’utilisateur adapte le fichier ConfigLumieres.h selon ses besoins et il obtient un automate opérationnel, sans écrire une seule ligne de code, seulement des données dans le fichier susmentionné. C’est une approche NO CODE - LOW CODE pour surfer sur le buzz des outils de programmation actuels.

Les types d’éclairages actuellement supportés sont :

  • lampe standard (allumé ou éteint)
  • néon neuf avec sa séquence d’allumage,
  • néon fatigué avec des glitchs aléatoires,
  • flash de photographe avec pre-flash,
  • poste de soudure à l’arc,
  • servo moteur 0-90° pour l’ouverture/fermeture d’une porte
  • gyrophare
  • bougie / brasero
  • feu clignotant qui ne clignote que s’il n’est pas permanent.

Sur les sorties PWM (D3,D5,D6,D9,D10 et D11), l’automate gère la variation d’intensité lumineuse pour plus de réalisme.

Licence : Le logiciel est sous licence GPL et peut être librement utilisé et modifié dans le cadre de notre passion de modélisme, vos éventuelles modifications doivent être reversées à la communauté.

Documentation : La débutante dans le décor - Ep13 - Que la lumière jaillisse ! - Google Docs

La documentation proposée présente aussi dans son chapitre 9 de nombreux exemples d’automatismes qui sont faciles à réaliser avec cet automate :

  • Photographe paparazzi dont le flash se déclenche au passage d’un train
  • Gestion de la séquence d’éclairage d’une fosse d’inspection au stationnement d’une locomotive
  • Animation d’un poste de soudure en présence d’une locomotive dans l’atelier
  • Animation de feux tricolores à un croisement routier, inclu les feux en panne (orange clignotant)
  • L’ouverture et la fermeture d’une porte de remise avec un gyrophare de sécurité lorsqu’une locomotive se présente devant la porte
  • Passage à niveau avec feu clignotant et barrière commandée par un servo

La suite est ici : GitHub - Julaye/Lumieres: Gestion d'une scène ou d'un ensemble d'éclairages avec un Arduino Nano (échelle HO 1/87 ou supérieure)


Retrouvez tous les épisodes de la débutante en clickant ici !

6 « J'aime »

Bonjour Julie, bonjour tout le monde,

Bon ben voila un néon qui clignote à l’allumage ! :grinning:

Dis moi, quelle est la réf de la plaque support utilisée pour le uno ? Les dominos verts sont-ils inclus sur la plaque ?

Merci d’avance.
Cdlt,

Claude papaciela

Nitpick: debug est une macro et devrait être en capitale.
Aussi je te recommanderais d’avoir une macro DEBUG de ce genre

#ifdef DEBUG
#define DEBUG_PRINT(x)  Serial.print(x)
#else
#define DEBUG_PRINT(x) do {} while (0)
#endif

Ça évite plein de conditionnelles dans ton code.

Et avoir des fonctions qui bricolent des variables globales stateRunning, stateBlink et myDelay c’est 𝕸𝖆𝖑…

1 « J'aime »

Hello,

Oui je sais mais j’ai une valeur dedans (0, 1 ou 2) avec un niveau. C’est plus simple comme ça à comprendre pour un débutant.

le puriste aurait écrit const int debug = 1; parce que les macros c’est mal.

En général oui, dans le cas d’une FSM et d’un séquenceur, non justement. C’est équivalent à du micro-code (cf Microcode - Wikipedia) avec une FSM et des registres globaux …

gStateRunning est l’état de la FSM
gStateBlink est l’état du séquenceur
gDelay est la transition globale

Donc ces trois variables globales sont normales et ne sont absolument pas un bricolage.

J’ai mis des fonctions pour découper un peu, mais normalement je mets un gros switch (et ces trois variables seraient locales au switch) et dans la vraie vie, je fais une “vraie FSM” (incompréhensible pour celui qui n’a jamais croisé cet objet informatique) avec un tableau de pointeur sur des fonctions et un tableau pour les transitions …

Pas envie de programmer objet non plus, c’est vrai que ça serait plus élégant avec l’instanciation d’une classe FSM et sa variable membre stateRunning et des accesseurs et toussa. Mais bof, pourquoi faire simple quand on peut faire compliqué :wink: ou l’inverse :rofl:

Attends la suite, il y a une FSM générale (gestion des séquences dans le bâtiment) et autant de FSM que de sorties. Du coup j’ai des stateRunning :roll_eyes:

@+

1 « J'aime »

Oh, j’aurais juste fait une classe avec des membres publics (un joli struct), mais ça me permet de savoir où est mon état, i.e. où il faudra mettre un mutex le cas échéant et le fait de le passer aux fonction les rends pures ce qui permet de les tester…

Je n’ai pas de problème avec les macros, on en utilise beaucoup au boulot…

1 « J'aime »

Effectivement je peux mettre un struct pour ranger un peu les différents registres de la FSM.

Le Mutex, sur un Arduino Nano (le plus petit de la gamme, moins de 5€ en volume la carte toute équipée), bof, le seul système de multitâche possible, c’est du coopératif avec de la co-routine. J’ai prévu d’en parler bientôt justement :). La co-routine c’est rigolo à implémenter et pour le coup les macros c’est très utile pour cacher la quincaillerie.

Sinon y a bien des NMI et autres IRQ, mais normalement le principe de l’Arduino c’est de faire de l’embarqué sans sortir l’artillerie lourde. De toute façon le système est trop petit pour arriver à caser une light thread, alors un process … oublie.

Je crois que mon approche est assez pragmatique. Les gens bricolent des petits automatismes de droite et de gauche pour les éclairages (fosses, bâtiments …), j’essaye de proposer une FSM qui permet de paramétrer plutôt que de programmer l’Arduino Nano.

@+

1 « J'aime »

Le code de la seconde partie est opérationnel après plusieurs heures d’efforts :slight_smile:

// Batiment01
// Exemple pour le forum 3rails / Julie Dumortier / Licence GPL
// 
// La gestion d'un batiment entier avec un petit Arduino Nano
//  v20211027 - première implémentation, mise au point, mode debug
//  v20211028 - mise au point, ajout de commandes (WAIT, WSTOP, UNSET), 
//            nettoyage du code, mode debug / verbose
//  v20211029 - utilisation de la BUILTIN led pour indiquer le bon fonctionnement 
//            du séquenceur / automate, nettoyage des traces verbose

Ce soir, vous n’aurez que le cartouche :slight_smile:


Je vais dormir dessus puis préparer une documentation pour le paramétrage du programme.

Je ne résiste pas à mettre un bout de code pour @Thias :wink:, un dérivé du programme néon. Et je n’ai plus aucun #define, que des const int :rofl:

// ---------------------------------------------------------------------
// fsmEclairage() est l'automate pour gérer un éclairage selon son état
//  OFF     : éteint
//  STPWRUP : début d'allumage si pas déjà allumé
//  PWRUP   : séquence d'allumage en cours, pour les néons
//  ON      : allumé, avec glitch aléatoire pour les vieux néons
//
// L'automate déroule OFF -> STPWRUP -> PWRUP -> ON
// 
// Eventuellement, de l'état ON on peut repasser en état PWRUP pour 
// simuler un glitch sur un vieux néon.
//
// A noter aussi que les éclairages standards passent directement de 
// l'état STPWRUP à l'état ON puisqu'il n'y a pas de séquence d'allumage
//
// De même, un éclairage déjà allumé passe directement de STPWRUP à ON
// car nul besoin de relancer une séquence d'allumage sur un néon déjà
// allumé ...
// ---------------------------------------------------------------------

void fsmEclairage() 
{
  for (int led=0; led<maxLights; led++) {

    switch (gEStateRunning[led]) {
      
      case estate_OFF : lightOff(led); break;

      case estate_STPWRUP : lightStartPowerUp(led); break;
    
      case estate_PWRUP : lightPowerUp(led); break;
    
      case estate_ON : lightOn(led); break;
    
    } // fin du switch
    
  } // pour chaque sortie

  // base de temps de nos FSM : la milliseconde (précision sommaire)
  delay(1);
}

J’ai gardé les variables globales mais je leur ai donné de jolis noms :slight_smile:

// state of the FSM : "stateRunning"
// chaque éclairage a un état particulier et un graphe de transition
const int estate_OFF       = 0; /* éclairage éteint */
const int estate_STPWRUP   = 1; /* éclairage en début d'allumage --> démarre la séquence PWRUP sauf si l'éclairage est déjà allumé */
const int estate_PWRUP     = 2; /* éclairage en allumage --> ON quand la séquence PWRUP sera terminée*/
const int estate_ON        = 3; /* éclairage allumé */

// Pour chaque éclairage, il faut garder son état (OFF, STPWRUP, PWRUP ou ON)
int gEStateRunning[maxLights];

// Pour chaque éclairage dans l'état PWRUP, il faut mémoriser la transition en cours
int gEStatePwrup[maxLights];

// delay avant la prochaine transition ON OFF
int gEStateDelay[maxLights];

A suivre avec une documentation détaillée et complète ainsi que l’ensemble des sources.

Le programme utilise 21% de l’espace de stockage de programmes (6K sur les 30 K octets disponibles), les machines à états finis sont des objets informatiques très compactes en exécutable binaire.

Par contre, les différentes variables et tableaux globals consomment pas mal de mémoire dynamique avec 52% de l’espace consommé (1K sur les 2K octets disponibles).

Il reste néanmoins suffisamment de place pour quelques évolutions futures …

@+

1 « J'aime »

Bonjour
S’il te plaît Julie, utilise un define d’une struct pour les éléments du tableau BlinkOn. Le nombre d’éléments sera sizeof(BlinkOn)/sizeof(struct). C’est du pinaillage, mais c’est bp plus propre. Le sizeof(int) doit couvrir un élément du tableau composé de 2 sous élément sur 8 bits (délai et on/off), mais ce n’est pas de la belle programmation et cela dépend de l’architecture du processeur…

2 « J'aime »

C’est fait. J’ai mis à jour la première partie avec une v02 un peu plus propre.

J’en ai profité de back porter quelques modifications que j’ai introduite dans la seconde partie …

C’est très sympa, cela dit tu as considéré de le mettre sur Github avec les explications en markdown?

1 « J'aime »

Bonjour,

Oui même si je pense que la cible principale à laquelle s’adresse ce programme n’utilise pas Github. Mais c’est important si l’on veut que des développeurs plus chevronnés se saisissent éventuellement du projet pour l’améliorer.

Code sous Github :GitHub - Julaye/Lumieres: Gestion d'une scène ou d'un ensemble d'éclairages avec un Arduino Nano (échelle HO 1/87 ou supérieure)


Encore une nocturne

Publication de la v20211030.2 qui propose un mode d’éclairage gyrophare en plus des modes néons.

Je pense ajouter prochainement le poste de soudure, le brasero, la lampe à pétrole, le flash d’un photographe … ainsi qu’une meilleure prise en compte de la conditionalité des éclairages en fonction d’entrées spécifiques (zones de détection, capteurs …).


Et une matinée supplémentaire qui m’a permis d’ajouter le Poste de soudure et le Flash de paparazzi, ainsi que la gestion d’entrées (zones de détection, capteurs).

1 « J'aime »

Seconde partie : la gestion d’une scène

Objectif du projet : Le programme Lumières est une tentative pour proposer aux adeptes du modélisme un automate de gestion des éclairages d’un bâtiment, d’une scène, ou plus généralement d’un ensemble d’éclairages, qui soit facile d’accès pour un non initié à la programmation d’un Arduino, et fortement paramétrable pour couvrir les besoins les plus courants.

L’utilisateur adapte le fichier ConfigLumieres.h selon ses besoins et il obtient un automate opérationnel, sans écrire une seule ligne de code, seulement des données dans le fichier susmentionné. C’est une approche NO CODE - LOW CODE pour surfer sur le buzz des outils de programmation actuels.

Les types d’éclairages actuellement supportés sont :

  • lampe standard (allumé ou éteint)
  • néon neuf avec sa séquence d’allumage,
  • néon fatigué avec des glitchs aléatoires,
  • flash de photographe avec pre-flash,
  • poste de soudure à l’arc,
  • servo moteur 0-90° pour l’ouverture/fermeture d’une porte
  • gyrophare
  • bougie / brasero
  • feu clignotant qui ne clignote que s’il n’est pas permanent
  • buzzer

Sur les sorties PWM (D3,D5,D6,D9,D10 et D11), l’automate gère la variation d’intensité lumineuse pour plus de réalisme.

Licence : Le logiciel est sous licence GPL et peut être librement utilisé et modifié dans le cadre de notre passion de modélisme, vos éventuelles modifications doivent être reversées à la communauté.

Langage de programmation : C for Arduino Nano

Langue du projet : Français / French

Version pre-release : v20231008

Documentation : La débutante dans le décor - Ep13 - Que la lumière jaillisse ! - Google Docs

La documentation proposée présente aussi dans son chapitre 9 de nombreux exemples d’automatismes qui sont faciles à réaliser avec cet automate :

  • Photographe paparazzi dont le flash se déclenche au passage d’un train
  • Gestion de la séquence d’éclairage d’une fosse d’inspection au stationnement d’une locomotive
  • Animation d’un poste de soudure en présence d’une locomotive dans l’atelier
  • Animation de feux tricolores à un croisement routier, inclu les feux en panne (orange clignotant)
  • L’ouverture et la fermeture d’une porte de remise avec un gyrophare de sécurité lorsqu’une locomotive se présente devant la porte
  • Passage à niveau avec feu clignotant et barrière commandée par un servo

La suite est ici : GitHub - Julaye/Lumieres: Gestion d'une scène ou d'un ensemble d'éclairages avec un Arduino Nano (échelle HO 1/87 ou supérieure)


Une petite vidéo pour démontrer quelques effets.

Quelques exemples d’animation possible avec l’automate Lumières.

De gauche à droite : un poste de soudage dans l’imprimerie, un néon fatigué dans la chambre, une bougie dans la chambre du haut et un gyrophare chez le cordonnier.

2 « J'aime »

Bonjour,

Hier j’ai pu tester le buzzer avec le poste de soudure. Le son est parfaitement synchronisé avec les flashs mais le son est vraiment pourri, ça m’étonnerait que cela ressemble à un bruit de soudure à l’arc :(.

Au pire, je pourrais mettre une petite led bleue à la place du buzzer, au moins d’un point de vue lumières ça sera plus réaliste avec une led blanche intense et une petite led bleu.

2 « J'aime »

Bonjour, … au cas où l’on est dans une phase de recherche d’idée, … pourquoi pas un feu de camps (mélange Led rouge et blanche)?

Bonsoir,

En fait je vais ajouter la possibilité de “coupler” deux sorties ensembles pour, par exemple, un poste de soudage avec lumière blanche et lumière bleue ou comme tu le proposes un feu de camp avec une led rouge et une led blanche. Ce couplage doit aléatoirement influer sur les deux leds pour plus de réalisme …

De même, j’ai prévu de mettre un filtre anti-rebond sur les entrées, par exemple si l’on détecte la présence d’un véhicule sur la voie.

Enfin, interfacer un petit lecteur MP3 car le buzzer ne rend absolument pas et il serait préférable de déclencher la lecture de petits sons pré-enregistrés.

Encore des évolutions en perspectives pour que cet automate soit le plus complet possible.

La liste des travaux en cours : Issues · Julaye/Lumieres · GitHub

Je pense aussi réaliser une petite platine dédiée pour simplifier son intégration, une sorte de shield dédié aux fonctions de l’automate. Cf ce que j’ai fait dans l’épisode 10.

@+

1 « J'aime »

Publication de la version 20231008 qui inclus des traces améliorées et un filtre anti-rebond sur les entrées.

Les infos ici : GitHub - Julaye/Lumieres: Gestion d'une scène ou d'un ensemble d'éclairages avec un Arduino Nano (échelle HO 1/87 ou supérieure)

2 « J'aime »

Bonjour Julie,

Je suis très impressionné par le travail que tu as réalisé (code, documentation…). J’imagine assez bien le résultat à la lecture du code. C’est une quasi bibliothèque pour de nombreux besoins d’animations lumineuses pour nos réseaux ferroviaires.

Tu dis dans la doc " Ce programme peut être diffusé sur la plateforme Locoduino". Oui, il enrichirait judicieusement le site. Pourquoi ne pas le faire ? Si tu veux en parler, je suis dispo. Un moyen peut-être de rapprocher le 3R et le 2R.

Je testerai ce programme mais sans doute pas tout de suite compte tenu du nombre déjà important de fers au feu.

Pour la machine d’état, ce n’est pas moi qui vais te contredire !!! Si ça t’amuse, tu pourras voir ici l’automate que j’ai développé pour générer le signal sur une centrale DCC générant un cutout : https://github.com/BOBILLEChristophe/DCCxx-ESP32-Railcom/blob/main/src/DCC.cpp

Christophe

1 « J'aime »

Bonjour Christophe,

Je manque cruellement de temps (…) le projet est en licence GPL v3 mais il me faudra soon ou later le proposer sur le site Locoduino avec des articles d’initiation. Un jour peut être lol.

Bon en pratique, j’attends d’avoir avancé suffisamment, à la fois sur la partie logicielle mais aussi sur la platine dite “Obourg” (cf L'héritière d'Obourg - #2490 par Julaye) qui embarque ce petit logiciel.

C’est la platine et le logiciel qui va partir en exposition en 2025 (…) sur le projet “Obourg station”.

Même si un simple Arduino Nano sur son Shield est suffisant pour ce petit logiciel, le plus judicieux sera d’utiliser cette platine dite “Obourg” pour ceux qui sont en 3R.

J’essaye en effet de me constituer un petit automate qui intègre, pour un prix inférieur au M83, ce que ce dernier aurait pu/du inclure … particulièrement pour ceux en voie C avec des automatismes déclenchés sur rétro-signalisation : PN (servo), signaux (leds ou servo), portes de remise (servos), bâtiments et fosses d’inspection (éclairages, poste de soudure) …

J’en suis à la V2 (en cours de test ce mois-ci) et je réfléchis déjà à la V3 pour intégrer un ou deux modules de freinage Marklin. Et une V4 pour prendre des commandes en DCC ?

Bon c’est juste une feuille de route à date pour Rail Expo (Dreux 2025 ?) Pour ceux qui me lisent depuis mon arrivée sur ce forum, tout peut (et va) changer d’ici là :upside_down_face:


A suivre donc. Merci pour tes retours, c’est encourageant !

Publication de la version v20231014 avec de très nombreuses évolutions :

  • Propose quelques automatismes pré-programmés (ConfigAutomatismes.h) en plus de deux automatismes programmables (ConfigLumieres.h)
  • Les entrées D7 (P0), D8 (P1) et D15 (SEQ) permettent de choisir l’automatisme à lancer au démarrage de l’automate
  • Corrige un problème sur le seedPin (A7/D21) pour le germe du générateur aléatoire
  • Réduit l’empreinte mémoire sur la SRAM
  • Réduit le nombre de sorties utilisables à 8
  • Ajoute la commande SETMODE pour modifier la configuration de sorties directement dans la séquence d’un automatisme
  • Ajoute la commande UNTIL pour allumer des sorties pendant x minutes sans bloquer la séquence
  • Ajoute trois entrées calculées (E1 or E2, E1 and E2, E1 xor E2)
  • La commande WSTOP échantillonne à la seconde l’entrée spécifiée pendant la durée mentionnée en paramètre
  • Support de deux servos moteurs sur les sorties D9 et D10
  • Rend l’automate compatible avec la platine “Obourg” Rétro v3
  • Revisite complétement la documentation de l’automate

Mise à jour de la documentation en conséquence : La débutante dans le décor - Ep13 - Que la lumière jaillisse !.pdf et en ligne La débutante dans le décor - Ep13 - Que la lumière jaillisse ! - Google Docs

Cette version est proche d’être complète par rapport à mon objectif initial. Elle mérite le statut de release Beta.

1 « J'aime »

Bonjour Julie,

Heureux de voir que l’avancement de ce projet corresponde à tes attentes. Cela fait une super belle palette d’animations lumineuses et qui plus est, simple à mettre en œuvre.

Je n’ai pas vu la signalisation (dont tu parles) ni dans la doc, ni dans le code. Mais je n’ai fait que survoler.

Bravo pour l’énorme travail et merci pour le partage. Et peut-être bientôt un article dans Locoduino auquel je veux bien apporter mon aide.

Je prendrai également le temps dans les semaines à venir pour tester le programme.

Christophe