009 – Piloter 35 LEDS avec 2 pins

Allez, un petit tuto pour apprendre piloter 35 leds (ou 66, ou encore plus) avec juste 2 pattes de son Arduino préféré, et un seul CI. Même pas besoin de résistance pour chaque led, elle est pas belle, la vie ?

C’est magique, et ça se passe avec le circuit MM5451 de Micrel.
J’avais fait ici un aperçu de ses capacités: Fiche MM5451 , voici le code et tuto Arduino qui va avec.

Commençons par un petit récap des capacités du circuit

  • Alimentation de 4.75 à 11v
  • Entrées compatibles logique 5v et 3.3v
  • Disponible en boitier DIP 40, parfait pour tester et créer des circuits classiques.
  • 35 sorties « Puit de courant » (pas besoin de résistance pour les leds)
  • Courant des leds ajustable, notamment par PWM (luminosité réglable)
  • Pilotage avec juste 2 fils, Clock et Data. SPI matériel comme bitbanging possible.

arduino pro miniPour voir ce que donne ce circuit, je sors donc un petit arduino pro mini. Cet arduino n’a pas de convertisseur série/usb intégré, j’utilise donc un modèle FTDI à part (les fils qui partent à gauche sur la photo).
Ce montage fonctionne aussi bien avec n’importe quel Arduino, par exemple un Nano.

Le branchement

Le réglage de luminosité sur une platine d'expérimentation

Le réglage de luminosité sur une platine d’expérimentation

Sur l’autre plaque, le MM5451.
Les branchements coté MM5451 sont simples :
Broche 1 au +
Broche 20 à la masse.
La broche 19 est le réglage de luminosité: point milieu d’un potentiomètre de 10k, placé entre +5v et la masse.
Les broches 21 et 22 sont respectivement l’horloge et les données. Dans mon test, elles vont sur les sorties Arduino 10 et 11.
Toutes les autres pattes sont les sorties de Leds.
Ici, je n’en ai placé quelques unes, mais on peut en mettre jusque 35 sans rien ajouter.

Avec du multiplexage, ou peut aller beaucoup plus loin.
Par exemple, si on réserve 5 Sorties du MM5451 pour multiplexer, on peut avoir 30*5=150 leds pilotées avec un seul circuit, et toujours 2 pattes coté Arduino, pas une de plus.

Le code, v1

La première version du code est basique: je pilote les 2 sorties « à la main », avec un digitalWrite() de base.
ça donne ça :

/*
Test du MM5451 Avec Arduino
http://www.Actuino.fr
Version Simple avec digitalWrite
Sans contrôle de luminosité
*/

define PIN_CLK  10 // La broche CLK du 5451
define PIN_DATA  11 // la broche DATA du 5451

void setup() {
  // Initialisation des broches
  pinMode(PIN_CLK, OUTPUT);
  pinMode(PIN_DATA, OUTPUT);
  // broches à LOW
  digitalWrite(PIN_CLK, LOW);
  digitalWrite(PIN_DATA, LOW);
}

// envoie un seul bit au 5451
void sendBit(boolean b) {
  // CLK est forcément à LOW.
  digitalWrite(PIN_DATA, b);   // on ajuste le bit DATA
 // _delay_us(1); // attendre 1µs
  digitalWrite(PIN_CLK, HIGH);   // Monter CLK
 // _delay_us(2); // attendre 2µs
  digitalWrite(PIN_CLK, LOW);   // Redescendre CLK
}

void sendByte(uint8_t a) {
  for (byte i=0;i<8;i++) {
    sendBit(boolean((a>>i) & 0x1)); // on envoie le LSB
  }
  /*
  shiftOut(PIN_DATA,PIN_CLK,LSBFIRST,a);
  //digitalWrite(PIN_CLK, LOW);   // Redescendre CLK
  */
}

// envoie 4 octets, soit 4x8=32 bits.
// les 3 derniers seront envoyés à 0
// on envoie le LSB en premier.
// donc le LSB de d jusque MSB de d, puis c.
// on termine par a puis les 3 zéros
void send4Bytes(uint8_t a,uint8_t b,uint8_t c,uint8_t d) {
  sendBit(1); // On commence par envoyer un bit de start, "1"
  sendByte(d);
  sendByte(c);
  sendByte(b);
  sendByte(a);
  sendBit(0); // Les 3 derniers bits à 0
  sendBit(0);
  sendBit(0);
}

void loop() {
  while (1) {
     delay(1000);
    send4Bytes(B10101010,B10101010,B10101010,B10101010); // une LED sur deux
    delay(1000);
    send4Bytes(255,255,255,255); // Tout allumé
    delay(1000);
    send4Bytes(0,0,0,0); // Tout eteint   
  }
}

Ce code de démo fait allumer une led sur deux, puis toutes, puis les éteint toutes.

Coté perfs, ça donne quoi ?
Un petit coup d’analyseur logique (je vous parlerai une autre fois de mon analyseur de compet’)
ça montre ça :
mm5451-Timing1

C’est sur une commande « une led sur deux ». Le channel 0 est l’horloge, le channel 1 les données.
Résultat des course, presque 800µs, pas loin d’une milliseconde, pour envoyer une trame (36 bits).
Si c’est pour piloter une guirlande de Noël, ça suffit largement.
Pour un système POV, ça le fait moins.
Par rapport aux spécifications du circuit (horloge max 500Khz) on est loin : 45khz…

On peut donc mieux faire.

Le code qui va vite

Version 2, toujours en bitbang direct, mais cette fois ci en adressant directement les registres d’entrée/sortie de l’Arduino.

La sortie de l’analyseur :
mm5451-Timing2

Allo Houston ??? Rien ne va plus. Ca va trop vite (100µs pour une trame, 10 fois plus rapide), le signal d’horloge ne reste pas assez longtemps à 1,
le circuit décroche.
On ajoute un petit délai de 1µs sur l’horloge

Le code qui va mieux

Voici le code avec le délai qui va bien:

/*
Test du MM5451 Avec Arduino
<div class="video-container"><blockquote class="wp-embedded-content" data-secret="obXBRGDfIh"><a href="http://www.actuino.fr/">Accueil</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" style="position: absolute; clip: rect(1px, 1px, 1px, 1px);" src="http://www.actuino.fr/embed#?secret=obXBRGDfIh" data-secret="obXBRGDfIh" width="600" height="338" title="« Accueil » — Actu'ino" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe></div>
Version Rapide avec manipulation directe des ports
Sans contrôle de luminosité
*/

include <util/delay.h> // pour avoir un _delay_us

define PIN_CLK  10 // La broche CLK du 5451
define PIN_DATA  11 // la broche DATA du 5451

// Les macros de manipulation directe des ports
define CLK_HIGH() PORTB |= B100 // Macro pour passer CLK à 1. PIN 10 = PORTB,bit 3
define CLK_LOW() PORTB &=!B100 // Macro pour passer CLK à 0. 
define DATA_HIGH() PORTB |= B1000 // Macro pour passer DATA à 1. PIN 11 = PORTB,bit 4
define DATA_LOW() PORTB &=!B1000 // Macro pour passer DATA à 0.

void setup() {
  // Initialisation des broches
  pinMode(PIN_CLK, OUTPUT);
  pinMode(PIN_DATA, OUTPUT);
  // broches à LOW
  //digitalWrite(PIN_CLK, LOW);
  CLK_LOW();
  //digitalWrite(PIN_DATA, LOW);
  DATA_LOW();
}

// envoie un seul bit au 5451
void sendBit(boolean b) {
  // CLK est forcément à LOW.
  if (b) {
    DATA_HIGH();
  } else {
    DATA_LOW();
  }
  CLK_HIGH();
   _delay_us(1); // attendre 1µs
  CLK_LOW();
}

void sendByte(uint8_t a) {
  for (byte i = 0; i < 8; i++) {
    sendBit(boolean((a >> i) & 0x1)); // on envoie le LSB
  }
}

// envoie 4 octets, soit 4x8=32 bits.
// les 3 derniers seront envoyés à 0
// on envoie le LSB en premier.
// donc le LSB de d jusque MSB de d, puis c.
// on termine par a puis les 3 zéros
void send4Bytes(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
  sendBit(1); // On commence par envoyer un bit de start, "1"
  sendByte(d);
  sendByte(c);
  sendByte(b);
  sendByte(a);
  sendBit(0); // Les 3 derniers bits à 0
  sendBit(0);
  sendBit(0);
}

void loop() {
  while (1) {
    delay(1000);
    send4Bytes(B10101010, B10101010, B10101010, B10101010); // une LED sur deux
    delay(1000);
    send4Bytes(255, 255, 255, 255); // Tout allumé
    delay(1000);
    send4Bytes(0, 0, 0, 0); // Tout éteint
  }
}

Et le signal final :
mm5451-Timing3
Là, on ne fera pas (beaucoup) plus rapide en bitbang.
Durée d’une trame (on peut les enchainer sans attendre) : 120µs
Fréquence de l’horloge : 220Khz, on de la marge par rapport aux spécifs du circuit.

P9130028-800

Avec une led sur deux allumée (je sais, je n’en n’ai pas plugué 35 🙂 )

L’avantage de faire du bitbanging avec des pattes quelconques, c’est qu’on n’a pas besoin de partager le port SPI avec d’autres périphériques.
Par contre, si le MM5451 est le seul périphérique SPI, autant utiliser le SPI hardware de l’arduino, qui va encore plus vite (ça sera l’objet d’un autre Tuto, sur le Attiny85).

Une autre solution serait d’utiliser des timers, pour envoyer les trames en tache de fond, sans bloquer le programme principal. Ca serait l’option à choisir si l’arduino doit faire d’autres choses en même temps.

Pour aller plus loin

Je n’ai pas parlé ici du contrôle de luminosité.
On peut piloter la patte 19 du MM5451 avec une sortie de l’Arduino plutôt qu’avec une tension fixe issue du potentiomètre.
Ainsi, en utilisant une sortie PWM et un signal carré dont on fait varier le rapport cyclique, on ajuste la tension et par conséquent, la luminosité des leds (forcément toutes en même temps).
On peut faire ainsi un affichage qui s’adapte à la luminosité ambiante, ou encore des leds qui ‘respirent’.

Des projets en prévision

  • Un POV RGB à base d’attiny85
  • Un cube à led 3d
  • Un globe POV
  • Un clavier midi avec feedback visuel

De votre coté, cher lecteur, vous avez déjà utilisé ce composant ?
ça vous donne des idées ?

4 Responses

  1. Rejean Devin dit :

    Je ne comprends pas que dans votre documentation de montage, vous indiquez de brancher la pin 1 sur le +. Dans le datasheet du MM5451, il indique que la pin 1 est à la terre.

  2. Aurelien dit :

    Bonjour votre montage m’intéresse, j’ai un arduino mega 2560, des leds ainsi qu’un mm5451N.
    Je n’y connais rien du tout en électronique et je me lance justement dedans. Toutefois d’après la doc du 5451 il semble que l’alimentation soit la broche 20 et non la broche 1 qui est justement la masse.

  3. Patrice Simon dit :

    Bonjour
    merci beaucoup pour ce partage, juste une remarque, il y à une erreur en ce qui concerne le raccordement de 5450 (et certainement sur le 5451) c’est la broche 1 à la masse et la broche 20 au +5V contrairement a ce que vous avez écrit

  4. Patrice dit :

    Bonjour,
    Je suis intéressé par votre tuto, j’ai reproduit votre montage que j’ai réussi à faire fonctionner.
    Je cherche un code pour que les leds s’allume une à une l’aide d’un capteur ou potentiomètre en envoyant un signal a une entrée de l’arduino. Je me débrouille en électronique mai je ne sais trop programmer un arduino.
    Merci pour votre aide, cordialement.