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.
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 :
#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 :
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 :
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 :
V. Le code▲
Le code est en ligne sur GitHub, et voici le fichier d'entête de la bibliothèque : 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
/*
* 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.