DigitalSpirit / Wiki / Ledmatrixhacking

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 :

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 :

  1. Connecteur propriétaire USB
  2. Le coeur du montage, l'AVR
  3. L'interrupteur
  4. Le connecteur pour la programmation
  5. 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 :

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 :

  1. Les lignes de données USB D+ et D- sont court-circuités
  2. 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)
  3. 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.

(Image de Wikipédia)

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 :

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 :

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 :

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 :

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

  1. Envoie du caractère associé à la commande (e, p, c)
  2. En réponse, ce même caractère est renvoyé
  3. La commande demandé est executée directement

Envoie d'une commande avec argument

  1. Envoie du caractère associé à la commande (s, d, l, w, v)
  2. En réponse, ce même caractère est renvoyé
  3. Envoie de l'argument (valeur ascii comprise entre 0 inclu et 9 inclu)
  4. Validation de l'argument en envoyant un retour chariot (CR, Carriage Return, code 013, touche entrée depuis un terminal)
  5. La valeur est renvoyé en guise d'accusé de réception

Envoie d'une commande m (Message)

  1. Envoie du caractère m
  2. 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)
  3. Une fois le caractère reçu, vous pouvez envoyer le message
  4. Validez la fin du message en envoyant un retour chariot (CR, Carriage Return, code 013, touche entrée depuis un terminal)
  5. 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.