Découverte des Neopixels (sur STM32F4)

Récemment, j’ai découvert les Neopixels de Adafruits. Il s’agit de modules de LEDs RGB pas chers et assez simples à mettre en oeuvre. Ils existent sous plusieurs formes (LED unique, platines de 8 LEDs, anneaux, guirlandes, matrices,…), et peuvent être pilotés par une multitude de processeurs (cela va du petit microprocesseur 8 bits au BeagleBone, en passant par les Arduinos, par exemple).

Les applications pour ce genre de modules sont variées. En me promenant sur le net, j’ai trouvé des gens qui en ont fait des bijoux high-tech, une batterie lumineuse, des horloges, et diverses autres créations très amusantes.

J’ai trouvé cela tellement amusant que j’ai décidé de me procurer un de ces petits modules. J’ai opté pour l’anneau de 16 LEDs. Il est pas cher, pas trop grand, pas trop petit, ne consomme pas trop et… je le trouve assez mignon.

Ne voulant pas acheter chez Adafruit (aux USA) directement, j’ai passé la commande chez MC-Hobby. Il s’agit d’un magasin belge, qui revends une bonne partie du catalogue d’Adafruit. La commande s’est passée sans aucun soucis, et a été traitée très rapidement. J’avais en effet commandé en même temps le nouveau RaspberryPi2 qui, à ce moment-là, était encore fort peu disponible de stock, et pourtant, j’ai reçu mon colis 2 jours après la commande (commandé le dimanche soir, reçu mardi dans l’après midi!).

Pour le pilotage des Neopixels, le RaspberryPi est déconseillé. En effet, le signal sur un fil demande de pouvoir générer des signaux assez précis dans le temps (pulses de 400ns et 800ns à +/-150ns). Le RaspberryPi tournant sous Linux ne permet sans doute pas d’atteindre ces performances sur les I/O. L’Arduino semble être souvent utilisé avec les Neopixels, mais je n’en ai pas. Par contre, j’ai sous la main plusieurs carte de développement à base de STM32, dont une carte bien garnie à base de STM32F4.

Me voici prêt à me lancer dans la mise en route de ces Neopixels.

Dans ce premier article, je vais détailler mon installation, ainsi qu’un programme de test (quick and dirty) permettant de voir ces LEDs s’illuminer.

Installation et connection

Commençons par la partie avec laquelle je suis le moins à l’aise : l’électronique. Heureusement, cela est assez simple. Adafruit fournit d’ailleurs un guide très complet à ce sujet. J’ai pu mettre en oeuvre mon petit module en suivant ce guide à la lettre.

La connection est relativement simple : les deux fils d’alimentation (5V) et un fil de contrôle. Il y a cependant 3 difficultés :

  • D’après Adafruit, il est conseillé de placer une résistance entre le processeur et le module (pour le fil de contrôle), et un condensateur aux bornes de l’alimentation. On va dire qu’on sait ce que je fais, et que je suis impatient, je n’ai donc pas monté ces composants. Cela fonctionne, mais cela ne semble pas recommandé…
  • La pin de mon processeur ne peut pas fournir assez de puissance pour permettre au module de fonctionner correctement. J’ai donc dû utiliser une alimentation stabilisée externe. J’ai par contre bien pris soin de connecter la masse de ma carte de développement avec la masse de cette alimentation
  • Le module Neopixel fonctionne en 5V, et mon processeur en 3.3V. Les règles de l’art voudraient que j’utilise un composant pour convertir le 3.3V en 5V mais… cela fonctionne sans pour le moment!

Dans tous les cas, je vous conseille fortement de lire la documentation d’Adafruits à propos de l’installation et de la connection de ces modules car je suis loin d’être un expert en la matière, et je sais que mon montage n’est pas tout à fait idéal.

Au final, voici mon setup:

Le module n’est pas facile à prendre en photo tellement il est lumineux! Donc, vous pouvez voir mon alimentation réglée sur ~5V (je préfère mettre un peu moins qu’un peu trop), le voltmètre pour vérifier la tension appliquée, la carte de développement avec son JTAG, et enfin, l’anneau Neopixe qui brille de mille feux.

Le programme

Bon, une fois que ces détails de câblage sont réglés, passons à la partie réellement intéressante : la programmation!

Comme je l’ai dis dans l’introduction, je vais utiliser une carte de développement basée sur le processeur STM32F4. Il s’agit d’un processeur relativement puissant, fonctionnant à 168Mhz, disposant de 192KB de mémoire RAM, de 1MB de mémoire FLASH et d’une multitude de timers, de ports de communication et autres types de périphériques. Autant dire qu’il s’agit d’un char d’assaut pour écraser une mouche. Un si gros processeur n’est pas nécessaire, et le même résultat pourrait être obtenu avec un petit micro-contrôlleur 8bits à 16Mhz. J’ai utilisé cette carte car je l’avais sous la main et que je la connais bien.

Le premier programme que j’ai écris est un programme « quick and dirty », écris rapidement afin d’arriver le + vite possible à un résultat afin de valider mon câblage et ma compréhension du fonctionnement du module. Il est donc relativement simple, mais d’une qualité discutable. Je suis en fait parti d’un programme d’exemple livré avec la carte.

Dans le cadre de cet exemple, le fil de contrôle du module est connecté sur la pin PA6 du processeur. Commençons par initialiser cette pin:

void LEDGpio_Init(void)
{
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

Vu que je n’utilise pas d’OS, je vais devoir gérer mes timings manuellement. J’ai donc besoin d’une fonction « Sleep() », et la manière la plus simple de l’implémenter, c’est de faire une boucle qui ne fait rien:

void Delay(int counter)
{
    while(counter--);
}

Par contre, il faut connaître le temps d’exécution de la boucle afin de pouvoir effectuer un « sleep » d’une durée précise. Et cela n’est pas toujours facile si on prends en compte les optimisation du compilateur, les mémoires cache, l’exécution parallèle des instructions,… Mais en chipotant un peu, on va y arriver!

Ensuite, je vais devoir générer les bons signaux sur la pin de contrôle afin de piloter chacune des LED. Le signal a générer est le suivant: pour chaque LED il faut envoyer 24 bits de données (8 bits pour le rouge, 8 bits pour le vert et 8 bits pour le bleu). Il faudra envoyer ces 3 bytes (3*8bits) autant de fois qu’il y a de LEDs (16 dans mon cas). Ensuite, il faudra attendre 50µs avant de recommencer.

Vu que le signal transmet la commande et l’horloge en même temps, il doit continuellement passer de 0 à 1 et de 1 à 0. Ce sera donc la durée des pulses à 1 et à 0 qui va déterminer la valeurs des bits:

  • Pour écrire un « 1 » : 800ns au niveau logique 1 et 450ns au niveau logique 0
  • pour écrire un « 0 » : 400ns au niveau logique 1 et 850ns au niveau logique 0

Les 50µs entre 2 trames se feront au niveau logique bas.

Sachant cela, nous pouvons passer à l’écriture de fonctions permettant d’allumer une LED en rouge, vert ou bleu.

Pour le rouge:

void draw_red(void)
{
	char i;
    /* 0 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(11);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(30);
    }

    /* 1 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(28);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(13);
    }
    
    /* 0 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(11);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(30);
    }
}

Le vert:

void draw_green(void)
{
	char i;
    /* 1 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(28);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(13);
    }

    /* 0 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(11);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(30);
    }
        
    /* 0 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(11);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(30);
    }
}

Et le bleu:

void draw_blue(void)
{
    char i;
    /* 0 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(11);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(30);
    }

    /* 0 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(11);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(30);
    }
    
    /* 1 */
    for(i = 0; i < 8; i++)
    {
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_SET);
        Delay(28);
        GPIO_WriteBit(GPIOA,GPIO_Pin_6,Bit_RESET);
        Delay(13);
    }
}

Et voici enfin le main(), qui va allumer toutes les diodes, alternativement en rouge, puis vert, puis bleu:

int main(void)
{
    char i;
    char c = 0;
    KEYGpio_Init();                           
    
    while (1)
    {
        for(i = 0; i < 16; i++)
        {
            switch(c)
            {
                case 0 :
                    draw_red();	
                    c = 1;
                    break;
                case 1:
                    draw_green();
                    c = 2;
                    break;
                case 2:
                    draw_blue();
                    c = 0;
                    break;
            }
        }        
        Delay(5000000);			
    }
}

Il s’agit ici d’un tout premier programme très simple. Maintenant que j’ai vu que cela fonctionne, je vais pouvoir améliorer le contrôle des LED en utilisant un timer PWM en DMA. Cela sera tellement plus sexy! La suite donc dans un prochain article!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *