Dans la partie précédente, nous avons vu que le programme suivant, qui permet d'allumer la LED de la carte:
#include <avr/io.h>
int main(void)
{
// Configure le bit 5 du registre DDRB en sortie
DDRB |= (1 << 5);
PORTB |= (1 << 5);
// Boucle infinie
while (1);
}
Compilons ce programme à l'aide de:
avr-gcc -mmcu=atmega328p -Os -o main.elf main.c
À l'aide de la commande:
$ avr-size main.elf
text data bss dec hex filename
138 0 0 138 8a main.elf
On peut constater que le code binaire du programme produit occupera 138 octets de mémoire flash. La commande:
$ avr-objcopy -O binary main.elf main.bin
Produit d'ailleurs bien un fichier binaire de 138 octets.
On constate tout d'abord que le code commence par __vectors
à l'adresse 0x0
de la mémoire flash:
| 00000000 <__vectors>:
| 0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
| 4: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| 8: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| 10: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| 14: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| 18: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| 1c: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| 20: 0c 94 3e 00 jmp 0x7c ; 0x7c <__bad_interrupt>
| ...
Cette zone est appelée la table des vecteurs d'interruption. Elle contient les adresses des fonctions à appeler lorsqu'une interruption se produit. La toute première correspond au reset du microcontrôleur, dans ce cas, le
microcontrôleur saute sur __ctors_end
l'adresse 0x68
.
À noter que, par défaut, toutes les autres interruptions sont redirigées vers la fonction __bad_interrupt
dont le code consiste en un saut en 0x0
:
| 0000007c <__bad_interrupt>:
| 7c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>
Plus bas, on peut lire le code suivant:
| 00000068 <__ctors_end>:
| 68: 11 24 eor r1, r1
| 6a: 1f be out 0x3f, r1 ; 63
| 6c: cf ef ldi r28, 0xFF ; 255
| 6e: d8 e0 ldi r29, 0x08 ; 8
| 70: de bf out 0x3e, r29 ; 62
| 72: cd bf out 0x3d, r28 ; 61
| 74: 0e 94 40 00 call 0x80 ; 0x80 <main>
| 78: 0c 94 43 00 jmp 0x86 ; 0x86 <_exit>
Ce code:
SREG
(0x3f
) à 0 (cf page 11 de la datasheet),SPH
(0x3e
) à 0x08
et SPL
(0x3d
) à 0xFF
(cf page 14 de la datasheet),
ces registres (Stack Pointer High et Stack Pointer Low) définissent l'adresse de la pile.0x08FF
, soit la fin de la mémoire RAM.main
à l'adresse 0x80
,_exit
à l'adresse 0x86
.Le désassemblage de la fonction main
donne:
| 00000080 <main>:
| 80: 25 9a sbi 0x04, 5 ; 4
| 82: 2d 9a sbi 0x05, 5 ; 5
| 84: ff cf rjmp .-2 ; 0x84 <main+0x4>
Les instructions sbi
permettent de mettre à 1 un bit d'un registre. 0x04
et 0x05
sont les adresses des registres PORTB
et DDRB
(cf page 72 de la datasheet).
Nos opérateurs de masque binaire C ont été traduit de cette façon en assembleur!
Enfin, la routine _exit
qui est appellée à la fin du programme désactive les interruptions et rentre dans une boucle infinie:
| 00000086 <_exit>:
| 86: f8 94 cli
|
| 00000088 <__stop_program>:
| 88: ff cf rjmp .-2 ; 0x88 <__stop_program>
Dans la partie suivant,
nous aborderons plus en détails le fonctionnement des broches, mais aussi des bus de communication