Apprendre à dessiner sur une matrice de LED avec Arduino en langage C

Francesco Balducci

Traducteur

f-leb

Ce tutoriel est le troisième de la série sur la programmation de carte Arduino en langage C :

- Partie 1 : Programmer l'Arduino en langage C

- Partie 2 : Utiliser un buzzer avec Arduino en langage C

À chaque fois, l'idée est de s'affranchir des facilités offertes par le fameux « langage Arduino » dans l'EDI standard. Ici, les programmes seront développés en langage C « pur » grâce aux outils de la chaîne de compilation avr-gcc.

L'objectif est double :

- développer des codes optimisés, efficaces et compacts ;

- démystifier le fonctionnement d'un microcontrôleur et prendre le contrôle des entrées-sorties, sans fard, en attaquant directement les registres du microcontrôleur.

Dans ce troisième volet, l'auteur détaille le fonctionnement d'une matrice de 7 x 5 LED et la technique de multiplexage qu'il a mise au point pour développer une bibliothèque C permettant de dessiner des caractères.

Commentez

- Titre : Apprendre à dessiner sur une matrice de LED avec Arduino en langage C

- Auteur : Francesco Balducci

- Traducteur : f-leb

- Parution : 9 mars 2017

- Public visé :

- Licence : Le contenu de cet article est rédigé par Francesco Balducci et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Voir également :

- Programmer l'Arduino en langage C

- Utiliser un buzzer avec Arduino en langage C

Sommaire

- I. La matrice de LED

- II. Une bibliothèque dédiée écrite en langage C

- III. Câblage de la matrice de LED et des composants

- IV. Balayage des points de la matrice par colonne

- V. Le code

- VI. Notes de la Rédaction Developpez.com

I - La matrice de LED

J'avais un LTP-7357AG dans mes tiroirs, une matrice de 35 LED vertes conditionnées dans un boîtier à 12 broches. J'ai voulu jouer avec, et pu ainsi commencer à l'interfacer avec mon Arduino Uno. Cet article est le troisième de la série sur la programmation Arduino en langage C.

Je vais commencer par vous présenter le résultat obtenu, puis vous fournir des explications sur ce qui a été fait et pourquoi. La photo suivante montre bien que l'Arduino est capable de dessiner une lettre sur l'afficheur.

Arduino UNO et une matrice de LED 7x5

II - Une bibliothèque dédiée écrite en langage C

J'ai réalisé cela en développant une bibliothèque en C qui pilote la matrice de LED connectée à des entrées-sorties particulières, et en même temps, j'ai écrit un programme principal qui reprend la bibliothèque pour afficher un « B » sur la matrice :

ledmatrix_test.c

#include <util/delay.h>
#include "ledmatrix.h"  // The header of my library.

#define FRAME_RATE_HZ 50 // This feels sufficient
#define SUBFRAME_RATE_HZ (FRAME_RATE_HZ * N_SUBFRAMES) // N_SUBFRAMES is in ledmatrix.h
#define SUBFRAME_DELAY_US (1000000 / SUBFRAME_RATE_HZ)

int main(void)
{
struct ledmatrix_frame fB = // struct ledmatrix is a 5-bytes struct holding a 7x5 frame.
LEDMATRIX_FRAME_INIT( // A helper macro to write frames easily
11110,  // This structure contains a 'B'
10001,  // 1 means LED is on
10001,  // 0 means LED is off
11110,  // they are transformed into binary numbers by 'magic' in LEDMATRIX_FRAME_INIT()
10001,
10001,
11110);

ledmatrix_setup(); // Initializes ports and stuff.

while(1)
{
ledmatrix_draw_next_subframe(&fB); // Turns on the LEDs of the next subframe.
_delay_us(SUBFRAME_DELAY_US); // This loop+delay might be replaced by a periodic interrupt.
}
return 0;
}

III - Câblage de la matrice de LED et des composants

Voici le diagramme interne du composant :

Schéma du circuit interne du LTP-7357AG

Ce qui est fondamental à comprendre pour chaque point de l'afficheur est que si, par exemple, on met la rangée 1 à l'état haut et la colonne 1 à l'état bas, on allume le point aux coordonnées (1, 1), alors que si l'on met la rangée 1 à l'état bas et la colonne 1 à l'état bas, on va l'éteindre.

Dans la plupart des tutoriels que l'on peut trouver sur Internet sur le pilotage de matrice de LED, on couvre tous les points de la matrice en effectuant un balayage par rangée ou par colonne. J'ai décidé de faire un balayage par colonne, et j'ai donc connecté une résistance électrique à chaque broche d'une rangée. La documentation de la matrice de LED (datasheet) indique que chaque LED a une tension de seuil de 2,1 V pour un courant à un fonctionnement typique de 20 mA. Étant donné l'alimentation 5 V en sortie des connecteurs de l'Arduino, cela donne une résistance d'environ 145 Ω (NDLR : (5 - 2,1)/20.10-3 = 145, voir le rappel sur le fonctionnement des LED). J'ai cependant utilisé des résistances de 220 Ω, les seules dont je disposais ; le revers étant que la luminosité des LED sera moins forte à cause du courant moins élevé qui les traverse, mais rien de dramatique.

Le mieux était de connecter les broches de la matrice de LED en suivant autant que possible la numérotation des connecteurs de l'Arduino. J'avais donc commencé par le connecteur 0 qui correspond au PORT D0 de la puce ATmega, mais je suis allé au-devant des problèmes avec les connecteurs 0 et 1, probablement à cause de la connexion avec l'adaptateur USB-série de l'Arduino Uno.

Voici le câblage final de l'Arduino et des différents composants :

Vue de la plaquette de câblage de l'Arduino Uno et de la matrice de LED, réalisée avec Fritzing

Notez que les couleurs des fils correspondent avec celles de la photo en début d'article. Voici un tableau qui indique explicitement les connexions :

Matrice de LED

Arduino

COL1 (PIN1)

PB0 (PIN8)

COL2 (PIN3)

PB1 (PIN9)

COL3 (PIN10)

PB2 (PIN10)

COL4 (PIN7)

PB3 (PIN11)

COL5 (PIN8)

PB4 (PIN12)

ROW1 (PIN12)

PB5 (PIN13)

ROW2 (PIN11)

PD7 (PIN7)

ROW3 (PIN2)

PD2 (PIN2)

ROW4 (PIN9)

PD3 (PIN3)

ROW5 (PIN4)

PD4 (PIN4)

ROW6 (PIN5)

PD5 (PIN5)

ROW7 (PIN6)

PD6 (PIN6)

IV - Balayage des points de la matrice par colonne

Dans beaucoup d'exemples, lorsqu'on effectue le balayage des points de la colonne, la colonne active est pilotée par l'état bas d'une broche, tandis que les autres colonnes ne sont pas pilotées : les entrées-sorties de l'Arduino sont configurées en entrée à haute impédance. De cette façon, on peut contrôler l'état de chaque point de la colonne en mettant à l'état haut ou bas la broche correspondante. Le problème avec le pilotage de sept points simultanément est que l'entrée-sortie de l'Arduino qui conserve la broche de la colonne à l'état bas absorbe trop de courant : la documentation (datasheet) de l'ATmega indique un courant maximal de 40 mA par broche, mais chaque point allumé de la matrice requiert environ 20 mA, donc nous devrions pouvoir allumer au plus deux points simultanément. Certaines personnes recommandent d'utiliser un circuit avec puits de courant (current sink) pour résoudre le problème, mais je n'ai pas un tel composant dans mes tiroirs. J'ai donc décidé de n'impliquer que deux points simultanément plutôt que la colonne complète ; les points impliqués en même temps sont ce que j'ai appelé « subframe » dans le code. Le GIF animé suivant montre le balayage colonne par colonne pour afficher un « B », chaque colonne n'impliquant au plus que deux points à la fois :

Animation montrant le balayage des points colonne par colonne

V - Le code

Le code est en ligne sur GitHub, et voici le fichier d'entête de la bibliothèque : ledmatrix.h

Ledmatrix.h

/*
* Copyright (c) 2014 Francesco Balducci
*
* This file is part of arduino_c.
*
*    arduino_c is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    arduino_c is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public License
*    along with arduino_c.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef LEDMATRIX_H
#define LEDMATRIX_H

#include <stdint.h>

/* Using LTP-7357AG
* 7x5 LED matrix
*/

#define N_COLS 5
#define N_ROWS 7
#define N_DOTS_ON_MAX 2

#define N_SUBFRAMES (((N_ROWS + N_DOTS_ON_MAX - 1)/ N_DOTS_ON_MAX) * N_COLS)

struct ledmatrix_frame {
uint8_t cols[N_COLS];
};

#define BITCONST_(bits) 0b ## bits

#define GETCOLDOT_(i_col, i_row, row) (((BITCONST_(row) >> (N_COLS - 1 - (i_col))) & 0x01) << (i_row))

#define GETCOL_(i_col, r0,r1,r2,r3,r4,r5,r6) \
( \
GETCOLDOT_(i_col, 0, r0)| \
GETCOLDOT_(i_col, 1, r1)| \
GETCOLDOT_(i_col, 2, r2)| \
GETCOLDOT_(i_col, 3, r3)| \
GETCOLDOT_(i_col, 4, r4)| \
GETCOLDOT_(i_col, 5, r5)| \
GETCOLDOT_(i_col, 6, r6)  \
)

#define LEDMATRIX_FRAME_INIT(r0,r1,r2,r3,r4,r5,r6) \
{{ \
GETCOL_(0, r0,r1,r2,r3,r4,r5,r6), \
GETCOL_(1, r0,r1,r2,r3,r4,r5,r6), \
GETCOL_(2, r0,r1,r2,r3,r4,r5,r6), \
GETCOL_(3, r0,r1,r2,r3,r4,r5,r6), \
GETCOL_(4, r0,r1,r2,r3,r4,r5,r6), \
}}

#define LEDMATRIX_FRAME_SET(f, r0,r1,r2,r3,r4,r5,r6) \
do { \
(f)->cols[0] = GETCOL_(0, r0,r1,r2,r3,r4,r5,r6); \
(f)->cols[1] = GETCOL_(1, r0,r1,r2,r3,r4,r5,r6); \
(f)->cols[2] = GETCOL_(2, r0,r1,r2,r3,r4,r5,r6); \
(f)->cols[3] = GETCOL_(3, r0,r1,r2,r3,r4,r5,r6); \
(f)->cols[4] = GETCOL_(4, r0,r1,r2,r3,r4,r5,r6); \
} while(0)

extern void ledmatrix_setup(void);

extern void ledmatrix_draw_next_subframe(const struct ledmatrix_frame *f);

#endif /* LEDMATRIX_H */

En gros, l'information sur les points qu'il faut allumer est encodée dans une structure à 5 octets, et des macros sont prévues pour « dessiner » sur la matrice avec des uns et des zéros. Puis il y a une fonction ledmatrix_draw_next_subframe() utilisée pour piloter deux points et qui contient en elle l'état des points suivants à considérer. Avec cette bibliothèque, il est possible, par exemple, d'avoir un alphabet de 128 caractères ASCII dans un tableau de 640 octets et de les afficher un par un pour former un mot.

L'implémentation de la bibliothèque est ici : ledmatrix.c

ledmatrix.c

/*
* Copyright (c) 2014 Francesco Balducci
*
* This file is part of arduino_c.
*
*    arduino_c is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    arduino_c is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public License
*    along with arduino_c.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/io.h>
#include <stdint.h>
#include "ledmatrix.h"

static
const uint8_t COL_MASK =
_BV(DDB0)
| _BV(DDB1)
| _BV(DDB2)
| _BV(DDB3)
| _BV(DDB4);

static
const uint8_t ROW_MASK_D =
_BV(DDD2)
| _BV(DDD3)
| _BV(DDD4)
| _BV(DDD5)
| _BV(DDD6)
| _BV(DDD7);

static
const uint8_t ROW_MASK_B =
_BV(DDB5);

static
void cols_off(void)
{
DDRB &= ~COL_MASK;
}

static
void col_on(int i_col)
{
DDRB |= _BV(i_col);
}

static
void cols_setup(void)
{
cols_off();
PORTB &= ~COL_MASK; /* disable pull-up */
}

static
void rows_setup(void)
{
DDRD |= ROW_MASK_D;
PORTD &= ~ROW_MASK_D; /* everything off */
DDRB |= ROW_MASK_B;
PORTB &= ~ROW_MASK_B; /* everything off */
}

static
uint8_t dots_portd(uint8_t dots)
{
uint8_t dots_d;
dots_d = dots & 0x7E; /* bit 0 is on PB5 */
dots_d |= (dots & 0x02)?_BV(DDD7):0x00; /* bit 1 is on PD7 */
dots_d &= ~0x02;
return dots_d;
}

static
uint8_t dots_portb(uint8_t dots)
{
uint8_t dots_b;
dots_b = (dots & 0x01)?_BV(DDB5):0x00;
return dots_b;
}

static
void draw_dots_portd(uint8_t dots)
{
uint8_t dots_d = dots_portd(dots);
uint8_t dots_on = ROW_MASK_D & dots_d;
uint8_t dots_off = ROW_MASK_D & ~dots_d;
PORTD |= dots_on;
PORTD &= ~dots_off;
}

static
void draw_dots_portb(uint8_t dots)
{
uint8_t dots_b = dots_portb(dots);
uint8_t dots_on = ROW_MASK_B & dots_b;
uint8_t dots_off = ROW_MASK_B & ~dots_b;
PORTB |= dots_on;
PORTB &= ~dots_off;
}

static
void draw_dots(uint8_t dots)
{
draw_dots_portd(dots);
draw_dots_portb(dots);
}

static
void ledmatrix_draw_col(int i_col, uint8_t dots)
{
cols_off();
draw_dots(dots);
col_on(i_col);
}

void ledmatrix_setup(void)
{
cols_setup();
rows_setup();
}

void ledmatrix_draw_next_subframe(const struct ledmatrix_frame *f)
{
static int i_next_col = 0;
static int i_next_row = 0;

uint8_t dots;
uint8_t dots_mask = (1<<N_DOTS_ON_MAX)-1;

dots = f->cols[i_next_col];
dots_mask <<= i_next_row;
dots &= dots_mask;
ledmatrix_draw_col(i_next_col, dots);
i_next_row += N_DOTS_ON_MAX;
if (i_next_row >= N_ROWS)
{
i_next_row = 0;
i_next_col++;
if (i_next_col == N_COLS)
{
i_next_col = 0;
}
}
}

Les ports PORTB et PORTD sont utilisés pour manipuler les points, et associer une broche de la matrice de LED à une broche d'un port est quelque peu délicat à cause de la disposition désordonnée, mais il n'est pas si compliqué de s'y retrouver. En compilant avec avr-gcc et en optimisant avec -Os (NDLR : voir les commandes utilisées ici), la bibliothèque occupe moins de 200 octets de code et de données.

J'espère que mon approche s'avère utile et vous a inspirés. Les bénéfices de cette approche sont d'une part qu'il n'y a pas besoin de circuit additionnel en plus de l'ATmega et la matrice de LED, et d'autre part la taille du code et des données est vraiment très compacte.

VI - Notes de la Rédaction Developpez.com

Cet article est la traduction du billet écrit par Francesco Balducci (alias Balau). Retrouvez la version originale de cet article ainsi que les autres chapitres consacrés à Arduino sur son blog.

Nous remercions les membres de la Rédaction de Developpez.com pour le travail de traduction et de relecture qu'ils ont effectué, en particulier : f-leb, gaby277 et KartSeven.