Un micro ecran à base de LED Neopixel

Un micro ecran à base de LED Neopixel

Cette semaine, nous allons parler du Yocto-Color-V2, ce module permet de contrôler jusqu'à 150 LEDs RGB. Dans un précédent article, nous avons vu comment créer des animations qui sont exécutées directement par le Yocto-Color-V2. Toutefois, il y a des limites et, pour les animations complexes, on est parfois obligé de calculer chaque frame de l’animation et mettre à jour les LEDs depuis l’application. C'est ce que nous allons réaliser cette semaine.




Note: Nous avons déjà un article qui explique les bases du Yocto-Color-V2, si vous n'êtes pas familier avec ce module nous vous recommandons de commencer par la lecture de cet article.

Un écran de 16x8 pixel

Comme nous l'avons dit, dans certaines situations, la seule solution est de calculer chaque frame de l’animation, et de mettre à jour toutes les LEDs. Pour illustrer cette situation, nous allons voir comment réaliser un mini-écran de 16x8 pixels à l'aide de deux panneaux de 8x8 LED Neopixel chaînés l'un à l'autre.

Les deux panneaux 8x8 LED Neopixel forment un écran de 16x8 pixels
Les deux panneaux 8x8 LED Neopixel forment un écran de 16x8 pixels



Bien que les LEDs forment visuellement un rectangle, elles restent câblées en série. C'est-à-dire que le numéro de chaque LED correspond à leur position sur la chaîne de LEDs. La LED 0 est celle qui est branchée directement au Yocto-Color-V2, La LED 1 est celle qui est branchée à la LED 0, etc..
Le Yocto-Color-V2 n'a aucune idée de la topologie des LEDs qu'il pilote.

La première étape consiste donc à déterminer comment les LEDs sont reliées entre elles. Dans notre cas, la chaîne de LEDs commence en bas à droite et monte pour ensuite continuer sur la deuxième colonne. Comme une image vaut mille mots, voici la topologie de notre chaîne de LEDs:

L'ordre des LEDs
L'ordre des LEDs



Un petit exemple en C#


Pour piloter cet écran, nous avons réalisé un petit programme en C#.

Comme il est beaucoup plus pratique de travailler avec un tableau à deux dimensions plutôt que d'une liste de LEDs, nous avons créé un objet ColorLedScreen. Cet objet a deux tâches, il doit dans un premier temps convertir les coordonnée X,Y en adresses de LED, et ensuite mettre à jour la couleur des LEDs.

Le constructeur de cet objet prend en argument un objet YColorLedCluster correspondant au Yocto-Color-V2 qui pilote les deux panneaux de LED.

La conversion de la position X,Y est effectuée par la méthode getLEDNumberFromXY. C'est cette fonction qu'il faudrait modifier si on assemble nos panneaux de LED différemment.

Enfin, la méthode drawBitmapRGB(int x, int y, int[,] bitmap) parcourt les pixels du bitmap passé en argument et met à jour les LEDs correspondantes.

class ColorLedScreen
{
    private YColorLedCluster colorleds;
    private int nbleds;
    private int width;
    private List<int> ledStripe;
    private int height;


    public ColorLedScreen(YColorLedCluster colorLeds, int width, int height)
    {
        this.colorleds = colorLeds;
        this.nbleds = width * height;
        this.width = width;
        this.height = height;
        this.ledStripe = colorLeds.get_rgbColorArray(1, this.nbleds);
    }

    private int getLEDNumberFromXY(int user_x, int user_y)
    {

        int tmp_x = user_x;
        int tmp_y = user_y;

        tmp_y = this.height - 1 - tmp_y;
        int ofs = tmp_x * this.height + tmp_y;
        return ofs;
    }

    public virtual int drawBitmapRGB(int x, int y, int[,] bitmap)

    {
        int ofs = 0;
        for (int i = 0; i < bitmap.GetLength(0); i++) {
            for (int j = 0; j < bitmap.GetLength(1); j++) {
                int led_ofs = getLEDNumberFromXY(x + i, y + j);
                this.ledStripe[led_ofs] = bitmap[i, j];
            }
        }
        this.colorleds.set_rgbColorArray(0, this.ledStripe);
        return YAPI.SUCCESS;
    }
}



Notez que l'objet ne met pas à jour individuellement les LEDs à l'aide de la fonction set_rgbColor mais travaille sur un cache (la propriété ledStripe) et met à jour toutes les LEDs à la fin. Cette solution est plus efficace car la librairie transmet toutes les données en bloc en une seule fois.

Pour vérifier que notre code fonctionne, on va écrire un petit programme qui va afficher un gradient sur le panneau:

static void Main(string[] args)
{
  string errmsg = "";
  string url = "usb";
  if (YAPI.RegisterHub(url, ref errmsg) != YAPI.SUCCESS) {
    Console.WriteLine("Unable to register hub with url "
    + url + " :" + errmsg);
    return;
  }

  YColorLedCluster colorLedCluster = YColorLedCluster.FirstColorLedCluster();
  if (colorLedCluster == null) {
    Console.WriteLine("No Yocto-Color-V2 is connected on hub " + url);
    return;
  }

  int w = 16;
  int h = 8;
  ColorLedScreen colorLedScreen = new ColorLedScreen(colorLedCluster, w, h);

  /*
   * Draw a gradient on the two 8x8 panel
   */

  int[,] gradient = new int[w, h];
  for (int j = 0; j < h; j++) {
    for (int i = 0; i < w; i++) {
      int r = i * 255 / w;
      int g = j * 255 / h;
      gradient[i, j] = (r << 16) + (g << 8) ;
    }
  }

  colorLedScreen.drawBitmapRGB(0, 0, gradient);
  YAPI.FreeAPI();
}



Le code est assez standard, on initialise l'API avec la méthode YAPI.RegisterHub, et on récupère un objet YColorLedCluster qui correspond au product>Yocto-Color-V2 puis on instancie notre objet ColorLedScreen.



Maintenant que l'on sait que notre code fonctionne, on peut commencer à s'amuser à faire des animations.

On commence par animer un peu notre gradient:

  



Voici un autre exemple, rendu de l'ensemble de Mandelbrot:

  



et pour finir l'affichage d'un texte défilant:

  



Le code complet de l'application et des animations est disponible sur GitHub: https://github.com/yoctopuce-examples/ColorLedScreen

Le taux de rafraîchissement


Maintenant que nous avons une application qui met à jour toutes les LEDs, on peut s'amuser à mesurer le temps qu'il faut pour rafraîchir l'écran à l'aide du code suivant:

public void bench()
{
  int w = 16;
  int h = 8;
  int[,] bitmap_r = new int[w, h];
  int[,] bitmap_g = new int[w, h];
  int[,] bitmap_b = new int[w, h];

  for (int j = 0; j < h; j++) {
      for (int i = 0; i < w; i++) {
          bitmap_r[i, j] = 0xf0000;
          bitmap_g[i, j] = 0xf00;
          bitmap_b[i, j] = 0xf;
      }
  }

  Stopwatch sw = new Stopwatch();
  sw.Start();
  for (int i = 0; i < 100; i++) {
      this.drawBitmapRGB(0, 0, bitmap_r);
      this.drawBitmapRGB(0, 0, bitmap_g);
      this.drawBitmapRGB(0, 0, bitmap_b);
  }

  sw.Stop();
  Console.WriteLine("frame refresh took {0}ms",
  sw.ElapsedMilliseconds / 300.0);
}



Au final avec une connexion USB, il nous faut un peu moins de 14 millisecondes pour rafraîchir tout l'écran, ce qui nous fait environ 70Hz. C'est plus que suffisant pour faire des animations fluides. Dans la plupart des cas, il faudra même ajouter une petite attente entre chaque frame pour nos yeux aient le temps de percevoir le changement.

Toutefois, il faut garder en tête que l'animation est pilotée par l'application et non plus par le Yocto-Color-V2. Cela implique que le taux de rafraîchissement est dépendant du temps de calcul de la frame mais aussi de la communication entre l'application et le Yocto-Color-V2.

Si le Yocto-Color-V2 est branché sur un port USB, il n'y a pas de problème. Mais si vous utilisez un Yoctohub sur un réseau très lent ou congestionné, votre animation risque de lagger.

Conclusion


Comme nous venons de le voir, il est relativement facile de réaliser des animations complexes en rafraîchissant en bloc toutes les LEDs connectées au Yocto-Color-V2.

Dans cet exemple, nous avons organisé nos LEDs pour qu'elles forment un rectangle de 16x8, mais la même technique peut être utilisée pour d'autres topologies.


Commenter aucun commentaire Retour au blog












Yoctopuce, get your stuff connected.