Processeur ARM Cortex 1
1 - Présentation de la Famille des processeurs Cortex
L’architecture ARM Cortex est une architecture brevetée, fabriquée par différents fondeurs ( ST, NXP, TI, … ).
Les modèles proposés s’adaptent aux applications en fonction des performances exigées en temps de calcul, consommation, prix, supports pour OS.
Exemple d’Application
2 - Equipement
2.1 - Matériel
- Carte ST Nucleo F411
- Carte mbed application shield pour l’étude des périphériques
2.2 - Logiciel
Plusieurs solutions sont possibles, je propose d’utiliser l’environnement de développement proposé par ST : STM32CubeIDE
3 - Les Registres
Nous retrouvons en vert les registres de mu0-risc :
L’architecture ARM-cortex comporte 13 registres de calcul ( R0-R12 )
4 - Quelques instructions ARM
L’objectif de cette partie n’est pas de passer en revue tous les instruction
de l’architecture ARM Cortex, mais simplement d’en voir quelques unes pour coder
des algorithmes simples.
L’assembleur reste uniquement un moyen pour bien comprendre les mécanismes dans un microprocesseur.
Nous passerons par la suite au langage C.
Toutes les instructions de calcul se font entre registres ( caractéristique d’une architecture RISC).
Il est possible dans une instruction d’indiquer une
valeur immédiate ( en utilisant # )
Cette valeur doit tenir sur 8 bits ( ou pouvoir s’exprimer avec un décalage )
4.1 - Instructions Arithmétiques et Logiques
Reprenons les instructions évoquées dans la partie précédente, avec quelques suppléments ou variantes.
mov r1, #5 // r1 <- 5
mov r1, r2 // r1 <- R2
add r1,r2,#1 // r1 <- r2+1
add r1,r2,r3 // r1 <- r2+r3
sub r1,r2,#1 // r1 <- r2-1
sub r1,r2,r3 // r1 <- r2-r3
mul r1,r2,r3 // r1 <- r2*r3
and r1,r2,r3 // r1 <- r2 and r3
orr r1,r2,r3 // r1 <- r2 or r3
REMARQUE :
Par défaut l’exécution d’une instruction ne modifie pas les bits d’état NZVC.
le suffixe s permet de modifier les bits d’état NZVC à l’issue d’un calcul :
adds r1,r2,#1 // r1 <- r2+1 ; modification(NZVC) ;
4.2 - Instruction de lecture/écriture mémoire de données
Lecture d’une donnée de 32 bits en mémoire
ldr r0,=val // r0 <- adr(val)
ldr r1, [r0] // r1 <- [r0]
Nous avons toujours le problème qu’une adresse fait 32 bits, et une instruction est codée sur 32 bits.
Je ne peux donc pas indiquer dans une même instruction le nom de l’instruction,
un numéro de registre et une adresse mémoire de 32 bits .
La pseudo instruction ldr r0,=val est remplacé à la compilation
par ldr r0,[PC+offset].
Le compilateur prendra soin lors de la compilation de placer l’adresse de
la variable val à une case mémoire située à offset instructions de ldr r0,[PC+offset].
Lecture d’une donnée de 8 bits en mémoire
Comme chaque octet possède une adresse, l’instruction ldrb permet de rapatrier 8 bits dans un registre de 32 bits.
L’extension de format se fait par défaut en considérant des nombres non signés ( ajout de zéros sur les poids forts )
ldr r0,=val // r0 <- adr(val)
ldrb r1, [r0] // r1 <- [r0]
Ecriture d’une donnée de 32 bits en mémoire
ldr r0,=val
str r1, [r0]
Ecriture d’une donnée de 8 bits en mémoire
ldr r0,=val
strb r1, [r0]
Post Incrémentation
Lors du parcours d’un tableau, au lieu d’utiliser l’instruction add pour faire évoluer la valeur du pointeur, il est possible décrire :
ldr r1,[r0],#4 // r1 <- [r0] ; r0 <- r0+4
4.3 - Instructions de comparaison et de Saut ( Branch )
b loop // PC <- loop : Saut inconditionnel, dans tous les cas on va à l’adresse loop
Une séquence If then else ou une boucle conditionnelle ( for / while ) en langage C se traduit par un saut conditionnel en Assembleur.
- Je fais un calcul ; ce calcul modifie les bits d’état NZVC
- En fonction des bits NZVC, je fais le saut ou j’exécute l’instruction suivante
REMARQUE : l’instruction cmp r0, r1 permet de faire la modification des bits NZVC pour une soustraction r0-r1 ( le résultat n’est pas récupéré )
beq loop // PC <- loop si résultat précédent égal à 0 ( on regarde NZVC pour cela )
bne loop // PC <- loop si résultat précédent différent de 0 ( Not Equal )
conditions de saut :
5 - Premier Programme : Somme des éléments d’un Tableau
PROJET SOURCE
WORKSPACE_F411_ASM_STM32CUBE.zip
Pour tester les différents exemples ci-dessous, il faudra modifier le fichier makefile
5.1 - Environnement de Développement ( IDE )
L’ IDE STM32CUBE comporte :
- Un compilateur : arm-none-eabi-gcc.
- Un Debugger : arm-none-eabi-gdb.
STM32CUBE est basé sur le logiciel ECLIPSE
Le fichier Makefile donne les conditions de compilation et d’édition de liens.
5.2 - Système de fichiers d’un projet
└── nucleof411_base
├── config
│ ├── ocd_st_nucleo_f4.cfg
│ └── stm32f411re_flash.lds
├── include
│ ├── board.h
│ ├── cmsis
│ ├── config.h
│ └── stm32f411xe.h
├── main.bin
├── main.elf
├── main.elf.map
├── main.hex
├── Makefile
├── nucleof411_base Debug.launch
├── src
│ ├── main.s
└── startup
├── rcc.c
├── rcc.h
├── startup_stm32f411xe.s
├── stm32f411_periph.c
├── sys_handlers.c
├── sys_handlers.h
├── system_stm32f4xx.c
Reprenons le programme faisant la somme des éléments d’un tableau afin de le tester :
On distingue le segment “.data” et ‘.text" :
- .data : Données initialisées, à placer en ROM (FLASH) pour les valeurs initiales et en RAM ( données modifiables au cours de l’exécution du programme ).
- .text : Le code, à placer en ROM (FLASH)
//======================================================================
// VARIABLES GLOBALES
.data
tab: .word 1,2,3,4,5
resultat: .word 0
//======================================================================
// PROGRAMME
.text
.thumb
.syntax unified
.global main
main:
mov r4,#0 // R4 <- 0
ldr r0,=tab // R2 <- addr tab
mov r1,#5
LOOP: ldr r5,[r0] // val <- tab[R0]
add r4,r4,r5 // acc <- acc+val
add r0,r0,#4 // R0++
subs r1,r1,#1 // compteur --
bne LOOP // PC <- PC - 12
ldr r0,=resultat // R0 <- addr resultat
str r4,[r0] // resultat <- acc
theend: b theend
//======================================================================
5.3 - Compilation et placement mémoire
Lorsque je compile mon programme ( raccourcis CTRL+B, j’exécute mon makefile.
Le makefile fait référence au compilateur arm-none-eabi-gcc pour transformer des fichiers C/C++ ou Assembleur en
fichiers objets binaires .o
L’éditeur de lien arm-none-eabi-ld permet de réaliser le placement mémoire de ces fichiers objet en tenant compte du
fichier de configuration stm32f411re_flash.lds ( ce fichier indique dans quelle zone mémoire il faut placer les différents segments ( code, datas )).
L’exécutable résultant main.elf peut alors être transmis à la cible.
extrait de stm32f411re_flash.lds :
_Min_Heap_Size = 0x200;; /* required amount of heap */
_Min_Stack_Size = 0x400;; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
....
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
KEEP(*(.data)) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
5.4 - Démarrage d’un programme : startup_stm32f411xe.s
A l’issue d’une mise sous tension ou d’un reset, PC reçoit l’instruction d’adresse Reset_Handler
On peut noter la boucle pour copier les données d’initialisation de la flash vers la RAM.
La fonction SystemInit() permet de configurer l’horloge du microcontrôleur.
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
// Copy the data segment initializers from flash to SRAM
ldr r0, =_sidata
ldr r1, =_sdata
ldr r2, =_edata
dloop: cmp r1, r2
beq dloope
ldr r4, [r0], #4
str r4, [r1], #4
b dloop
dloope:
// Zero fill the bss segment
ldr r0, =_sbss
ldr r1, =_ebss
mov r2, #0
bloop: cmp r0, r1
beq bloope
str r2, [r0], #4
b bloop
bloope:
// Call the clock system intitialization function.
bl SystemInit
// Call static constructors
bl __libc_init_array
// Call the application's entry point
bl main
_exit: b _exit
5.5 - Test d’un Programme
Après avoir compilé et donc généré un exécutable .elf ( à condition qu’il n’y ait pas eu d’erreur de compilation ), nous pouvons charger et debugger notre programme.
La configuration de debug se trouve dans Run –> Debug Configurations ; nous y faisons référence à main.elf.
Accès direct :
Pour placer un point d’arrêt, double cliquer dans la marge ( ou appuyer sur SHIFT+CTRL+B )
Pour aller directement au point d’arrêt, appuyer sur F8 (Resume).
Mode pas à pas pour analyser l’exécution de chaque instruction :
- F5 : Step Into : pas à pas total
- F6 : Step Over : pas à pas sans entrer dans les fonctions ( mais en les exécutant quand même ).
Dans l’environnement de Debug, les registres sont visibles dans la fenêtre Registers ( Window -> Show View -> Registers )
Pour observer la mémoire, utiliser la fenere Memory Browser :
6 - Appel d’une fonction
Dans le makefile, effectuer la modification suivante :
ASRC += src/main_calc_2.s ### A MODIFIER ###
Instruction BL ( Branch with Link)
//======================================================================
// VARIABLES GLOBALES
.data
tab: .word 1,2,3,4,5
resultat: .word 0
//======================================================================
// PROGRAMME
.text
.thumb
.syntax unified
.global main, sum_tab
main:
ldr r0,=tab
mov r1,#5
bl sum_tab // Appel fonction sum_tab
ldr r1,=resultat
str r0,[r1] // resultat <- acc
theend: b theend
//-----------------------------------------------------------------------
sum_tab: mov r4,#0 // R3 <- 0
LOOP: ldr r5,[r0] // val <- tab[R0]
add r4,r4,r5 // acc <- acc+val
add r0,r0,#4 // R0++
subs r1,r1,#1 // compteur --
bne LOOP // PC <- PC - 12
mov r0,r4
mov pc,lr
//======================================================================
Lors de l’étude du langage C, nous avons évoqué l’intérêt du découpage d’un programme en fonctions ( rangement et réutilisation ).
Une fonction reste un bout de code situé à une certaine adresse.
Pour y accéder je dois donc effectuer un saut.
L’instruction b pourrait être utilisée, mais après exécution de la fonction, il faut retourner à l’instruction suivant l’appel de la fonction.
Je doit donc mémoriser l’adresse de retour.
L’instruction bl permet donc de :
- Effectuer un saut à une certaine adresse
- Mémoriser l’adresse de retour dans le registre R14 ( LR - Link Register )
Par convention, les paramètres des fonctions sont placés dans R0 et R1 ( puis R2, R3 si nécéssaire ).
Le paramètre de retour de fonction est placé dans R0.
7 - Traitement d’une Chaine de caractères
Dans le makefile, effectuer la modification suivante :
ASRC += src/main_str.s ### A MODIFIER ###
Représentation d’une chaine de caractères dans un processeur : le code ASCII
Comme toute donnée dans un système à processeurs, les caractères sont codés avec des 0 et des 1.
Algorithme : passage de minuscules à majuscules
Le codage du caractère ‘a’ est 0x61
Le codage du caractère ‘A’ est 0x41
Pour toute lettre, il y a une différence de 0x20 entre la minuscule et la majuscule.
Considérons la chaîne de caractères “abcdefgh”, et observons la en mémoire :
C’est un tableau d’octets, dont l’adresse du premier élément est 0x200000b0
Le dernier élément du tableau est le caractère Nul ( 0x00 ).
L’algorithme consiste donc à :
- accéder à chaque caractère en mémoire ( avec l’instruction ldrb )
- soustraire 0x20 à la valeur récupérée
- écrire en mémoire le valeur modifiée ( avec strb )
//======================================================================
// VARIABLES GLOBALES
.data
chaine: .asciz "abcdefgh"
//======================================================================
// PROGRAMME
.text
.thumb
.syntax unified
.global main, change_casse
main:
ldr r0,=chaine
bl change_casse
theend: b theend
//-----------------------------------------------------------------------
change_casse:
ldrb r4,[r0]
cmp r4,#0
beq fin_chaine
sub r4,r4,#0x20
strb r4,[r0],#1
b change_casse
fin_chaine: mov pc,lr
//-----------------------------------------------------------------------
8 - TRAVAUX PRATIQUES
PROJET SOURCE
WORKSPACE_LAB_F411_ASM_STM32CUBE.zip
Traitement d’une chaine de caractères
Dans le makefile, effectuer la modification suivante :
# SRC += src/main.c
ASRC += src/main_remove_space.s
# ASRC += src/int2ascii.s
Q1. Proposer un codage de la fonction remove_space permettant de remplacer tout espace dans une chaine de caractères ( ch_src ) par un undescore (’_’).
La chaîne modifiée sera placée à l’adresse ch_dest.