Vous venez de terminer un programme pour votre carte Arduino et vous vous apprêtez à le téléverser dans la mémoire programme du microcontrôleur de la carte. Dès que vous aurez cliqué sur le bouton, une suite d’opérations va avoir lieu pour transformer le texte de votre programme en code binaire adapté au microcontrôleur et l’envoyer dans la puce. Même si toutes ces opérations sont transparentes pour vous, il peut être intéressant de comprendre le processus.
Du sketch à l’exécutable
.
Par :
DIFFICULTÉ :★★★
Comme vous le savez, un programme écrit pour une carte Arduino avec l’IDE s’appelle un sketch, ce qui signifie en anglais croquis ou esquisse. Cette tradition est héritée de Processing qui a été conçu pour être utilisé par des artistes [1]. Et quand on y réfléchit, ce terme n’est pas si mal choisi car votre programme, qui utilise des fonctions écrites en langage C, n’est pas à proprement parler un programme en langage C puisqu’il ne contient pas de fonction main qui est pourtant obligatoire dans ce langage. Ce n’est donc qu’une esquisse, un sketch dans notre jargon, qui va avoir besoin d’être transformée pour être utilisable par le microcontrôleur de la carte. C’est ce que l’on appelle le processus de construction (build process en anglais).
Ce processus de construction requiert quatre étapes que nous allons décrire, au moins dans les grandes lignes : prétraitement, compilation, assemblage et liaison. Chaque étape est réalisée par un outil de la chaîne avr-gcc dont une description est donnée dans cette page. À la fin de ces quatre étapes, le code exécutable est présenté sous la forme d’un fichier hexadécimal au standard Intel, fichier qui est utilisé pour programmer le microcontrôleur via le bootloader ou via un programmateur ISP ; ceci constitue deux étapes supplémentaires, conversion et téléversement. L’IDE se charge bien évidemment de réaliser les différentes étapes en appelant les bons outils, ce qui masque la complexité de l’ensemble du processus, mais comme nous le verrons plus loin, il est possible d’observer ce qu’il fait.
Préparation du code source
Le code source est le programme que vous avez écrit avec l’IDE : il peut être écrit avec plusieurs onglets. Le processus commence par ajouter une directive #include <Arduino.h>
si elle n’est pas présente (ce fichier header contient des définitions nécessaires au noyau standard Arduino). Le préprocesseur insère également les fichiers d’en-tête (fichiers .h) à la place de chaque directive #if
, #else
, #endif
. Le résultat est un fichier ne comportant que des instructions et rien d’autre.
Les fichiers qui n’ont pas d’extension de nom sont fusionnés avec le croquis principal alors que les fichiers dont l’extension est .c ou .cpp seront compilés séparément. L’atelier (IDE) cherche ensuite toutes les définitions de fonctions trouvées dans le code source pour créer des prototypes (sauf pour les deux fonctions obligatoires dans le sketch setup et loop). Ces prototypes sont ajoutés au début du fichier source juste après les directives de préprocesseur (#define
et #include
). Des directives #lines
sont également ajoutées pour générer des messages d’erreur. La dernière opération consiste à ajouter le fichier standard qui définit la fonction principale main (nécessaire dans tout programme en langage C) ; comme le montre la figure 1, cette fonction appelle la fonction setup de l’esquisse et contient une boucle infinie qui appelle la fonction loop du sketch. À partir de maintenant, l’esquisse ressemble à un programme C pouvant être compilé.
Le sketch est aussi scanné de façon récursive pour déterminer toutes les dépendances qui doivent être attachées à l’esquisse (par exemple, des bibliothèques utilisées). Ceci est fait selon un algorithme très précis prenant en compte l’architecture, ou des règles de priorité de noms de répertoires ou de localisation de bibliothèques.
Compilation et assemblage
La compilation consiste à transformer les différentes composantes d’un programme écrit en langage C en un code binaire propre au microcontrôleur qui équipe la carte (c’est pourquoi il faut renseigner le type de carte utilisée via le menu Outils de l’IDE).
Le programme qui effectue la compilation (le compilateur) s’appelle avr-gcc et est une version spécialement conçue pour les puces AVR du compilateur GCC (GNU Compiler Collection, aussi appelé GNU C Compiler). Si le programme est écrit en langage C++, c’est avr-g++ qui est utilisé ; toutes les possibilités du langage C++ ne sont pas disponibles pour des raisons de contraintes d’espace mémoire (mais toutes les possibilités du C sont disponibles).
GCC va traduire les différentes formes possibles du langage C (C normalisé ANSI, C++, Objective C ou Objective C++) et produire un fichier intermédiaire en langage assembleur qui sera utilisé par l’outil assembleur avr-as (le compilateur de langage assembleur pour la famille AVR). Les fichiers binaires obtenus sont appelés fichiers objets (fichiers .o) mais ils ne peuvent pas être exécutés directement puisque les références externes de l’un vers l’autre ne sont pas encore résolues.
Les fichiers .o (object) sont mis dans un répertoire temporaire de construction ; un fichier spécial .d (dependency) fournit une liste de tous les fichiers inclus par la source.
Si vous souhaitez plus de renseignements sur GCC, vous pouvez consulter cette page internet.
Liaison
La liaison consiste à mettre en correspondance les appels de fonctions dans les fichiers objets et les fonctions qui se trouvent dans les modules des bibliothèques. En effet, le compilateur traite les fichiers de code source un par un et ne peut donc pas savoir où se trouvent les cibles des éléments non encore compilés. L’éditeur de liens (encore appelé lieur) trouve ces cibles et raccorde les différents modules pour obtenir un fichier binaire exécutable unique.
Conversion
Le fichier binaire produit par le lieur doit être converti dans un format spécial, le format Intel Hex (pour hexadécimal), format qui sera accepté par le bootloader du microcontrôleur. Cette conversion est confiée au programme avr-objcopy.
La figure 2 montre un exemple de fichier au format Intel Hex. Chaque ligne comportant le code binaire exécutable est constituée :
- D’un symbole de début de lignes : (deux points)
- Un compteur d’octets (quasiment tout le temps égal à 10 en hexadécimal, soit 16 octets)
- L’adresse sur 16 bits (4 chiffres hexadécimaux) à laquelle le code doit être écrit dans la mémoire flash (modifiable éventuellement par le bootloader)
- Un code d’enregistrement sur deux chiffres
- Le code exécutable sous forme de caractères hexa en ASCII (deux caractères par octets, soit 16 octets comme annoncé)
- Sur deux chiffres, une somme de contrôle en fin de ligne
La partie surlignée de la figure 2 correspond au code binaire exécutable ; il y a 30 octets au total (une première ligne de 16 octets (10 en hexadécimal) et une seconde ligne de 14 octets (0E en hexadécimal). Vous trouverez plus de détails sur la norme Intel HEX avec cette page.
Téléversement
La dernière étape de ce processus consiste à envoyer le code binaire exécutable vers le microcontrôleur de la carte par une liaison USB, opération qui est gérée par le bootloader avec l’aide du programme avrdude (AVR Download UploaDEr). Comme son nom l’indique, cet outil est capable de transférer dans un sens ou dans l’autre des données concernant les espaces de stockage des microcontrôleurs AVR ; il permet par exemple d’implanter des données en EEPROM. Comme nous le verrons dans un prochain article, avrdude est utilisable en ligne de commande du mode console. Pour le processus qui nous intéresse ici, avrdude utilise le fichier au format .hex généré à l’étape précédente.
Conclusion
Voilà comment on passe du sketch au code exécutable par le microcontrôleur. Vous trouverez plus d’explications sur le site d’Arduino à cette page.
Vous pouvez d’ailleurs observer comment l’IDE effectue tout ce processus en utilisant les différents outils à sa disposition si vous cochez les cases « Afficher résultats détaillés pendant : compilation et téléversement » comme le montre la figure 3 ; les lignes de commande des différentes opérations s’affichent dans l’espace au bas de l’IDE, là où vous lisez le nombre d’octets de votre programme (cet espace peut s’agrandir et être scrollé avec l’ascenseur situé à droite de l’espace).
Connaître le processus de construction utilisé par l’IDE d’Arduino vous permettra sans aucun doute d’aller plus loin dans la programmation des cartes Arduino ou des microcontrôleurs de la série AVR. Un prochain article vous montrera le côté pratique de ce que nous venons de décrire. Et tout cela vous aidera si un jour vous décidez de passer à la série ARM avec d’autres outils ou bien à d’autres microcontrôleurs. On ne sait pas de quoi l’avenir sera fait mais on peut s’attendre à des progrès dans les performances de ces petites bêtes. Alors soyons prêts…
[1] Un des co-inventeurs d’Arduino, Massimo Banzi lui-même, le dit dans son livre "Démarrez avec Arduino" (Dunod) : "Le logiciel de développement Arduino ... utilise le formalisme Processing déjà utilisé par de nombreux concepteurs et artistes"