
Bon après-midi Aujourd'hui, je veux vous dire comment écrire un programme minimal qui s'exécute sur un ARM Cortex-M3 et imprime «Bonjour tout le monde!». Nous essaierons de déterminer par étapes le minimum nécessaire dont nous avons besoin pour cela. Nous allons exécuter l'émulateur QEMU. Par conséquent, n'importe qui peut se reproduire, même s'il n'a pas un morceau de fer à portée de main.
Alors allons-y!
L'émulateur QEMU prend en charge le noyau Cortex-M3 et émule la plate-forme Stellaris LM3S811 de Texas Instruments sur la base de celui-ci. Nous fonctionnerons sur cette plateforme. Nous avons besoin de la chaîne d'outils arm-none-eabi- (vous pouvez la télécharger
ici ). Ensuite, nous devons écrire la logique principale de notre programme, le code de démarrage, qui transférera le contrôle au programme, et le script de l'éditeur de liens.
Sur un habr déjà de très
bons articles sur la façon de clignoter une diode sur un morceau de fer à partir de zéro. Par conséquent, ici, je ne vais pas me plonger dans quoi et comment cela fonctionne, mais je ne donnerai que le minimum de connaissances nécessaires pour commencer.
Notre bonjour dans le fichier test.c:
static volatile unsigned int * const UART_DR = (unsigned int *)0x4000c000; static void uart_print(const char *s) { while (*s != '\0') { *UART_DR = *s; s++; } } void c_entry(void) { uart_print("Hello, World!\n"); while (1) ; }
Cette adresse 0x4000c000 est extraite de la documentation, il y a le registre DR zéro zéro.
Nous ne serons pas impliqués dans la configuration de l'UART (cela devra être fait sur le matériel), mais nous essaierons de mettre immédiatement les symboles directement dedans.
Maintenant, nous devons en quelque sorte transférer le contrôle à notre fonction with_entry dans le fichier test.c. Pour ce faire, créez le code suivant (fichier startup.S), puis placez-le dans l'image ELF finale au début.
.type start, %function .word stack_top .word start .global start start: ldr r1, =c_entry bx r1
Le premier mot à 0x0 doit être un pointeur vers le haut de la pile (SP). À 0x4, il y a un PC qui, comme SP, est chargé dans les registres. Notez que start est déclaré précisément comme une fonction et non comme une étiquette car le code Cortex-M est exécuté en mode Thumb (il s'agit d'un ensemble simplifié de commandes ARM) et il est nécessaire que les adresses des fonctions dans le vecteur d'interruption soient sous la forme (adresse | 0x1) - c'est-à-dire le dernier bit de l'adresse doit être 1.
Ensuite, la fonction de démarrage charge simplement l'adresse de notre fonction c_entry () à partir du fichier test.c et y passe le contrôle par «bx r1».
Il ne reste plus qu'à lier avec succès notre programme. Pour ce faire, vous devez paramétrer la carte mémoire de notre microcontrôleur. Dans la documentation, vous pouvez trouver les adresses et les tailles de mémoire flash (ROM) et de RAM (RAM). Voici le script de l'éditeur de liens test.ld:
SECTIONS { . = 0x0; .text : { startup.o(.text) test.o(.text) } . = 0x20000000; .data : { *(.data) } .bss : { *(.bss) } . = ALIGN(8); . = . + 0x1000; stack_top = .; }
Il est important de faire attention aux adresses. "." dans le script de l'éditeur de liens indique la position actuelle. Nous mettons la section .text au début de la ROM (adresse 0x0), en suivant l'ordre - startup.o (.text) va en premier. Ensuite, allez dans la RAM (. = 0x20000000;) et mettez-y les données (données globales initialisées) et bss (données globales non initialisées). Ci-dessous, nous voyons ALIGN (8) - ARM nécessite un nivellement SP (Stack Pointer) de 8. Puisque la pile se développe, l'allocation d'espace pour la pile n'est qu'un ajout. =. + 0x1000 ”. Nous connaissons bien notre programme, donc une pile de 4 Ko est suffisante avec une grande marge.
C'est tout, il reste à tout mettre ensemble. J'apporte build.sh:
Tout ici doit être plus ou moins compris, à l'exception du drapeau-indépendant. Dans ce cas, il est facultatif de l'ajouter (vous pouvez le vérifier), mais comme nous préparons une image baremetal à partir de zéro, il est préférable de le dire au compilateur afin qu'il ne prête pas attention aux fonctions telles que main ().
En conséquence, nous avons obtenu le fichier ELF test.elf. Exécutez-le sur QEMU:
$ qemu-system-arm -M lm3s811evb -kernel test.elf -nographic Hello, World!
Ça marche.
Bien sûr, il s'agit d'une étude de cas conçue pour comprendre ce qui se passe. Si vous avez besoin de fonctionnalités plus significatives, vous devez utiliser des choses prêtes à l'emploi. Nous avons ajouté le support de cette plate-forme dans
Embox . Ce modèle est appelé platform / stellaris / lm3s811evb. Par conséquent, si quelqu'un veut essayer d'exécuter une chose légèrement plus sérieuse (console, minuterie, interruptions), vous pouvez collecter et essayer. Dans ce cas, je le répète, vous n'avez pas besoin d'avoir une carte matérielle.
Et pour ceux qui ont encore peu d'émulateurs, ou qui veulent nous poser des questions et jouer avec les glandes, nous attendrons ce samedi et dimanche au festival IT
techtrain.ru à Saint-Pétersbourg. Nous aurons différents morceaux de fer sur le stand, et dans la zone de démonstration nous essaierons de dire comment les programmer.