LedMatrix hacking
Introduction
DealExtreme propose pour 7,9€, un afficheur à led (rouge, bleu) alimenté par une pile bouton et programmable directement par des boutons situés sur le verso de ce dernier, en plus de tout cela, il est vaguement indiqué qu'il existe une connection USB permettant peut être une programmation depuis un PC.
Il possède 203 leds disposées en 29 colonnes de 7 leds.
Quelques semaines après la commande, je reçois cet afficheur, il fonctionne parfaitement bien, malheureusement, le connecteur USB est d'un type inconnu, un démontage s'impose alors pour en savoir plus sur ce dernier, pas de vis, il va falloir employer une manière un peu brutale pour séparer les 2 coques collées.
Une fois ouvert, plusieurs surprises, bonnes et mauvaises :
- Le connecteur USB ne risque pas de fonctionner, il est connecté nul part (je m'apercevrai plus tard pourquoi)
- Le tout est piloté par un seul et unique micro-controleur, un Atmel AVR Atmega88, aucun multiplexeur...
- Un port de programmation est disponible
Une question se pose alors, comment arrive-t-il à piloter autant de led avec un seul micro-controleur disposant en tout de 3 ports de 8 bits ? sachant que l'on a théoriquement un port USB à gérer et 7 boutons de programmation et une EEPROM externe ?
Ouverture du boitier
Le boitier est constitué de 2 parties thermocollées l'une à l'autre, oui, mettre des vis aurait été trop couteux et puis, <ironie>
de toute façon, qui voudrait le démonter ?</ironie>
Du coup, l'ouverture n'est pas vraiment une partie de plaisir et il vous faudra à l'aide d'un cutter suivre la jointure des 2 coques afin de les décoller.
Comment ça marche ?
Vue globale
Voici les principales zones du module :
- Connecteur propriétaire USB
- Le coeur du montage, l'AVR
- L'interrupteur
- Le connecteur pour la programmation
- Une EEPROM pour stocker les messages
Au centre, on aperçoit l'emplacement de la pile bouton, sur la gauche, les 6 boutons et enfin, 1 bouton isolé en haut à droite.
Le coeur
Le coeur du montage est un Atmel AVR ATMega88PA, dans un boitier TQFP dont voici les principales caractéristiques :
- 8 Ko de mémoire Flash (préparez le chausse pied)
- 512 Ko d'EEPROM
- 2 timers 8 bits
- 1 timer 16 bits
- 23 entrées / sorties
- Tension d'alimentation comprise entre 1,8V et 5,5V (pile bouton de 3V)
- Très faible consommation (d'ou l'utilisation possible avec une autonomie correct avec juste une pile bouton)
Il est cadencé à 8Mhz grâce à l'oscillateur interne permettant ainsi d'éviter d'avoir à ajouter un quartz externe et vu que la précision de la base de temps n'est pas recherché dans ce montage, c'est tout à fait adapté.
Interface de programmation
Un connecteur est disponible et permet de programmer comme bon nous semble l'AVR. D'origine, ce dernier est bien évidemment illisible, le fabricant à protégé l'accès au code contenu dans l'AVR grâce aux fusibles adéquates.
Le connecteur de programmation, on ne sait pas trop d'oû il sort, il n'est pas compatible au niveau connectique avec un ISP6 mais facilement adaptable :
Si par mégarde, vous commettez une erreur avec les fusibles de l'AVR et qu'il ne répond plus, il est toujours possible de reprogrammer ce dernier en parallèle mais c'est vivement déconseillé ;) :
L'interface USB
Le connecteur USB est propriétaire et ne permet donc pas de brancher n'importe quel cable USB dessus.
D'origine, l'interface USB n'a absolument aucune chance de fonctionner :
- Les lignes de données USB D+ et D- sont court-circuités
- Il manque des composants (Q2, R12 et R13) qui ont l'air impliqué dans une possible gestion du port (on ne sait comment, la magie peut être)
- L'AVR étant cadencé à 8Mhz en interne, il est très peu probable, même en mode « Low Speed » qu'il soit possible de gérer quoique ce soit du protocole (Le projet V-USB indique qu'il faut au minimum une fréquence de 12Mhz)
En fait, l'interface USB décrite sur le site de Dealextreme ressemblerait plus à une interface série RS232 car si on suit les pistes des broches RX et TX de l'AVR, on arrive tout droit sur Q1, R9, R12, R13, Q2 qui eux, donnent vers les connecteurs externe.
La gestion des boutons
Les 7 boutons sont tout simplement reliés à une seule entrée de l'AVR, et pas n'importe laquelle, à une entrée reliée au CAN (Convertisseur Analogique Numérique), chaque bouton forme, une fois pressé, grâce à des résistances bien placées et correctement dimensionnées, un pont diviseur de tension.
L'avantage de cette manière de faire, c'est que l'on peut gérer plusieurs boutons avec seulement une broche du micro-controleur utilisée, l'inconvénient majeur de cette méthode étant que la gestion de plusieurs boutons pressés simultanément s'en trouve quelque peu compliquée...
Voici le principe de fonctionnement :
Lorsqu'un bouton est pressé, le courant passe au travers de ce dernier et de la résistance reliée à la masse (R1, R2 ou R3) formant alors un pont diviseur avec la résistance Rup relié à la tension d'alimentation. On trouve alors sur l'entrée du CAN, une tenstion permettant de connaitre quel bouton à été pressé.
Voici comment j'ai nommé les boutons, vu de derrière :
Les correspondances entre les boutons et leur valeurs renvoyés par le CAN
Bouton | Valeur |
---|---|
A | 128 |
B | 155 |
C | 79 |
D | 57 |
E | 19 |
F | 42 |
G | 192 |
Les boutons étant en métal, lorsque l'on appuie dessus, la valeur renvoyée par le CAN fluctue, ainsi, si on appuie sur le bouton A, on obtient 128, 129, 127, etc... un traitement logiciel est donc fait pour lisser tout ça en élargissant le champs de valeurs admissible pour un bouton (ex: pour le A, c'est de 126 à 130, etc...)
La matrice de LED
203 Leds, disposées en 7 lignes par 29 colonnes sont utilisées dans ce montage, pourtant, 15 lignes de données seulement permettent de piloter l'écran, et aucun autre circuit intégré n'est là, par quel moyen astucieux y arrivent-ils ?
Les ports B, C et D sont utilisés pour le pilotage des leds, le port B est utilisé en entier tandis que les bits 0 à 3 sont utilisés pour le port C et les bits 5 à 7 sont utilisés pour le port D, récapitulons :
Utilisation des ports pour le pilotage des Leds
Port B
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|
Port C | x | x | x | x | 3 | 2 | 1 | 0 | | - | - | - | - | - | - | - | - |
Port D | 7 | 6 | 5 | x | x | x | x | x | | - | - | - | - | - | - | - | - |
Avec 15 lignes, il ne serait pas possible de piloter 203 avec des leds disposées comme dans une matrice conventionnelle, on arriverait au mieux, avec une disposition carré à 56 leds.
La solution qu'ils ont trouvé est d'utiliser la capacité des lignes des ports de l'AVR à se mettre dans un état dit à haute impédance, on entend cela aussi sous le nom de Three-State Logic, c'est un état dans lequel le courant ne circule pas du tout, c'est comme ci, on avait un interrupteur ouvert, cela permet une isolation totale.
Sur l'image ci-dessus, on comprend assez bien le fonctionnement de la porte logique à 3 états, tant que la commande B est inactive, la porte fonctionne normalement avec uniquement 2 états, ainsi, dans le cas d'un buffer standard (porte OUI), si on applique un état haut à l'entrée A, sur la sortie C, nous aurons aussi un état haut, si on active la fonction à haute impédance par le biais de l'entrée B, la porte bascule alors en haute impédance, le courant ne peut plus circuler sur la sortie de la porte.
Dans le circuit, chaque bit des ports commandant les leds permet d'allumer 14 leds en même temps, mais selon la configuration des autres ports (niveau bas, niveau haut ou haute impédance), 1 seule ou plusieurs leds s'allumeront.
Pour connaitre quelle combinaison de valeur il faut envoyer sur les ports pour faire s'allumer telle led, il a fallu reconstituer virtuellement le mapping des connections des leds aux ports et tester les différentes combinaisons possible. Une loupe m'a aussi été d'une bonne aide afin de voir les connections physiques sur l'afficheur.
On aperçoit à l'oeil nu les connections reliants les leds entre elles (difficile à voir sur la photo).
Mise à jour : Apparemment, nous avons affaire à un multiplexage de type Charlieplexing
Code source
Pour l'affichage d'un seul pixel, on a besoin d'alimenter 6 zones mémoires : les 3 ports B, C et D (PORTB, PORTC et PORTD) ainsi que les ports de directions (DDRB, DDRC et DDRD), toutes ces valeurs sont stockées en mémoire programme dans un tableau multidimensionnel contenant pour chaque colonne, 6 x 7 valeurs, 7 étant le nombre de lignes.
Voici un extrait du tableau :
unsigned char matrix[29][7][6] PROGMEM = {
// 0
{
{ 4, 4, 0, 4, 0, 0 },
{ 2, 4, 0, 2, 0, 0 },
{ 1, 4, 0, 1, 0, 0 },
{ 0, 4, 128, 0, 0, 128 },
{ 0, 4, 32, 0, 0, 32 },
{ 128, 4, 0, 128, 0, 0 },
{ 64, 4, 0, 64, 0, 0 },
},
// 1
{
{ 4, 0, 64, 0, 0, 64 },
{ 2, 0, 64, 0, 0, 64 },
{ 1, 0, 64, 0, 0, 64 },
{ 0, 0, 64+128, 0, 0, 64 },
{ 0, 0, 64+32, 0, 0, 64 },
{ 128, 0, 64, 0, 0, 64 },
{ 64, 0, 64, 0, 0, 64 }
},
[...]
Ainsi, lorsque l'on veut allumer un pixel, on fait appel à la fonction setLed qui va s'occuper d'aller chercher les données et les mettre oû il faut pour allumer la bonne led.
inline void setLed(unsigned int x, unsigned int y) {
if (x >= MATRIX_COL_COUNT || y >= MATRIX_LINE_COUNT) {
return;
}
// Reset all ports
DDRB = DDRC = DDRD = 0;
PORTB = PORTC = PORTD = 0;
DDRB = pgm_read_byte(&(matrix[x][y][_DDRB]));
DDRC = pgm_read_byte(&(matrix[x][y][_DDRC]));
DDRD = 0b11100000 & pgm_read_byte(&(matrix[x][y][_DDRD]));
PORTB = pgm_read_byte(&(matrix[x][y][_PORTB]));
PORTC = pgm_read_byte(&(matrix[x][y][_PORTC]));
PORTD = 0b11100000 & pgm_read_byte(&(matrix[x][y][_PORTD]));
}
Accès à la mémoire EEPROM externe
Todo
Je n'ai pas investiguer là dessus car pas totalement indispensable...
Ajoutons une connection série
Voici oû il est possible de se connecter sur la carte pour accéder à l'interface UART de l'AVR, en rouge, le TX, en vert le RX (par rapport à l'AVR).
Une fois la pose de l'adaptateur pour la programmation (connecteur au milieu au dessus) ainsi que le connecteur pour l'interface UART (connecteur à 4 broches légèrement à gauche en bas) :
Le connecteur UART que j'ai placé possède 4 broches permettant de véhiculer TX, RX, GND et VCC pour alimenter directement le montage depuis le connecteur.
Vous voulez piloter le montage directement depuis un module USB
Des modules USB bien pratiques permettant de dialoguer avec des modules au travers d'une liaison série, ils ne coutent pas bien chèr et des pilotes existent pour tous les OS :
Personnellement, j'utilise un modèle de chez 4DSystems commandé chez Sparkfun pour le développement, il à l'avantage de fonctionner aussi bien avec des tensions de 3 ou 5V (pour la ligne série) : Breakout Board for FT232RQ USB to Serial
Des modules sont aussi disponible en France chez lextronic :
- Version 5V : Module "FTDI Basic Breakout" - 5V
- Version 3,3V : Module "FTDI Basic Breakout" - 3,3V
Si vous possédez déjà une connection série RS232
Une connection série RS232 fonctionne à l'aide de signaux oscillants entre +12V et -12V, pour pouvoir brancher le présent montage sans souci sur une liaison RS232, il faut un adaptateur de niveaux qui permettra de convertir les niveaux de tension en quelque chose de compréhensible par les 2 parties.
Le schéma de principe ci-dessous fonctionne grâce au célèbre MAX232 qui est un convertisseur de niveaux série / TTL, le schéma est facilement reproductible sur une carte perforé :
Le micro-logiciel libre
Le code source du micro-logiciel de l'AVR (firmware) est disponible sur GitHub à cette adresse : https://github.com/hugokernel/203LedMatrix, il à été développé avec la chaine d'outils de compilation libre AVR-GCC et l'environnement de développement AVR Studio.
Les fichiers sources :
- font5x7.h Ce fichier contient la définition des caractères ASCII affichable par l'afficheur
- LedMatrix.c C'est le fichier contenant le code principal
- LedMatrix.h Le fichier entête contenant les différentes configurations possibles
- usart.h Le code source permettant de piloter la liaison série
Il y a 2 modes pour piloter l'écran, un mode fait pour piloter l'écran directement depuis un terminal, très pratique pour tester en direct, ce mode est le mode interactif, et un autre mode fait spécifiquement pour être piloter depuis un script, ce mode ne possède pas de menu et est suffisamment fiable pour être utilisé sur un serveur 24/7.
Le choix du mode se fait à la compilation en éditant la constante INTERACTIVE_MODE du fichier LedMatrix.h.
Mode interactif
Le mode interactif permet de piloter l'écran directement depuis une liaison série dans une console avec des menus.
Voici une vue capture de console une fois la connection avec l'écran établie :
M:[1], S:[9], D:[1], L:[0], C:[1]
Action :
[0...3] Select msg
[t] Edit msg
[c] Clear
[s] Set scroll speed...
[l] Letter spacing...
[d] Reverse direction
[a] Chain mode
[w] Save conf to EEPROM
[b] Read button...
Your choice :
En haut, on distingue la ligne de status « M:[1], S:[9], D:[1], L:[0], C:[1] » permettant de voir :
- M:[x] Numéro du message
- S:[x] Vitesse de défilement
- D:[x] Direction du défilement
- L:[x] Espace entre les lettres
- C:[x] Chainage des messages ou non
Dans le mode interactif, il est possible d'enregistrer 4 messages (0 à 3) dans l'EEPROM, on choisit le message en tapant directement son numéro dans le terminal, il est alors chargé.
Ensuite, vous apercevez les actions avec des noms bien identifiable, je ne rentrerai donc pas plus dans les détails à part pour l'action « Chain mode », elle permet d'afficher à tour de rôle les messages.
Mode non interactif
Ce mode permet à un script automatisé d'envoyer des ordres à l'afficheur, le mode interactif vu plus haut n'est pas adapté pour cela, ce mode non interactif est beaucoup plus fiable, par contre, son approche est beaucoup moins intuitive, normal puisqu'il à été conçu pour dialoguer avec un bout de code et non avec un humain... ;)
explication du protocole
Voici la liste des commandes disponibles dans ce mode :
- m (Message) : Modifier le message
- s (Speed) : Modifier la vitesse du défilement
- d (Direction) : Modifier la direction du défilement
- l (letter Spacing) : Espacement entre les lettres
- e (Eeprom) : Sauvegarder le message dans l'EEPROM
- p (Print) : Afficher le message sur la liaison série
- c (Conf) : Afficher la conf sur la liaison série
- w (Watchdog) : Configuration du chien de garde
- v (Verbose) : Configurer le niveau de verbosité
Des commandes attendent un arguments (m, s, d, l, w, v), d'autres non (e, p, c).
On considère l'hôte comme étant le périphérique envoyant les ordres d'affichage au module et la cible étant l'afficheur.
Envoie d'une commande sans argument
- Envoie du caractère associé à la commande (e, p, c)
- En réponse, ce même caractère est renvoyé
- La commande demandé est executée directement
Envoie d'une commande avec argument
- Envoie du caractère associé à la commande (s, d, l, w, v)
- En réponse, ce même caractère est renvoyé
- Envoie de l'argument (valeur ascii comprise entre 0 inclu et 9 inclu)
- Validation de l'argument en envoyant un retour chariot (CR, Carriage Return, code 013, touche entrée depuis un terminal)
- La valeur est renvoyé en guise d'accusé de réception
Envoie d'une commande m (Message)
- Envoie du caractère m
- La cible va envoyé un accusé de réception (un catactère point « . ») vous indiquant qu'elle est prête à recevoir le message (fin du défilement)
- Une fois le caractère reçu, vous pouvez envoyer le message
- Validez la fin du message en envoyant un retour chariot (CR, Carriage Return, code 013, touche entrée depuis un terminal)
- En guise d'accusé de réception, 3 caractères vous sont alors renvoyés, ils représentent le nombre de caractère que comportait le message (ex: 009 pour un message de neuf caractère)
Note 1: Pour l'envoie de message, à l'étape 2, tant que le caractère d'accusé de réception n'est pas reçu, il faut attendre avant de passer à la suite. Note 2: Lors de l'envoi de message, il est important de respecter un délai minimal entre chaque caractère sinon, des caractères vont être ignorés.