Programmation d'un MSP430G2202 sans utiliser Energia

Quand notre MSP430 n'est pas supporté, il faut revenir à la méthode à l'ancienne

Bonjour,

Aujourd'hui nous allons voir comment créer un petit environnement de développement afin de programmer des MSP430 sans utiliser Energia, le clone d'Arduino pour les microcontrôleurs de chez TI.

Si la programmation avec Energia vous intéresse, j'ai déjà rédigé un précédent article sur le sujet (avec l'utilisation d'un Makefile pour se passer de l'interface graphique tout sauf utilsable).

Contenu de l'article

Présentation du G2202

Lorsque j'ai acheté ma carte de développement Launchpad MSP-EXP430G2 pour passer à autre chose qu'Arduino à l'époque, j'ai également acheté une poignée de microcontrôleurs MSP430 au format DIP afin de les programmer avec la carte.

Parmis ceux-ci, des MSP430G2202 qui sont presque deux fois moins chers que les MSP430G2553. Les principales différences entre les deux est rapidement résumée dans le tableau suivant :

Comparaison entre les deux microcontrôleurs
Référence MSP430G2553 MSP430G2202
Flash 16 Ko 2 Ko
RAM 500 o 256 o
GPIO 24 16
ADC 8 0
UART 1 0
I2C 1 1
SPI 1 1
Prix unitaire 2,16 € 1,20 €

On remarque donc que nous avons moité moins de RAM à notre disposition, ainsi que 8 fois moins de mémoire flash. La RAM n'est pas trop un problème à mon avis. Pour avoir fait des programmes sur des petits microcontrôleurs ATTiny13A, c'est plutôt le manque d'espace flash (de 1 Ko sur ce dernier) qui devient rapidement le facteur limitant.

D'ailleurs, après vous avoir présenté comment faire un blink classique, on regardera ensemble comment optimiser un petit exemple afin d'utiliser le moins de mémoire flash possible sur le MSP430.

MSP430G2202 monté sur la carte MSP-EXP430G2

La bête est solidement mise en place… reste à la programmer

Pourquoi pas Énergia ?

Je rappelle que Energia est le port d'Arduino pour TI ; il se peut que j'intervertisse les termes sans réelles conséquences.

Comme vous avez pu le voir dans la grille, on a un composant moins puissant dans les mains, et avec beaucoup moins de place en mémoire flash. On pourrait se dire à cet instant que l'utilisation de la surcouche Arduino va utiliser beaucoup de mémoire flash et nous laisser vraiment peu d'espace pour écrire notre propre programme.

Cependant, cela n'est pas du tout le problème.

Compilation d'Energia cassée

En effet, il est actuellement impossible de faire fonctionner Energia pour ce composant. Déjà parce-que Energia supporte très peu de composants par défaut, mais pas seulement. Si on utilise la ruse afin de faire en sorte que Energia puisse programmer ce nouveau composant pourtant semblable au G2553, on arrive à une erreur de compilation d'Arduino pour cette plateforme :

[...]/wiring_analog.c: In function 'analogFrequency':
[...]/wiring_analog.c:109:3: error: 'analog_div' undeclared (first use in this function)
[...]/wiring_analog.c:109:3: note: each undeclared identifier is reported only once for each function it appears in
[...]/wiring_analog.c:120:2: error: 'analog_period' undeclared (first use in this function)
[...]/wiring_analog.c: In function 'analogResolution':
[...]/wiring_analog.c:126:2: error: 'analog_res' undeclared (first use in this function)
[...]/wiring_analog.c: In function 'analogWrite':
[...]/wiring_analog.c:142:28: error: 'analog_res' undeclared (first use in this function)
[...]/wiring_analog.c:172:43: error: 'analog_period' undeclared (first use in this function)
[...]/wiring_analog.c:175:60: error: 'analog_div' undeclared (first use in this function)

Si on creuse un peu en allant directement regarder dans le code source d'Energia au niveau de l'erreur, on remarque effectivement que ces variables ne sont pas définies.

En creusant un peu, il se trouve que l'implémentation d'Energia ne prend pas en compte le fait que le microcontrôleur soit dépouvrvu d'ADC. C'est pour cela que l'on a ces erreurs, qui sont au niveau de l'implémentation de la bien connue analog_read() par exemple.

J'ai donc demandé ce qu'en pensaient les gars de chez TI, mais n'ayant pas de réponse facile à ma question ils m'ont gentillement invité à aller la reposer ailleurs sur des forums dédiés à Energia. N'ayant pas envie de prendre le temps de créer encore un nouveau compte sur un nouveau forum, j'ai donc pris le taureau par les cornes et ai décidé d'utiliser la méthode à l'ancienne sans passer par Energia.

Cette solution est convenable si vous souhaitez faire un petit projet qui ne nécessite pas l'utilisation de bibliothèque Arduino, ou si vous n'avez pas peur de les recoder vous-même.

Je veux quand même utiliser Energia

Et je vous comprend, recoder les bibliothèques Arduino ça n'est pas du temps forcément bien investi.

Vous pouvez vous baser sur cette investigation pour patcher Energia afin que la compilation ne plante pas quand aucun ADC n'est présent sur la carte, par exemple en forçant l'implémentation de analogRead() à retourner une constante quand l'ADC est absent. Néanmoins, n'espérez pas trop faire remonter votre patch à la communauté en passant par le dépôt officiel sur Github. Il semble en effet que de nombreuses autres propositions d'améliorations soient restées sans réponses depuis des années.

La vraie solution est de partir sur le MSP430G2231, supporté par Energia car possédant un ADC et seuleument dix centimes plus cher qu'un 2202, pour un tarif affiché à 1,31 €. Mais bon, ça ne serait pas drôle et ça serait déjà la fin de cet article (et je n'ai également pas envie de jeter mes 10 MSP430G2202 mais d'en faire des projets rigolos).

Installation du nécessaire de développement

Étant donné que ArchLinux est ma distribution GNU/Linux de prédilection pour un usage quotidien, je ne détaillerai la procédure d'installation que pour celle-ci. Et cela tombe bien pour tout le monde car, grâce aux gentils créateurs de paquets sur AUR, on devrait s'en sortir assez facilement.

Voici les trois paquets AUR que j'ai dû installer afin d'avoir un environnement permettant compilation + programmation :

Nom Rôle
mspdebug Débuggeur et programmeur nous permettant de flasher notre programme
msp430-elf-binutils Utilitaires GNU pour la target msp430-elf
mspgcc-ti Toolchain GNU (as, gcc, g++, ld, gdb) pour MSP430

On trouve également sur AUR d'autres paquets permettant d'obtenir la toolchain GNU via chacun des composants (par exemple msp430-elf-gcc). Cependant ces recettes vont aller sur le site de TI, télécharger les sources du GCC en question et tout compiler. C'est très long et il faut une machine solide pour compiler un GCC.

C'est pourquoi une gentille personne met à disposition mspgcc-ti qui télécharge la version déjà compilée par TI de la toolchain GNU pour Linux, et l'installe directement sous forme de paquet sur le système.

Le Makefile qui va bien

Ce Makefile a directement été copié d'un blog dédié à l'utilisation de la carte de développement Lauchpad sous Linux, puis modifié par mes soins pour qu'il puisse fonctionner avec les paquets que nous venons d'installer sur Archlinux.

PROJ=blink
PREFIX=msp430-elf
CC=$(PREFIX)-gcc
MCU=msp430g2202
CFLAGS=-Os -g -Wall -mmcu=$(MCU)
LDFLAGS=-g -mmcu=$(MCU)

OBJS=main.o

all:$(OBJS)
        @echo linking: $(PROJ).elf
        $(CC) $(LDFLAGS) -o $(PROJ).elf $(OBJS)
        @echo  "--== Size of firmware ==--"
        $(PREFIX)-size $(PROJ).elf
%.o:%.c %.h
        @echo compiling file: $<
        $(CC) $(CFLAGS) -c $<

clean:
        @echo cleaning $<
        rm -fr $(PROJ).elf $(OBJS)

flash: all
        mspdebug rf2500 'erase' 'load $(PROJ).elf' 'exit'

Exercice : encodeur visuel de nombres en morse

Je vous propose maintenant un petit exercice d'optimisation d'encodeur morse utilisant une LED afin de nous communiquer des nombres. Le fait que je sois en train d'apprendre activement le code morse pour faire de la CW n'y est pas sans doute pour rien.

Implémentation LUT naïve

Voici le code de base que j'ai pondu naïvement. Il est très loin d'être optimisé, c'est voulu pour cette première étape. Il utilise un tableau pour en déduire les valeurs à afficher (ou LUT pour look-up table).

#include <msp430.h>
#include <stdbool.h>

#define SYMBOL_SPACE 100
#define LETTER_SPACE (3 * SYMBOL_SPACE)
#define DOT_DELAY SYMBOL_SPACE
#define DASH_DELAY (3 * SYMBOL_SPACE)

void delay(unsigned int delay)
{
        unsigned volatile int j, i;
        for (j = 0; j < 100; j++)
                for (i = 0; i < delay; i++);
}

char *morse_digits[] = {
        "-----",
        ".----",
        "..---",
        "...--",
        "....-",
        ".....",
        "-....",
        "--...",
        "---..",
        "----.",
};

void show_digit(int digit)
{
        bool dash;
        for (int i = 0; i < 5; i++) {
                dash = morse_digits[digit][i] == '-';

                P1OUT |= BIT0;
                if (dash) {
                        delay(DASH_DELAY);
                } else {
                        delay(DOT_DELAY);
                }
                P1OUT &= ~BIT0;

                delay(SYMBOL_SPACE);
        }
        delay(LETTER_SPACE);
}

int main()
{
        WDTCTL = WDTPW + WDTHOLD;
        P1DIR |= BIT6 | BIT0;

        P1OUT = 0;

        while(1) {
                show_digit(7);
                //delay(100);
                show_digit(3);
                delay(1000);
                P1OUT |= BIT6;
                delay(100);
                P1OUT &= ~BIT6;
                delay(1000);
        }

        return 0;
}

Voici la taille de celui-ci :

--== Size of firmware ==--
msp430-elf-size blink.elf
   text    data     bss     dec     hex filename
    686      36      22     744     2e8 blink.elf

744 octets contre 542 pour le blink. On remarque que celui-ci fonctionne et nous envoie bien « 73 », code radio-amateur signalant « je vous envoie mes amitiés ».

Implémentation algorithmique

On remarque que le schéma de construction des chiffres en code morse est plutôt simple et pourrait être généré par un petit algorithme plutôt que de stocker les valeurs en dûr dans la mémoire flash.

Regardons ce que cela donne.

void show_digit(int digit)
{
        for (int i = 0; i < 5; i++) {
                P1OUT |= BIT0;
                if (digit == 0) {
                        delay(DOT_DELAY);
                } else if (digit <= 5) {
                        if (i < digit) {
                                delay(DOT_DELAY);
                        } else {
                                delay(DASH_DELAY);
                        }
                } else {
                        if (i < digit - 5) {
                                delay(DASH_DELAY);
                        } else {
                                delay(DOT_DELAY);
                        }
                }
                P1OUT &= ~BIT0;
                delay(SYMBOL_SPACE);
        }
        delay(LETTER_SPACE);
}

Avons-nous gagné de l'espace ?

msp430-elf-gcc -g -mmcu=msp430g2202 -o blink.elf main.o
--== Size of firmware ==--
msp430-elf-size blink.elf
   text    data     bss     dec     hex filename
    652      16      22     690     2b2 blink.elf

On est passé à 690, contre 744 avec le tableau statique. L'algorithme nous permet donc d'économiser de la place mais aura un déroulement plus long. Vu que le microcontrôleur est très rapide et que notre génération doit être suffisamment lente pour être compréhensible par un humain, c'est tout à fait acceptable.

Mais peut-on encore faire mieux ?

Implémentation LUT binaire

Les tableaux statiques sont un très bon moyen d'économiser beaucoup de code et de place quand on s'y prend bien. Nous allons retenter le tableau mais cette fois ci en utilisant la ruse.

char morse_digits[] = {
        0b00000000,
        0b00010000,
        0b00011000,
        0b00011100,
        0b00011110,
        0b00011111,
        0b00001111,
        0b00000111,
        0b00000011,
        0b00000001,
};

void show_digit(int digit)
{
        bool dash;
        for (int i = 0; i < 5; i++) {
                dash = (morse_digits[digit] & (1 << (4 - i))) == 0;

                P1OUT |= BIT0;
                if (dash) {
                        delay(DASH_DELAY);
                } else {
                        delay(DOT_DELAY);
                }
                P1OUT &= ~BIT0;

                delay(SYMBOL_SPACE);
        }
        delay(LETTER_SPACE);
}

Mhmm, utiliser du binaire pour gagner de la place… Malin me direz vous. Sauf que à mon grand désarroi cela n'arrange pas les choses.

--== Size of firmware ==--
msp430-elf-size blink.elf
   text    data     bss     dec     hex filename
    752      26      22     800     320 blink.elf

Avec ce code on atteind la barre des 800 octets, soit plus qu'en stockant bêtement des caractères dans un tableau. Je ne m'y attendait pas du tout. En fait je pense que l'introduction d'instructions pour faire les opérations binaires prennent plus de place que ce que prenait le tableau contenant les chiffres.

Implémentation LUT binaire bis

Dans un dernier espoir je vais réordonner les bits pour retirer la soustraction de l'algorithme.

char morse_digits[] = {
        0b00000000,
        0b00000001,
        0b00000011,
        0b00000111,
        0b00001111,
        0b00011111,
        0b00011110,
        0b00011100,
        0b00011000,
        0b00010000,
};

void show_digit(int digit)
{
        bool dash;
        for (int i = 0; i < 5; i++) {
                dash = ((morse_digits[digit] >> i) & 0x01) == 0;

                P1OUT |= BIT0;
                if (dash) {
                        delay(DASH_DELAY);
                } else {
                        delay(DOT_DELAY);
                }
                P1OUT &= ~BIT0;

                delay(SYMBOL_SPACE);
        }
        delay(LETTER_SPACE);
}

Voici ce que l'on obtient niveau taille du code :

--== Size of firmware ==--
msp430-elf-size blink.elf
   text    data     bss     dec     hex filename
    754      26      22     802     322 blink.elf

802 octets ! Comme quoi la solution précédente était mieux, bien que l'on pouvait croire qu'elle génèrerait plus d'instructions.

Tableau récapitulatif

Voici un petit récapitulatif de nos expérimentations.

Implémentation Taille Différence
Algorithmique 690 +0
LUT naïve 744 +54
LUT binaire 800 +110
LUT binaire bis 802 +112

On remarque que la meilleure solution reste l'implémentation algorithmique si on veut optimiser la place occupée par le programme.

On pourrait se poser la question de quelle technique utiliser pour stocker les lettres de l'alphabet cette fois ci. Peut-être que la LUT binaire sera plus rentable avec une table plus longue, comme celle avec les caractères de l'alphabet. Attention cependant car la longueur est variable et peut aller de 1 à 4 symboles. Heureusement, il nous reste les 4 autres bits pour stocker le nombre de symboles.

Enfin l'implémentation des caractères spéciaux comme la ponctuation aura également ses défis. De quoi vous amuser à implémenter tout ça si vous avez du temps cet été.

Et après ?

Ce petit article va me servir d'aide mémoire quand j'aurai à reprogrammer ces petits microcontrôleurs 16 bits bien sympathiques. Par contre TI ne fourni plus de samples pour les particuliers et les independent designers, dommage (et débile à mon avis, mais bon). Il me reste à essayer de passer par le boulot pour la demande de samples quand j'aurai épuisé mes stocks.

J'aurai également pu aborder la programmation avec Rust, mais on a déjà un article bien garni. Ce que j'ai à en dire c'est que c'est sympa mais pas encore au point. En effet LLVM sur lequel se base Rust n'existe pas pour MSP430, il faut donc entrelacer LLVM, Rust et msp430-gcc pour arriver à une bafouille qui fonctionne.

Un jour peut-être.

Je voulais vous mettre des vidéos mais mon Mediagoblin ne veut rien entendre, et mon plugin Pelican pour GMG ne supporte que les photos, donc la grosse flemme. Tout ça pour mettre une LED qui clignote et vous donner une occasion de ne pas lire le contenu.

Une question ou remarque ? N'hésitez pas à me contacter en envoyant un mail à microjoe, suivi d'un arobase, puis encore microjoe et enfin un « point org ».