⚙️ Langage d’Assemblage ARM CORTEX - Semestre 6

Année Universitaire : 2022-2023
Semestre : 6
Crédits : 2 ECTS
Spécialité : Systèmes Embarqués et Programmation Bas Niveau


PART A - Présentation Générale du Cours

Vue d'ensemble

Ce cours enseigne la programmation en langage assembleur pour les microcontrôleurs ARM Cortex-M (architecture 32 bits). Il couvre l’architecture du processeur, le jeu d’instructions Thumb-2, et la programmation bas niveau pour systèmes embarqués. Les travaux pratiques utilisent des STM32F103 (Cortex-M3) avec l’environnement Keil µVision.

Objectifs pédagogiques :

Position dans le cursus

Ce cours complète et approfondit :

Il prépare aux applications :


PART B - Expérience Personnelle et Contexte d’Apprentissage

Organisation et ressources

Le module était structuré en 4 séquences progressives :

1. Séquence 1 : Architecture ARM Cortex-M

2. Séquence 2 : Jeu d’instructions de base

3. Séquence 3 : Sous-programmes et pile

4. Séquence 4 : Accès périphériques

Registres ARM Cortex-M

Figure : Organisation des registres ARM Cortex-M - R0-R15, SP, LR, PC et PSR

Environnement de développement :

Déroulement des TDs et TPs

TD2 : Conversion et affichage

Objectif : lire 4 chiffres au clavier, les convertir en entier, et afficher le résultat.

Fonctions à implémenter :

; Lecture de 4 caractères via UART
BL GetKey_Echo      ; Lit un caractère et l'affiche
STRB R0,[R7]        ; Stocke dans la mémoire RAM

; Conversion ASCII → nombre
LDRB R3,[R7],#1     ; Charge caractère
SUB R3,#'0'         ; Convertit '0'-'9' en 0-9
MOV R4,#10
MUL R3,R3,R4        ; Décalage décimal

; Affichage d'une chaîne
Affichaine PROC
    LDRB R0,[R3],#1  ; Charge caractère
    CBZ R0,Fin       ; Si 0, fin de chaîne
    BL SendChar      ; Envoie via UART
    B Car_suivant    ; Boucle
Fin_Chaine
    POP {R0,PC}      ; Retour
    ENDP

Concepts clés :

TD3 : Manipulation de tableaux

Travail sur tableaux d’entiers : tri, recherche, calculs statistiques.

Exemple : recherche du maximum

; R0 = adresse du tableau
; R1 = taille du tableau
; Retour : R2 = valeur maximale

Recherche_Max PROC
    PUSH {R3,R4,LR}
    LDR R2,[R0],#4   ; Premier élément = max initial
    SUBS R1,#1       ; Compteur -1
    
Boucle
    LDR R3,[R0],#4   ; Élément suivant
    CMP R3,R2
    BLE Suivant      ; Si <= max, passe au suivant
    MOV R2,R3        ; Nouveau max
Suivant
    SUBS R1,#1       ; Décrément compteur
    BNE Boucle       ; Tant que != 0
    
    POP {R3,R4,PC}
    ENDP

TD4 : Fonctions mathématiques

Implémentation d’opérations complexes (division, racine carrée par approximations successives).

TP : Projet “Roue Magique”

Projet fil rouge en plusieurs étapes pour créer un jeu de lumières avec LEDs.

Étape 1 : Contrôle d’une LED

; Allumer une LED sur PC10
Allume_LED PROC
    LDR R0,=0x40010C10  ; Adresse GPIOC_BSRR (Bit Set Reset Register)
    MOV R1,#(0x01 << 10) ; Bit 10 pour PC10
    STR R1,[R0]          ; Écriture → LED allumée
    BX LR                ; Retour
    ENDP

; Éteindre la LED
Eteint_LED PROC
    LDR R2,=0x40010C14  ; Adresse GPIOC_BRR (Bit Reset Register)
    MOV R3,#(0x01 << 10)
    STR R3,[R2]
    BX LR
    ENDP

Étape 2 : Lecture d’un bouton

; Lire l'état d'un bouton sur PB8
Capteur PROC
    LDR R4,=0x40010808   ; Adresse GPIOB_IDR (Input Data Register)
    LDRH R5,[R4]         ; Lecture 16 bits
    AND R5,R5,#0x0100    ; Masque bit 8
    CMP R5,#0
    BEQ Bouton_Presse    ; Si 0, bouton appuyé
    BX LR
    ENDP

Étape 3 : Séquence de LEDs

Faire clignoter plusieurs LEDs en séquence (chenillard).

Étape 4 : Interruptions et timers

Utiliser un timer pour créer des délais précis sans attente active.

Difficultés rencontrées

Gestion de la pile : Oublier de faire POP après PUSH provoque des erreurs de retour de fonction. Le LR (Link Register) doit être sauvegardé si la fonction appelle d’autres fonctions.

Adressage mémoire : Confusion entre adresse et valeur. LDR R0,=0x2000 charge l’adresse, LDR R0,[R1] charge la valeur à l’adresse contenue dans R1.

Flags de condition : Les instructions avec ‘S’ (ADDS, SUBS) modifient les flags NZCV. Nécessaire avant CMP pour les branchements conditionnels.

Registres de périphériques : Adresses spécifiques à mémoriser ou utiliser des fichiers include. Documentation du STM32 indispensable.


PART C - Aspects Techniques Détaillés

1. Architecture ARM Cortex-M3

Banc de registres :

Le Cortex-M3 dispose de 16 registres 32 bits :

Registre Nom Usage
R0-R12 Registres généraux Calculs, données temporaires
R13 (SP) Stack Pointer Pointeur de pile
R14 (LR) Link Register Adresse de retour de fonction
R15 (PC) Program Counter Adresse de l’instruction en cours

Registres spéciaux :

Organisation mémoire :

Région Adresses Usage
Code 0x00000000 - 0x1FFFFFFF Flash, instructions
SRAM 0x20000000 - 0x3FFFFFFF RAM, données
Périphériques 0x40000000 - 0x5FFFFFFF Registres memory-mapped
Système 0xE0000000 - 0xFFFFFFFF NVIC, SysTick, debug

Pipeline 3 étages :

  1. Fetch : lecture instruction
  2. Decode : décodage
  3. Execute : exécution

Conséquence : le PC pointe toujours 2 instructions en avance (PC+4 en Thumb-2).

2. Jeu d'Instructions Thumb-2

Instructions arithmétiques :

Instruction Syntaxe Description
ADD ADD Rd, Rn, Rm Rd = Rn + Rm
SUB SUB Rd, Rn, Rm Rd = Rn - Rm
MUL MUL Rd, Rn, Rm Rd = Rn × Rm
SDIV SDIV Rd, Rn, Rm Rd = Rn / Rm (signé)
UDIV UDIV Rd, Rn, Rm Rd = Rn / Rm (non signé)

Variantes avec flags : ADDS, SUBS (modifient NZCV).

Instructions logiques :

Instruction Description
AND Rd, Rn, Rm ET logique
ORR Rd, Rn, Rm OU logique
EOR Rd, Rn, Rm OU exclusif (XOR)
BIC Rd, Rn, Rm Bit Clear (Rd = Rn AND NOT Rm)
MVN Rd, Rm NOT (inversion)

Décalages :

Instruction Description
LSL Rd, Rn, #n Logical Shift Left (décalage gauche)
LSR Rd, Rn, #n Logical Shift Right (décalage droite, complète avec 0)
ASR Rd, Rn, #n Arithmetic Shift Right (conserve le signe)
ROR Rd, Rn, #n Rotate Right (rotation)

Déplacements de données :

MOV R0,#42          ; R0 = 42 (immédiat)
MOV R1,R2           ; R1 = R2 (copie)
MOVW R0,#0x1234     ; Charge 16 bits bas
MOVT R0,#0x5678     ; Charge 16 bits haut → R0 = 0x56781234

3. Accès Mémoire

Instructions de chargement :

Instruction Taille Description
LDR Rd,[Rn] 32 bits Charge mot
LDRH Rd,[Rn] 16 bits Charge demi-mot (halfword)
LDRB Rd,[Rn] 8 bits Charge octet (byte)
LDRSB Rd,[Rn] 8 bits signé Charge octet avec extension de signe
LDRSH Rd,[Rn] 16 bits signé Charge demi-mot avec extension de signe

Instructions de stockage :

Instruction Taille Description
STR Rd,[Rn] 32 bits Stocke mot
STRH Rd,[Rn] 16 bits Stocke demi-mot
STRB Rd,[Rn] 8 bits Stocke octet

Modes d’adressage :

; Direct
LDR R0,[R1]         ; R0 = mem[R1]

; Offset
LDR R0,[R1,#8]      ; R0 = mem[R1+8]

; Pré-incrémenté
LDR R0,[R1,#8]!     ; R1 = R1+8, puis R0 = mem[R1]

; Post-incrémenté
LDR R0,[R1],#8      ; R0 = mem[R1], puis R1 = R1+8

; Indexé par registre
LDR R0,[R1,R2]      ; R0 = mem[R1+R2]

; PC-relatif (litteral pool)
LDR R0,=0x20000000  ; Charge adresse via pool constant

4. Contrôle de Flux

Branchements inconditionnels :

B etiquette         ; Branchement simple
BL fonction         ; Branchement avec Link (LR = PC+4)
BX Rm               ; Branchement vers adresse dans Rm
BLX Rm              ; Branchement avec Link vers Rm

Branchements conditionnels :

Basés sur les flags NZCV (Negative, Zero, Carry, oVerflow).

Suffixe Condition Flags
EQ Égal Z=1
NE Non égal Z=0
GT Supérieur (signé) Z=0 ET N=V
LT Inférieur (signé) N≠V
GE Supérieur ou égal (signé) N=V
LE Inférieur ou égal (signé) Z=1 OU N≠V
HI Supérieur (non signé) C=1 ET Z=0
LS Inférieur ou égal (non signé) C=0 OU Z=1

Comparaisons :

CMP R0,R1           ; Compare R0 et R1 (calcule R0-R1, met à jour flags)
CMN R0,R1           ; Compare negative (calcule R0+R1)
TST R0,R1           ; Test bits (AND, met à jour flags sans stocker)
TEQ R0,R1           ; Test equal (XOR, met à jour flags)

Exemple de boucle :

    MOV R0,#10      ; Compteur
Boucle
    ; ... code ...
    SUBS R0,#1      ; Décrément et mise à jour flags
    BNE Boucle      ; Tant que R0 != 0

5. Sous-programmes et Pile

Convention d’appel AAPCS (ARM Architecture Procedure Call Standard) :

Registre Usage Sauvegarde
R0-R3 Paramètres et retour Caller-saved
R4-R11 Variables locales Callee-saved
R12 Temporaire Caller-saved
R13 (SP) Pile -
R14 (LR) Adresse retour -

Appel de fonction :

; Appelant (caller)
    MOV R0,#5       ; Premier paramètre
    MOV R1,#10      ; Deuxième paramètre
    BL MaFonction   ; Appel (LR = adresse retour)
    ; R0 contient le résultat

; Fonction (callee)
MaFonction PROC
    PUSH {R4,LR}    ; Sauvegarde registres utilisés
    
    ; Calculs utilisant R0, R1, R4
    ADD R4,R0,R1
    MOV R0,R4       ; Résultat dans R0
    
    POP {R4,PC}     ; Restaure et retourne
    ENDP

Gestion de la pile :

La pile croît vers les adresses décroissantes (full descending).

PUSH {R0-R3,LR}     ; Sauvegarde multiple
; SP = SP - 20 (5 registres × 4 octets)

POP {R0-R3,PC}      ; Restauration et retour
; SP = SP + 20

Équivalent :

6. Accès aux Périphériques

Memory-mapped I/O :

Les périphériques sont accessibles via des adresses mémoire spécifiques.

Exemple : GPIO (General Purpose Input/Output)

Registres du GPIOC (Port C) pour STM32F103 :

Registre Adresse Usage
CRL 0x40011000 Configuration broches 0-7
CRH 0x40011004 Configuration broches 8-15
IDR 0x40011008 Input Data Register (lecture)
ODR 0x4001100C Output Data Register (écriture)
BSRR 0x40011010 Bit Set/Reset Register
BRR 0x40011014 Bit Reset Register

Configuration d’une broche en sortie :

; Configurer PC10 en sortie push-pull, 50 MHz
    LDR R0,=0x40011004  ; GPIOC_CRH
    LDR R1,[R0]
    BIC R1,#(0xF << 8)  ; Clear bits pour PC10
    ORR R1,#(0x3 << 8)  ; MODE=11 (50MHz), CNF=00 (push-pull)
    STR R1,[R0]

Manipulation de bits :

; Mise à 1 d'un bit (set)
LDR R0,=0x40011010  ; GPIOC_BSRR
MOV R1,#(1 << 10)   ; Bit 10
STR R1,[R0]         ; PC10 = 1

; Mise à 0 d'un bit (reset)
LDR R0,=0x40011014  ; GPIOC_BRR
MOV R1,#(1 << 10)
STR R1,[R0]         ; PC10 = 0

; Lecture d'un bit
LDR R0,=0x40011008  ; GPIOC_IDR
LDR R1,[R0]
TST R1,#(1 << 8)    ; Test bit 8
BEQ Bit_A_Zero      ; Branch if zero

7. Directives Assembleur

Organisation du code :

    AREA MonCode, CODE, READONLY, ALIGN=2
    ; Sections de code
    
    AREA MesDonnees, DATA, READWRITE
    ; Sections de données

Définition de données :

; Constantes
Valeur  EQU 42          ; Équivalent #define

; Données initialisées
Tableau DCD 1,2,3,4,5   ; Define Constant Data (32 bits)
Chaine  DCB "Hello",0   ; Define Constant Byte (8 bits)
Mot     DCW 0x1234      ; Define Constant Word (16 bits)

; Données non initialisées
Buffer  SPACE 100       ; Réserve 100 octets

Déclarations :

    EXPORT main         ; Rend le symbole visible à l'extérieur
    IMPORT fonction     ; Importe un symbole externe
    
    PROC                ; Début de procédure
    ENDP                ; Fin de procédure
    
    END                 ; Fin du fichier source

8. Optimisations

Taille de code :

Instructions Thumb-2 sont 16 ou 32 bits. Préférer les instructions 16 bits quand possible.

; 16 bits
ADDS R0,R1,R2       ; Si R0-R7 et résultat modifie flags

; 32 bits
ADD.W R0,R1,R2      ; Force 32 bits

Boucles efficaces :

; Inefficace (compare à 0 à chaque itération)
    MOV R0,#0
Boucle
    ; ...
    ADD R0,#1
    CMP R0,#100
    BLT Boucle

; Efficace (décrémente et teste zéro implicitement)
    MOV R0,#100
Boucle
    ; ...
    SUBS R0,#1
    BNE Boucle

Éviter les branchements :

Utiliser les instructions conditionnelles quand possible.

; Avec branchement
    CMP R0,R1
    BLE Suivant
    MOV R2,R0
Suivant
    ; ...

; Sans branchement (IT = If-Then)
    CMP R0,R1
    IT GT
    MOVGT R2,R0     ; Exécuté si GT

PART D - Analyse Réflexive et Perspectives

Compétences acquises

Compréhension bas niveau : La programmation en assembleur force à comprendre exactement ce qui se passe au niveau processeur : chaque instruction, chaque accès mémoire, chaque modification de flag. Cette compréhension aide à débugger et optimiser le code C.

Contrôle total du matériel : Accès direct aux registres de périphériques, manipulation bit à bit, timing précis. Essentiel pour développer des drivers ou du code temps réel critique.

Optimisation : Conscience du coût de chaque instruction. Savoir où optimiser (boucles critiques) et quand laisser le compilateur faire le travail.

Points clés à retenir

1. La pile est votre amie (et votre ennemie) : PUSH/POP doivent toujours être équilibrés. Oublier un POP ou faire POP du mauvais registre provoque des bugs difficiles à tracer.

2. LR doit être sauvegardé : Si une fonction appelle d’autres fonctions, LR doit être sauvegardé (PUSH {LR}) et restauré (POP {PC}) pour retourner correctement.

3. Flags de condition : Les instructions avec ‘S’ (ADDS, SUBS) modifient les flags. Nécessaire pour les branchements conditionnels. Attention à ne pas écraser les flags entre CMP et Bxx.

4. Adressage mémoire : Bien distinguer adresse et valeur. LDR R0,=etiquette charge l’adresse, LDR R0,[R1] charge la valeur à l’adresse contenue dans R1.

5. Documentation indispensable : Avoir toujours sous la main :

Applications pratiques

Systèmes embarqués critiques : Bootloaders, routines d’initialisation, handlers d’interruption critiques. Code assembleur pour timing précis ou optimisation extrême.

Drivers de périphériques : Accès direct aux registres matériels pour performances maximales. Utile quand les HAL (Hardware Abstraction Layer) sont trop lourds.

Reverse engineering : Comprendre l’assembleur permet d’analyser du code compilé, débugger des problèmes obscurs, ou étudier des malwares.

Inline assembly en C : Intégrer quelques instructions assembleur dans du code C pour optimisations locales.

// Exemple inline assembly GCC
__asm volatile (
    "MOV R0, #42\n"
    "ADD R1, R0, R0\n"
    : "=r" (resultat)
    : "r" (entree)
    : "r0", "r1"
);

Assembleur vs C

Quand utiliser l’assembleur :

Quand préférer le C :

Les compilateurs modernes optimisent très bien. L’assembleur manuel n’est souvent nécessaire que pour quelques pourcents du code.

Retour d'expérience sur le projet TP

Le projet “Roue Magique” a permis d’appliquer tous les concepts :

Progression pédagogique : Chaque étape ajoute de la complexité. On construit progressivement un système complet. Sentiment d’accomplissement en voyant les LEDs s’allumer selon notre code.

Difficultés : Débugger en assembleur est plus difficile qu’en C. Pas de printf facile. Utilisation intensive du debugger et de l’observation des registres.

Limites et ouvertures

Limites du cours :

Ouvertures vers :

Évolution technologique

Tendances actuelles :

Outils modernes :

Conclusion

La programmation en assembleur ARM est un passage obligé pour comprendre en profondeur le fonctionnement des microcontrôleurs. Même si 95% du code moderne est écrit en C/C++, connaître l’assembleur permet :

L’architecture ARM Cortex-M est omniprésente dans les systèmes embarqués modernes (IoT, wearables, drones, automobile). La maîtriser est un atout majeur pour tout ingénieur en systèmes embarqués.

Recommandations :

Liens avec les autres cours :


📚 Documents de Cours

📖 Séquence 1 - Introduction ARM

Introduction à l'architecture ARM Cortex-M, registres, jeu d'instructions de base et organisation mémoire.

📥 Télécharger

📖 Séquence 2 - Instructions ARM

Jeu d'instructions ARM complète : arithmétique, logique, branchements, et modes d'adressage.

📥 Télécharger

📖 Séquence 3 - Fonctions et Pile

Convention d'appel AAPCS, gestion de la pile, prologue/épilogue de fonctions et passage de paramètres.

📥 Télécharger

📖 Référence Jeu d'Instructions

Tableau synthétique complet du jeu d'instructions ARM Cortex-M avec syntaxe et exemples.

📥 Télécharger


Cours enseigné en 2022-2023 à l’INSA Toulouse, Département Génie Électrique et Informatique.