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 :
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.
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'
C'est l'heure du blink
Voici le code du blink que nous allons utiliser :
#include <msp430.h>
void delay(unsigned int delay)
{
unsigned volatile int j, i;
for (j = 0; j < 100; j++)
for (i = 0; i < delay; i++);
}
int main()
{
WDTCTL = WDTPW + WDTHOLD;
P1DIR |= BIT6 | BIT0;
P1OUT = BIT6;
while(1) {
P1OUT ^= (BIT6 | BIT0);
delay(500);
}
return 0;
}
On lance la compilation en appelant make :
microjoe@dellice $ make msp430-elf-gcc -Os -g -Wall -mmcu=msp430g2202 -c -o main.o main.c main.c:1:0: warning: devices.csv not found on any include paths. Please obtain the latest version of the msp430-gcc-support-files archive from: "http://software-dl.ti.com/msp430/msp430_public_sw/mcu/msp430/MSPGCC/latest/index_FDS.html" and place the full path to the "include" directory containing devices.csv on an include path specified with -I Defaulting to the hard-coded device data... #include <msp430.h> linking: blink.elf 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 504 16 22 542 21e blink.elf
On remarque que la taille du programme pour un simple « hello world » est déjà de 542 octets. Il faut voir cette taille comme la taille minimale du programme. Ensuite avec les fonctionnalités que l'on ajoutera cette taille grandira, jusqu'à un maximum de 2048 octets pour notre G2202.
Je n'ai pas pris le temps de regarder comment réparer le warning, bien que des indications claires soient mentionnées pour le résoudre. Si vous voulez rendre service, regardez comment réparer ça et faites un patch pour mspgcc-ti afin que ce fichier CSV soit directement inclus dans le paquet AUR.
On peut ensuite passer à la programmation, qui se fait tout aussi simplement via make flash :
microjoe@dellice $ make flash linking: blink.elf 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 504 16 22 542 21e blink.elf mspdebug rf2500 'erase' 'load blink.elf' 'exit' MSPDebug version 0.25 - debugging tool for MSP430 MCUs Copyright (C) 2009-2017 Daniel Beer <dlbeer@gmail.com> This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Chip info database from MSP430.dll v3.3.1.4 Copyright (C) 2013 TI, Inc. Trying to open interface 1 on 004 Initializing FET... FET protocol version is 30394216 Set Vcc: 3000 mV Configured for Spy-Bi-Wire Device ID: 0x2452 Code start address: 0xe000 Code size : 8192 byte = 8 kb RAM start address: 0x200 RAM end address: 0x2ff RAM size : 256 byte = 0 kb Device: MSP430G2xx2 Number of breakpoints: 2 fet: FET returned NAK warning: device does not support power profiling Chip ID data: ver_id: 5224 ver_sub_id: 0000 revision: 00 fab: a0 self: 0000 config: 00 fuses: 00 Device: MSP430G2xx2 Erasing... Writing 2 bytes at fffe [section: __reset_vector]... Writing 12 bytes at f800 [section: .rodata2]... Writing 502 bytes at f80c [section: .text]... Writing 4 bytes at fa02 [section: .data]... Done, 520 bytes total
On passe de 542 octets prévus à 520 octets progammés par mspdebug.
Et notre carte effectue son blink avec joie et bonne humeur !
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.