Accueil Avancé NeoPixel progressif
🌈 Affichage Avancé

NeoPixel progressif

Afficher une valeur numérique sur un bandeau WS2812 sans saut visible — interpolation entre deux LEDs adjacentes avec correction gamma. Classe réutilisable : jauge, VU-mètre, niveau d'ascenseur, barre de progression.

Le problème des positions discrètes

Un bandeau NeoPixel de 8 LEDs ne peut afficher que 8 positions. Si une valeur passe de 374 à 376, rien ne bouge — puis d'un coup une LED bascule. Sur un affichage de niveau (ascenseur, jauge, VU-mètre), cet effet de saut est très visible.

❌ Sans interpolation

La LED s'allume ou s'éteint d'un coup. Sur 8 LEDs pour 1000 valeurs, chaque LED couvre 125 unités — saut très visible à l'œil.

✓ Avec interpolation

Deux LEDs adjacentes sont allumées proportionnellement. La progression paraît continue même avec seulement 8 LEDs physiques.

L'algorithme en deux étapes

La position d'une valeur x tombe en général entre deux LEDs. On calcule quelle fraction de chaque LED allumer :

VariableCalculSignification
entierx * n // maxIndex de la LED "juste avant" la position
frac2(x*n/max - entier) * 255Luminosité de la LED suivante (0–255)
frac1255 - frac2Luminosité de la LED courante (complément)

La correction gamma

Même avec interpolation linéaire, la transition peut paraître brusque. L'œil perçoit la lumière de façon non-linéaire : il est très sensible aux faibles luminosités et peu sensible aux fortes. La correction gamma compense ça :

c2 = int((frac2 / 255) ** coef * 255)   # LED suivante — courbe en puissance
c1 = int((frac1 / 255) ** coef * 255)   # LED courante

Avec coef = 1.8, les valeurs faibles sont "tassées" et les fortes amplifiées — la progression paraît régulière. Augmenter coef adoucit encore la transition. Valeur 1.0 = linéaire (pas de correction).

⚠️ Algorithme original

La combinaison interpolation fractionnelle + correction gamma appliquée à la fraction (et non à la luminosité finale) est absente des bibliothèques NeoPixel existantes — ni en MicroPython ni en Arduino. C'est un algorithme développé spécifiquement pour ce module.


Fichiers commentés

Chaque fichier est précédé de son contexte. Le code est affiché directement — bouton copier + lien GitHub disponibles.

🌈 La classe NeoProgressif

Le module réutilisable — fonctionne indépendamment de tout projet GRAFCET.

1 neoprog.py — Interpolation + correction gamma

La classe NeoProgressif encapsule un bandeau NeoPixel et expose une seule méthode utile : afficher(x). Elle gère en interne l'interpolation et la correction gamma.

  • pin — numéro de broche GPIO du bandeau
  • n — nombre de LEDs (défaut : 8)
  • coef — exposant gamma (défaut : 1.8)
  • couleur — tuple RGB (défaut : bleu)

Usage minimal :

from neoprog import NeoProgressif
neo = NeoProgressif(pin=26, n=8, couleur=(0, 255, 0))
neo.afficher(500)    # position milieu
neo.eteindre()       # tout éteindre
neoprog.py — NeoPixel progressif avec correction gamma .python
avance/neoprog/neoprog.py
102 lignes GitHub
# =============================================================================
# neoprog.py — Affichage progressif sur bandeau NeoPixel
# =============================================================================
# Classe NeoProgressif : affiche une valeur numérique sur un bandeau WS2812
# avec interpolation douce entre les LEDs (pas de saut visible).
#
# PRINCIPE :
#   Une valeur x dans [0, max] est mappée sur n LEDs.
#   La position tombe en général ENTRE deux LEDs.
#   → les deux LEDs adjacentes sont allumées proportionnellement,
#     avec correction gamma pour que l'œil perçoive une transition régulière.
#
# USAGE :
#   from neoprog import NeoProgressif
#
#   neo = NeoProgressif(pin=26, n=8)   # bandeau sur Pin 26, 8 LEDs
#   neo.afficher(500)                  # position milieu
#   neo.eteindre()                     # tout éteindre
# =============================================================================

from neopixel import NeoPixel    # pilotage des LEDs WS2812
from machine  import Pin         # accès aux broches ESP32


class NeoProgressif:
    """
    Affichage progressif d'une valeur sur un bandeau NeoPixel.
    Interpole la luminosité entre deux LEDs adjacentes pour un rendu fluide.
    """

    def __init__(self, pin, n=8, coef=1.8, couleur=(0, 0, 255)):
        """
        Initialise le bandeau NeoPixel.

        :param pin:     numéro de broche GPIO (ex: 26 sur la carte ENIM)
        :param n:       nombre de LEDs du bandeau (défaut : 8)
        :param coef:    exposant de correction gamma (défaut : 1.8)
                        — compense la perception non-linéaire de l'œil
                        — augmenter pour une transition plus "en douceur"
        :param couleur: tuple RGB de la couleur d'affichage (défaut : bleu)
        """
        self.np     = NeoPixel(Pin(pin), n)   # objet NeoPixel sur la broche
        self.n      = n                        # nombre de LEDs
        self.coef   = coef                     # coefficient gamma
        self.couleur = couleur                 # couleur RGB (0-255 par canal)
        self.eteindre()                        # éteindre toutes les LEDs au démarrage


    def afficher(self, x, max=1000):
        """
        Affiche la valeur x sur le bandeau avec interpolation douce.

        :param x:   valeur à afficher, dans [0, max]
        :param max: valeur maximale de l'échelle (défaut : 1000)

        Exemple :
            neo.afficher(0)     → bandeau éteint (position 0)
            neo.afficher(500)   → position centrale, 2 LEDs à 50%
            neo.afficher(1000)  → bandeau plein
        """

        # --- Calcul de la position ---
        # entier : index de la LED "juste avant" la position (0 à n-1)
        entier = x * self.n // max

        # frac2 : fraction de la LED suivante (0 = pas allumée, 255 = pleine)
        # représente à quel % on est entre entier et entier+1
        frac2 = int((x * self.n / max - entier) * 255)

        # frac1 : complément — luminosité de la LED courante
        frac1 = 255 - frac2

        # --- Correction gamma ---
        # Sans correction, l'œil perçoit les transitions de façon non-linéaire.
        # La courbe en puissance (** coef) rend la progression perçue régulière.
        c2 = int((frac2 / 255) ** self.coef * 255)   # luminosité LED suivante
        c1 = int((frac1 / 255) ** self.coef * 255)   # luminosité LED courante

        # --- Calcul des couleurs avec luminosité appliquée ---
        r, g, b = self.couleur
        couleur2 = (r * c2 // 255, g * c2 // 255, b * c2 // 255)   # LED suivante
        couleur1 = (r * c1 // 255, g * c1 // 255, b * c1 // 255)   # LED courante

        # --- Éteindre tout le bandeau ---
        for i in range(self.n):
            self.np[i] = (0, 0, 0)

        # --- Allumer les deux LEDs de la transition ---
        if entier != self.n:             # évite le débordement en fin de bandeau
            self.np[entier] = couleur2
        if entier > 0:                   # évite l'index -1 en début de bandeau
            self.np[entier - 1] = couleur1

        self.np.write()                  # envoyer les données au bandeau


    def eteindre(self):
        """Éteint toutes les LEDs du bandeau."""
        for i in range(self.n):
            self.np[i] = (0, 0, 0)
        self.np.write()

🏗️ L'ascenseur avec NeoPixel progressif

Le même ascenseur GRAFCET, enrichi d'un affichage visuel fluide du niveau.

1 ascenseur_enim_v2.py — Grafcet + NeoProgressif

Cette version combine les deux modules : grafcet.py pour la logique séquentielle et neoprog.py pour l'affichage. La cabine simulée descend de 0 à -100 — convertie en valeur 0–1000 pour NeoProgressif :

neo.afficher(int(-niveau * 10))
# niveau=0   → afficher(0)    → bandeau éteint (cabine en haut)
# niveau=-100 → afficher(1000) → bandeau plein  (cabine en bas)

Fichiers nécessaires sur l'ESP32 : grafcet.py, neoprog.py, essential.py (sans OLED).

ascenseur_enim_v2.py — Ascenseur ENIM avec NeoProgressif .python
avance/neoprog/ascenseur_enim_v2.py
133 lignes GitHub
# =============================================================================
# ascenseur_enim_v2.py — Ascenseur ENIM avec affichage NeoPixel progressif
# =============================================================================
# Version 2 : utilise NeoProgressif pour un affichage fluide du niveau.
#
# Fichiers nécessaires sur l'ESP32 :
#   grafcet.py    (moteur GRAFCET)
#   neoprog.py    (affichage NeoPixel progressif)
#   essential.py  (déclarations carte ENIM — sans OLED)
#
# GRAFCET (inchangé) :
#   Étape 0 — Repos     │ T0 : bpA ET tempo[0] > 200 ms
#   Étape 1 — Descente  │ T1 : bpC OU niveau ≤ -99
#   Étape 2 — Montée    │ T2 : bpB OU niveau ≥ -1
# =============================================================================

from machine  import Pin
from grafcet  import Grafcet
from neoprog  import NeoProgressif      # ← nouveau : affichage fluide

from essential import (
    synchro_ms,
    bpA, bpB, bpC,
    led_bleue, led_verte, led_jaune,
)

# --- NeoProgressif sur Pin 26, 8 LEDs, couleur verte ---
# On ne récupère plus np depuis essential : NeoProgressif crée son propre NeoPixel
neo = NeoProgressif(pin=26, n=8, couleur=(0, 255, 0))

# --- Broches moteur sur les connecteurs libres ---
sortie_descente = Pin(12, Pin.OUT)
sortie_montee   = Pin(13, Pin.OUT)
sortie_descente.value(0)
sortie_montee.value(0)


# =============================================================================
# GRAFCET
# =============================================================================

g = Grafcet(nb_etapes=3, etape_initiale=0)

T = [
    (0, (0,), (1,)),   # Repos → Descente
    (1, (1,), (2,)),   # Descente → Montée
    (2, (2,), (0,)),   # Montée → Repos
]

# =============================================================================
# VARIABLES
# =============================================================================

Descendre = False
Monter    = False
Start     = False
Haut      = False
Bas       = False

niveau  = 0      # position simulée : 0 = haut, -100 = bas
vitesse = 1      # déplacement par cycle

transitions = [False] * len(T)


# =============================================================================
# SIMULATION DU NIVEAU — NeoProgressif
# =============================================================================

def ascenseur(inc):
    """
    Déplace la cabine simulée et met à jour le NeoPixel progressivement.
    :param inc: -vitesse = descente, +vitesse = montée
    """
    global niveau

    niveau = niveau + inc
    if niveau < -100: niveau = -100
    if niveau >    0: niveau =    0

    # Conversion niveau [-100, 0] → valeur [1000, 0] pour NeoProgressif
    # niveau=0   → x=0    (bandeau éteint, cabine en haut)
    # niveau=-100 → x=1000 (bandeau plein, cabine en bas)
    neo.afficher(int(-niveau * 10))


# =============================================================================
# CYCLE GRAFCET
# =============================================================================

def gerer_actions():
    global Descendre, Monter
    if g.etapes[0]: Descendre = False ; Monter = False
    if g.etapes[1]: Descendre = True  ; Monter = False
    if g.etapes[2]: Descendre = False ; Monter = True


def affecter_sorties():
    led_bleue.value(g.etapes[0])
    led_verte.value(Descendre)
    led_jaune.value(Monter)
    sortie_descente.value(Descendre)
    sortie_montee.value(Monter)
    if Descendre: ascenseur(-vitesse)
    if Monter:    ascenseur(+vitesse)


def lire_entrees():
    global Start, Haut, Bas
    Start = bpA.value()
    Bas   = bpC.value() or (niveau <= -99)
    Haut  = bpB.value() or (niveau >= -1)


def calculer_transitions():
    transitions[0] = g.etapes[0] and Start and (g.tempo[0] > 200)
    transitions[1] = g.etapes[1] and Bas
    transitions[2] = g.etapes[2] and Haut


# =============================================================================
# BOUCLE PRINCIPALE
# =============================================================================

while True:
    g.franchir(T, transitions)
    g.tick(20)
    gerer_actions()
    affecter_sorties()
    lire_entrees()
    calculer_transitions()
    synchro_ms(20)

Réutiliser NeoProgressif ailleurs

Copie neoprog.py dans n'importe quel projet. La classe est indépendante de GRAFCET — utilisable pour une jauge de température, un VU-mètre audio, une barre de progression, etc.