Une autre librairie WS2812B

Il me fallait une librairie compacte et rapide, simple.

J’ai commencé par essayer de jouer avec les timers pour générer les trains de bits, mais sans grand succès car le timing est vraiment très serré. Pour rappel, je mets quelques éléments pris dans la documentation du fabriquant de ce produit :

T0H : 350 ns          T0L : 580 ns à 1 µs
T1H : 580 ns à 1 µs T1L : 350 ns
Treset : > 280 µs

Les résultats avec les timers n’ont pas été assez stables. Je suis donc reparti vers une piste plus classique du bit bangging qui consiste à actionner directement la sortie en respectant le protocole.

Le bit bang

L’idée, pour garantir un traitement rapide et stable est de bien définir les intervalles de temps et d’éviter les éléments variables en temps d’exécution dans le code :

  1. On monte la sortie ; pour simplifier on travaille directement sur un port dont on masque un bit en particulier ;
  2. Après 350 ns, on calcule l’état de la sortie si suivant la valeur du bit, puis on applique la valeur à la sortie, toujours en masquant ;
  3. Après 580 ns, on abaisse systématiquement la sortie ;
  4. C’est terminé !
#define NOP1 "nop\n\t" 
#define NOP2 NOP1 NOP1 
#define NOP5 NOP2 NOP2 NOP1

  inline 
  void sendBit(const bool b) const {
    PORTB |= _BV(PIN);
    __asm__(NOP2);
    PORTB &= b ? 0x0FF : !_BV(PIN);
    __asm__(NOP5);
    PORTB &= !_BV(PIN);
  } 

Pour accélérer la transmission des 24 bits, c’est la méthode précédente qui sera appliquée successivement en-ligne (inline) afin d’éviter les pertes de temps avec la gestion de la pile lors des appels et des retours de fonction. La rédaction est un peu verbeuse mais reste efficace.

A noter qu’il faut désactiver les interruptions pendant ces phases précises d’exécution.

struct rgb_t {
    byte r;
    byte g;
    byte b;
  };

void sendRGB(const rgb_t& rgb) const {
    cli();
    sendBit(0x80 & rgb.r); 
    sendBit(0x40 & rgb.r); 
    sendBit(0x20 & rgb.r); 
    sendBit(0x10 & rgb.r); 
    sendBit(0x08 & rgb.r); 
    sendBit(0x04 & rgb.r); 
    sendBit(0x02 & rgb.r); 
    sendBit(0x01 & rgb.r); 
    sei();
  
    cli();
    sendBit(0x80 & rgb.g); 
    sendBit(0x40 & rgb.g); 
    sendBit(0x20 & rgb.g); 
    sendBit(0x10 & rgb.g); 
    sendBit(0x08 & rgb.g); 
    sendBit(0x04 & rgb.g); 
    sendBit(0x02 & rgb.g); 
    sendBit(0x01 & rgb.g); 
    sei();
  
    cli();
    sendBit(0x80 & rgb.b); 
    sendBit(0x40 & rgb.b); 
    sendBit(0x20 & rgb.b); 
    sendBit(0x10 & rgb.b); 
    sendBit(0x08 & rgb.b); 
    sendBit(0x04 & rgb.b); 
    sendBit(0x02 & rgb.b); 
    sendBit(0x01 & rgb.b); 
    sei();
  }

Pour le reste, on stocke en mémoire l’état des chaque LED que l’on peut modifier par un accesseur (void setRGB(const byte pos, const rgb_t& rgb)). Enfin, on met à jour la chaîne physique de LEDs par une simple commande (void flush() const) qui transmet le contenu de la mémoire.

La classe

Le tout est présenté dans une classe paramétrée (template) qui contient le nombre de LEDs et le port de sortie associé :

template<byte PIN = 1, byte LEN = 1>
class WS2812b;

Ah ! Encore une chose : cette librairie ne doit fonctionner qu’avec un processeur Atmel à 16 MHz (testée sur ATmega328 et ATtiny85, tous @16 MHz).

Vous pouvez retrouver le code sur mon GitHub :

https://github.com/Marcussacapuces91/lib-ws2812b

One Thought to “Une autre librairie WS2812B”

Comments are closed.