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

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

Les programmes 4 à 7 ne sont pas encore terminés ou testés, et notamment celui sur les signaux SNCB.

J’ai mis à jour la documentation, elle correspond à l’état du logiciel. A suivre donc. J’attends la platine V3 pour reprendre des tests plus sérieux.

Merci de ton retour !

Oui je vais y réfléchir avec la V4 (module de freinage inclu) pour présenter un ensemble complet.

@+

Un message a été scindé en un nouveau sujet : Automate, Satellites et Autres Considérations

bonjour Julie
vous utilisez une “const struct blink”
qui est la définition et puis juste après il y a l’initialisation des zones qui est faite par le compilateur

auriez-vous la gentillesse de me montrer une ou deux utilisations “BlinkOn” dans les lignes de votre programme svp
merci
Patrice

Bonjour Patrice,

Julie n’a pas dit qu’elle utilisait la structure blink. C’est à l’utilisateur de mettre une telle structure dans son propre code (en général dans le fichier principal .ino ou main.cpp) et il (l’utilisateur) pourra obtenir des effets particuliers, ici, de néon qui s’allume.

En quelque sorte, elle a déjà fait une partie de votre travail !

Il y a deux exemples dans un programme qu’elle donne un peu après dans ce 1° post :grinning:

// 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);
}

Ligne 158 :

lightOn = blinkOn[gStateBlink].intensity;

et ligne 168 :grinning:

gDelay = blinkOn[gStateBlink].duration;

Christophe

2 « J'aime »