Comment régler l’intensité lumineuse d’une LED ?

NIVEAU 2

Objectifs

  • Comprendre le principe de la modulation de largeur d’impulsions
  • Configurer un signal modulé sur une carte Nucléo

Pré-requis

Modulation de largeur d’impulsions

Principe

La modulation en largeur d’impulsions (MLI ou PWMPulsed Witdh Modulation – en anglais) est une méthode permettant de générer un signal rectangulaire de période \(T\) fixe (ou de fréquence \(1/T\) fixe) et dont le rapport cyclique, i.e. le rapport du temps haut sur la période, noté \(RC = \frac{t_h}{T}\), est variable.

Le rapport cyclique (ou RC) décrit la durée pendant laquelle le signal est à l’état haut (actif) en pourcentage de la durée d’un cycle complet. La fréquence détermine la vitesse à laquelle la MLI effectue un cycle (par exemple, 1000 Hz serait 1000 cycles par seconde) et par conséquent à quelle vitesse il passe de l’état haut à l’état bas et vice versa.

Certaines applications, contrôlées en tension (rotation d’un moteur à courant continu…) ou en courant (luminosité d’une LED…), sont “lentes” à réagir. Par application d’un signal de type PWM (ou MLI), ces systèmes n’ont pas le temps de s’arrêter (lors d’un passage par un temps bas) ou de redémarrer (lors d’un passage par un temps haut). Ce que voit alors ces systèmes est la valeur moyenne du signal MLI appliqué sur leur entrée.

La valeur moyenne du signal de sortie \(s(t)\) vaut alors :
\(<s(t)> = \frac{1}{T} \int_0^T s(t) dt = \frac{t_h}{T} \cdot E = RC \cdot E\)

En modifiant le rapport cyclique, on modifie la valeur moyenne du signal vue par le système à piloter par cette méthode.

Sorties modulées

Certaines broches du composant (notée PWMx sur les connecteurs de la carte, où x est un nombre) peuvent être utilisées comme des sorties modulées de type PWM.

Déclaration d’une sortie modulée

L’ensemble des classes et des fonctions permettant de réaliser des opérations sur les entrées/sorties de la carte se trouvent dans la bibliothèque “mbed.h” (ou “mbed-os.h”). Il est donc indispensable d’avoir au préalable inclut cette bibliothèque :

#include "mbed.h"

La première étape est la déclaration au compilateur de cette broche en sortie PWM. Il faut la placer après la déclaration des ressources externes (bibliothèques) et avant toute définition de fonction (voir tutoriel Tester ma première application sur Nucléo – Code d’exemple /* Déclaration des entrées/sorties */).

PwmOut  ma_sortie_pwm(D3);

Dans l’exemple précédent, on configure la broche D3 (du connecteur Arduino) en sortie modulée PWM. Dans ce code, il y a trois notions importantes :

  • PwmOut‘, la classe qui définit le type de la broche, ici une sortie numérique
  • ma_sortie_pwm‘, le nom de la variable qui va être associée au port donné dans les parenthèses
  • D3‘, le nom de la broche que l’on va associer sur le composant

Utilisation d’une sortie modulée

Après avoir déclaré la broche en sortie modulée, il faut régler deux paramètres différents du PWM (ou MLI) :

  • la période
  • le rapport cyclique

Période

Pour régler la période, il existe 3 fonctions différentes :

  • la fonction period(float s), qui permet de saisir une valeur de période en secondes
  • la fonction period_ms(int ms), qui permet de saisir une valeur de période en millisecondes
  • la fonction period_us(int us), qui permet de saisir une valeur de période en microsecondes

Par exemple, pour fixer la période du signal de sortie PWM à 20ms (soit 50Hz), on peut utiliser l’une des trois instructions suivantes :

ma_sortie_pwm.period(0.02);
ma_sortie_pwm.period_ms(20);
ma_sortie_pwm.period_us(20000);

Rapport Cyclique

Pour régler le rapport cyclique, il y a deux méthodes différentes :

  • définir directement le rapport cyclique
  • définir la durée où le signal est actif

Rapport cyclique

Pour définir le rapport cyclique, il existe la fonction write(float rc) (où rc est une valeur comprise entre 0 et 1).

Par exemple, pour définir le rapport cyclique à 30 %, on pourra utiliser l’instruction suivante :

ma_sortie_pwm.write(0.3);

Durée du temps haut

Pour définir la durée du signal actif, il existe 3 fonctions différentes :

  • la fonction pulsewidth(float s), qui permet de saisir une valeur de temps haut en secondes
  • la fonction pulsewidth_ms(int ms), qui permet de saisir une valeur de temps haut en millisecondes
  • la fonction pulsewidth_us(int us), qui permet de saisir une valeur de temps haut en microsecondes

Par exemple, pour définir un temps haut de 3ms, on pourra utiliser l’une des instructions suivantes :

ma_sortie_pwm.pulsewidth(0.003);
ma_sortie_pwm.pulsewidth_ms(3);
ma_sortie_pwm.pulsewidth_us(3000);

Exemple complet avec la définition du temps haut

#include "mbed.h"

PwmOut ma_sortie_pwm(D3);

int main() {
    ma_sortie_pwm.period_ms(10);
    ma_sortie_pwm.pulsewidth_ms(1);
    while(1);
}

Dans cet exemple, on définit la sortie D3 comme sortie modulée avec un signal de période 10ms et de temps haut de 1ms (soit un rapport cyclique de 10%).

On peut noter sur la figure précédente (échelle des temps en millisecondes, échelle des tensions en V) que la période est bien de 10 ms et que le temps haut (ou temps actif) dure 1 ms.

Exemple complet avec la définition du rapport cyclique

#include "mbed.h"

PwmOut ma_sortie_pwm(D3);

int main() {
    ma_sortie_pwm.period_ms(10);
    ma_sortie_pwm.write(0.1);
    while(1);
}

Dans cet exemple, on définit la sortie D3 comme sortie modulée avec un signal de période 10ms et de rapport cyclique 10% (soit un temps haut de 1ms).

On relève ici le même oscillogramme que dans l’exemple précédent.

Luminosité d’une LED

Méthode analogique

Source de courant réglable

La première méthode pour modifier le courant traversant la LED est d’utiliser une source de courant. Si cette source de courant est réglable en intensité alors il sera possible de moduler l’intensité lumineuse de la LED.

Cependant, une source de courant est difficile à mettre en oeuvre. On préfère les sources de tension.

Source de tension et résistance variable

Pour transformer une source de tension en source de courant, il suffit d’ajouter une résistance à cette source de tension. On obtient une source de tension non parfaite et par transformation Thévenin/Norton, on peut ainsi obtenir une source de courant non idéale.

Les deux montages précédents permettent d’obtenir quasiment le même fonctionnement, à savoir modifier le courant transitant dans la LED par modification : a) de la résistance associée à la source de tension, b) de la tension aux bornes de l’ensemble résistance et LED.

En effet, si on s’intéresse au cas où la LED est passante (i.e. où elle émet un flux lumineux), quand le courant \(I_{LED}\) est positif et la tension aux bornes de la LED est supérieure à la tension seuil (ou directe) \(V_F\), on a alors la relation suivante : \(E = R \cdot I_{LED} + V_F\)

Le courant vaut alors \(I_{LED} = \frac{E – V_F}{R}\).

Attention Avec cette méthode, il faut garantir que le courant ne dépasse pas le courant maximal admissible par la LED. Dans la cas du montage b, il faut en plus garantir que la tension \(E\) est supérieure à la tension seuil \(V_F\) de la LED.

Pilotage numérique

Le problème de la méthode précédente est qu’il n’est pas possible de contrôler à distance l’intensité lumineuse de la LED sans avoir recours à des potentiomètres (ou résistances variables) commandables numériquement (type MCP4022 ou MAX5434). Ces potentiomètres numériques nécessitent l’utilisation d’un pilotage numérique (d’un microcontroleur par exemple). Or il existe une méthode numérique permettant de se passer de ce type de composant.

Persistance rétinienne et MLI

L’être humain est capable de voir les éléments qui l’entourent, grâce à ses yeux, qui captent les informations depuis l’extérieur, et son cerveau, qui traite ces données acquises. Or comme tout système de “traitement” de l’information, il a ses limites :

  • les longueurs d’onde que l’oeil est capable de transmettre
  • la fréquence maximale des événements que le cerveau est capable de traiter

Si on prend l’exemple du cinéma, les images diffusées se font au rythme de 24 images/seconde. A cette fréquence là (i.e. 24 Hz), le cerveau humain est biaisé et n’arrive pas à distinguer deux images successives. Ce que nous voyons est une suite de mouvements fluides. De la même façon, beaucoup d’éléments autour de nous “clignotent” : les écrans d’ordinateur (60 ou 70 Hz), les smartphones (autoud de 100 Hz), les néons (100 Hz)…

En utilisant le principe de la modulation de largeur d’impulsions et en appliquant le signal résultant sur une LED, celle-ci va donc s’allumer durant le temps haut du signal et s’éteindre le reste de la période.

Si ce signal est appliqué avec une fréquence suffisamment élevée pour que l’être humain ne distingue plus l’allumage et l’extinction de la LED, ce dernier verra alors une LED constamment allumée avec une intensité lumineuse variant en fonction du rapport cyclique.

Câblage d’une LED

Pour ce faire, on peut utiliser l’un des câblages suivants pour une LED standard (i.e. dont le courant nominal est de l’ordre du courant admissible par le microcontroleur qui génère le signal PWM) :

Dans le montage 1, un signal modulé de polarité normale, i.e. le temps actif est le temps à l’état haut, permet d’avoir une luminosité au niveau de la LED qui est proportionnel au rapport cyclique (défini par le rapport du temps haut sur la période du signal).
Dans le montage 2, un signal modulé de polarité inversée, i.e. le temps actif est le temps à l’état bas, permet d’avoir une luminosité au niveau de la LED qui est proportionnel au rapport cyclique (défini par le rapport du temps haut sur la période du signal).

Câblage d’une LED de puissance

Parfois, on souhaite piloter des LED de puissance par ce principe. Les microcontrôleurs ne sont cependant pas capables de fournir beaucoup de courant pour commander ce type d’éléments (typiquement les sorties des microcontroleurs sont capables de fournir des courants de l’ordre de la dizaine de milliampères, pour des tensions de l’ordre du volt).

Pour pouvoir les contrôler, il est alors nécessaire de passer par des éléments qui amplifient le courant : des transistors.

Un transistor est un composant à trois broches qui permet d’amplifier un courant soit à partir d’un autre courant plus faible (ce sont des transistors dits bipolaires – NPN, PNP…) soit à partir d’une tension de commande (ce sont des transistors à effet de champs – MOS, JFET, MOSFET…).

Le montage que l’on peut utiliser est le suivant (avec un transistor MOSFET de type IRF540A) :

Sur ce montage, lorsque la sortie D10 est à ‘0’, le transistor n’est pas commandé, il se comporte alors comme un circuit ouvert entre les broches nommées D et S. Il n’y a pas de courant qui passe dans la branche de la LED. Elle est donc éteinte.
Lorsque la sortie D10 est à ‘1’, le transistor est commandé, il se comporte alors comme un circuit fermé entre les broches nommées D et S. Il y a passage de courant dans la branche de la LED. Elle est donc allumée.

La résistance \(R\) est une résistance d’une dizaine de kiloohms, permettant d’assurer le passage d’un courant minimal sur la sortie D10.

N.B. Il existe également des composants tout intégré qui sont constitués de transistors bipolaires (ou MOS pour certains) montés en Darlington (amplification en courant plus importante) de type ULN2804.

Exemple de réalisation

On propose d’appliquer sur une LED rouge standard un signal MLI piloté par le programme suivant à l’aide d’une carte Nucléo :

#include "mbed.h"

PwmOut ma_sortie_pwm(D10);
double rc;

int main() {
    rc = 0;
    ma_sortie_pwm.period_ms(10);
    while(1){
        rc += 0.1;
        if(rc > 1)  rc = 0;
        ma_sortie_pwm.write(rc);
        wait_us(200000);
    }
}

Dans ce programme, on initialise une sortie PWM avec une période de 10ms et un rapport cyclique nul. Dans la boucle infinie, on incrémente ce rapport cyclique de 10% toutes les 200 ms. On obtient alors une rampe de luminosité sur la LED, entre 0 et 100% par pas de 10%, changeant toutes les 200ms.

Bandeau de LEDs ou LED de puissance

Principe de fonctionnement

Les bandeaux de LED sont constitués de LEDs trichromes dont on peut séparément contrôler le rouge, le vert et le bleu.
Ces bandeaux doivent être alimentés sous une tension de 12V et consomment, à pleine puissance, 7W/m.

Elles sont conçues selon le schéma suivant. Les résistances de limitation du courant sont déjà intégrées au bandeau.

Cablage

Même si on peut utiliser le même principe de modulation de largeur d’impulsions pour piloter la luminosité de ces LEDs, la tension des cartes Nucléo n’est pas suffisante pour les piloter (3.3V) ou/et le courant de sortie disponible n’est pas assez important. Il est donc impossible d’alimenter chacune des voies de ces bandeaux sans ajouter un étage de puissance.
Cet étage est constitué d’un transistor permettant d’amplifier le courant.

Ce type de transistor (appelé MOSFET) est commandé en tension. Lorsqu’une tension est appliquée entre les broches G (grille) et S (source), il y a passage de courant possible entre les broches D (Drain) et S (Source). Lorsque cette tension est nulle, il n’y a pas de courant qui passe entre ces broches. On parle alors de commutation du transistor qui se comporte alors comme un interrupteur commandé en tension.

Il faudra faire attention à prendre un transistor dont la tension maximale \(V_{DS}\) est supérieure à la tension d’alimentation du bandeau et un courant maximal \(I_{D}\) (courant transitant dans le bandeau) supérieur au courant maximal consommé par chacune des voies du bandeau. Il faut également que la tension minimale de commande \(V_{GS}\) soit inférieure à la tension de sortie des cartes Nucléo (ici 3.3V). On pourra par exemple s’intéresser aux transistors BS170 ou IRL540.

LEDs RGB Pilotables

Exemple d’un arc de cercle de 15 LEDs (4 arcs de cercle = 60 Leds… = 1 heure ?)

LED WS2812

Les LEDs de type WS2812 sont la combinaison de 3 LEDs trichromes (rouge, verte et bleue) et d’un circuit de commande numérique (registres à décalage et PWM pour chacune des couleurs).

Elles se pilotent donc en numérique par le biais d’une trame binaire de 8 bits par couleur (vert, rouge, bleu – 256 niveaux par couleur), soient 24 bits au total.

Les ‘0’ et les ‘1’ sont codés de la façon suivante :

Les temps haut et bas sont quantifiés : T0H = 0.35us / T0L = 0.8us / T1H = 0.7us / T1L = 0.6us / RES > 50 us.

L’intérêt de ces modules de LED est la possibilité de les mettre en cascade :

Ainsi, on peut transmettre une suite de N x 24 bits successifs pour pouvoir contrôler l’intégralité des N LEDs.

ATTENTION Il existe des matrices de LED basées sur des LED de type SK6812, qui se programme de la même façon que les WS2812, à ceci prêt qu’il faut envoyer 32 bits par LED et non 24 bits. Le dernier octet à émettre est vide (‘0’), mais il faut l’émettre 🙂

Utilisation sous MBED

L’exemple d’application suivant est réalisé autour d’une carte Nucléo L476RG : https://os.mbed.com/teams/IOGS_France/code/EITI_Neopixel/

Bibliothèques nécessaires

Pour pouvoir utiliser cet exemple, les deux bibliothèques suivantes sont nécessaires (à importer) :

Bibliothèque PixelArray

La bibliothèque PixelArray permet de créer et de mettre à jour un tableau de pixels de 3 octets (RGB).
Il est créé à l’aide de la commande suivante :

PixelArray px(10);

L’objet px est alors un tableau de 10 pixels contenant chacun les champs : (R)ed (B)lue (G)reen.

Il est alors possible de modifier chacun de ces pixels à l’aide de 3 fonctions :

void SetR(int i, int val);
void SetB(int i, int val);
void SetG(int i, int val);

qui permettent respectivement de changer la couleur rouge (SetR), la couleur bleu (SetB) et la couleur verte (SetG) de la LED numéro i (la première ayant le numéro 0 et la dernière N-1) avec la valeur val comprise entre 0 et 255 (0 = 0% – 255 = 100%).

Par exemple, pour mettre la couleur R = 20% – B = 40% – G = 20% sur la troisième LED, on peut utiliser les instructions suivantes :

px.SetR(2, 20*255/100.0); px.SetB(2, 40*255/100.0); px.SetG(2, 20*255/100.0);

Sur les versions White des LED, la fonction SetI permet de rajouter du blanc.

Il est également possible d’aller lire chacune des valeurs des pixels grâce aux commandes :

int GetR(int i);
int GetB(int i);
int GetG(int i);

qui permettent respectivement de récupérer la couleur rouge (SetR), la couleur bleu (SetB) et la couleur verte (SetG) de la LED numéro i (la première ayant le numéro 0 et la dernière N-1).

Pour récupérer la valeur rouge de la 5ème LED, il faudra par exemple faire :

int k = px.GetR(4);

Bibliothèque WS2812

La bibliothèque WS2812 permet de mettre à jour la matrice de LED en respectant le protocole demandé par le fabricant de ces LED.
Il est créé à l’aide de la commande suivante :

WS2812 ws(D9, NB_LED, 3, 12, 9, 12);

L’objet ws permet alors de piloter un bandeau de NB_LED LEDs sur la broche D9 de la carte Nucléo. Les valeurs qui suivent permettent de respecter les contraintes de temps imposées par le fabricant pour la carte Nucléo L476RG.

Pour les versions White des LED, il est nécessaire d’utiliser la bibliothèque WS2812 modifiée et d’ajouter le paramètre 1 à la fin de l’initialisation.

WS2812 ws(D9, NB_LED, 3, 12, 9, 12, 1);

Il est indispensable d’initialiser la matrice à l’aide de la commande suivante :

ws.useII(WS2812::GLOBAL);

pour pouvoir modifier de manière globale la luminosité de l’ensemble des LEDs.

On peut alors utiliser la commande suivante pour modifier globalement l’intensité des LEDs avec une valeur val comprise entre 0 et 255 :

void setII(int val);

Par exemple, pour limiter la luminosité à 100 sur 255 l’intensité du bandeau, on peut utiliser la commande suivante :

ws.setII(100);

Enfin, il faut pouvoir transmettre l’ensemble des informations aux différentes LEDs. Pour cela, il faut utiliser la fonction suivante :

void write(int tab[]);

où le paramètre est un tableau de N entiers, pouvant correspondre à un objet de type PixelArray.

Exemple d’application

Ne prêtez pas attention à mes qualités d’artiste pour ce code… Je me suis contenté de faire un exemple fonctionnel sur un arc de cercle de 15 LED.

Jpeg

Pour la partie artistique, laissez libre cours à votre imagination… Et n’hésitez pas à m’envoyer vos meilleurs programmes !

#include "mbed.h"
#include "PixelArray.h"
#include "WS2812.h"

#define NB_LED  15    // Nombre de LEDs en cascade

// Variables globales 
PixelArray px(NB_LED);           // Tableau de pixels / Nb de LEDs en cascade
// For Nucleo F476 : 3, 12, 9, 12
WS2812 ws(D9, NB_LED, 3, 12, 9, 12);    // Lien vers la série de LEDs / Sortie utilisée-Nb de LEDs
// NE PAS MODIFIER LES 4 DERNIERS PARAMETRES

int main() {
    int i, k = 0;
    ws.useII(WS2812::GLOBAL);  // Initialisation de la liaison avec les LEDs
    ws.setII(0);               // Luminosité maximale à 0/255
    
    // Reinitialisation ecran
    for (i = 0; i < NB_LED; i++) {
        px.Set(i, 0);
    }  
    ws.write(px.getBuf());      // Mise à jour de la matrice
    ws.setII(30);               // Luminosité maximale à 30/255

    while(1) {
        for(i = 0; i < NB_LED; i++){
            px.SetR(i, k);
        }
        ws.write(px.getBuf());   // Mise à jour de la matrice
        if(k == 255) k = 0;
        else k++;
        wait_us(200000);               // 200 ms
    }
}

Tutoriel lié

MInE Prototyper Prototyper avec Nucleo et MBED

Nucléo – Régler l’intensité lumineuse d’une LED