Explorer les Livres électroniques
Catégories
Explorer les Livres audio
Catégories
Explorer les Magazines
Catégories
Explorer les Documents
Catégories
Microordinateur
Programmation assembleur
Interface C / Assembleur
Avant-propos
Ce manuscrit est distribué aux étudiants du module « Informatique 3 » de la section Electricité et système de
communication de la Haute école spécialisée bernoise. Il est complété par des exercices, qui sont distribués en
supplément durant le cours, et un projet, qui est réalisé à la fin du semestre.
La première partie du manuscrit traite la structure d’un microordinateur. Cette partie comprend l’organisation de
la CPU et sa connexion avec les composants externes. La seconde partie du manuscrit introduit la
programmation en assembleur. Cette introduction s’effectue avec des exemples, qui se basent sur un
microcontrôleur RISC à 32 bits : le PXA270 de Intel. La dernière partie du manuscrit traite l’interface C –
assembleur, en se référant aux cours de C (modules informatiques 1 et 2).
Tous les exercices et le projet sont réalisés avec le kit de développement CARME, qui est introduit au deuxième
semestre.
Un grand remerciement à G. KRUGER et R. WEBER pour leur aides apportées pour la définition de la version
en allemand du manuscrit ; ainsi qu’à M. MÜHLEMANN et E. STUCKI pour la mise en place de
l’environnement de développement.
SISD SIMD
Single Instruction, Single Data Single Instruction, Multiple Data
Ordinateur séquentiel Ordinateur vectoriel en pipeline
MISD MIMD
Multiple Instruction, Single Data Multiple Instruction, Multiple Data
Tableau systolique Multiprocesseur et multi ordinateurs
SISD:
Les ordinateurs SISD travaillent de façon séquentielle. Ces
derniers traitent un seul jeu de donnée en fonction d’une seule load A
instruction : load B
t
Single instruction veut dire, qu’une instruction est exécutée par C=A+B
cycle d’horloge. store C
Single Data veut dire, qu’un jeu de donnée est traité par cycle
d’horloge. CPU
Les ordinateurs SISD sont les ordinateurs les plus répandus,
comme par exemple les ordinateurs personnels (PC) ou les
stations de travail.
Toutefois, avec l’introduction des processeurs multi corps, les
systèmes MIMD seront bientôt plus nombreux.
SIMD:
Les ordinateurs SIMD permettent d’exécuter la même instruction
sur plusieurs jeux de données. Ces derniers sont utilisés par load A1 load A2
exemple pour le traitement des images (JPEG, MPEG). load B1 load B2
t
Single Instruction veut dire, que tous les CPU exécutent la même C1 = A1 + B1 C2 = A2 + B2
instruction à un cycle d’horloge donné. store C1 store C2
Multiple Data veut dire, que chaque CPU traite un jeu de donnée
différent. CPU 1 CPU 2
De nombreux microprocesseur, comme par exemple les PowerPC
ou les x86, possèdent des extensions SIMD avec des instructions
supplémentaires. La CPU peut ainsi traiter simultanément
plusieurs jeux de données, en fonction d’une seule instruction.
MISD:
Un seul jeu de donnée est traité simultanément par plusieurs
CPU, avec des instructions différentes. load A1 load A1
Multiple Instruction veut dire, que chaque CPU exécute une load B1 load B1
t
instruction différentes. C1 = A1 + B1 C2 = A1 * B1
Single Data veut dire, que chaque CPU traite le même jeu de store C1 store C2
donnée.
Les ordinateurs MISD ne sont presque plus utilisés aujourd’hui. CPU 1 CPU 2
MIMD:
Aujourd’hui presque tous les ordinateurs multiprocesseurs se
basent sur l’architecture MIMD. load A1 load D
Multiple Instruction veut dire, que chaque CPU exécute une load B1 E = func1(D)
t
instruction différente par cycle d’horloge. Des programmes C1 = A1 + B1 F=D/2
entiers ou des partitions de ces derniers (process, Thread, task) store C1 func2()
sont souvent répartis entre les différents CPU.
Multiple Data veut dire, que chaque CPU traite un jeu de données CPU 1 CPU 2
différent.
Mémoire
programme
Mémoire des
CPU données
Périphérie
La mémoire programme, comme son nom l’indique, contient les instructions du programme. Cette dernière est
en générale non volatile, c’est à dire que son contenu n’est pas perdu lorsque l’alimentation est coupée. Quant à
la mémoire des données, elle contient les variables du programme et est en générale volatile. La CPU lit les
instructions dans la mémoire programme et traite (lecture et écriture) les variables, qui sont stockées dans la
mémoire des données ou les composants périphériques, en fonction de ces dernières.
Le goulot de Von Neumann désigne la limitation du transfert des informations entre la CPU et la mémoire
(programme et données). Ce goulot résulte du système de bus unique.
Mémoire des
données
Bus de données
Mémoire du
programme Bus des instructions CPU
Périphérie
L’architecture de Harvard est souvent utilisée dans les DSP (Digitale Signal Processor). L’avantage de cette
architecture réside dans la lecture simultanée des instructions et des données du programme. Cette architecture
est donc plus rapide que celle de Von Neumann, mais elle est également plus complexe, ce qui influence
forcément le prix.
Dans les chapitres suivant nous nous limiterons à l’architecture de Von Neumann.
1.3 La CPU
La CPU (Central Processing Unit) est le cœur de l’ordinateur. Il contrôle l’exécution du programme et traite les
données. La Figure 6 illustre la structure générale de la CPU. Cette dernière varie en fonction du type et du
fabriquant de l’ordinateur.
Interface du bus
Register
Program-Counter
Opérande
Bus interne
Instruction
Unit
ALU
Résultat
L’unité de contrôle est responsable de l’exécution du programme. Il est composé des éléments suivants :
Instruction Unit (dispositif de commande) : Il interprète et exécute les instructions du programme.
Program-Counter (PC): Il contient l’adresse de la prochaine instruction du programme à exécuter.
L’unité de calcul est responsable du traitement des données. Il contient les composants suivants :
ALU (Arithmetic Logical Unit), qui exécute les opérations arithmétiques et logiques. L’ALU n’exécute
que des opérations avec des nombres entiers. Pour des instructions à virgule flottante ou des instructions
mathématiques plus complexes on emplois souvent un FPU (Floating Point Unit). Les opérations
typiques sont les suivantes :
Les opérations de transfert (chargement et stockage)
Les opérations logiques (AND, OR, EXOR et NON)
Les opérations arithmétiques (addition, soustraction, multiplication)
Comparaison et décision (compare et réalise le saut de programme si la condition est réalisée)
Les opérations de décalage (shift left, shift right)
Les registres de données, qui sont destinés aux stockages des opérandes et du résultat des opérations
(accumulateur). Un registre est une case mémoire rapide, qui se trouve à l’intérieure du processeur. Ce
dernier peut être accédé directement, c’est à dire sans système de bus, par la CPU.
Le bus interne relie l’unité de contrôle, celle de calcul et l’interface du bus. L’unité de contrôle gère les
opérations, qui doivent être exécutées dans l’unité de calcul. Cette dernière retourne des informations concernant
son état à l’unité de contrôle. L’interface du bus gère la communication avec des composants externes (mémoire
et périphérie).
Bus de contrôle
Bus d’adresse
CPU
Mémoire Mémoire des Périphérie
programme données
Bus de données
Le bus d’adresse permet de transmettre les adresses (pour la sélection des cases mémoires) Avec qui?
aux composants externes (mémoire et périphérie), qui sont connectés à la CPU. Ces adresses
sont déterminées par l’unité de contrôle de la CPU. Souvent des décodeurs d’adresse
supplémentaire doivent être utilisés, afin de pouvoir sélectionner un des composants externes
(voir chapitre 1.4.3).
Le bus de données permet de transmettre les instructions et les données du programme. Ces quoi?
dernières sont
a) lues par la CPU à partir des composants de stockage externes (RAM ou ROM) ou
périphériques.
b) générées par la CPU et écrites dans les composants de stockage externes ou
périphériques.
Le bus de contrôle permet de transmettre des informations supplémentaires pour la gestion de comment?
la communication (read/write, reset, interrupts, requests et acknowledge, handshake etc.).
Exemple :
Le fonctionnent du système de bus peut être illustré plus en détail, à l’aide d’un transfert de données depuis la
périphérie, en passant par la CPU, à la mémoire des données. Exemple : lecture de la température à l’aide d’une
sonde x (périphérie) et stockage de cette dernière dans la variable "temp", qui se situe dans le RAM. En C cella
serait définie de la manière suivante :
1) La CPU dépose l’adresse de l’instruction à lire sur le bus d’adresse. Cette adresse est stockée dans le
compteur de programme (Program counter).
2) La CPU fixe le sens du transfert « lecture », à l’aide du bus de contrôle.
3) La mémoire programme fournit l’instruction stockée dans la case mémoire, qui est sélectionnée par le
bus d’adresse, sur le bus de donnée.
4) La CPU lit cette instruction.
5) La CPU fournit l’adresse du composant périphérique, à partir de laquelle les données doivent être lues,
sur le bus d’adresse.
6) La CPU fixe le sens du transfert « lecture », à l’aide du bus de contrôle.
7) La périphérie fournit l’information, contenue dans la case mémoire sélectionnée, sur le bus de données.
8) La CPU lit les données.
9) La CPU sélectionne la case de destination « temp » dans la mémoire de donnée, à l’aide du bus
d’adresse.
10) La CPU fournit les données sur le bus de données.
11) La CPU fixe le sens du transfert « écriture », à l’aide du bus de contrôle.
12) La mémoire des données saisit les données sur le bus de données, et les stocke dans la case mémoire
sélectionnée par le bus d’adresse.
Bus de RD RD WR
contrôle
La tension du bus est stable et valable, elle est soit high ou low
Les contrôleurs 8 bits (8 bit définit ici la largeur du bus des données) possèdent généralement un bus d’adresse
de largeur 16 bits. Ils sont ainsi en mesure d’adresser une zone de mémoire de 64 kilos bytes (0xFFFF). Quant
aux microcontrôleurs 32 bits, ils possèdent un bus d’adresse de largeur 32 bits et sont ainsi en mesure d’adresser
une zone de mémoire de 232 bytes (4 Giga bytes).
La taille de la mémoire dépend du type du contrôleur. Les contrôleur 8 bits possède en général quelques kilo
bytes de mémoire. Cela est suffisant, car ces derniers sont destinés aux applications relativement simples, qui ne
nécessitent pas de systèmes d’exploitation. Les contrôleurs 32 bits, qui sont destinés aux systèmes embarqués,
nécessitent en générale plusieurs Méga bytes de mémoire.
Les décodages d’adresse simples sont réalisés avec de la logique discrète (AND, OR ou 1 of X decoder). Les
composants nécessaires pour réaliser ce genre de décodeur ne sont pas très chers. Toutefois, la logique discrète
nécessite plus de place sur la platine et elle n’est pas flexible. Par conséquent, les erreurs de conception ou les
éventuelles adaptations du plan de mémoire nécessitent souvent une nouvelle platine.
A cause des problèmes de la logique discrète décrits ci-dessus, les décodeurs d’adresse sont souvent réalisés à
l’aide de la logique programmable (GAL, PAL, CPLD etc.). Le décodeur d’adresse correspondant au plan de
mémoire de la Figure 9 peut être réalisé de la manière suivante :
CS0 Flash
CS1 RAM 1
Adresse Logique
program. CS2 RAM 2
CS3 Périphérie
Les équations logiques permettent de programmer ces décodeurs d’adresse. Par exemple les signaux CS pour le
plan de mémoire de la Figure 9 seraient programmés de la manière suivante :
CS1 = /A15
CS2 = A15 * /A14 * /A13
CS3 = A15 * /A14 * A13
CS4 = A15 * A14 * A13
La Figure suivante montre un schéma bloque d’un circuit contenant un décodeur d’adresse ainsi que des
composants de stockages externes et périphériques. Ce circuit contient une Flash de 32 kilos bits, deux RAM de
8 kilos bits et un convertisseur A/D à 1 Byte:
Bus de données
CS4
CS3 32k * 8 8k * 8 8k * 8 Périphérie
Décodeur RAM 1 RAM 2 (convertisseur
CS2 Flash
d’adresse A/D de 1 byte)
CS1
Bus d’adresse
Figure 11: Schéma bloque du circuit avec le décodeur d’adresse, les composants de stockage (Flash et ROM) et
périphérique
Remarque :
Ce schéma bloque ne contient pas les bits de contrôle /RD et /WR.
Le convertisseur A/D est sélectionné avec le chip select CS4. Du fait qu’il ne possède qu’une seule adresse
physique, il sera reproduit dans toute la zone de mémoire réservée à la périphérie. C'est-à-dire que la CPU peut
l’adresser entre 0xE000 et 0xFFFF.
1.5 La mémoire
1.5.1 Les technologies
Fondamentalement il existe les mémoires non volatiles, qui sont utilisée uniquement en lecture, et les mémoires
volatiles, qui peuvent être utilisées en lecture ou en écriture.
Mémoire volatile
Ces mémoires perdent leur contenu dès que leur alimentation est coupée. Ces mémoires sont en générale
qualifiées de RAM (Random Access Memory). Random veut dire que les cases mémoires peuvent être accédées
de façon aléatoire. Il existe les catégories suivantes :
DRAM (Dynamic RAM) : Ces mémoires stockent l’information à l’aide de condensateur et doivent, par
conséquent, être rafraîchies périodiquement.
SRAM (Static RAM) : Ces mémoires possèdent une structure plus complexe que les mémoires DRAM
mais, par contre, elles ne doivent pas être rafraîchies périodiquement. Les SRAM sont plus rapide,
nécessitent moins de courant et sont plus cher que les DRAM.
SDRAM (Synchronous Dynamic RAM) : Ces mémoires sont une version cadencée des DRAM. La
fréquence d’horloge est prédéfinie par le bus du système. Chez les DDR-SDRAM (Double Data Rate
SDRAM), les accès mémoires sont possibles aux flans montant et aux flans descendants de l’horloge.
Aujourd’hui il est également possible de travailler avec des technologies RAM non volatile (FeRAM, MRAM).
Toutefois, ces dernières possèdent une faible capacité de stockage et sont relativement chères.
Mémoire
Adresse Byte
La plus petite adresse 0 0
1 7 0 1
MSB LSB
~ ~
Le nombre de bit d’adresses, qui sont utilisés pour adresser la mémoire, dépend de la taille de la mémoire. Avec
m bits d’adresse, il est possible d’adresser 2m cases mémoires. Par exemple avec 16 bits d’adresses, il est
possible d’adresser un do maire de 64 k (ce qui est souvent utilisé avec de microcontrôleur à 8 bits).
Row-Decoder
A0 .. Memory-Matrix
An
Column-Output
D0 ..
Dn Column-Decoder
An+1
.. Az
OE Control
CS
Les composants ROM possèdent typiquement un bus de données de largeur 8 ou 16 bits. La largeur du bus
d’adresse dépend de la capacité de stockage. Les signaux de contrôle sont composés d’un CS (Chip Select) pour
la sélection du composant, d’un OE (Output Enable) pour activer des amplificateurs de sortie du bus de données.
La Figure 13 ne contient que les signaux de contrôle, qui permettent la lecture de la ROM. La programmation
nécessite des signaux de contrôle supplémentaires.
A0 .. Memory-Matrix
An
Column-In/Output
D0 ..
Dn Column-Decoder
An+1
.. Az
OE Control
CS
R/W
La structure des composants SRAM est identique à celle des composants ROM. Toutefois, le bus de données est
bidirectionnel, afin que la CPU puisse lire et écrire. Un signal de contrôle supplémentaire (Read/Write) est par
conséquent nécessaire, afin de pouvoir définir le sens de la communication. Certain fabriquant utilise même deux
signaux de contrôle distincts (Read et Write) pour réaliser cette tâche.
pouvoir signaliser laquelle de ces adresses est fournie sur le bus d’adresse. Ces signaux sont le RAS (Row
Address Strobe) et le (Column Address Strobe) CAS.
Dout valid
t access RAM
1.5.4 La documentation
Les caractéristiques les plus importantes des composants de stockage sont décrites dans la documentation, qui est
fournie par le fabriquant. Ces dernières sont typiquement:
La taille de la mémoire (kilo bit ou kilo byte)
Assignation des pins
Organisation de la mémoire (bit, byte ou mot)
Les caractéristiques DC (tension, consommation de courant ...)
Les caractéristiques AC (temps d’accès ...)
Table de vérité (logique de contrôle)
Cache
Mémoire de travail
Les registres :
Les registres sont des cases mémoire très rapide, destinées au stockage des opérandes et du résultat de l’ALU. La
CPU peut accéder directement aux registres, c’est à dire sans passer par le système de bus (voir également
chapitre 1.3).
La mémoire cache :
La mémoire cache est une mémoire intermédiaire, qui se situe entre les registres et la mémoire de travail. Cette
mémoire rapide est destinée au stockage des instructions (Instruction-Cache) et des données (Data-Cache) du
programme, qui doivent être utilisées souvent.
Les CPU actuelles sont en mesure de travailler à des fréquences d’horloge supérieures à celles de leurs systèmes
de bus. En d’autres termes, le temps d’accès à la mémoire de travail est supérieur aux cycles d’horloge interne du
processeur, et représente souvent le talent d’Achille des systèmes à microprocesseur. Par conséquent, lorsque la
CPU lit les instructions ou les données à partir de la mémoire de travail, elle les dépose également dans la
mémoire cache. Si ces données doivent être réutilisées, elles seront chargées automatiquement à partie de la
mémoire cache au lieu de la mémoire de travail. Ce procédé est transparent plus l’utilisateur, c'est-à-dire que le
contrôle la mémoire cache est effectué par le hardware.
On parle d’un « Cache Miss », lorsque des données ne sont pas disponibles dans la mémoire cache. Ces données
seront alors lues à partir de la mémoire de travail et stockées dans un « Cache Line », dont la taille est
typiquement de quelques bytes. Lorsque la mémoire cache est pleine, les « lignes de cache » existantes doivent
être sur écrites.
On parle d’un « Cache Hit », lorsque des données à charger sont disponibles dans la mémoire cache. L’accès à
ces données est plus rapide que si elles devaient être lues à partir de la mémoire de travail.
Les mémoires cache possèdent typiquement une taille de 100 kilos bytes. Toutefois, seul les microprocesseurs
puissants possèdent une telle mémoire.
Les mémoires caches peuvent être échelonnées. Le premier échelon est alors une mémoire cache interne, plus
petite mais très rapide (L1). Le second échelon est une mémoire RAM externe, plus grande mais moins rapide
(L2).
La mémoire de travail :
La mémoire de travail est destinée au stockage des instructions (Flash) et des données (SRAM et SDRAM) du
La mémoire de masse :
Les mémoires de masse, comme par exemple les disques dures ou les CD, ne sont pas utilisées dans les systèmes
embarqués. Par contre, ces dernières sont souvent employées dans les PC industriels. Toutefois, il ne faut pas
oublier les désavantages de la mécanique dans les environnements industriels (la température, les vibrations,
l’humidité, la poussière etc.). Par conséquent, on préfère utiliser ici les cartes CF, les cartes SD ou les mémoires
sticks en tant que mémoire de masse.
1.7 La MMU
Die MMU (Memory-Management Unit) est une unité hardware destinée à la gestion de la mémoire. Les
avantages du MMU apparaissent essentiellement avec les systèmes d’exploitation. Avec les processeurs 32 bits,
la MMU est souvent intégrée sur le même chip que celui de la CPU.
Le programme et la CPU travaillent avec des adresses virtuelles. Les accès aux composants de
mémoire s’effectuent avec des adresses physiques.
Processeur
CPU
CPU fournit une
adresse virtuelle Mémoire Mémoire de Peripherie
de travail masse
MMU
MMU fournit une
adresse physique pour
accéder à la mémoire
Bus
La tâche du MMU est de transformer l’adresse virtuelle en une adresse physique ou de réallocation (relocation).
Pour obtenir l’adresse physique, la MMU additionne l’adresse virtuelle à la valeur qui est contenue dans les
registres de réallocation (relocation register). Simultanément il contrôle si l’accès au segment est autorisé ou non,
à l’aide des registres de limitation.
Limit Relocation
register register
jes Work
CPU < + memory
Programm
Virtual Physical
address no address
Lorsque l’on passe d’un programme à un autre, il faut recharger les registres de réallocation et de limite. Ce qui
est la tâche du système d’exploitation. Chez les processeurs, qui ne possèdent pas de MMU, Les adresses
virtuelles correspondent aux adresses physiques.
Des Tableaux de pages sont utilisés pour convertir les adresses virtuelles en adresses physiques. Les adresses
virtuelles sont composées des champs suivants :
1. Le numéro de la page, qui indique l’index du Tableau de pages. Le Tableau de pages contient l’adresse
de base de chaque trame (adresse physique).
2. Un offset de page, qui est additionné au contenu du Tableau de pages (adresse de base) pour construire
l’adresse physique.
Adresse physique
0 1 0 0 0 0 0 0 0 0 0 10 0 0 0
Numéro de trame = 2
obtenu du tableau de
page
15 000 0
14 000 0
13 000 1
12 000 0
11 111 1
Tableau 10 000 0
de page 9 101 1
8 000 0
7 100 1
6 000 0
5 011 1
4 010 1 010
Numéro de page = 4 3 110 1
correspond à 2 000 0
l’indexe du tableau
de page 1 001 1 Offset
bit de
0 000 0 validité
0 1 0 0 0 0 0 0 0 0 0 0 10 0 0 0
Adresse virtuelle
La Figure de l’exemple de ci-dessus montre, que les zones de mémoire virtuelles peuvent être plus grandes que
les zones de mémoire physiques. Le bit de validité indique si le contenu du Tableau de pages est valide ou pas.
Si ce n’est pas le cas, les données doivent d’abord être copiées depuis la mémoire de masse dans la mémoire de
travail.
Le calcul des adresses physique peut être optimisé à l’aide des tampons auxiliaires de traduction (Translation
Look aside Buffers abrégé en TLB). Ces derniers sont également intégrés dans la MMU. Le TLB est un Tableau
contenant 8 à 64 entrées, qui correspondent aux entrées des Tableaux de pages.
Le TLB contient des informations sur les pages : numéro de la page, trame, bit de validité, bit de modification
(montre si le contenu d’une trame a été modifié et, par conséquent, si ce contenu doit être sauvé avant son
élimination) et bits de protection (droit d’accès sur une trame donnée).
Le processus de la conversion de l’adresse virtuelle en une adresse physique est le suivant : D’abord la MMU
contrôle si la page recherché existe dans le TLB. Pour cela la demande est comparée avec les numéros de page
du TLB. Lorsqu’il y a correspondance (TLB hit), le numéro de la trame est lu directement à partir du TLB.
Lorsqu’il n’y a pas de correspondance (TLB miss), la MMU continue la recherche avec le Tableau de pages.
Finalement une demande est choisie dans le TLB et est remplacée avec le numéro de la page, contenu dans le
Tableau de pages. Le TLB contient par conséquent tous les numéros de pages, qui ont été utilisés récemment.
Adresse virtuelle
Page Frame
Number Number
TLB hit
TLB
Page Table
1.8 DMA
L’accès direct à la mémoire (Direct Memory Access abrégé par DMA) est un procédé, qui permet de transférer
rapidement les données entre la mémoire de masse, ou les composants périphériques, et la mémoire de travail.
Un contrôleur DMA est nécessaire pour cela.
Sans le contrôleur DMA, les transferts des données entre la périphérie et la RAM doivent passer par la CPU.
Dans ce cas, ce dernier doit gérer les adresses source, les adresses de destination et les registres tampons. Ce
transfert doit bien entendu être programmé et nécessite, par exemple, plusieurs cycles de bus pour ne transférer
CPU
Le contrôleur DMA permet de transférer les données sans que ces derniers passent par la CPU. La CPU est alors
découplé du bus, afin que contrôleur DMA puisse reprendre le contrôle de ce dernier. La tâche de la CPU se
limite dans ce cas à l’initialisation du contrôleur DMA (adresse source, adresse destination et quantité de
données).
CPU Contrôleur
DMA
1.9 La périphérie
Les composants suivant sont considérés comme périphériques :
a) Les composants qui permettent d’accéder aux données de l’environnement du système. Ces données
sont lues avec des senseurs ou d’autre type d’interfaces.
b) Les composants qui permettent de transmettre les données à l’environnement du système. Ces données
sont transmises avec des actuateurs ou d’autres types d’interface.
La périphérie représente l’interface entre le processeur et son environnement. Les composants périphériques sont
en général connectés au bus du processeur.
D
Port Port Port
Out Out Out
Open Drain
Les sorties Open Drain (avec la technologie FET) ou Open Collector (avec la technologie bipolaire) possèdent
un seul transistor, qui permet de tirer une résistance (Pull-up) contre la masse. Les résistances pull-up peuvent
être sois internes ou externes. Les sorties Open Drain et Open Collector peuvent être connectées en parallèle,
comme par exemple avec les sources d’interruption :
IRQx
Tri state
Les sorties tri state peuvent avoir un état à haute impédance. L’état de sortie active ou « tri state » est activée à
l’aide d’un signal de contrôle supplémentaire (G).
En générale les sorties, qui fournissent des signaux sur un bus, sont réalisées avec des tri states, comme par
exemple les entrées et sorties des données (I/O) d’une RAM.
Les entrées et sorties digitales sont utilisées pour connecter les éléments d’affichage (lampes, LED etc.), les
relais, les moteurs, les soupapes etc.
Les sorties digitales possèdent souvent un étage d’amplification, afin qu’elles puissent fournir le courant
nécessaire à la transmission (ex. contrôle des moteurs, des soupapes etc.). Les entrées digitales possèdent
souvent des filtrés (circuit RC et trigger de Schmitt). Elles sont également protégées contre les sur tensions
(comme par exemple à l’aide de diodes Transzorb et des varistances).
Contrôleur de
Microcontrôleur moteur
Motor
GPIO1
GPIO2
Interrupteur
…
Protection
d’entrée
Les propriétés les plus importants de la transmission sérielle sont les suivants :
Le nombre de bit par seconde (baud rate)
La tension
Le protocole
CPU
1.9.3.1 RS232
L’interface RS232 est très importante dans le domaine des systèmes embarqués. Cette interface permet par
exemple de gérer un affichage LCD ou une ligne de commande (Command Line Interface). La plupart des
microcontrôleurs possèdent déjà une interface RS232. Toutefois, les tensions de sortie de ces dernières
correspondent au niveau TTL. Par conséquent, un convertisseur de tension externe doit être ajouté au système.
Les interfaces RS232 soutiennent différents modes de fonctionnement. Par exemple ils peuvent fonctionner soit
en mode synchrone ou en mode asynchrone. La fréquence de transmission est souvent définie avec un timer
standard. Certaine interface RS232 possèdent toutefois leur propre générateur de fréquence. Le timer peut ainsi
être utilisé pour d’autres applications.
Transmitter
Transmit FIFO
Control Baudrate
Unit Generator
Receiver
Receive Shifter RxD
Receive FIFO
1.9.3.2 SPI
L’interface SPI (Serial Peripheral Interface) est un système de bus sériel à haut débit, destiné à la communication
entre le microcontrôleur et la périphérie. Ce dernier est souvent utilisé pour la communication avec des
extensions I/O, des affichages LCD ainsi que des convertisseurs A/D et D/A. Il peut également être utilisé pour
la communication entre microcontrôleurs.
7 0 7 0
Participant 1 Participant n
L’interface SPI est toujours utilisée en mode maître esclave. Le maître est alors responsable de la génération de
la fréquence d’horloge. Le SPI peut travailler de façon duplexe à l’aide de deux lignes de transmission : MOSI
(Master Out Slave In) et MISO (Master In Slave Out). Les esclaves peuvent être connectés soit de façon
parallèle (c’est à dire que toutes les sorties des esclaves sont rassemblée et connectées à l’entré MISO du maître)
ou de façon sérielle (la sortie d’un esclave est connectée à l’entrée du prochain esclave et la sortie du dernier
esclave est connecté à l’entrée MISO du maître).
Le microcontrôleur écrit les données à transmettre dans un tampon de transmission. Ces dernières sont
sérialisées à l’aide d’un registre à décalage (comme une transmission RS232). Les données reçues sont
également converties à l’aide d’un registre à décalage. Le microcontrôleur peut alors lire ces données de façon
parallèle dans le tampon de réception. Du fait que les interfaces SPI ont une bande passante relativement élevé,
les tampons de transmission et de réception contiennent souvent plusieurs bytes.
Donnée Donnée
Registre à Registre à
décalage décalage
MOSI
MISO
SCK
FIFO de FIFO de
SPI maître réception tansmission
1.9.3.3 I2C
Le bus I2C (Inter Integrated Circuit) a été développé par l’entreprise Philips. Ce bus est utilisé pour connecter les
composants périphériques comme l’EEPROM, les affichages LCD ou RTC (Real Time Clock) au
microcontrôleur.
Le bus I2C est composé de deux fils, ce qui réduit la partie hardware de façon drastique. Ce bus comprend une
ligne d’horloge SCL (Serial Clock) et une ligne de donnée SDA (Serial Data). La communication est par
conséquent synchrone. Les modes d’utilisation du bus sont maître et esclave.
Figure 32 : Le maître (transmetteur) adresse l’esclave (récepteur) avec une adresse composé de 7 bits
et envoi 2 bytes de données, source [Philips_I2C]
Le nombre de bytes à transmettre et le sens de la transmission peuvent varier selon les composants utilisés. Ces
informations sont fournies par documentation de ces derniers. Par exemple : le maître n’écrit qu’un byte dans
un convertisseur D/A. Alors qu’avec une EEPROM, qui possède une zone de mémoire interne, le maître doit
d’abord envoyer l’offset de la case mémoire à accéder. Le transfert de données n’est effectué qu’ensuite,
jusqu'au bit d’arrêt.
Le Tableau suivant illustre quelques exemples de transmission de données avec l’interface I2C (le bit
confirmation n’est pas affiché) :
Composant Protocole
Convertisseur Démarrage Adresse Wr Données Arrêt
D/A de
l’esclave
Ecriture RAM Démarrage Adresse Wr Données Données Arrêt
de
l’esclave
Lecture RAM Démarrage Adresse Wr Données Arrêt Démarrage Adresse Rd Données Arrêt
de de
l’esclave l’esclave
Figure 33 : Exemple pour le protocole I2C
SDA
SCL
Start Stop
ACK :
Le bit de confirmation (Acknowledge) veut dire que la réception est a été bonne ou que le récepteur est prêt pour
des transmissions supplémentaires. Du point de vue du maître, l’esclave doit confirmer chaque byte écrit. Du
point de vu de l’esclave, le maître doit confirmer chaque byte lu. Si ces confirmations n’ont pas lieu, le processus
de transmission sera rompu avec le bit d’arrêt.
NACK :
Not Acknowledged signal une erreur de transmission ou la fin de la disposition à recevoir.
SDA 1 0 0 1 0 1 1 1
SCL
Dans la Figure 35, le maître envoi en premier la condition de démarrage, ensuite il envoi l’adresse de l’esclave
(1001011) et pour finir le bit R/W. L’esclave doit signaler sa présence avec le bit de confirmation. Il est
important que le maître maintienne le SDA à un état haut impédance durant cette phase. Les données peuvent
être transmises après le bit de confirmation (cela n’est pas montré dans la Figure). La communication est
interrompue avec le bit d’arrêt.
Admettons que l’on veut connecter un émetteur à plusieurs participants avec un bus I2C. Dans ce cas tous les
participants doivent se mettent dans un état à haute impédance. Certain microcontrôleur peuvent configurer leurs
sorties en tri state. Si cela n’est pas possible, il faut configurer les pins SCL et SDA en entrée.
1.9.4 Le timer
Le timer est utilisé comme base de temps dans les processeurs (système d’exploitation, horloge etc.) ou pour les
composants externes (ex. signal PWM, horloge pour les interfaces sérielles ...).
Oscillateur
Timer
Mode Timer / Counter Overflow
Register Flag
Counter
Entrée
externe
En générale des registres de 8 ou 16 bits sont utilisés dans les timers ou les compteurs. Dans le mode timer, ces
registres sont incrémentés périodiquement à l’aide de l’horloge interne du système. Dans le mode compteur,
cette incrémentation s’effectue en fonction de signaux externes.
Le mode « de rechargement automatique» (auto reload) permet d’initialiser le registre du timer avec une valeur
prédéfinie, qui est contenue dans le « registre de rechargement » (reload register). Ce qui permet de générer des
signaux avec des périodes variables.
Reload
Reload
Register
Dans le mode de rechargement automatique, il n’y a pas seulement une mise à un du flag overflow, mais le
compteur du timer est également initialisé avec la valeur du registre de rechargement.
Les convertisseurs A/D possèdent souvent plusieurs entrées analogiques, qui peuvent être sélectionnées à l’aide
d’un multiplexeur.
Les caractéristiques les plus importants pour un convertisseur A/D sont les suivants :
Largeur de bit (les valeurs typiques sont 8, 10, 12 ou 16 bits)
Temps de conversion
Control-Register Data-Register
CPU
Les caractéristiques les plus importants pour un convertisseur D/A sont les suivants:
Largeur de bit (les valeurs typiques sont 8, 10, 12 ou 16 bits)
Temps de conversion
La conversion D/A peut être également effectuée avec une sortie PWM, qui est connectée à un filtre passe bas.
Complex Instruction Set Computer (CISC): Les instructions peuvent être relativement complexes. Une seule
instruction assembleur est subdivisée en une séquence de micro instructions. L’exécution d’une instruction
nécessite donc plusieurs cycles d’horloge. Le micro programme est intégré par le fabricant. Par conséquent,
l’utilisateur n’y a pas d’accès.
Reduced Instruction Set Computer (RISC): Les instructions sont relativement simples. Il n’y a pas se micro
instructions et les instructions sont exécutées directement.
Interface sérielle
Convertisseur A/D
PWM
CAN
USB
PIC Le PIC est une famille de contrôleur de 8, 16 et 32 bits de Microchip. Il est utilisé avant tout
dans les applications à bas prix et à basse consommation.
68HCxx Les familles 8 bits de Motorola 68HC05, 68HC08 et 68HC11 sont très répandues dans
l’industrie et dans l’enseignement. Cependant, elles ne sont plus très utilisées dans les
nouvelles applications.
MC683xx Le contrôleur 32 bits de Motorola se base sur la CPU32. Les registres de la CPU ont une
largeur de 32 bits. En fonction du contrôleur, le bus de données peut contenir 16 à 32 bits et
le bus d’adresse 24 à 32 bits. Ces contrôleurs sont utilisés pour des applications, qui
nécessitent des puissances de calcul moyennes, comme la télécommunication et l’industrie
automobile. La famille est construite de façon modulaire et contient plusieurs membres.
Cette famille est remplacée dans les nouvelles applications par celle du ColdFire.
ColdFire La famille des ColdFires remplace celle des MC683xx de Motorola. ColdFire est un
processeur de 32 bits pur, c. à d. qu’il contient des bus d’adresse et de données de 32 bits.
Les processeurs ColdFire possèdent la même architecture que ceux 683xx. Toutefois, ils
sont plus puissants que leurs prédécesseurs.
ARM La société ARM propose des cœurs divers, qui sont fabriqués sous licence par différentes
compagnies. ARM est une abréviation pour Advanced RISC Machine. Actuellement les
familles ARM7 et ARM9 (tous deux à 32 bits) sont très répandues dans le domaine des
contrôleurs embarqués. Ces contrôleurs fournissent une bonne puissance de calcul pour une
consommation relativement basse.
MSP430 Le MSP40 est une famille de contrôleur du type RISC à 16 bits, produite par TI. Il est
utilisé avant tout dans les applications à consommation ultra basse (ex. les appareils à
batteries).
AVR, SAM Différentes familles de contrôleur de la compagnie Atmel comme AVR (processeur 8 bits
RISC), AVR32 (architecture 32 bits avec un DSP et la fonctionnalité SIMD) ou AT91SAM
(contrôleur très bon marché qui se base sur l’architecture ARM7). Ces contrôleurs ont une
grande croissance dans le domaine de l’électronique embarqué.
Consommation électrique
Prix
Disponibilité de deuxième source
Composants périphériques (Timer, UART etc.)
Carte d’évaluation et support du développement
2 Le microcontrôleur STM32F407
La première partie de ce chapitre traite les différentes versions de l’architecture ARM. La seconde partie décrit le
digramme de block et la structure des registres du microcontrôleur STM32F407 de Intel. Ce chapitre ne se limite
toutefois qu’aux aspects généraux, qui sont nécessaires à la programmation de ce dernier. De plus amples
informations sont fournies dans les manuels d’utilisation [ARM_ArchRefMan] à [Cortex-M4_Devices] ou la
littérature spécialisée [Sloss_SysDevGuide] ou [Furber_SoCArch]
Les microprocesseurs, qui sont basés sur l’architecture ARM, sont actuellement très répondus. Les raisons en
sont multiples :
Architecture commune
Puissance de calcul élevée
Faible consommation (typiquement moins d’un Watt)
Coût réduit
Les caractéristiques les plus importantes de l’architecture ARM sont les suivantes :
Les caractéristiques typiques de l’architecture RISC :
Un grand banc de registre à 32 bits
Architecture « Load / Store ». Le traitement de données ne peut être effectué que sur les
registres. Les données, qui sont stockées dans de la mémoire, ne peuvent pas être traitées par la
CPU.
Les instructions sont orthogonales
Adressage simple à l’aide des registres standard
Trois formats d’adressage
La longueur des instructions est fixe (32 bits). Ce qui simplifie le décodage des instructions.
Les propriétés typiques de l’architecture ARM :
Une instruction unique permet d’utiliser l’ALU avec le « barrel shifters » (shift left / right)
Incrémentations et décrémentations automatiques des différents modes d’adressages
Opérations « Load / Store » simultanées pour plusieurs registres
Exécution conditionnelle des instructions (conditional execution)
Les types de données byte (8 bits), halfword (16 bits) et word (32 bits)
Les jeux d’instructions ARM à 32 bits, Thumb à 16 bits et Jazelle. Ce dernier est prévu pour
l’exécution du code byte Java
L’ARM possède un système de traitement des interruptions simple et efficace pour les types
d’exception suivants :
16 exceptions au niveau du système : Reset, NMI, HardFault, MemManage, BusFault,
UsageFault, SVCall, PendSV et SysTick
Jusqu’à 240 exceptions au niveau application (interruptions hardware)
Les registres d’état : L’état actuel du processeur est stocké dans le registre d’état du programme
(Program Status Register: PSR) (voir chapitre 2.5.1) :
4 bits d’état
9 bits pour le numéro de l’exception, qui est actuellement traitées
1 bit pour le mode ARM ou Thumb
ARM propose plusieurs versions d’architecture. Les nouvelles versions sont compatibles avec les anciennes. Ces
dernières mettent à disposition de nouvelles instructions et de nouvelles fonctionnalités hardware. Chaque
version d’architecture est déclinée en différentes familles, qui à leur tour possèdent différents cœurs (Core). Le
Tableau 4 fournit un aperçu de ces dernières.
Divers familles de processeur ARM sont proposées pour une version d’architecture donnée (Par exemple :
ARM7, ARM9, ARM10 etc.). Au sein d’une famille il existe plusieurs cœurs (Par exemple : ARM7TDMI,
ARM740T). La nomenclature pour ces derniers est la suivante :
ARM{x}{y}{z}{T}{D}{M}{I}{E}{J}{F}{S}
x: Famille
y: Gestion de mémoire / unité de protection
z: Mémoire cache
T: Décodeur d’instruction Thumb à 16-bit
D: Débuggeur JTAG
M: Multiplication rapide
I: Embedded ICE Macrocell
E: Instruction améliorée
J: Jazelle (code byte Java)
F: Unité à virgule flottante vectorielle
S: Version synthétisable
Tous les cœurs, qui ont été développés après l’ARM7TDMI, contiennent les fonctionnalités TDMI.
Aujourd’hui il existe beaucoup de fournisseur de technologie silicium, qui possèdent des licences du cœur ARM
et qui les implémentent dans leur microcontrôleur. Voici un aperçu de ces fabricants : Analog Device, Atmel,
Freescale, Infineon, Intel, NEC, Philips, Samsung, TI et Toshiba.
La microarchitecture du Cortex M4 possède en supplément du Cœur d’exécution, qui sera décrit dans le chapitre
2.2.2 de façon plus détaillée, les composants suivants :
Bus Matrix: La matrice du système de bus connecte le processeur et l’interface de débogage aux
systèmes de bus externes suivants :
ICode Interface Bus (32 Bit). Ce bus charge (fetch) les instructions/vecteurs qui sont stockés
dans la zone d’adressage du programme (0x0000_0000 à 0x1FFF_FFFF).
DCode Interface Bus (32 Bit). Ce bus lit/écrit les données qui sont stockées dans la zone
d’adressage du programme.
System bus (32 Bit). Ce bus est destiné pour charger les instructions/vecteurs ainsi que la
lecture et l’écriture des données, qui sont stockés dans la zone d’adressage du système
(0x20000000 à 0xDFFFFFFF et 0xE0100000 à 0xFFFFFFFF).
PPB (32 Bit): Ce bus est prévu pour la lecture et l’écriture des données dans la zone
d’adressage privée (0xE004_0000 à 0xE00F_FFFF) afin de pouvoir tracer le
comportement du logiciel avec un analyseur de signaux.
Nested Vectored Interrupt Controller (NVIC): Le NVIC est un contrôleur d’interruption embarqué,
qui permet de traiter les interruptions avec un temps de latence très réduits. Le NVIC met à disposition
16 exceptions systèmes et 240 canaux d’interruption hardware. Il propose 256 niveaux d’interruption et
contient une interruption non masquable (NMI).
System Timer (SysTick): Le SysTick est une horloge 24 Bit, qui peut être utilisé comme horloge du
« système d’exploitation temps réel » (RTOS) ou comme un compteur standard.
Optional Memory Protection Unit (MPU): La MPU augmente la fiabilité du système à l’aide
d’attributs de mémoire pour les différents zones de stockage.
Optional Floating-Point Unit (FPU): La FPU réalise des opérations à virgule flottante de simple
précision qui sont compatibles au standard IEEE754 (32 bit float).
Optional Wakeup Interrupt Controller (WIC): Le WIC est un composant périphérique en option qui
est capable de détecter certains signaux d’interruption afin de réactiver le processeur à partir du mode
veille.
Optional Instrumentation Trace Macrocell (ITM): L’ITM est une source de signaux de traçage, qui
fournit des informations concernant le système d’exploitation et les évènements de l’application.
Optional Embedded Trace Macrocell (ETM): L’ETM permet de tracer les instructions afin de
pouvoir réaliser un suivi complet des instructions durant la phase de débogage.
Optional Trace Port Interface Unit (TPIU): Le TPIU rassemble les données d’ITM et d’ETM en un
seul flux de données, qui peuvent être envoyé à un analyseur de signaux.
Optional AHB Access Port (AHB-AP): Le processeur peut contenir une interface AHB-AP pour le
débogage. Dans ce cas, un port de débogage peut être connecté à cette interface. Le Cortex-M4 les
formats de port de débogage suivants :
JTAG
Serial Wire
JTAG (Joint Test Action Group) est une interface de débogage standard.
Serial Wire (SW): Le SW permet d’exporter les messages générés par le logiciel, les données
enregistrées et les informations concernant le profil de façon sérielle à l’aide d’un seul pin. Cela permet
un débogage similaire aux appels printf() sans devoir implémenter une interface UART, USB ou
Ethernet.
Optional Debug Access Port (DAP): Le DAP permet au débogueur externe d’accéder à l’ensemble des
zones mémoires du système sans devoir commuter le processeur en mode débogage. Cela est
spécialement utile pour accéder aux registres durant les phases de transition ou dans le mode basse
puissance.
Le traitement des données au sein du cœur du Cortex-M4 s’effectue toujours à l’aide de registres. Il n’existe pas
d’instruction, qui soit capable de traiter directement de contenu de la mémoire. Les données sont donc
transférées dans le banc de registres, qui est composé de 16 registres de 32 bits.
La plupart des instructions ARM interprètent le contenu des registres comme des valeurs entières 32 bits
(signées ou non signées). L’unité « Sign extend » permet de convertir les valeurs signées 8 et 16 bits, qui ont été
lues à partir de la mémoire externe, en des valeurs signées 32 bits.
La plupart des instructions ARM traite deux opérandes (Rn et Rm). Le contenu de ces registres est transférer
avec les bus internes A et B. Le second opérande Rm peut être également décalé, soit vers la gauche ou vers la
droite, avec le « Barrel Shifter ».
L’ALU (Arithmetic Logic Unit) ou le MAC (Multiply Accumulate Unit) traitent les opérandes Rn et Rm, et
écrivent le résultat dans le registre (Rd).
Les instructions Load / Store permettent de transférer les données, soit à partir de la mémoire externe dans un
registre, ou inversement à partir d’un registre dans la mémoire externe. L’adresse de la case mémoire à
sélectionner est calculée par l’ALU. Cette dernière est copiée dans le registre d’adresse, afin d’être fournie sur le
bus d’adresse. L’incrémenter (Incremented) permet d’incrémenter ou de décrémenter cette adresse, après chaque
opération d’écriture et de lecture.
Data
Sign extend
Write Read
Barrel shifter
A B Acc
ALU MAC
Result-Bus
Address register
Incrementer
Address
Figure 40: Le transfert des données au sein du cœur ARM, source : [Sloss_SysDevGuide]
Les trois étages du pipeline doivent être exécutés pour chaque instruction. L’exécution de chaque étage nécessite
un cycle d’horloge. Par conséquent, l’exécution d’une instruction complète nécessite trois cycles d’horloge. Ce
système permet toutefois d’exécuter une instruction par cycle d’horloge, car le pipeline traite simultanément
plusieurs instructions. L’exemple suivant illustre ce procédé :
Cycle 1 Instr. 1
Traitement des interruptions à durée déterministique et de haute performance pour les applications
temps réel
Unité de protection de la mémoire (MPU) pour les applications critiques
Mise à disposition de nombreux outils de débogage et de traçage. Les signaux de débogage et de traçage
sériels réduisent massivement le nombre de pins nécessaires.
supplémentaires. Les composants les plus importants du diagramme de bloc de la Figure 42 sont les suivants :
Le processeur ARM Cortex-M4 avec les mémoires flash et RAM embarqués, dont les propriétés ont
été décrites au chapitre 2.2.1.
Power-Management flexible pour divers applications à basse consommation. Fondamentalement trois
modes en sont proposés : Sleep, Stop et Standby.
Jusqu’à 1 MB Flash
192 KB de SRAM interne, parmi lesquels 112 KB SRAM, 16 KB SRAM, 64 KB RAM CCM (core
coupled memory) et 4 KB SRAM de backup (4 KB BKPSRAM)
Multi-AHB Busmatrix. La matrice de bus relie tous les bus maîtres (CPU, DMA, Ethernet et USB HS)
aux bus esclaves (Flash, RAM, FSMC ainsi que le périphériques AHB et APB).
Nested vectored interrupt controller (NVIC), qui peut gérer jusqu’à 256 niveau de priorités. Le NVIC
peut traiter 16 exceptions au niveau système et jusqu’à 82 canaux d’interruption hardware.
External interrupt/event controller (EXTI), qui est composé de 23 détecteurs de flanc d’interruptions
ou d’évènements.
16 minuteries (timer) parmi lesquels :
2 minuteries de référence pour la génération de signaux PWM (TIM1 et TIM8)
10 minuteries d’utilité générale avec rechargement automatique (reload), parmi lesquels 4 sont
équipés avec des compteurs vers le haut et vers le bas (TIM2 à TIM5) et 6 seulement avec un
compteur vers le haut (TIM9 à TIM14).
2 minuteries standards (TIM6 et TIM7) pour la génération de signaux de déclenchement
(trigger)
2 minuteries watchdog (WWDG).
Real Time Clock (RTC), backup SRAM and Register. Le RTC est composé fondamentalement d’un
compteur décimal codé binaire (BCD) et des registres dédicacés, qui contiennent les secondes, les
minutes, les heures, le jour de la semaine, le jour du mois, le mois et l’année sous format BCD. Le
SRAM backup et les registres sont utilisés pour stocker les données, qui doivent être conservées dans le
mode VBAT.
General Purpose Input/Output (GPIO) : De nombreux pins du STM32F40x sont multifonctionnel et
peuvent être utilisés comme entrée/sortie numérique.
External Memory Controller (FSMC). Le FSMC commande les bus d’adresse, de données et de
control externes et il supporte les technologies de stockage suivantes : PCCard/Compact Flash, SRAM,
PSRAM, NOR et NAND Flash.
2 Direct Memory Access Controller (DMA1 et DMA2) pour les transferts de données de mémoire à
mémoire, de périphérique à mémoire et de mémoire à périphérique.
De nombreuses interfaces sérielles comme par exemple :
4 USART (USART1, USART2, USART3 et USART6)
2 UART (UART4 et UART5)
Jusqu’à 3 bus I2C (I2C1 à I2C3)
Jusqu’à 3 SPI (SPI1, SP2 et SP3)
Jusqu’à 2 I2S standards (I2S2 et I2S3)
Audio PLL I2S (PLLI2S)
Interface Ethernet MAC
2 Interfaces CAN (bxCAN1 et bxCAN2), qui soutiennent les standards 2.0A et 2.0B Standards
USB on-the-go full-speed (OTG_FS)
USB on-the-go high-speed (OTG_HS)
Interface Secure digital input/output (SDIO) et MultiMedia-Card (MMC), qui soutient les cartes secure
digital et multimedia.
Digital Camera Interface (DCMI), qui peut être connecté à travers une interface de 8 ou 14 bits à un
module de caméra ou une sonde CMOS.
3 convertisseurs A/D (ADC1 à ADC3) avec une précision de 12 Bits. Chaque convertisseur peut gérer
jusqu’à 16 canaux d’entrée.
Sonde de température qui génère une tension linéaire entre 1.8 et 3.6 volt en fonction de la
température.
2 convertisseurs D/A (DAC1 et DAC2) avec une précision de 12 Bits.
Interface de test / débogage
Dans le mode Thread, le bit [0] du registre CONTROL qui permet de spécifier, si le programme doit être exécuté
au niveau privilégié (bit [0] = 0) ou non privilégié (bit [0] = 1). Dans le mode Handler, le programme est
toujours exécuté au niveau privilégié.
Dans le mode Thread, seul un programme privilégié peut accéder au registre de contrôle afin de modifier le
niveau de privilège. Les programmes non privilégiés doivent alors réaliser un appel superviseur avec
l’instruction SVC afin de remettre à nouveau le niveau privilégié.
La CPU peut accéder aux 16 registres d’utilité générale, qui sont désignés ici de R0 à R15, dans les deux
niveaux de privilège. Par conséquent, ces registres peuvent être utilisés librement à quelques exceptions près.
Selon la convention, le registre R13 doit être utilisé comme pointeur de pile (SP). Le registre SP se décline en
deux versions : Main Stack Pointer (MSP), qui n’est accessible qu’au niveau privilégié, et le Process Stack
Pointer (PSP), qui est accessible dans les deux niveaux de privilège. Dans le mode Thread, le bit [1] du registre
de CONTROL permet de spécifier le pointeur de pile, qui doit être utilisé :
Bit [1] = 0 MSP, qui est utilisé après le reset
Bit [1] = 1 PSP
Le registre R14 est également appelé registre de lien (LR). Ce dernier contient l’adresse de retour lors d’un appel
de sous routine. Les deux niveaux de privilège permettent d’accéder à LR.
Le registre R15 contient le compteur de programme (PC). Les deux niveaux de privilège permettent d‘accéder
au registre PC. Durant la phase de reset, la valeur de son vecteur (qui est stockée à l‘adresse 0x00000004 dans
la mémoire) est chargée dans le registre PC. Cette valeur est l’adresse de départ du programme, qui doit être
exécuté par le processeur.
L’utilisation des registre est spécifié plus précisément par le standard APCS (ARM Procedure Call Standard),
voir également le chapitre 6 Les sous routines.
ou thumb à 16 bits. Attention le processeur Cortex-M4 ne peut exécuter que du code thumb.
Ces registres possèdent de champs de bits qui s’excluent mutuellement en formant ensemble le registre PSR
(voir Figure 46)
Les registres PSR peuvent être utilisés de façon isolé ou combiné avec les instructions MSR ou MRS (voir
chapitre 4.3.7). Die Tableau 5 illustre les différentes possibilités de combinaison pour l’utilisation des registres
d’états.
Les sauts de programme conditionnels sont toujours réalisés en fonction des bits de condition. Si le programme
contient par exemple l’instruction « if (A == 0) », il exécutera un saut conditionnel en fonction de la valeur
du bit de condition Z. Les processeurs ARM permettent également d’exécuter les instructions conventionnelles
de façon conditionnelle à l’aide des bits de condition (Conditional Execution).
Le Tableau suivant contient les bits de condition du registre APSR ainsi que leur description :
SUB ADD
231
Nulle
0
-1 1
2
N=1 N=0
SUB Négatif Positif ADD
- 231 - 1 231
V=1 V=1
Dépassement avec Dépassement avec
addition soustraction
Le registre de masquage de priorité (PRIMASK) empêche le traitement des exceptions, dont la priorité est
configurable (voir le tableau des propriétés des exceptions en annexe C). Cela représente tous les exceptions mis
à part le Reset, l’interruption non masquable (NMI), qui peut être généré par certains périphériques ou par le
logiciel lui-même, et HardFault, qui est généré par les erreurs survenant durant le traitement des exceptions.
L’affectation des bits du registre PRIMASK est la suivante :
Le registre de masquage par défaut (FAULTMASK) empêche le traitement de toutes les exceptions, excepté
l’interruption non masquable (NMI). L’affectation des bits du registre FAULTMASK est la suivante :
Le registre de priorité de base (BASEPRI) défini la priorité minimale pour le traitement des exceptions. Lorsque
la valeur du registre BASEPRI est différente de zéro, le traitement des exceptions dont la priorité est égale ou
plus petite que cette valeur sera bloqué. L’affectation des bits du registre BASEPRI est la suivante :
2.5.4 CONTROL-Register
Le registre de CONTROL permet de spécifier le niveau de privilège et la pile (stack) à utiliser dans le mode
Thread. Lorsque une FPU est disponible, ce registre indique si le contexte de cette dernière est active ou pas.
L’affectation des bits du registre CONTROL est la suivante :
Les processeurs ARM - à partir de la version v4 - soutiennent les formats de données suivants :
Byte signés et non signés (8 bits, char)
Demi-mot (half word) signés et non signés (16 bits, short int)
Mot (word) signés et non signés (32 bits, long int)
Les processeurs ARM sont basés sur l’architecture Load/Store. C'est-à-dire que les opérandes de l’ALU doivent
toujours être stockés dans des registres. Les opérateurs de l’ALU traitent ainsi les données sous formant mot (32
bits). Les seules instructions, qui sont capables de transférer des données entre la mémoire et les registres, sont
les instructions de transfert de donnée (Load et Store). Ces instructions sont capables de transférer des données
du type byte, demi-mot et mot. Lorsqu’une donnée du type byte ou demi-mot est lue à partir de la mémoire, elle
est étendue à 32 bits avec l’unité d’extension de signe « Sign Extend » (voir Figure 40).
Certaine conditions doivent être respectées pour le stockage des données dans la mémoire. Les Figures suivantes
illustres le stockage des bytes, des demi-mots et des mots dans la mémoire :
Adresse
N Byte 0
n+1 Byte 1
n+2 Byte 2
n+3 Byte 3
Figure 52: Model de mémoire du type byte
Adresse
n Demi-mot 0 Byte de poids plus faible du demi-mot 0
Byte de poids plus fort du demi-mot 0
n+2 Demi-mot 1 Byte de poids plus faible du demi-mot 1
Byte de poids plus fort du demi-mot 1
Figure 53: Model de mémoire du type demi-mot
Un demi-mot est toujours stocké à une adresse paire (frontière 2 bytes). Le byte de poids plus faibles sera stocké
à l’adresse inférieure (paire), et le byte de poids plus for à l’adresse supérieur (impaire). Le bit de poids le plus
faible de l’adresse de base A0 est donc nul.
Adresse
n Mot 0 Byte de poids le plus faible du mot 0
Un mot est toujours stocké à une adresse, qui est divisible par 4. Le byte de poids le plus faible du mot est stocké
à l’adresse n, alors que le byte de poids le plus fort à l’adresse n+3. Les deux bits de poids les plus faibles de
l’adresse de base (A0 et A1) sont donc nuls.
Les bus AHB1 (ARM High-performance Bus 1), AHB2, APB1 (ARM Peripheral Bus 1) et APB2 permettent
d’accéder aux composants périphériques internes (voir Figure 42). Chaque composant périphérique interne est
connecté à un de ces bus, qui possède son propre domaine d‘adressage. Le tableau 10 de la fiche technique du
STM32F407xx [STM32F407xx_Datasheet] fournit de plus amples information à ce sujet.
Le bus AHB3 permet de connecter des composants de stockage externes supplémentaires. 4 blocs de 256 MB
chacun sont mis à disposition dans ce segment. Le bus AHB3 soutient en outre les standards suivants :
PCCard/Compact Flash, SRAM, PSRAM ainsi que les flash NOR et NAND.
0xE010_0000 Reserved
0xE000_0000 Cortex-M4 internal periphery
Reserved
0x6E00_0000 CAN controller SJA1000 (CS3)
0x6D00_0000 CARME Extension 2 (CS3)
0x6C00_0000 CARME Extension 1 (CS3)
0x6800_0000 LCD SSD1963 controller (CS2)
0x6400_0000 External RAM (CS1)
0x6000_0000 External Flash (CS0)
0x5006_0C00 Reserved
0x5000_0000 AHB2
0x4008_0000 Reserved
0x4002_0000 AHB1
0x4001_5800 Reserved
0x4001_0000 APB2
0x4000_7800 Reserved
0x4000_0000 APB1
0x2002_0000 Reserved
0x2000_0000 128 Kilo SRAM
0x0810_0000 Reserved
0x0800_0000 1 Mbyte Flash (Vector Table)
0x0000_0000 Reserved
Figure 56 : Le plan de mémoire du kit de développement CARME
3 La programmation en assembleur
3.1 Introduction
Ce chapitre traite la programmation en assembleur de façon générale. Toutefois, les exemples d’assembleur y
sont définis avec le jeu d’instruction ARMV5TE des processeurs XScale. La syntaxe de l’assembleur s’oriente à
celle de GNU. Cette dernière diffère dans un certain nombre de point par rapport à celles des autres fournisseurs.
L’assembleur est un langage de programmation proche du hardware. Il dépend par conséquent du hardware, c’est
à dire qu’il a été développé pour une famille de processeur bien définie.
Avantage : l’assembleur est optimisé pour le hardware (registre, architecture etc.)
Désavantage : l’assembleur n’est pas portable, c. à d. qu’il ne peut pas être utilisé avec un autre CPU.
Abstraction
LDR r4,beer Assembler
SUB r4,#1
STR r4,beer
0xe59f4020 Code machine
1110’0101’1001’1111’0100’0000’0010’0000
0xe2444001
1110’0010’0100’0100’0100’0000’0000’0001
0xe0802001
1110’0000’1000’0000’0010’0000’0000’0001
a) La langue naturelle est la plus compréhensive pour l’homme. Elle est malheureusement trop floue et trop
redondante. Elle ne convient donc pas pour la programmation.
b) Le pseudo code est souvent utilisé dans la phase de design. Son niveau d’abstraction est très élevé et il n’est
pas standardisé.
c) Les langages de programmation haut-niveau possèdent également un niveau d’abstraction élevé. Les
compilateurs traduisent ces langages dans des codes machine, dont l’efficacité dépend du langage de
programmation et du compilateur. Les langages de programmation les plus répondus pour les
microprocesseurs sont le C et C++. Les langages de programmation haut niveau sont standardisés (ex. ANSI-
C). Ce qui permet de porter les programmes d’un processeur à un autre, avec plus ou moins d’efforts.
d) Les programmes en assembleur sont très difficilement compréhensibles pour l’homme. Toutefois, les
programmes en assembleur sont plus performants et utilisent mieux les ressources que les programmes haut-
niveau.
/*************************************************************************
* *
* Projet : NomProjet *
* *
* *
**************************************************************************
* Programme/Module : NomModule *
* Nom du fichier : NomFichier.68k *
* Version : 1.00 *
* Définition : dd.mm.yyyy * Entête (header)
* Auteur(s) : NomAuteur * (en option)
* -----------------------------------------------------------------------*
* Description : Description brève du module *
* *
**************************************************************************
* Modification: *
* Auteur Date de la modification *
* m.n dd.mm.yyyy *
*************************************************************************/
/************************************************************************* Interface du
* PUBLIC DECLARATIONS * module
*************************************************************************/
(en option)
.global mysub @ exported subroutines
/************************************************************************* Code
* CODE *
*************************************************************************/
.text
.end
Les entêtes (Header) dépendent de la société. Il existe normalement dans les sociétés des lignes directives pour la
programmation, qui définissent l’aspect de l’entête. L’entête devrait toutefois contenir au minimum les
informations suivantes :
Nom du projet
Brève description du module
Historique
La partie « PUBLIC DECLARATIONS » contient tous les noms de sous-routines et de variables, qui sont
définies dans le module courant et doivent être exportées dans les autres modules.
La partie « EXTERNAL DECLARATIONS » contient tous les noms de sous-routines et de variables, qui doivent
être importées depuis les autres modules.
Les fichiers, qui contiennent la description des registres du contrôleur, peuvent être importés dans la partie
« include DEFINITIONS ». include copie tout le contenu du fichier en question dans le module.
La partie « EQUATE DEFINITIONS » contient les définitions EQUATE (correspond à la directive #define
de C).
La partie « VARIABLE AND CONSTANT DEFINITIONS » contient les définitions des variables et des
constantes.
La partie « CODE » contient les instructions du programme. Cette partie est divisée en 4 colonnes (voir chapitre
4.3.1).
Etiquette :
Le champ des étiquettes commence à la première colonne et se termine avec « : ». L’étiquette peut contenir le
nom d’une variable, d’une sous-routine ou d’un label. Un label est une sorte de marque, qui est utilisé pour les
sauts de programme.
Opération / instructions :
Le champ des opérations / instructions contient les instructions pour la CPU ou les directives pour l’assembleur.
Dans l’exemple ci-dessus « LDR » est une instruction, qui commande à la CPU de copier la valeur constante
« MAX » dans le registre « R0 ».
Toutes les instructions d’un processeur sont définies dans son jeu d’instruction (voir chapitre 4, qui traite le jeu
Exemple :
Ce concept permet d’économiser de nombreux sauts de programme en comparaison avec d’autre famille
d’assembleur. La condition est définie avec un suffixe ou un code de condition à la fin de l’instruction. Par
exemple, le suffixe « NE » de la deuxième comparaison a pour conséquence que cette instruction n’est exécutée
que si le résultat de l’opération précédente est différent de zéro. De la même façon, l’addition n’est exécutée que
si le résultat de l’opération précédente est égal à zéro (EQ).
L’instruction, qui vient d’être exécutée, fixe la condition. Cette dernière est évaluée l’aide des bits d’état courant
du programme (flags), qui se situent dans le registre du même nom (Current Program Status Register abrégé par
PSR). Dans l’exemple de ci-dessus les conditions sont fixées par les deux opérations de comparaisons.
Toutes les opérations arithmétiques, qui sont exécutées par l’ALU, influencent les bits d’état N, Z, C et V. Les
opérations logiques ne modifient que N, Z et C.
Les opérandes :
Les opérandes des instructions ou des directives assembleur sont définis dans le champ des opérandes. Dans
l’exemple de ci-dessus les opérandes de l’instruction « LDR » sont la constante « MAX » et le registre « r0 ».
Le processeur ARM travail fondamentalement avec trois opérandes. Certaines instruction, comme par exemple
les sauts de programme, se limitent à un ou deux opérandes. Le jeu d’instruction ARM ne contient pas
d’instruction sans opérande.
Exemple :
B start
SWI 0x10
Exemple :
MOV pc,lr
LDR r0,waleur
Dans cette expression « dest » représente le registre de destination et « src » celui de source. Les registres r0
à r15 peuvent être utilisés pour cela. L’opérande « shift » permet de traiter un opérande à l’aide du « barrel
shifter » avant l’exécution de l’opération. Il permet également de définit un valeur constante de 8 bits.
Chez les processeurs RISC, les instructions de traitement de données ne se limitent en générale à des opérations
entre registres. Les opérations entre des registres et la mémoire de travail sont exclusivement des opérations de
transfert de données.
Les commentaires :
Les commentaires du champ de commentaire sont définis pour améliorer la compréhension du code.
L’assembleur GNU prévoit trois types de commentaires : les commentaires en bloc, les commentaire en ligne et
les commentaire de fin de ligne.
Les commentaires en bloc sont formulés entre « /* » et « */ », comme en ANSI-C. Les commentaires en ligne
sont définis uniquement sur une ligne et ils sont introduits avec un dièse « # ». Les commentaires de fin de ligne
sont défini à la fin de la ligne et ils sont introduits avec un « @ ». Il est très important de bien commenter les
programmes assembleur. Toutefois, le commentaire ne devrait pas décrire l’instruction, mais améliorer la
compréhension du déroulement du programme.
Exemple :
/*
Commentaire de bloc
*/
# Commentaire en ligne
LDR r0,=operand1 @ Commentaire fin de ligne
Figure 63: Les trois formes des commentaires assembleur
Les constantes peuvent être spécifié avec le caractère « = ». Cela est valable pour les valeurs numériques et les
adresses, qui sont représentées à l’aide de symbole.
Les valeurs, qui ne peuvent pas être chargées par les instructions « MOV », sont automatiquement adressées de
façon relative avec compteur de programme (ici [pc,#4]). Dans l’exemple suivant, la constante
0x12345678 est chargée de la mémoire programme de cette façon. Par contre, la valeur un est directement
chargée avec l’instruction MOV.
Exemple :
start: LDR r0,=1 @ Charger la valeur
constante 1
loop: LDR r1,=0x12345678 @ Charger la valeur
0x12345678
ADD r0,r0,r1 @ Additionner les deux
valeurs
B loop
A partir du code source de ci-dessus, l’assembleur génère un code objet, dont la structure peut être affichée dans
la fenêtre de désassemblage :
main:
080001dc: ldr r0, [pc, #8] ; (0x80001e8 <main+12>)
080001de: ldr r1, [pc, #12] ; (0x80001ec <main+16>)
080001e0: add r0, r1
080001e2: b.w 0x80001dc ; <main>
Remarque :
La valeur constante est stockée à l’adresse 0xA00004F8. Le compteur de programme adresse la prochaine
instruction à exécuter, qui est stockée sous 0xA00004EC. Le chargement des instructions relatif au compteur de
programme, s’effectue toujours avec un décalage préalable de - 8. Ce qui fournit le décalage à noter dans le code
suivant : 0xA00004F8 - 0xA00004EC – 8 = 4.
Le Tableau suivant contient les opérateurs, qui sont autorisées dans les expressions assembleur. Des espaces
vides ne sont pas autorisés avant ou après ces opérateurs :
+ Addition
- Soustraction
* Multiplication signée
/ Division signée
% Reste de la division signée (Modulo)
& Opération binaire et
| Opération binaire ou
^ Opération binaire ou exclusive
&& Opération logique et
|| Opération logique ou
==, <> Egalité et inégalité
>, >= Plus grand que, plus grand que ou égale à
<, <= Plus petit que, plus petit que ou égale à
<< Opération de décalage vers la gauche
>> Opération de décalage vers la gauche
- Négation par complément à deux
~ Inversion binaire, complément à un
Tableau 11 : les opérateurs pour les expressions assembleur
Le résultat d’une opération de comparaison est toujours un nombre entier dans l’assembleur GNU. La valeur 0
représente faut et la valeur -1 vrai. Les opérations booléenne && et || fournissent par contre une valeur +1,
lorsque le résultat est vrai. Cette différence ne joue aucun rôle pour l’évaluation des conditions, En effet tous ce
qui est différent de 0 est considéré comme vrai.
Exemple:
Les bits 3 et 11 du registre r0 doivent être effacés.
BIC r0, r0, #(1 << 3) | (1 << 11)
L’instruction BIC efface les bits dans le registre r0. Les bits à traiter sont spécifiés avec le paramètre
« #(1 << 3) | (1 << 11) ». La position du bit y est définie avec l’opérande « << », alors que l’opérande « | »
permet d’associer ces derniers.
La Figure suivante illustre une vue d’ensemble des fichiers, qui sont générés durant la phase de développement :
Editor
Source Source
assembler
Source C / Source
C++
*.s
assembler *.c
C / C++
*.asm *.s *.cpp*.c
*.asm *.cpp
Assembler Compiler
Linker/Locator
3.5.1 L’éditeur
L’éditeur permet de définir le code source en assembleur ou avec un langage de programmation évolué. La
plupart des éditeurs sont sensible à la syntaxe et mettent en évidence les mots clés.
Exemple :
Le code en assembleur suivant a été défini à l’aide d’un éditeur.
.arm
.global start
.text
.end
3.5.2 L’assembler
L’assembleur est toujours spécifique à la famille des processeurs. Si vous voulez développer du code pour des
processeurs différents, vous devez également utiliser des assembleurs différents. On parle de « Cross
Assembler », lorsque le code en assembleur est développé sur un ordinateur différent de celui, qui doit exécuter
le programme (ex. l’assembleur fonctionne sur un PC et le code généré sur le microcontrôleur d’une machine à
café).
L’assembleur traduit le code source assembleur, qui est contenu dans un fichier ASCII avec une terminaison
« .s » ou « .asm », en un fichier objet (.o). Des fichiers supplémentaires peuvent être générés, en fonction des
options de l’EDI (ex. fichier de listing : List-File avec une terminaison « .lis »). Le listing de l’assembleur pour
l’exemple ci-dessus a été illustré dans la Figure suivante :
1 2 3
1 # 1 "..\\src\\main.s"
1 .syntax unified
0
0
2 .cpu cortex-m3
3 .fpu softvfp
4 .thumb
5
6 .text
7
8 .global main
9
10
11 main:
12 0000 0248 LDR r0,=1
13 0002 0349 LDR r1,=0x12345678
14
15 0004 0844 ADD r0,r0,r1
16 0006 FFF7FEBF B main
16 00000100
16 00007856
16 3412
Figure 67 : Le listing assembleur
Nr. de Contenu
colonne
1 Numéro de ligne du code source
2 Adresse relative de départ du code
3 Code machine, par exemple "0000A0E3" veut dire que le registre r0 doit être chargé avec la
valeur 0. "0100080E0" veut dire que le contenu du registre r1 doit être additionné au registre
r0. Toutes les instructions nécessitent 4 bytes (1 mot) de stockage. Elles sont donc stockées à des
adresses mot (divisibles par 4).
reste Code original en assembleur
Tableau 13 : Colonnes du fichier listing
3.5.3 Le compilateur
Comme l’assembleur, le compilateur traduit le programme haut niveau, qui est défini dans un fichier source, en
un fichier objet. Les compilateurs C ou C++ sont utilisés généralement pour les applications embarqués.
« elf » est une abréviation pour « Executable and Linking Format ». Ce format est un standard pour la gestion
des exécutables, des codes objets des bibliothèques, du mappage de la mémoire et des informations nécessaires
au débuggeur. Le format elf est entre temps très répandu et il a remplacé conceptuellement le COFF et de
nombreux autres formats de débuggeur.
Le contenu de ce fichier ne devrait pas intéresser le programmeur. Il ne peut pas être lu avec l’éditeur, mais par
contre, il peut être exploité avec les applications « objdump.exe » ou « elfdump.exe ».
Figure 68 : Les informations contenues dans le fichier elf exploitées avec OBJDUMP.EXE
3.5.5 Le débuggeur
Le débuggeur permet de tester le code, en représentant le contenu des registres, des variables et du code sur
l’écran du PC. L’insertion de points d’arrêt (break points) dans le code source et son exécution contrôlée, pas par
pas, font partie des exigences minimales pour un débuggeur.
On parle de « target debugging » lorsque le débogage est exécuté directement sur le système en développement.
Dans ce cas le débuggeur, qui est exécuté sur le PC, est responsable du téléchargement du code de débogage sur
le système en développement et de la communication avec ce dernier. Alors que le processeur du système en
développement n’est responsable que de l’exécution de ce code.
Les environnements de développement modernes (IDE), comme Eclipse ou MS Visual Studio, proposent ces
fonctionnalités.
La plupart des processeurs ARMv7 soutiennent les deux jeux d’instructions ARM et Thumb. Les instructions
ARM sont codées de façon uniforme avec 32 bits alors que les instructions Thumb le sont avec 16 bits. Les deux
jeux d’instructions sont équivalents dans leur ensemble. Toutefois le jeu d’instructions Thumb permet une
densité de code plus grande (pratiquement de plus de 50%). Cela apporte de de nombreux cas une amélioration
de la performance. Toutefois les instructions Thumb ne sont pas aussi bien structurées que leur équivalents
ARM. Une description plus détaillé de ces différences est disponibles en annexe D.
Les processeurs Cortex-Mx ne soutient cependant que le jeu d‘instructions Thumb et Thumb-2 (ARMv7-M). Le
second jeu d’instructions est une extension du premier et comprend des instructions à 16 bits mais également à
32 bits, lorsque ces dernières ne peuvent pas être codées avec 16 bits.
Les chapitres suivants traitent uniquement le jeu d’instruction Thumb-2. Les descriptions de l’architecture ARM
et des registres en relation avec ces instructions sont disponibles dans les chapitres 2.4 et 2.5.
Ex. : ADD, ADd with Carry, Reverse SuBtract, Exclusive OR, BIt Clear,
Les instructions Thumb2 sont codées fondamentalement avec 16 bits. Toutefois, contrairement aux instructions
ARM, les instructions Thumb2 ne possèdent pas de format uniforme. En effet, certaines instructions sont codées
avec 32 bits (p.ex. les sauts de programme lointains, les instructions arithmétiques et logiques conditionnelles,
qui doivent être précédées avec une instruction If-Then).
Cet encodage engendre un certain nombre de restrictions qui ne sont pas très graves dans la pratique :
Il n’est pas possible de charger des valeurs directes de 32 bits. Mis à part si ces dernières peuvent être
construites avec une valeur (mantisse) de 8 bits et 5 bits de décalage. Toutes les autres valeurs doivent
être chargées directement à partir de la mémoire programme.
Les instructions saut de programme ne possèdent ne peuvent être réalisées qu’avec un déplacement
relatif (offset) de 24 bits. Les sauts de programme relatifs sont ainsi limités à un espace de 32 MB par
rapport à l’adresse courante. Les sauts de programme absolus de 32 bits sont réalisés avec le
chargement d’une valeur de 32 bits dans le registre du compteur de programme.
Les instructions Load/Store ne peuvent fonctionner qu’avec un offset de 12 bits. Les accès aux variables
relatifs sont ainsi limités à un espace de 4 KB, par rapport à l’adresse courante.
Instruction Fonction
ADC Addition de deux valeurs de 32 bits avec la retenue
ADD Addition de deux valeurs de 32 bits
AND Opération logique ET entre deux valeurs à 32 bits
B Saut relatif ± 32 Mégas bytes
BIC Effacer les bits d’une valeur 32 bits
BKPT Point d’arrêt software (break point)
BL Saut avec stockage de l’adresse de retour (appel de sous-routine)
BLX Saut avec stockage de l’adresse de retour et changement de registre
BX Saut avec changement de registre
CDP CDP2 Opération coprocesseur
CLZ Compter les zéros, qui précèdent
CMN Comparaison inversée de deux valeurs 32 bits
CMP Comparaison de deux valeurs 32 bits
EOR Opération logique OU exclusive entre deux valeurs 32 bits
LDC LDC2 Charger le coprocesseur avec une ou plusieurs valeurs 32 bits
LDM Charger plusieurs valeurs de 32 bits depuis la mémoire dans les registres ARM
LDR Charger la valeur avec une adresse virtuelle dans un registre ARM
MCR MCR2 MCRR Charger une ou plusieurs registres ARM dans le coprocesseur
MLA Multiplication et accumulation d’une valeur 32 bits
MOV Charger la valeur 32 bits dans un registre
MRC MRC2 MRRC Charger depuis le processeur dans un ou plusieurs registres
MRS Copier le registre de statut (PSR ou SPSR) dans un registre ARM
MSR Copier depuis le registre ARM dans un registre d’état (PSR ou SPSR)
MUL Multiplier deux valeurs de 32 bits
MVN Charger une valeur 32 bits, préalablement inversée, dans un registre
ORR Opération logique OU entre deux valeurs 32 bits
PLD Indique l’adresse de la case mémoire, qui est en train d’être chargée
QADD Addition signée de deux valeurs 32 bits avec limitation du dépassement de capacité
QDADD Addition signée double de deux valeurs 32 bits et limitation du dépassement de capacité
QDSUB Soustraction signée double de deux valeurs 32 bits et limitation du dépassement de
capacité
QSUB Soustraction signée de deux valeurs 32 bits avec limitation du dépassement de capacité
RSB Soustraction inverse de deux valeurs 32 bits
RSC Soustraction inverse de deux valeurs 32 bits avec la retenu
SBC Soustraction de deux valeurs 32 bits avec retenu
SMLAxy Multiplication/addition signées (16x16)+32 = 32 Bit
SMLAL Multiplication/addition larges signées (32x32)+64 = 64 Bit
SMLALxy Multiplication/addition larges signées (16x16)+64 = 64 Bit
SMLAWxy Multiplication/addition signées ((32x16)>>16)+32 = 32 Bit
SMULxy Multiplication signée (16x16) = 32 Bit
SMULWy Multiplication/addition signées ((32x16)>>16)+32 = 32 Bit
STC STC2 Stocker une ou plusieurs valeurs depuis le processeur dans la mémoire
STM Stocker plusieurs registres ARM dans le processeur
STR Stocker des registres à des adresses virtuelles
SUB Soustraire deux valeurs à 32 bits
SWI Interruption software
SWP Echanger les registres ARM avec la mémoire de façon byte ou mot
TEQ Comparer deux valeurs 32 bits
TST Tester un bit dans un registre 32 bits
UMLAL Multiplication/addition larges non singées (32x32)+64 = 64 Bit
UMUL Multiplication large non signée (32x32) = 64 Bit
Tableau 14 : Jeu d’instruction du processeur ARM à partir de la version v5E.
Syntaxe :
Tous les registres de R0 à R15 peuvent être utilisés pour Rd et Rn. L’opérande N permet de pré traiter
facultativement un des opérandes avec le décalage à barilier (Barrel Shifter). N peut être une valeur constante, un
registre ou un registre décalé. L’opération de décalage ne modifie pas le contenu du registre source. Toutes les
valeurs constantes ne sont toutefois pas admises, car ces dernières ne peuvent être composées que de valeurs 8
bits décalées dans un registre 32 bits. Des informations plus détaillées concernant les opérations de décalage sont
fourni au chapitre 4.3.2.
Exemple :
Avant :
R5 = 5
R7 = 8
Après :
R5 = 5
R7 = 20
Les conditions, qui doivent être évaluées pour l’exécution des instructions conditionnelles, sont lues à partir des
bits du registre APSR. Ces derniers sont normalement fixés par l’instruction, qui a été exécutée en dernier par le
processeur (en générale des opérations de comparaison). La condition est notée comme suffixe (ou code de
condition) après l’instruction. Le Tableau 16 contient les différentes conditions.
Les opérations non comparatives, qui sont exécutées par l’unité arithmétique (ALU), peuvent également
influencer les bits d’état. Dans ce cas il faut ajouter un suffixe S au code d’opération. Les opérations
arithmétiques peuvent influencer les bits N, Z, C et V ; les opérations logiques les bits N et Z ; et le décalage à
barillet (barrel-shifter) uniquement le bit C.
Les suffixes LO ou HS peuvent être utilisées au lieu de CC ou CS. Ces derniers, comme les suffixes LS et HI, se
réfèrent à des opérations de comparaison non signée. Par contre LT, GE, LE et GT se réfèrent à des opérations de
comparaison signée. Le suffixe AL (always) doit être mentionné pour des raisons d’intégralité. Ce dernier existe
de façon formelle et représente la condition standard en absence de suffixe. Toutefois l’assembleur gcc ne
l’admet pas comme suffixe en présence d’instructions.
Exemple :
Définition du GGT (a, b), qui peut être défini dans un langage de programmation évoluée à l’aide d’une
division en chaîne de la manière suivante :
while (a != b) {
if (a > b)
a -= b ;
else
b -= a ;
}
Une première implémentation en assembleur ARM avec les registres r1 et r2 pour le stockage respectif de a
et b :
Cette solution est basée sur des sauts de programme. A chaque saut il faut vider le pipeline des instructions
lorsque le comportement de ce dernier n’a pas être anticipé. Ce qui ralenti fortement le temps d’exécution du
programme. Les instructions conditionnelles permettent de réduire ce temps d’exécution. De plus le programme
devient plus court.
Le registre de compteur de programme peut être utilisé comme registre de destination avec toutes les opérations
ALU. Les processeurs ARM soutiennent les formats petit-boutistes et gros-boutistes (little and big endian). Le
<MOV|MVN>{S}{<cond>} Rd, N
Le code d’opération MOV charge une valeur alors que MVN en charge le complément à un. Un suffise de
condition <cond> du Tableau 16 peut être ajouté à ce dernier afin de pouvoir l’exécuter de façon
conditionnelle. Quant au suffixe S, il permet à l’opération de changer les bits d’état du processeur. L’opérande
Rd représente toujours le registre de destination, alors que l’opérande N peut représenter soit le contenu d’un
registre (éventuellement décalé) ou une valeur constante.
Exemple :
MOV r0, r1 @ r0 := r1
MOVS r0, #10 @ r0 := 10, fixe les bit d’état du programme
MVN r1, r0 @ r1 := ~r0
MOV pc, lr @ retour depuis la sous-routine
L’ALU traite effectivement la valeur contenue dans le registre Rn et celle fournie par le décalage à barilier N.
Rn Rm
Barrel-Shifter
Zwischenresultat N
ALU
Rd
Certaines instructions de traitement de données comme MUL, CLZ ou QADD ne sont pas en mesure d’utiliser le
décalage à barilier. Dans ces cas, la valeur du registre Rm ne peut être utilisée que de façon non décalée.
Exemple :
Avant l’opération arithmétique, le contenu du registre Rm est décalé de deux bits vers la droite (LSR). Ce qui
correspond à une division par 4 de la valeur du registre. L’opération de décalage est équivalent à l’opérateur >>
du langage de programmation C.
Avant :
r0 = 0x11
r1 = 0x22
Après :
r0 = 0x8
r1 = 0x22
Le décalage à barilier connaît 5 sortes d’opération de décalage. Ces derniers sont fournis dans le Tableau 18 :
La syntaxe du décalage permet de définir le nombre de bit à décaler soit sous forme direct #<immed5> ou à
l’aide d’un registre Rs, qui contient le nombre de bit du décalage. Le Tableau 19 illustre pratiquement toutes les
formulations possibles.
Exemple :
L’opération MOVS copie le contenu du registre r1, préalablement décalé de un bit vers la gauche, dans le
registre r0. Du fait que l’instruction contient le suffixe S, elle est en mesure de changer le contenu du registre
d’état courant du programme. Le bit 31 du registre r1 est ainsi copié dans le bit de carry.
Avant :
PSR = nzcvq (Majuscule: mise à un, minuscule: mise à zéro)
r0 = 0x00000000
r1 = 0x80008001
Après :
PSR = nzCvq
r0 = 0x00010002
r1 = 0x80008001
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
cpsr r1
r0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Figure 70 : L’instruction MOVS avec une opération de décalage LSR #1. Le bit 31 est copié dans le bit de carry
du registre d’état courant du processeur, car l’instruction MOV a été définie avec le suffixe S.
L’adresse de la case mémoire peut être définie de plusieurs manières. Dans les cas les plus simples, cette
dernière peut être une constante ou un registre. Les modes d’adressage sont traités plus en détails dans le chapitre
suivant.
Les instructions LDRD et STRD sont des extensions du jeu d’instruction DSP. Ces dernières permettent
respectivement de charger et de stocker le contenu de deux registres juxtaposés. La condition pour Rd est un
registre avec un numéro pair. Par exemple, l’instruction STRD avec Rd = r2 stockent le contenu des registres
r3 et r4 dans la mémoire.
Exemple :
Les instructions LDR et STR permettent respectivement de charger et de stocker les données dans la mémoire. La
condition pour l’adressage de la case mémoire est son alignement avec les types de données à transférer. C'est-à-
dire que cette dernière doit être divisible par 4 pour les accès mot et paires pour les accès demi-mot.
Selon la formule de lecture ARM, les modes d’adressage sont qualifiés de « méthode d’indexation ». Il existe
fondamentalement trois méthodes d’indexations. Ces dernières peuvent être complétées avec des nombreuses
variantes de calcul du déplacement (offset).
Dans la méthode pré indexation, l’adresse de la case mémoire est obtenue par addition du contenu du registre de
base avec le déplacement. Le contenu du registre de base reste constant durant toute l’opération de transfert.
Cependant, si l’on définit une récriture en retour (write back) avec « ! », la nouvelle adresse est copié dans le
registre de base avant le transfert de données.
La méthode post indexation reprend également la nouvelle adresse, mais après le transfert de données.
Les méthodes post et pré indexation avec write back sont surtout utilisées pour les accès aux Tableaux (array).
Quant à la méthode pré indexation, elle est souvent utilisée pour accéder aux structures de données.
Exemple :
L’exemple suivant montre les accès avec les modes d’adressage selon le Tableau 21, avec les valeurs des
registres avant et après l’exécution du transfert de données. Toutes les instructions possèdent la même situation
initiale.
Avant :
r0 = 0x000000000
r1 = 0x000100000
mem32[0x00010000]=0x10101010
mem32[0x00010004]=0x20202020
Après :
r0 = 0x20202020
r1 = 0x00010004
Pré indexation :
LDR r0,[r1,#4]
Après :
r0 = 0x20202020
r1 = 0x00010000
Post indexation :
LDR r0,[r1],#4
Après :
r0 = 0x10101010
r1 = 0x00010004
Il existe de nombreuses variantes pour le calcul du déplacement (offset) : des valeurs directes, des contenus de
registre ou des contenus de registre décalés. Tableau 22 fournit une vue d’aperçu des modes d’adressage pour les
accès du type mot ou byte non signés.
Des limitations existent pour les autres instructions. Par exemple, les instructions STRSB, STRSH, STRH et
STRB ne peuvent pas utiliser le décalage à barilier (barrel-shifter). De plus le déplacement est limité à +/- 8 bits
autour de l’adresse de base.
Exemple en conclusion :
str r1, [r2, r4]! @ store r1 in [r2+r4] & copy the new address in r2
str r1, [r2], r4 @ store r1 in [r2] & copy the new address in r2
ldr r1, [r2, #16] @ load r1 with [r2+16]. r2 will not change
Exemple :
L’instruction SWP charge la donnée depuis la case mémoire 0x0001A000 dans le registre r0 et copie le
contenu du registre r1 dans la case mémoire 0x0001A000.
Avant :
r0 = 0x555555555
r1 = 0xAAAAAAAAA
r2 = 0x0001A0000
mem32[0x0001A000]=0x10101010
Après :
r0 = 0x10101010
r1 = 0xAAAAAAAAA
r2 = 0x0001A0000
mem32[0x0001A000]=0xAAAAAAAA
r0 0x55555555
0x10101010
r1 0xAAAAAAAA
0x19FFC
r2 0x0001A000
0x10101010
0xAAAAAAAA
r3
0x1A004
r4
0x1A008
r5
0x1A00C
r6
0x1A010
r7
0x1A014
r15
L’exécution des instructions LDM et STM nécessite 2+N*t cycles d’horloge ; ou N représente le nombre de
registres à transférer et t le nombre de cycle d’horloge nécessaire pour accéder à la mémoire externe. Ces
instructions sont également atomiques, c'est-à-dire qu’elles ne peuvent pas être interrompues par des
interruptions. L’utilisation des instructions LDM et STM peut par conséquent détériorer le temps de latence du
traitement des interruptions.
Le mode d’adressage définit l’accès mémoire selon le Tableau 25. Il est possible de travailler avec des adresses
croissantes (incrémentation) ou décroissantes (décrémentation). De plus, le calcul de la nouvelle adresse peut
être effectué avant ou après le transfert des données. La nouvelle adresse peut également être écrite dans le
registre Rn, comme la pré-indexation avec l’écriture en retour (write back) !
Le registre Rn contient l’adresse mémoire, ou les données ont été lues ou déposées dans la mémoire. Si le
registre a été défini avec un opérateur d’écriture en retour (!), la nouvelle adresse sera écrite dans le registre,
comme avec les instructions LDR et STR. Le bloc de registre est défini entre accolades. Ce dernier contient tous
les registres, dont le contenu doit être transféré, soit par énumération avec des virgules ({r0, r3, r6}) ou
sous forme de domaine avec un trait d’union ({r0-r3}). Il n’y a pas de règle concernant la définition par
énumération des registres. Cependant, les registres avec les numéros les plus bas sont transportés en premier.
Exemple :
Les exemples suivants montrent l’effet des quatre modes d’adressage du Tableau 25, pour le stockage des
registres r0, r4 et r1 dans la mémoire à partir de l’adresse 0x00010000.
0x0000FFF0 0x0000FFF0
r9end r9end r0
r0 r1
r1 r4
r9start r4 0x00010000 r9start 0x00010000
Exercice :
Le bloc des registres r0 à r4 doit être stocké dans la mémoire à partir de l’adresse 0x0001A000, dans un
ordre croissant. Le registre r6 doit être utilisé comme pointeur et il doit contenir, à la fin de l’instruction, la
dernière adresse mémoire. C'est-à-dire qu’il doit adresser le contenu du registre r4.
Solution :
Premièrement, il faut utiliser l’opérateur d’écriture en retour (!), afin que r6 contienne la dernière adresse après
l’exécution de l’instruction. Ensuite, il faut choisir le mode incrémenté avant, pour que r6 adresse le dernier
élément. Finalement, il faut soustraire 4 à l’adresse mémoire, afin que le programme écrive le contenu du
premier registre à l’adresse 0x0001A000.
r0
r1
LDR r6,0x0001A000-4 0x00019FFC r6 start
STMIB r6!,{r0-r4} r2
0x0001A000 r0
r3
0x0001A004 r1
r6start 0x0001A000-4 r4
0x0001A008 r2
r0 0x0001A000 r5
0x0001A00C r3
r1 r6 0x0001A000-4
0x0001A010
0x0001A010 r4 r6end
r2 r7
0x0001A014
r3
r6end r4 0x0001A010 r15
Les architectures ARM permettent de configurer librement le fonctionnement de la pile. Par exemple, la pile
peut croître soit de façon ascendante (vers les adresses les plus hautes) ou de façon descendante (vers les
adresses les plus bas). Le mode d’adressage du pointeur de pile est également configurable. Ce dernier peut
adresser soit le dernier élément stocké, en mode plein (Full), ou la prochaine case libre, en mode vide (Empty).
La pile devrait être configurée soit de façon conventionnelle ou en fonction de codes déjà existants. Le standard
d’appel de procédure ARM-Thumb (ARM-Thumb Procedure Call Standard ≡ ATPCS) recommande d’utiliser la
pile en modes décroissant et plein (Full Descending). Les opérations « push » sont ainsi réalisées avec STMFD et
celles « pop » avec LDMFD (voir Tableau 26).
Le Tableau 26 illustre les différentes opérations push et pop en fonction des modes de configuration introduit ci-
dessus :
STMFD sp!,{r1,r2}
Vorher: r1=0x00000005 Nachher: r1=0x00000005
r2=0x00000006 r2=0x00000006
sp=0x00020014 sp=0x0002000C
Exemple :
L’opération push avec STMED a pour conséquence, que le pointeur de pile adresse la prochaine case vide de la
pile.
STMED sp!,{r1,r2}
Vorher: r1=0x00000005 Nachher: r1=0x00000005
r2=0x00000006 r2=0x00000006
sp=0x00020014 sp=0x0002000C
L’avantage des instructions PUSH et POP par rapport à leur homologues STM et LDM, c’est qu’avec ces dernières
il suffit de définir uniquement le bloc de registre à transférer entres accolades.
L’instruction MSR permet d’écrire dans les registres spéciaux. Inversement l’instruction MRS permet de lire à
partir de ces registres.
L’accès aux registres spéciaux avec l’instruction MSR dépend néanmoins du niveau de privilège. En effet, les
programmes non privilégié ne peuvent qu’écrire dans le registre APSR. Par contre les programmes privilégiés
peuvent écrire dans tours les registres spéciaux.
Exemple :
L’activation respectivement le masquage global des interruptions peut être effectué avec le bit PRIMASK (2.5.3).
Pour cela il faut d’abord écrire la valeur souhaitée de ce bit dans un registre de travail (dans cet exemple le
registre R1). Le contenu de ce registre peut ensuite être transféré dans le registre PRIMASK.
Avant :
PRIMASK = P
Après :
PRIMASK = p
Cet exemple part du principe que le programme est exécuté au niveau privilégié.
Les instructions de comparaison et de test sont des opérations arithmétiques spéciales. En effet, ces dernières ne
modifient que les bits de condition du registre PSR en fonction du résultat de l’opération. Les bits de condition
constituent la base de décision pour l’exécution des instructions conditionnelles.
La spécialité des processeurs ARM est que leurs instructions arithmétiques et logiques ne modifient pas ses bits
de condition. Toutefois, cela peut être forcé avec le suffixe « S » qu’il faut ajouter à la fin de l’instruction. Les
opérations étendues, comme par exemple les instructions DSP (QADD, SMLA etc.) qui ne font pas partie du jeu
d’instruction du noyau, ne peuvent pas être complétées avec ce suffixe. Elles ne sont donc pas en mesure de
modifier les bits de condition.
Instruction Description
ADD Adition 32 bits
ADC Adition 32 bits avec report de la retenu
AND Opération logique ET
BIC Effacer un bit (NICHT UND)
CMP Comparaison
CMN Comparaison inverse
EOR Opération logique OU exclusive
MUL Multiplication 32 bits
MLA Multiplication et addition 32 bits
ORR Opération logique OU
QADD Addition 32 bits avec protection contre les dépassements de capacité (DSP)
QDADD Addition 32 bits avec protection contre les dépassements de capacité (DSP)
QDSUB Soustraction 32 bits avec protection contre les dépassements de capacité (DSP)
QSUB Soustraction 32 bits avec protection contre les dépassements de capacité (DSP)
RSB Soustraction 32 bits inversé
RSC Soustraction 32 bits inverser avec report de la retenu
SMLA Multiplication 16 bits / addition 32 bits signées (DSP)
SMLAL Multiplication 16 bits / addition 64 bits signées (DSP)
SMLAW Multiplication 32x16 bits / addition signées (DSP)
SMUL Multiplication 16 bits signée (DSP)
SMULW Multiplication 32x16 bits signée (DSP)
SUB Soustraction 32 bits
UMLULL Multiplication 32 bits non signée
Tableau 27 : Aperçu des instructions arithmétiques et logiques.
Cette famille comprend également celles de comparaison et de test.
L’opérande N peut être une valeur direct (constante définie avec #), la valeur d’un registre ou le contenu d’un
registre décalé. Les résultats 64 bits sont toujours déposés dans une paire de registres.
Les suffixes <x> et <y> peuvent être soi B (Bottom) ou T (Top). Ces suffixes permettent de limiter les bits de
l’opérande : B correspond au 16 bits de poids plus faibles [0 - 15] et T au 16 bits de poids plus fort [16-31] de ce
dernier. Ici <x> se réfère au registre Rm et <y> au registre Rs.
Certaines instructions DSP possèdent un préfixe Q qui spécifie un traitement avec de l’arithmétique saturée. Ce
qui signifie que les dépassements de capacité ne sont pas possible durant les opérations arithmétiques. Dans ce
cas, lorsqu’il y a dépassement de capacité, le résultat est la valeur signée maximale ou minimale qui peut être
représentée avec 32 bits.
Du fait que l’opérande N peut prendre plusieurs formes, il est logique de pouvoir réaliser la soustraction
également à l’envers. Les instructions de soustraction à l’envers RSB et RSC sont mise à disposition pour cela.
Les instructions CMP, CMN, TEQ, TST réalisent respectivement les opérations SUB, ADD, EOR et AND. Toutefois
ces dernières ne stockent pas le résultat dans un registre. Les instructions MUL et MLA ont la limitation suivante :
le registre Rd ne peut pas être PC ou Rn.
Seules les opérations de comparaison sont en mesure de modifier les bits de condition du registre APSR. Par
défaut, les opérations arithmétiques ne peuvent pas modifier ces bits. Toutefois, si cela devrait être le cas, il
faudrait ajouter un suffixe « S » à la fin de ces dernières.
Exemple :
L’instruction SUB permet de réaliser une simple soustraction. Le code suivant soustrait la valeur du registre R2
à celle du registre R1 et stocke le résultat dans le registre r0
Avant :
R0 = 0x00000000
R1 = 0x00000005
R2 = 0x00000002
Après :
R0 = 0x00000003
Exemple :
Le jeu d’instruction ARM ne comprend pas les instructions d’inversion. Cela peut être réalisé avec une avec une
soustraction inversée de la manière suivante : –z=0-z.
Avant :
R0 = 0x00000002
RSB 0, r0, #0
Après :
R0 = 0xFFFFFFFE
Exemple :
Une boucle de compteur peut être réalisée facilement avec l’instruction SUBS. Le code suivant décrémente la
valeur du registre R1. Le résultat (qui dans cet exemple est nul) est stocké dans le registre R1. Parallèlement, Le
bit de condition Z du registre d’état APSR est mis à un. Ce qui pourrait être la condition de base pour un saut
conditionnel.
Avant :
APSR = nzcvq
R1 = 0x00000001
SUBS R1, R1
Après :
APSR = nZcvq
R1 = 0x00000000
Exemple :
Les processeurs ARM avec l’extension E possèdent de nombreuses instructions DSP. Cela concerne également
des opérations, qui ne traitent qu’une partie de l’opérande. Le suffixe B permet dans ces cas de spécifier le demi-
mot inférieur (bits 0-15) et le suffixe T le demi-mot supérieur (bits 16-31).
Avant :
R0 = 0x11111111
R1 = 0x22222222
R2 = 0x33334444
R3 = 0x11112222
Après :
R0 = 0x159E1D95
R1 = 0x22222222
R2 = 0x33334444
R3 = 0x11112222
L’opérande N peut être une valeur direct (constante avec #), le contenu d’un registre ou le contenu d’un registre
décalé. Contrairement aux nombreux autres processeurs, les opérations logiques n’influencent pas par défaut les
bits de conditions du registre d’état APSR. Si toutefois cela devrait être le cas, il faudrait ajouter un suffixe S à la
fin du code d’opération.
Exemple :
L’instruction BIC permet d’effacer certains bits dans un mot sans influencer les autres. Dans l’exemple suivant,
tous les bits de R2, dont la valeur correspond à « 1 », sont effacés dans le registre R1 et le résultat est stocké
dans R0.
Avant :
R1=0b1111
R2=0b0101
Après :
R0 =0b1010
Exemple :
L’instruction ORR réalise une opération logique OU. Dans l’exemple suivante, cette opération est réalisée entre
les valeurs des registres R1 et R2 et le résultat est stocké dans R0.
Avant :
R0=0x00000000
R1=0x10305070
R2=0x02040608
Après :
R0=0x12345678
Le jeu d’instructions ARM ne soutient pas directement ce type d’opération. Toutefois, ces dernières peuvent être
réalisées indirectement à l’aide de l’instruction MOV (voir chapitre 4.3.1). Par contre le jeu d’instruction Thumb
possède ses propres instructions de décalage et de rotation (voir Tableau 30).
L’opérande N peut être une valeur direct (constante avec #), le contenu d’un registre ou le contenu d’un registre
décalé.
Exemple :
Le contenu du registre R0 doit être déplacé de un bit vers la gauche. Ce qui correspond à une multiplication par
2. L’instruction LSL est la solution la plus simple pour ce problème.
Avant :
R0 = 0x00004321
APSR = nzCvq
Après :
r0 = 0x00008642
APSR = nzcvq
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 1
cpsr r0
r0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 1 0 0
n z c v 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Un saut de programme conditionnel n’est exécuté que si certaines conditions (bit de condition) sont réalisées. Par
contre, un saut de programme inconditionnel est toujours exécuté.
Les sauts de programme absolus engendrent des sauts à des adresses fixes. Ce type de sauts n’est pas soutenu
directement par les processeurs ARM. Les sauts de programme relatifs engendrent des sauts relatifs, c'est-à-dire
avec un certain déplacement (offset) par rapport à l’adresse courante. Ce déplacement est calculé
automatiquement par l’assembleur. Les processeurs ARM soutiennent uniquement les sauts relatifs.
Saut absolu (par exemple Intel x86, Motorola 68x mais pas ARM!)
Départ du programme à l’adresse 0x0000 : Le même programme est démarré à l’adresse 0x2000 :
0x0000
0x0000 prog. Start
...
...
JMP 0x6000 0x2000 prog. Start
... ...
... ...
JMP 0x6000
...
0x6000 instruktion 1
0x6000 instruktion x
0x8000 instruktion 1
Le programme de gauche, qui est exécuté à partir de l’adresse 0x0000 (Figure de gauche), saute à l’adresse
0x6000 après l’instruction « JMP 0x6000 ». Ce qui est juste. Si ce même programme est exécuté à partir de
l’adresse 0x2000 (Figure de droite), le programme saute toujours à l’adresse 0x6000 après l’instruction
« JMP 0x6000 ». Ce qui est faux.
Les programmes, qui contiennent des sauts absolus, ne peuvent pas être déplacés dans la mémoire.
Départ du programme à l’adresse 0x0000 : Le même programme est démarré à l’adresse 0x2000 :
0x0000
0x0000 prog. Start
...
...
B label1 Offset 0x2000 prog. Start
... ...
... ...
B label1 Offset
label1: ...
0x6000
0x6000 instruktion x
0x8000 label1:
Le programme, qui est exécuté à partir de l’adresse 0x0000 (Figure de gauche), exécute un saut relatif (Branch)
d’une certaine distance (offset) à l’étiquette label1. Si ce programme est déplacé dans la mémoire (image de
droite), alors il continuera de fonctionner correctement. Car la distance entre l’instruction « B lable1 » et
l’étiquette lable1 reste constante.
Les programmes, qui contiennent des sauts relatifs, peuvent être déplacés dans la mémoire.
<B|BL> <label>
BX Rm
BLX Rm
Les instructions B et BL nécessitent une étiquette (label). En tant que constante symbolique, cette dernière
représente l'adresse à laquelle le programme doit se poursuivre. L’assembleur calcul la distance (offset) entre
l’adresse de destination et l’adresse de l’instruction en cours d’exécution. Du fait que cet offset doit être stocké
avec 24 bits dans le code d’opération, la destination doit se situer dans une zone de 16 MB par rapport à
l’instruction en cours d’exécution.
Les instructions BX et BLX réalisent le saut selon le contenu du registre Rm. Cela s’avère très utile, lorsque
l’adresse de destination est calculée durant l’exécution du programme ou lue à partir d’un tableau.
Les appels de sous routine (sous-programme) doivent être réalisés avec l’instruction BL. En effet, BL stocke
également l’adresse de l’instruction suivante dans le registre de lien LR (R14). Cette dernière peut alors servir
d’adresse de retour depuis la sous-routine.
A l’origine, le bit [0] du registre Rm est destiné aux changements de jeu d’instructions : ARM ou Thumb.
Concrètement, si l’on veut effectuer un saut dans une sous routine programmée avec le jeu d’instruction Thumb,
le bit [0] du registre Rm doit être 1. Inversement, si l’on veut effectuer un saut dans une sous routine programmée
avec le jeu d’instructions ARM, ce dernier doit être 0. Par conséquent, les processeurs ARM réalisent le saut
absolu selon l’adresse Rm & 0xFFFFFFFE.
Attention : Du faut que le processeur Cortex-M4 ne fonctionnent qu’avec le jeu d’instruction Thumb2, le bit [0]
du registre Rm doit être toujours être 1.
Exemple :
Les sauts de programme peuvent être réalisés soit en avant ou en arrière. Le calcul de l’offset est effectué
automatiquement par l’assembleur. L’exemple suivant illustre des sauts non conditionnels. Le premier saut
franchit les trois opérations d’addition et arrive directement à la première soustraction. Le deuxième saut exécute
un retour en arrière et réalise ainsi une boucle infinie.
B ahead
ADD R1, R2, #4
ADD R0, R6, #2
ADD R3, R7, #4
ahead:
SUB R1, R2, #4
back:
ADD R1, R2, #4
SUB R1, R2, #4
ADD R4, R6, R7
B back
Exemple :
Les sous-routines sont appelées normalement avec l’instruction BL. Le retour s’effectue avec l’adresse de retour,
qui a été stockée avec BL dans le registre de lien R14 ou LR. L’exemple suivant illustre le principe des appels de
sous-routines et le retour à partir de ces dernières.
...
BL subroutine
ADD r0, r1, r2
...
subroutine:
<Subroutine code >
MOV pc, lr @ Return from subroutine
L’assembleur ARM ne prévoit pas d’instruction spécifique pour le retour à partir des sous-routines. Ce dernier
doit être réalisé soi avec l’instruction « MOV pc, lr », ou avec la lecture de l’adresse de retour depuis la pile.
<B|BL>{<cond>} label
BX{<cond>} Rm
BLX{<cond>} Rm
L’expression <cond> est le code condition sous forme de suffise selon le Tableau 32. Le reste est identique au
chapitre 4.5.2.
Exemple :
Une sous-routine doit calculer la faculté d’un nombre entier positif. Ce dernier est transmis à la sous-routine
avec le registre r0. La sous-routine doit ensuite enchaîner les multiplications et retourner le résultat avec le
registre r0.
start:
MOV r0, #5 @ Example 5!
BL FacSuB @ Compute (r0!)
B start
loop:
MUL r0, r1, r0 @ multiplication n*(n-1)*(n-2)*...*3*2
SUB r1, r1, #1
CMP r1, #1
BGT loop
endFac:
MOV pc,lr @ return to the calling subroutine
Syntaxe :
IT{x{y{z}}} <cond>
<cond> spécifie la condition de l’instruction principale et x, y, z spécifient la condition pour les instructions
suivantes selon le Tableau 32.
Exemple :
ITTE NE ; the next 3 instructions are conditional
ANDNE R0, R0, R1 ; conditional ANDNE don’t change the condition bits
ADDSNE R2, R2, #1 ; conditional ADDSNE change the condition bits
MOVEQ R2, R3 ; conditional MOV
dloop:
ITTEE EQ ; the next 4 instructions are conditional
MOVEQ R0, R1 ; conditional MOV
ADDEQ R2, R2, #10 ; conditional ADD
ANDNE R3, R3, #1 ; conditional AND
BNE dloop ; conditional branch,
; which can be defined only at the end of the IT block
L’instruction SVC engendre un saut de programme à l’adresse 0x0000002C, dans le Tableau des vecteurs
d’interruption (SVCall).
SVC{<cond>} {<immd8>}
L’opérande de SVC est une valeur entre 0x00 et 0xFF. Ce dernier ne concerne pas le processeur mais le traiteur
d’interruptions software SVCall.
Exemple :
Le code suivant illustre génère une interruption SVC avec le numéro 0x12, qui est utilisé par le débuggeur.
Avant :
EPSR = ici/it_T_ici/it
PC = 0x00008000
LR = 0x003FFFFF
R0 = 0x12
SWI 0x12
Après :
EPSR = ici/it_T_ici/it
PC = 0x0000002C
LR = r14= 0x0008004
R0 = 0x12
BKPT {<immed16>}
CLZ{<cond>} Rd, Rm
Le registre Rm ne peut pas être le registre du compteur de programme PC. L’instruction CLZ retourne la valeur
32 lorsque Rm = 0.
Exemple :
Le nombre de bits zéro, qui précèdent dans une valeur binaire, indique de combien une valeur peut être décalée
vers la gauche, sans qu’il n’y ait un dépassement de capacité.
Avant :
r0 = 0x12
r1 = 0x1234
CLZ r0, r1
Après :
r0 = 0x13
r1 = 0x1234
Exemple :
La séquence d’instruction suivante normalise la valeur contenue dans le registre R0. Le premier bit, qui est
différent de zéro, se trouvera ainsi dans D31. L’exemple admet que la valeur à normaliser est différente de 0.
Avant :
R0 = 0x8421
Après :
R0 = 0x84210000
R1 = 0x10
5.1 Introduction
Les directives de l’assembleur sont des pseudo-instructions, qui permettent de gérer l’assemblage du programme.
Contrairement aux instructions réelles, ces dernières ne sont pas traduites en code machine.
Les directives de l’assembleur dépendent de l’assembleur. En générales les fonctionnalités suivantes sont mises à
disposition :
Contrôle de l’assembleur
Contrôle du relieur (Sections et leurs adresses de départ)
Définition des variables et des constantes
Formatage de fichiers
5.2 Aperçu
Le Tableau suivant fournit un aperçu des directives les plus importantes de l’assembleur :
Exemple :
.arm
... @ 32-Bit ARM Instruction
.thumb
... @ 16-Bit Thumb Instruction
Exemple:
main.s lcd.s
La sous-routine InitDisplay est appelée dans le fichier main.s (le chapitre 6 traite les sous-routines de
façon plus détaillée). La sous-routine InitDisplay est définie et exportée avec la directive .global dans le
fichier lcd.s. L’oubli de la directive .global génère des erreurs de relieur.
Pour des raisons de documentation, la directive .extern permet de montrer que le symbole InitDisplay a
été défini dans un autre module. La directive .extern n’est pas nécessaire pour le relieur (linker).
Exemple :
Un programme minimal en assembleur, qui exécute une boucle infinie, a été défini dans le segment .text. Le
code de démarrage doit effectuer un saut à l’étiquette start, après avoir initialisé le hardware.
.arm
.global start
.text
start: B start
5.5 .align
La directive .align permet de s’aligner sur une adresse 32 bits (modulo 4). Cette directive insère par
conséquent zéro à trois bytes. Les processeurs ARM exigent que les instructions et les données du type mot
(word) du programme soient déposées à des adresses 32 bits.
Exemple :
0000 AA55 .byte 0xaa, 0x55
0002 0000 .align @ Insertion of 2 bytes 0x00
0004 48616C6C .asciz "Hallo"
6F00
.align AL {, FB}
L’opérande AL permet de spécifier le déplacement. Ce dernier dépend de la plateforme. Par exemple, la directive
« .align 3 » n’a pas la même signification chez les processeurs ARM et i386. Cette dernière correspond
respectivement à un déplacement de huit et trois bytes. L’opérande FB permet de définir les bytes de
remplissage.
Exemple :
Le code suivant illustre les effets des directives .align. Dans cet exemple, la directive .align 3 permet de
s’aligner sur la prochaine adresse, qui est un modulo de 23 = 8.
Du fait que la directive .align dépend des plateformes, deux nouvelles directives d’alignement ont été
spécifiées par le standard GNU : balign et .p2align. Ces dernières ne dépendent plus des plateformes.
Exemple :
La directive .align 8 engendre un alignement à la prochaine adresse modulo 28 = 256:
.data
0000 55 .byte 0x55
0001 11111111 .align 8, 0x11
11111111
11111111
11111111
11111111
0100 FF .byte 0xff
0101 00000000
5.5.1 .balign
La directive .balign (byte align) fonctionne comme .align, mis à part que le paramètre d’alignement AL
est toujours défini en byte. Ce dernier décrit la valeur modulo, sur laquelle l’alignement doit avoir lieu. La
directive .balign 8 engendre, indépendamment de la plateforme, un alignement sur une adresse modulo 8
bytes.
Les directives .balignw et .balignl sont des cas particuliers de .balign. En effet, ces derniers
considèrent le byte de remplissage comme un demi-mot (2 bytes) ou un mot (4 bytes).
Exemple :
La directive .balign peut être utilisée de façon universelle. Le paramètre d’alignement doit toujours être
défini en byte et doit être une puissance de 2. Les autres valeurs engendrent un message d’erreur.
.data
0000 FF .byte 0xFF
.balign
0001 FF .byte 0xFF
.balign 2
0002 FF .byte 0xFF
0003 00 .balign 4
0004 FF .byte 0xFF
0005 000000 .balign 8
0008 FF .byte 0xFF
Exemple :
La directive « .balignw 4,0x1234 » exécute un alignement à une adresse modulo 4. Si un déplacement de
2 bytes a lieu, il sera rempli avec la valeur 0x1234. La déposition des bytes dans la mémoire dépend du mode
boutiste du processeur. Les déplacements de un ou trois bytes engendrent des messages d’erreurs.
.data
0000 FFFFFFFF b1: .byte 0xFF, 0xFF, 0xFF, 0xFF
0004 .balignw 4, 0x1234
0004 FFFF b2: .byte 0xFF, 0xFF
0006 3412 .balignw 4, 0x1234
5.5.2 .p2align
La directive .p2align (power of 2 align) fonctionne comme .align, mis à part que le paramètre de
déplacement AL y est défini sous la forme exponentielle. Cette dernière fournit la valeur modulo, sur laquelle il
faut s’aligner. La directive « .p2align 3 » engendre, indépendamment de la plateforme, un alignement sur
une adresse modulo 8 bytes.
Les cas particulier sont ici .p2alignw et .p2alignl. Ces derniers considèrent les bytes de remplissage
respectivement comme des demi-mots et des mots.
Exemple :
La directive « .p2align 3 » se réfère toujours sur des adresses, qui sont des multiples de 8. Les espaces de
déplacement sont remplis avec des valeurs nulles.
.data
0000 10 b1: .byte 0x10
0001 00000000 .p2align 3
00000
0008 33 b2: .byte 0x33
Exemple :
Les directives « .p2alignw 2, 0x1234 » génèrent des déplacements sur des adresses, qui sont des
multiples de 4. Les espaces de déplacement du type mot sont remplis avec la valeur 0x1234. La suite des bytes
dépend du mode boutiste du processeur. Les déplacements de un ou trois bytes engendrent des messages
d’erreurs.
.data
0000 FF b1: .byte 0xFF
0001 565656 .p2align 2, 0x56
0004 FFFF b2: .byte 0xFF, 0xFF
0006 3412 .p2alignw 2, 0x1234
Exemple :
0000 48616C6C .ascii "Hello"
6F
0005 57656C74 .asciz "World!"
2100
5.7 .byte
La directive .byte permet d’insérer des constantes et des variables du type byte dans le code. Plusieurs valeurs
séparées par des virgules peuvent être définies. Ces dernières sont déposées dans le même ordre dans la
mémoire.
La syntaxe de .byte est la suivante :
L’étiquette (label) représente le nom de la constante. Cette dernière est utilisée comme adresse symbolique afin
de pouvoir accéder à la constante. Les valeurs négatives sont stockées en complément à deux. Les valeurs hexa
décimales commencent avec 0x ou 0X, les valeurs octales avec un 0 et les valeurs binaires avec 0b ou 0B. Les
valeurs décimales ne commencent avec un chiffre différent de zéro.
Exemple :
0000 416225 .byte 'A', 'b', 37
0003 20D0 .byte 0x20, -0x30
0005 F608 .byte -10, 010
0007 0A .byte 0B1010
Exemple :
Les directives .2byte et .hword sont des synonymes. Elles déposent les constantes à 16 bits en respectant
l’ordre de leurs énumérations.
.data
0000 1100 h1: .2byte 0x11
0002 33224400 h2: .2byte 0x2233, 0x44
0006 1100 h3: .hword 0x11
0008 33224400 h4: .hword 0x2233, 0x44
Exemple :
Les directives .4byte et .word sont des synonymes. Elles déposent les constantes à 32 bits en respectant
l’ordre de leurs énumérations.
.data
0000 11000000 w1: .4byte 0x11
0004 55443322 w2: .4byte 0x22334455, 0x66
66000000
000c 11000000 w3: .word 0x11
0010 55443322 w4: .word 0x22334455, 0x66
66000000
5.8 .space
La directive .space réserve de l’espace mémoire pour un bloc, contenant un certain nombre de bytes. Ce
dernier est normalement utilisé pour le stockage de données.
Le paramètre AB spécifie le nombre de bytes, qui doivent être réservés pour le bloc. Le paramètre en option FB
défini le byte de remplissage, qui sera utilisé pour tout le bloc.
Exemple :
Les blocs de données sont des bytes, qui sont stockés successivement dans la mémoire. Ces derniers peuvent
également être initialisés. Dans les applications embarquées, l’initialisation de cette zone de mémoire est la tâche
du chargeur de démarrage (boot loader).
.data
00000 00000000 .space 0x10
00000000
00000000
00000000
00010 AAAAAAAA .space 0x10000, 0xAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
AAAAAAAA
10010 00000000 .space 0x1234
00000000
00000000
00000000
00000000
5.9 .include
La directive .include copie du code le contenu du fichier à inclure dans le code source. Cette opération est
effectuée durant le processus d’assemblage du programme. Le fichier à inclure doit se trouver dans le répertoire
courant du projet. C'est-à-dire celui, qui contient les fichiers objets. L’option –I de la ligne de commande permet
de spécifier le chemin d’accès aux fichiers à inclure.
Exemple :
.include "pxa270.s"
Exemple :
1 .text
2 .equ newline, 0xa
3 .set bitmask, 0b10010111
4 TAB = 011
5
6 0000 0A00A0E3 MOV R0, #newline
7 0004 9710A0E3 MOV R1, #bitmask
8 0008 011000E0 AND R1, R0, R1
9 000c 0930A0E3 MOV R3, #TAB
10
11 .equ newline, 100 /* Redefinion of newlin */
12
13 0010 6400A0E3 MOV R0, #newline
Les symboles newline, bitmask et TAB sont dotés de valeurs numériques. La programmation peut être
effectuée avec ces symboles au lieu de leurs valeurs numériques. Cette définition centrale augment la lisibilité du
code et facilite son entretient. La définition directe de valeurs numérique dans le code devrait constituer une
exception.
5.11 .org
La directive .org défini une adresse absolue, à partir de laquelle les instructions (ou les données) du programme
doivent être déposées dans la mémoire. La définition des segments avec .org constitue une exception, avec les
processeurs ARM. Cela provient du fait que les sauts de programme y sont toujours réalisés de façon relative.
Exemple :
.org 0xa0000800
mysub: ...
MOV PC, LR
Dans l’exemple de ci-dessus l’adresse de départ de la sous-routine « mysub » est fixée à 0xa0000800. En
règle générale, l’utilisation de la directive .section est beaucoup plus élégante.
5.12 .section
L’espace mémoire des instructions et des données du programme peut être partagé en plusieurs blocs (segments
ou sections). On distingue fondamentalement le segment du code et celui des données. Le segment du code
(.text) contient les instructions du programme, qui sont stockées en générale dans une ROM. Le segment des
données est composé de variables, de la pile (stack) et d’autres types de grandeurs. On distingue ici l’espace
mémoire à initialiser (.data) et celui à ne pas initialiser (.bss). Le gestionnaire d’amorçage (boot loader) est
responsable de l’initialisation de l’espace .data. Dans les programmes C, ce gestionnaire est appelé par le code
de démarrage du module crt0.s.
Plusieurs segments peuvent être regroupés dans une classe. Les classes les plus importantes sont les suivantes :
.text, .data et .bss. Dans ces classes il est possible de définir des sous classes (segments).
Des nouveaux segments ne doivent en générales pas être définis dans des petits programmes. En générale les
classes prédéfinies, .text, .data et .bss, sont suffisent pour ces derniers. La définition des nouveaux
segments est en revanche nécessaire avec les programmes plus complexes, qui utilisent des modules étrangers.
La directive .section permet de définir des nouveaux segments, ou de modifier ceux qui existent déjà. Toutes
les instructions, qui suivent la directive .section, sont affectées à ce dernier. Contrairement à .org,
l’attribution de l’adresse de départ au segment n’est pas absolue. Cette dernière est réalisée par le processus de
reliage (linker).
Dans l’environnement de développement CARME, l’attribution des adresses absolues aux segments .section
s’effectue avec le script de reliage « ldscript_ram ».
Le symbole <NomSection> permet de définir un nouveau segment. Le drapeau "flag" permet de spécifier
les droits d’accès sur le segment. Le Tableau 35 contient les différents drapeaux et leur signification. Les droits
d’accès des classes de bases restent valables en absence de spécification.
Flag Signification
A Segment pour l’allocation de mémoire
W Segment en écriture
X Segment contenant du code exécutable
Tableau 35 : Droit d’accès des segments
Les changements de segment, qui ne sont pas en rapport avec les segments de base, doivent être définis avec la
directive .section. Cette dernière peut être omise avec les classes de base :
.text
.data
.bss
.section .subroutines, "x"
.section .text @ Synonyme to .text
.section .startup
.section MySection, "w"
Exemple :
Lorsque des nouvelles sections sont nécessaires, il faut les définir dans le module assembleur. Dans l’exemple
suivant, deux sous-routines sont définies dans le segment « subroutines » :
.text
start:
BL mysub1
BL mysub2
B start
.section .subroutines
mysub1:
MOV pc,lr
.data
b1: .byte 0x22,0x33
msg: .asciz ″Hello″
.section .subroutines
mysub2:
MOV pc,lr
L’affectation du segment subroutines aux classes de base (.text, .data ou .bss) est spécifiée dans le
script du relieur « stm32f4_flash.map ». Ce dernier devrait être affecté logiquement à la classe de base
.text.
Le nouveau segment subroutines doit être ajouté aux sections qui existent déjà dans le manuscrit du relieur
(voir Figure 72). Ce qui permet également de spécifier l’ordre avec lequel les sections doivent être placées dans
la mémoire. Il va sans dire que, les modifications de de manuscrit ne devrait être réalisées qu’en connaissance de
cause et en prenant toutes les précautions nécessaires. Des erreurs à ce niveau se traduisent souvent par des
craches du système.
/* Entry Point */
ENTRY(Reset_Handler)
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x1000; /* required amount of heap */
_Min_Stack_Size = 0x1000; /* required amount of stack */
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
Figure 72 : Insertion de la nouvelle section dans le manuscrit pour le relieur (linkscript)
Les adresses absolues peuvent être lues dans le plan du relieur (linkmap):
Memory Configuration
0x08000234 DMA2_Stream0_IRQHandler
0x08000234 vApplicationMallocFailedHook
0x08000234 TIM4_IRQHandler
0x08000234 I2C1_EV_IRQHandler
0x08000234 DMA1_Stream6_IRQHandler
0x08000234 DMA1_Stream1_IRQHandler
0x08000234 UART4_IRQHandler
0x08000234 TIM3_IRQHandler
0x08000234 RCC_IRQHandler
0x08000234 TIM8_BRK_TIM12_IRQHandler
…
0x08000234 OTG_FS_IRQHandler
0x08000234 SPI3_IRQHandler
0x08000234 DMA1_Stream4_IRQHandler
0x08000234 I2C1_ER_IRQHandler
0x08000234 DMA2_Stream6_IRQHandler
0x08000234 DMA1_Stream3_IRQHandler
*(.subroutines)
.subroutines 0x08000238 0x4 src\main.o
*(.rodata)
*(.rodata*)
*(.glue_7)
5.13 .end
La directive .end indique la fin du code source. Toutes les instructions, qui suivent .end, sont ignorées par
l’assembleur. La directive .end est facultative avec l’assembleur ARM_GNU.
Exemple :
mysub:
...
MOV pc, lr @ dernière instruction
L’assembleur GCC connaît deux opérateurs signés, qui sont équivalent à ceux de C.
Opérateur Descriptions
- Inversion arithmétique (complément à 2)
~ Inversion binaire (complément à 2)
Tableau 37: Les opérateurs signés de l’assembleur
Les opérateurs arithmétiques et logiques sont décrits dans le Tableau 38. La priorité de ces opérateurs est
identique à celle des opérateurs ANSI-C.
Le résultat d’une opération de comparaison est toujours une valeur numérique. La valeur -1 représente ainsi vrai
et celle 0 faux. Toutes les opérations de comparaisons permettent de comparer des valeurs non signées.
Les opérations logiques livrent une valeur 1 correspond à vrai et celle 0 à faux.
Les calculs arithmétiques ne sont pas possibles si les opérandes se situent dans des sections différentes.
.if <ExpressionLogique>
...
{.else}
.endif
Lorsque la condition de l’expression « ExpressionLogique» est vrai, le bloque .if sera assemblé. Si non
c’est le bloque .else, qui sera assemblé.
Exemple :
La macro suivante permet de décaler a, soit vers la gauche ou vers la droite, en fonction du signe de b.
.macro SHIFTLEFT a, b
.if \b < 0
MOV \a, \a, ASR #-\b
.else
MOV \a, \a, LSL #\b
.endif
.endm
.ifdef <Symbole>
...
{.else}
.endif
Exemple :
Du code supplémentaire peut être inséré dans un programme, afin de pouvoir le tester. Dans l’exemple suivant,
le point d’arrêt n’est exécuté que si le symbole DEBUG a été défini auparavant.
.set DEBUG, 1
start:
LDR r0, =0x80
.ifdef DEBUG
BKPT
.endif
LDR r1, =0x80
.ifndef <Symbole>
...
{.else}
.endif
Le nom doit remplir les critères, qui régissent les noms des symboles. La convention veut que l’on définisse les
noms de macro avec des majuscules. Les arguments représentent formellement les paramètres, comme pour une
fonction C. L’accès à ces paramètres doit s’effectuer dans la macro avec un caractère back slash ‘\’. Une
instruction macro peut être quittée avec la directive .exitm. Cela est toutefois en désaccord avec la
programmation structurée, mais peut dans certain cas simplifier le codage de la macro.
Exemple :
.exitm permet de définir une macro sans la directive .else.
Avant:
r0 = 0x80
r1 = 0x80
.macro SHIFTLEFT a, b
.if \b < 0
MOV \a, \a, ASR #-\b
.exitm
.endif
MOV \a, \a, LSL #\b
.endm
SHIFTLEFT r0, 2
SHIFTLEFT r1, -2
Après:
r0 = 0x200
r1 = 0x20
Exemple :
La macro ADR est une macro prédéfinie pour charger l’adresse des objets. Cette dernière exécute
automatiquement le calcul de l’offset par rapport au registre du compteur de programme pc. La macro peut être
utilisée comme une instruction du processeur.
Exemple :
Le chargement de l’adresse de base d’un Tableau peut être effectué très simplement avec la macro :
.text
...
ADR r0,Tab
...
.data
Tab: .word 0, 1, 2, 3, 4
.rept <Quantite>
{BlocCode}
.endr
Exemple :
Les mots 0x55AA55AA, 0x00000000, 0xAA55AA55, 0x11111111 doivent être insérés 16 fois dans la
mémoire. La directive .rept permet de réaliser cela de façon compacte :
.rept 16
.word 0x55AA55AA
.word 0x00000000
.word 0xAA55AA55
.word 0x11111111
.endr
Le débuggeur permet de voir, comment ces valeurs ont été déposées dans la mémoire :
Exemple :
.rept permet également de générer un Tableau contenant des valeurs pré calculées à l’aide de symbole.
L’exemple suivant montre la construction d’un Tableau du type byte contenant des valeurs allant de 0 à 255.
Exemple :
La directive .irp permet de répéter un bloc en fonction d’une liste de paramètres. Le code suivant additionne
.irp param, 1, 2, 3, 4
MOV r1, #\param
ADD r0, r0, r1
.endr
.irp param, 1, 2, 3, 4
MOV r1, #\param
ADD r0, r0, r1
.endr
6.1 Introduction
Les sous routines sont des sous-programmes en assembleur. Ils correspondent, par conséquent, aux fonctions ou
aux procédures des langages de programmation évolués.
Dans les langages de programmation évolués, le compilateur gère l’appel des sous programmes (callee) à partir
des programmes appelants (caller). Cela n’est malheureusement pas le cas dans les langages de programmation
en assembleur : ici le programmeur est l’unique responsable du déroulement de cet appel. Typiquement, les
problèmes suivants doivent être considérés par ce dernier :
Appel de la sous routine et retour depuis cette dernière
Echange de données entre la fonction appelante et la sous routine (transfert des paramètres)
Les variables locales
subr1:
n. instruction
retour
suivantes.
Dans l’architecture ARM, les appels de sous routine sont réalisés avec les instructions « Branch-and-Link »
(BL). Cette instruction initialise le compteur de programme (PC) avec l’adresse de départ de la sous routine et
stocke simultanément l’adresse de retour (l’adresse de l’instruction qui suit l’appel de la sous routine dans le
programme appelant) dans le registre de reliage (LR).
main:
BL subr1 @ branch to subr1 & copy the return address in LR
... @ next instruction
subr1:
... @ instructions of the subroutine
MOV pc, lr @ return from the subroutine
L’instruction « MOV PC, LR » est mise à disposition pour le retour depuis la sous routine. Le contenu du
registre de reliage (l’adresse de retour) est ainsi copié dans le compteur de programme. Cette opération peut
également être définie de la manière suivante : « MOV R15, R14 ».
La sous routine ne devrait pas modifier le contenu du registre de reliage, car ce dernier contient l’adresse de
retour. En particulier, la sous routine ne devrait pas appeler d’autre sous routine, sans sauver préalablement le
contenu du registre de reliage.
subr1 subr2
n. instruction n. Instruction
i t
retour retour
Une sous routine peut s’appeler elle-même, générant ainsi un code récursif. Avec les codes récursifs, il faut faire
attention à la condition d’arrêt. Si non, la pile (stack) pourrait déborder.
Les adresses de retour doivent être systématiquement déposées sur la pile (stack), avec les appels emboîtés de
sous routines. Pour cela il faut :
a) Définir le domaine de la pile avec un le plan de mémoire
b) Initialiser de la pile
La pile est une structure de donnée dynamique, qui fonctionne selon le principe « Last In, First Out ». Les
opérations suivantes sont mises à disposition pour gérer cette dernière :
Initialisation
Push (poser un élément sur la pile)
Pop (chercher le dernier élément de la pile)
Un nouvel élément est posé sur la pile avec l’opération « push ». L’élément, qui a été déposé en dernier sur la
pile, peut être recherché avec l’opération « pop ». Ces opérations modifient la taille de la pile.
Début de la pile
1. Elément
2. Elément
3. Elément
Pointeur de pile
push pop
Le pointeur de pile (stack pointer) adresse la fin de la pile, c. à d. l’endroit où la prochaine opération aura lieu.
Le registre R13 sert de pointeur de pile dans les processeurs ARM. Il est également possible de définir « SP » au
lieu de R13.
Par conséquent, il est important que la même version de la pile soit utilisée par le compilateur et le programmeur
en assembleur. Le standard d’appel de procédure ARM (ARM Procedure Call Standard, APCS, voir chapitre
6.5) spécifie pour la pile le mode de fonctionnement « full descending ». Les opérations push sont alors
réalisées avec les instructions STR (sauvegarder uniquement du contenu du registre de reliage) et STMFD ("store
multiple full descending", afin de pouvoir sauver plusieurs registres simultanément). Les opérations pop peuvent
être réalisées soit avec les instructions LDR (recherche uniquement du contenu du registre de reliage) et LDMFD
("load multiple full descending", afin de rechercher le contenu de plusieurs registre à partir de la pile).
L’instruction STMDB ("store multiple decrement before") peut être également utilisée au lieu de STMFD. Car les
deux instructions sont équivalentes. Pour les même raisons, l’instruction LDMFD peut être remplacée par
LDMIA ("load multiple increment after").
main:
BL subr1 @ branch to subr1 & copy return address in LR
... @ next instruction
subr1:
STR lr, [sp, #-4]! @ push lr on the stack
... @ code subroutine 1
BL subr2 @ call subroutine 2
LDR pc, [sp], #4 @ return: pop return adress from stack
L’instruction STR décrémente d’abord le pointeur de pile de 4 bytes (la pile croît vers le bas). L’adresse de
retour du registre LR est ensuite déposée sur la pile.
L’opération contraire est réalisée à la fin de la sous routine : L’adresse de retour est d’abord transférée depuis la
pile dans le registre compteur de programme pc. Le pointeur de pile est ensuite corrigé de 4 bytes vers le haut.
Le programme retourne finalement dans la routine appelante. C'est-à-dire, qu’il exécute l’instruction qui suit
l’appel de la sous routine. Cette dernière se situe dans l’exemple de ci-dessus à l’adresse 0x2004.
L’initialisation de la pile exige d’une part la définition d’un bloc de mémoire et d’autre part l’initialisation du
pointeur de pile. L’extrait de code suivant illustre cela pour le pointeur de pile principale (MSP) :
Exemple :
Manuscrit du relieur (stm32f4_flash.map) :
/* highest address of the main mode stack */
_estack = 0x20002000
L’adresse de base de la pile principale (main stack) est définie dans le manuscrit du relieur (link script). Du fait
que selon le standard APCS la pile (stack) doit croître vers le bas, cette adresse de base correspond à l’adresse de
la fin du bloc de mémoire réservé pour la pile.
L’adresse de base de la pile est le premier vecteur du tableau de vecteurs d’interruption (voir chapitre 7.7). Cette
dernière est copiée automatiquement dans le registre MSP Au démarrage du système (voir chapitre 10.1).
Remarque : Les processeurs ARM peuvent travailler avec des piles différentes (voir chapitre 2.5: Le modèle des
registres). Le Cortex-M4 possède encore un pointer de pile de processus (PSP), qui n’est pas initialisé dans notre
code de démarrage.
Les instructions pour les appels et les retours de sous routines sont fournies dans le Tableau suivant :
sp return return
0x3200 Adr 1 Adr 1
sp return
0x31FC Adr 2
sp
0x31F8
Stack a) b) c)
Programme principale :
start: MOV r4, #0 @ initialize loop variable r4
loop: BL subr3 @ branch to subroutine
ADD r4, r4, #1 @ increment loop variable
CMP r4, #10 @ check if 10 loops done
BNE loop @ no branch to label loop
...
Sous routine 3 :
subr3: STR lr, [sp, #-4]! @ push lr on the stack
...
MOV r4, #0 @ modify r4
...
LDR pc, [sp], #4 @ return
Le problème de cette séquence de code est que le contenu du registre r4, qui est utilisé dans le programme
principal pour le stockage de la variable de compteur, est modifié dans la sous routine subr3 !
Dans ce cas la règle est le principe des contributions : Les contenus des registres, qui sont modifiés par la sous-
routine, doivent être sauvés au début et restitués à la fin de cette dernière (exception voir chapitre 6.5). Les
instructions du Tableau 39 sont mises à dispositions pour ces opérations. Par conséquent, dans l‘exemple de ci-
dessus, la sous routine subr3 doit également sauvegarder le contenu du registre r4.
Remarque : L’instruction LDMFD copie directement l’adresse de retour dans le registre pc.
Les fonctions C et les sous routines en assembleur peuvent posséder plusieurs paramètres. Il est donc important
que le compilateur C et le programmeur en assembleur respecte les mêmes directives. Le standard d’appel de
procédure ARM (ARM Procedure Call Standard, APCS) fixe les règles de transmission des paramètres pour les
processeurs ARM (voir chapitre 6.5) :
Les paramètres sont gérés à l’aide d’une liste. Les 4 premiers paramètres sont transmis avec les registres r0
à r3. Les autres paramètres sont transmis, dans un ordre inversé, à l’aide de la pile.
Call by Value:
Une copie de la valeur originale est transmise en tant que paramètre à l’aide des registres (r0 à r3). Dans ce cas
la sous routine ne pas accéder à la variable originale.
Call by Reference:
L’adresse de la variable est transmise en tant que paramètre à l’aide des registres (r0 à r3). Dans ce cas la sous-
routine peut accéder à la variable originale.
Exemple :
Considérons comme exemple une sous routine « sum », qui possède 3 paramètres. Cette dernière peut être
définie da la manière suivante en C :
int op1 = 2;
int op2 = 5;
int res;
main:
ADR r0, op1 @ copy address op1 into r0
LDR r0, [r0] @ read value op1, 1st parameter by value
ADR r1, op2 @ copy address op2 into r1
LDR r1, [r1] @ read value op2, 2nd parameter by value
ADR r2, res @ read address res, 3rd parameter by reference
BL sum
Remarque : Le standard APCS n’exige pas de sauver le contenu du registre r3 à l’aide de la pile (voir chapitre
6.5).
super_sum:
STMFD sp!, {r4, r5, lr} @ push r4, r5 and lr
MOV r4, r0 @ temp = p1
ADD r4, r1 @ temp += p2
ADD r4, r2 @ temp += p3
ADD r4, r3 @ temp += p4
LDR r5, [sp, #12] @ buffer = p5
ADD r4, r5 @ temp += p5
LDR r5, [sp, #16] @ buffer = p6
ADD r4, r5 @ temp += p6
MOV r0, r4 @ copy return value to r0
LDMFD sp!, {r4, r5, pc} @ pop r4, r5 and lr, return
SP 6. 6.
0x3200 Operan Operan
d d
5. 5. 16
Operan Operan
d d
12
SP
lr
0x31F8
r5 8
r4 4
SP 0
0x31EC
6.5 APCS
Le standard d’appel de procédure ARM (ARM Procedure Call Standard, APCS) fixe l’utilisation des registres de
la CPU. Cette convention est importante, afin que les compilateurs et les assembleurs puissent utiliser les mêmes
conventions pour la transmission des paramètres, la sauvegarde des registres etc.
Les variables locales sont définies normalement à l’aide des registres r4 à r8, r10 et r11. Lorsque des
variables locales supplémentaires sont nécessaires, il faut stocker ces dernières sur la pile. Dans ce cas une zone
de mémoire, dont la taille correspond au bloc de variables locales, est allouée sur la pile. Un registre de pointeur
de trame (Frame Pointer), qui est normalement le registre r11, est initialisé avec l’adresse de base de ce bloc.
myFunc:
STMFD sp!, {r11, lr} @ push r11 and return address, (1)
SUB sp, sp, #40 @ allocate 40 bytes local variable space (2)
MOV r11, sp @ r11 points to first local variable (3)
...
...
ADD sp, sp, #40 @ free local variable space
LDMFD sp!, {r11, pc} @ pop r11 and lr, return
SP alt
lr
r11 alt
1
1
lokaler
Variablen-
bereich 3
La zone de mémoire contenant les variables locales peut être adressée à l’aide du pointeur de trame r11 et d’un
offset positif. Le programmeur est responsable du calcul correct de cet offset, comme ce fut le cas pour le
transfert des paramètres !
Le pointeur de trame adresse la partie inférieure la zone de mémoire contenant les variables locales. Toutefois,
ce pointeur peut être initialisé afin qu’il adresse la partie supérieur de cette zone. L’offset, pour pouvoir accéder
aux variables locales, doit alors être négatifs. Une autre variante est l’utilisation du pointeur de pile au lieu du
pointeur de trame pour adresser ces variables locales.
Lorsque des variables locales sont utilisées simultanément avec une transmission de paramètre avec la pile (voir
chapitre 6.4.2), les paramètres peuvent également être adressés avec le pointeur de trame au lieu du pointeur de
pile. Remarque : certain compilateur C initialise toujours un pointeur de trame, même si la sous routine n’utilise
pas de variables locales allouées sur la pile. Le code est ainsi un peu plus lent mais, par contre, le calcul des
offsets pour l’accès aux paramètres devient beaucoup plus simple.
Les erreurs de gestion de la pile génèrent toujours des chutes de programme, car les adresses de retour ne sont
plus justes !
Il s’en suit la question : pourquoi utilise-t-on les macros. La réponse est dans le comportement temporel : un
appel de sous routine avec l’instruction « BL » et le retour depuis cette dernière nécessite plus de temps (même si
cela ne concerne que deux instructions). Dans les applications, qui sont très critiques au niveau temps, cette
diminution de la vitesse d’exécution favorise la macro.
Dans des applications standards, il faut utiliser en générale des sous routines. Alors que dans les applications, qui
sont critique au niveau temps, il faut utiliser des macros.
Remarque: Un bon design software et des bons algorithmes sont beaucoup plus efficaces pour la performance du
processeur, que le remplacement systématique des sous routines par des macros.
7.1 Introduction
Les exceptons sont des évènements qui peuvent survenir durant l’exécution d’un programme et qui requiert un
traitement par ce dernier. Les exceptions ARM peuvent être réparties en 3 catégories :
1. Les exceptions qui surviennent durant l’exécution d’une instruction. Ce type d’exception apparaît de
façon synchrone au déroulement au programme. Cette catégorie comprend les exceptions suivantes :
Les interruptions software
Les instructions non définies (l’instruction à exécuter est inconnue ou destinée à un
coprocesseur non disponible)
Prefetch Abort (exception générée à cause d’une erreur d’accès mémoire durant la lecture de
l’instruction)
2. Les exceptions qui apparaissent comme effet secondaire à une instruction. Ce type d’exception apparaît
également de façon synchrone au déroulement au programme. Les exceptions, qui composent cette
catégorie, sont les suivantes :
Data Abort (exception générée à cause d’une erreur d’accès mémoire durant la lecture ou
l’écriture d’une donnée)
3. Les exceptions générées de façon externe. Ce type d’interruption apparaît de façon asynchrone au
déroulement du programme. C’est à dire que le programme peut être interrompu à n’importe quel
instant. Les exceptions qui composent cette catégorie sont les suivantes :
Reset (mise sous alimentation, appui de la touche reset ou activation du « watch dog »)
Les interruptions hardware (Interrupt Request IRQ)
Lorsqu’une exception survient dans un système, ce dernier doit appeler une sous-routine pour son traitement.
Dans ce contexte on parle de « traiteur d’exception » (Exception Handler) ou pour le cas d’interruption hardware
de « routine de service d’interruption » (Interrupt Service Routine : ISR).
Le déroulement du programme est identique pour les trois catégories d’interruption : le programme principal est
interrompu et l’exception est traitée par le traiteur d’exception. Une description exacte du déroulement est
donnée aux chapitres 7.7et 7.8.
Première
instruction
Exception
Instruction
courante
Instruction
suivante
Dernière
instruction
Les interruptions software sont souvent utilisées pour appeler des fonctions du système d’exploitation. Car ces
dernières font entrer le microprocesseur dans le mode superviseur. Ces fonctions ont ainsi la possibilité
d’accéder à tous les registres.
Le chapitre 4.6.2 fournit de plus ample informations sur les interruptions software et l’instruction SVC.
Priorité
Type de Adresse du
Acronyme Description
priorité vecteur
L’activation et le masquage des exceptons/interruptions peuvent être réalisés en deux étapes dans les processeurs
Cortex-M4 (voir Figure 84). Dans un premier temps, les exceptions/interruptions peuvent être activées ou
désactivées collectivement au niveau cœur du Cortes-M4-Cores. Dans un deuxième temps, ces dernières peuvent
être activées ou désactivées individuellement au niveau du contrôleur d’interruption imbriqué (Nested Vectored
Interrupt Controller : NVIC).
NVIC Cortex-M4
Core
IRQ
Interrupt
IRQ [82] treatment
IRQ #
ISERx IPRx
ICERx
Dans le cœur du processeur Cortex-M4, les exceptions/interruptions peuvent être activées ou désactivées
collectivement avec les bits PRIMASK et FAULTMASK. Ces bits de commande se situent dans les registres
PRIMASK respectivement FAULTMASK.
Le bit PRIMASK permet de masquer toutes les exceptions/interruptions avec une priorité configurable (voir
Figure 85). Cela représente toutes les exceptions/interruptions du Tableau 41 à partir du numéro de priorité 0
(Memory management). Un 1 siginifie que ces exceptions/interrutions sont masquées et un 0 qu’elles ne les sont
pas.
Le bit FAULMASK est prévu pour le masquage de toutes les 82 interruptions hardware périphériques (voir Error!
Reference source not found.). Cela représente toutes les exceptions/interruptions du Tableau 41 à partir du
numéro de position 0 (Window Watchdog interrupt).
Les 82 interruptions hardwares périphériques peuvent être activées ou désactivées individuellement avec les
registres « Interrupt Set-Enable » (ISER0 à ISER7) respectivement « Interrupt Clear-Enable » (ICER0 bis
ICER7) du NVIC. L’affectation des bits de ces derniers est la suivante :
Figure 87 : Affectation des bits dans les registres ISERx (adresse de base : 0xE000E100),
source [Cortex-M4_Devices_GenUserGui]
Figure 88 : Affectation des bits dans les registres ICERx (adresse de base : 0xE000_E180),
source [Cortex-M4_Devices_GenUserGui]
Figure 89 : Affectation des bits des registres IPRx (adresse de base : 0xE000_E400),
source : [Cortex-M4_Devices_GenUserGui]
Ce qui de spécial avec les processeurs Cortex-M4, c’est que les champs de priorité sont composés de bits de
priorité principale (preempted or group priority bits) et de bits de priorité complémentaire (non preempted or sub
priority bits). Une exception/interruption de priorité principale supérieure peut interrompre le traitement d’une
exception/interruption de priorité principale inférieure (preemption). La priorité complémentaire n’intervient que
si deux exceptions/interruptions de priorité principale similaire apparaissent simultanément. Dans ce cas, c’est
celle de priorité complémentaire supérieure qui sera traitée en premier.
La part des bits, qui composent la partie principale respectivement la partie complémentaire des champs de bits
de priorité, peut être spécifiée avec le champ « Interrupt Priority Grouping Bits » (PRIOGROUP) du registre
« Application Interrupt and Reset Control Register » (AIRCR) selon le Tableau 42.
Avec les processeurs Cortex-M4, toutes les exceptions/interruptions sont traitées dans le mode Handler (voir
chapitre 2.4 : Les modes de fonctionnement et les niveaux de privilège). Ce qui pour avantage que certains
registres ne doivent pas être sauvés car ces derniers sont à double : un registre pour le mode Thread et un autre
registre du même nom pour le mode Handler (banked register).
Exemple :
Le programme principal est interrompu par la requête d’interruption SysTick (priorité principale programmée 3).
Durant le traitement de l’interruption par son traiteur « SysTick_Handler », apparaît une requête
d’interruption SVC (priorité principale programmé 1). Par conséquent, le traiteur SysTick_Handler sera
interrompu au détriment du traiteur « SVCall_Handler ». La Figure 90 montre le comportement temporel de
cet enchaînement d’évènements.
Priorité
SVCall_Handle
SysTick_Handle
Programme
principale Temps
System SVC
Tic
L’adresse de base du tableau des vecteurs d’interruption est normalement 0. Toutefois, avec les processeurs
Cortex-M4, ce dernier peut être déplacé dans la mémoire. Pour cela il faut spécifier la valeur du déplacement
(TBLOFF) dans le registre de déplacement de la table des vecteurs d’interruption (Vector Table Offset Register :
VTOR). La Figure 91 Illustre l’attribution des bits de ce registre.
Dans certaines familles de processeurs, le tableau des vecteurs d’interruption contient des instructions à exécuter.
Dans ce cas, lorsqu’apparaît une exception/interruption, l’instruction stockée dans le vecteur d’interruption
correspondant est exécutée par la CPU. Cette instruction est en générale un saut de programme avec un offset.
Dans d’autres familles de processeurs, le tableau des vecteurs d’interruption contient les adresses des traiteurs
d’exception (exception Handler). Le processeur charge alors cette adresse afin de réaliser le saut de programme
dans la routine de service correspondant. Les processeurs Cortex-M4 soutiennent cette variante.
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
/* External Interrupts */
.word WWDG_IRQHandler /* Window WatchDog */
.word PVD_IRQHandler /* PVD through EXTI Line detection */
.word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through the EXTI line*/
.word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */
.word FLASH_IRQHandler /* FLASH */
.word RCC_IRQHandler /* RCC */
.word EXTI0_IRQHandler /* EXTI Line0 */
.word EXTI1_IRQHandler /* EXTI Line1 */
.word EXTI2_IRQHandler /* EXTI Line2 */
.word EXTI3_IRQHandler /* EXTI Line3 */
.word EXTI4_IRQHandler /* EXTI Line4 */
…
Le Tableau des vecteurs d’exception de ci-dessus est construit de manière statique. C'est-à-dire que les adresses
ont été définies de façon fixe pendant la programmation, et sont introduites par le relieur (linker) durant la
procédure de compilation.
Lorsque le tableau des vecteurs d’exception est stocké dans une RAM, il peut être construit de façon dynamique.
Il peut ainsi être initialisé au démarrage du programme et changé durant son déroulement. La procédure est la
suivante :
1) Définition des adresses des traiteurs d’interruption
2) Ecriture de la valeur obtenue dans le tableau des vecteurs d’interrutions avec STR
Entrée :
1) Le traitement de l’exception/interruption n’a lieu que si la requête a suffisamment de priorité et soi
le processeur se trouve dans un mode Thread ou
la nouvelle exception/interruption a une priorité plus grande que celle qui est actuellement en
traitement.
Si l’exception/interruption interrompt le traitement d’une autre exception/interruption, cette dernière
sera imbriquée momentanément.
Suffisamment de priorité signifie ici que l’exception/interruption a une priorité plus grande que la limite
fixée dans les registres de masquage de priorité (voir chapitre 2.5.3).
2) Ensuite certain registres sont sauvegardé sur la pile (stack). Cette pile contient entre autre la valeur du
registre du compteur de programme (PC) avant l’apparition de l’exception/interruption. Cette valeur
correspond à l’adresse de l’instruction qui suit celle qui a été interrompue par l’exception/interruption.
A la fin du traitement de l’exception/interruption, cette valeur sera de nouveau réécrite dans le registre
PC, afin de pouvoir réaliser l’opération de retour à partir du traiteur d’exception/interruption. Lorsque la
FPU est active, les registres à virgule flottante seront également sauvegardés sur la pile (voir Figure 96).
3) Durant les opérations de sauvegarde a lieu l’opération d’appel de vecteur. Cette dernière copie l’adresse
de base du traiteur d’exception contenu dans le vecteur d’interruption dans le registre PC. Le pointeur
de pile (MSP ou PSP), le mode de fonctionnement (Thread ou Handler) et le niveau de privilège
(privilégie ou non privilégie) utilisés avant l’apparition de l’exception/interruption sont également écrits
dans le registre LR (EXC_RETURN).
Retour :
Le retour du traiteur d’exception/interruption ne s’effectue que lorsque le processeur se trouve dans le mode
Handler et s’il exécute une des instructions suivantes :
Une instruction LDM, POP ou LDR qui charge le contenu du registre LR (EXC_RETURN) préalablement
sauvegardé sur la pile dans PC.
Une instruction BX avec LR
Lorsque le registre PC reçoit la valeur EXC_RETURN, alors il connaît la pile avec laquelle les registres ont été
sauvegardés. Le processeur est ainsi en mesure de démarre la restauration des valeurs des registres sauvegardées
durant la phase d’entrée. Après cette procédure, le registre PC retrouve l’adresse de l’instruction, qu’il contenait
avant l’apparition de l’exception/interruption. Le processeur peut ainsi continuer avec l’exécution du programme
interrompu comme si rien n’était arrivé.
Lorsque le registre LR est utilisé dans le traiteur d’exception, il faut également le sauver sur la pile avec les
instructions STMFD ou PUSH. Dans ce cas, le retour du traiteur peut être programmé avec les instructions
LDMFD ou POP.
Exemple :
SysTick_Handler:
PUSH {R4-R7, LR}
…
BL Scheduler
…
POP {R4-R7, PC} @ PC=LR (return from ISR)
main:
instr 1
instr 2
Timer Tick
instr3
LR = EXC_RETURN PC = EXC_RETURN
Vector Table
Changement dans le mode Handler Retour éventuel dans le mode Thread
MSP sera fixé comme SP MSP ou PSP est fixé comme SP
SysTick_Handler:
instr a
instr b
bx lr
Dans l’exemple ci-dessus les instructions 1 et 2 sont exécutées séquentiellement. Ensuite apparaît une
interruption SysTick qui interrompt l’exécution du programme principale (toutefois l’instruction 2 est exécutée
jusqu’au bout). Ensuite la CPU lit l’adresse du traiteur d’exception SysTick_Handler à partir du tableau des
vecteurs d’interruption et exécute ce dernier afin de traiter l’interruption SysTick. Finalement l’état d’origine
est restauré et la CPU continue avec l’exécution des instructions du programme principale.
Interrupt Latency
Interrupt Recovery
Interrupt Response
Time
Le temps de latence d’interruption (Interrupt Latency) est le temps entre l’apparition de la requête d’interruption
(Interrupt Request) et l’exécution de la première instruction du traiteur d’interruption (interrupt handle) :
Le temps de réponse à l’interruption (Interrupt Response) est le temps entre l’apparition de la requête
d’interruption (Interrupt Request) et l’exécution de la première instruction de sa routine de service (ISR) :
Le temps de recouvrement de l’interruption (Interrupt Recovery) est le temps nécessaire pour revenir dans le
programme principal après exécution de la routine de service (ISR) :
Interrupt
Disable Interrupts
1 spsr_mode = cpsr
pc = vector table entry
Save context
2
Interrupt Handler
3
Interrupt Service
4
Routine
Restore context
5
Enable Interrupts
6 pc = lr - 4
cpsr = spsr_mode
return
irq_Handler:
SUB lr, lr, #4 @ adjust lr for return address
STMFD sp!, {r0-r3, r12, lr} @ save context
LDR r0, =ICIP @ load IRQ status register address
LDR r0, [r0] @ read IRQ status register
TST r0, #1<<PER_ID_OST @ test if OST caused IRQ
BLNE timer_isr @ if yes, call timer ISR
TST r0, <other source> @ test other IRQ sources
...
LDMFD sp!, {r0-r3, r12, pc}^ @ restore context and return
8.1 Introduction
Le déroulement d’un programme peut être influencé à l’aide de structure de contrôle. Pour cela il existe les
procédures suivantes :
Les séquences
Les ramifications
Les boucles
Une séquence est une suite d’instruction, qui est exécutée sans critères préalables. Alors que les ramifications et
les boucles ne sont exécutées qu’en fonction des critères externes, comme par exemple l’état du processeur.
Les langages de programmation évolués mettent à disposition des instructions standardisées pour définir des
structures de contrôle. Par exemple le langage de programmation C contient les instructions suivantes :
Dans le langage de programmation assembleur, il est également possible d’utiliser ces structures de contrôle. Ce
qui permet de mieux structurer le code et, par conséquent, de faciliter son entretient.
Contrairement aux langages de programmations évolués, l’assembleur ne met pas à disposition des instructions
pour la définition des structures de contrôle. Toutefois, ces structures peuvent être réalisées avec des séquences
d’instructions en utilisant des chablons. Les chapitres suivants montrent, comment ces structures de contrôles
peuvent être réalisées en assembleur.
if condition
true false
true- false-
block block
Lorsque la condition est réalisée, le programme exécute les instructions qui sont contenues dans le bloc
« then » ; sinon, il exécute les instructions du bloc « else ». Le bloc « else » est en option. C’est à dire
qu’une ramification simple ne peut contenir que du bloc « if ».
Exemple de code :
C Assembler
if if: TST <condition>
(condition)
{ BNE false
/* true-Block */ true: ... @ true-block
} B endif
else
{ false: ... @ false-block
/* false-Block */
} endif:
Une ramification simple sans le bloc « else » peur être programmée de la manière suivante :
C Assembler
if (condition) if: TST <condition>
{ BNE Endif
/* true-Block */ true: ... @ true-block
}
endif:
Une ramification simple contient toujours une instruction d’entrée (ici « TST »), qui modifie les bits d’état le
registre PSR (Current Program Status Register), suivie d’une instruction de saut de programme conditionnel (ici
« BNE »). Toutes les instructions, qui influencent les bits d’état, peuvent être utilisées comme instruction
d’entrée (TST, CMP, MOVS etc.) ; et toutes les instructions « Bcc » peuvent être utilisées comme sauts de
programme conditionnels.
Les architectures ARM soutiennent une autre forme de ramification simple : L’exécution conditionnelle des
instructions. Toutefois cela n’est raisonnable que pour des conditions simples, qui réunissent un à trois
En assembleur ARM, il est possible de programmer cet algorithme avec des instructions conditionnelles. Ce qui
évite l’utilisation des sauts conditionnels ou inconditionnelles de la structure proposée au Tableau 43 (les
variables a, b et c sont stockées dans les registres r0, r1 et r2) :
CMP r0, r1 @ if (a > b)
ITE GT
MOVGT r2, r0 @ c = a
MOVLE r2, r1 @ c = b
switch expression
case const-expr1
default
block
Exemple de code :
C Assembler
switch (expression) switch: MOV r0,#expression
{
Case (const-expr1): CMP r0,#const_expr1
... BEQ case1
break;
case (const-expr2): CMP r0,#const_expr2
... BEQ case2
break;
... ...
default: B default
...
break;
}
case1: ... @ const-expr1 block
B endSwitch
case2: ... @ const-expr2 block
B endSwitch
default: ... @ default block
endSwitch:
Au début de la ramification multiple, l’expression à évaluer est copiée dans un registre, afin de pouvoir être
comparée avec les constantes des étiquettes case. Lorsqu’il y a correspondance, le programme effectue un saut
à l’étiquette correspondant à la constante, pour y exécuter les instructions.
Le code peut devenir très lent, lorsque l’instruction switch contient de nombreuses étiquettes case. Dans ce
cas, il est recommandé d’utiliser des Tableaux de sauts (jump table). Ces derniers peuvent être programmés de la
manière suivante :
switch: ADR r1, jumptable @ load r1 with base address of jump table
MOV r0, #expression @ load r0 with expression to test
CMP r0, #tablemax @ check if value is in jump table
IT LO
LDRLO pc, [r1, r0, LSL #2] @ ok load pc with label form jump table
B default @ expression > tablemax default
case1: … @ const-expr1 block
B endswitch
case2: … @ const-expr2 block
B endswitch
default:
… @ default block
endswitch:
…
jumptable:
.word default @ case 0 not defined default
.word case1
.word case2
constantes (entier de 32 bits). Un Tableau de saut n’est pas adéquat pour évaluer les expressions de 1 à
10000. Cela nécessiterait un Tableau avec 10000 entrées.
La taille du Tableau doit être testée durant l’exécution du programme (CMP r0,#tablemax)
Il faut introduire dans le Tableau le label par défaut pour les valeurs non définies (l’exemple de ci-
dessus ne contient pas le cas 0. Il faut donc introduire une valeur par défaut à l’index 0 du Tableau).
Condition de la boucle
Corps de la
boucle
Exemple de code :
C Assembler
while (condition) while: TST <condition>
{ BEQ endwhile
// Corps de la boucle … @ Corps de la boucle
} B while
endwhile:
Les bits d’état courant du programme sont modifiés en fonction de l’instruction d’entrée (comme ce fut le cas
avec la ramification simple). Lorsque la condition « n’est pas réalisée », le programme effectue ensuite un saut
de programme conditionnel à la fin de la boucle. Après exécution des instructions du corps de la boucle, le
programme fait un saut de programme non conditionnel au début de la boucle, afin de réitérer l’évaluation de la
condition.
Le code du Tableau 46 n’est pas très efficace, car il nécessite deux sauts de programme (B et BEQ), qui doivent
être exécutés à chaque passage. Ce code peut être optimisé avec une réorganisation. Dans ce cas, un seul saut de
programme conditionnel (BNE) ne doit être exécuté par passage.
while: B test
loop: … @ Schleifenkörper
test: TST <condition>
BNE loop
endwhile:
Corps de la
boucle
Condition de la boucle
Exemple de code :
C Assembler
do do:
{
// Corps de la boucle … @ Corps de la boucle
} while (condition); while: TST <condition>
BNE do
enddowhile:
Les bits d’état sont également modifiés dans cet exemple en fonction de l’instruction d’évaluation (ici « TST »).
Lorsque la condition est « vraie », le programme effectue un saut de programme conditionnel au début de la
boucle. La boucle do - while correspond ainsi à la version optimisée de la boucle while, introduite au
chapitre 8.4, sans la première instruction de branchement.
Corps de la
boucle
Exemple de code :
C Assembler
for (expr1; expr2; expr3) for: MOV r0,#valeur_début
{ loop: CMP r0,#valeur_fin
// Corps de la boucle BGE endfor
} ... @ Corps de la boucle
ADD r0,r0,#pas
bra loop
endfor:
Un registre rx (dans cet exemple r0) est utilisé comme variable compteur. Au début cette variable est initialisée
avec la valeur « valeur_début ». A l’étiquette « loop », le critère d’arrêt est évalué à l’aide de l’instruction
CMP, qui teste si la variable compteur a atteint la valeur « valeur_fin ». Si cette dernière atteint cette valeur
limite, le programme fait un saut de programme conditionnel (BGE) à la fin de la boucle. Si non, le programme
exécute les instructions du corps de la boucle et incrémente la variable compteur en fonction de la valeur
« pas ».
Des autres instructions que BGE peuvent également être utilisées pour évaluer la condition d’arrêt. L’instruction
ADD peut être remplacée par SUB afin de réaliser des pas de compteur décroissant.
9.1 Introduction
Les structures de données rassemblent plusieurs variables (de type simple ou composé) dans une structure. Voici
un exemple de structure de donnée en C :
struct Adresse
{
char Nom [MAX_CHAR];
char Prenom [MAX_CHAR];
char Route [MAX_CHAR];
int Numero;
char Lieu [MAX_CHAR];
};
Les langages de programmation évolués soutiennent les structures de données. Il en résulte les avantages
suivants :
Compréhension et entretient facilité du programme
Meilleur structure
En assembleur il est également possible de profiter des avantages des structures de données. Ce chapitre traite ce
sujet.
Les structures de données sont toujours orientées problème. C'est-à-dire, qu’ils essayent de reproduire au mieux
la réalité à l’aide d’un jeu de données (par exemple les données d’un processus). Il existe également une relation
étroite entre ces structures et le code du programme. Par conséquent, la modification d’une structure aura un
effet sur le code.
La directive « .space » permet de réserver un certain nombre de bytes, qui peuvent être initialisés. Dans cet
exemple 4 * 4 bytes ont été réservés au total et chaque byte a été initialisé avec la valeur 0x12. Si le Tableau ne
doit pas être initialisé, il peut également être défini dans la section « .bss ».
L’adresse de base du Tableau est d’abord copiée dans r0. Ce dernier adresse ainsi le premier élément du
Tableau (indexe 0). L’index de l’élément à accéder est ensuit copier dans r1. L’accès au Tableau est effectué
finalement avec l’instruction STR. L’adressage y est indirect et est obtenu avec « r0 + r1 * 4 ». La
multiplication avec un facteur scalaire (défini dans cet exemple avec LSL #2) est importante, car les adresses
sont toujours calculées en byte.
Important : Un facteur scalaire est utilisé pour l’adressage indirect. Ce dernier possède les valeurs suivantes :
1 pour les bytes
2 pour les demi-mots
4 pour les mots
Le code ci-dessus doit être modifié de la manière suivant avec un Tableau, qui contient des éléments du type
byte au lieu du type mot :
.data
array: .space 4,0x34 @ array of 4 bytes,
@ which are initialized with 0x34
.text
LDR r0,=array @ load r0 with base address of array
LDR r1,#2 @ load r1 with index 2
MOV r2,#0 @ value to write is 0
STRB r2,[r0,r1] @ array[2] = 0 (Byte)
Les éléments de ce Tableau ne peuvent être stockés soit en ligne ou en colonne. Par exemple, le stockage en
ligne du Tableau, contenant 3 lignes et 4 colonnes, s’effectue de la manière suivante :
[0,0]
[0,1] 1. ligne
[0,2]
[0,3]
[1,0]
[1,1] 2. ligne
[1,2]
[1,3]
[2,0]
[2,1] 3. ligne
[2,2]
[2,3]
Adresse = 4 * i + j
Cette formule n’est valable que pour des Tableaux, qui possèdent une adresse de base nulle et 4 colonnes du type
byte. La formule générale, qui contient également l’adresse de base du Tableau et la taille des éléments, est la
suivante :
L’évaluation de cette formule peut s’avérer coûteuse, en fonction de la famille du processeur. Notamment
l’opération de multiplication « SZ * i » peut exiger un temps de calcul important. La multiplication avec le
facteur scalaire S peut, par contre, être effectuée avec des opérations de décalages (facteur 2 ou 4).
Remarque : Le caractère nul (′\0 ′) et celui du chiffre zéro (′0′) possèdent des valeurs ASCII différentes.
La structure « date » possède deux membres de type byte et un membre de type demi-mot. L’accès aux
membres de la structure s’effectue à l’aide de déplacements (offset), qui définissent la position relative des
membres dans la structure. Ces derniers sont les suivants pour la structure « date » :
.set OFFSET_MONTH,1
.set OFFSET_YEAR,2
Le membre « day » ne nécessite pas d’offset, car son adresse est identique à celle de base de la structure date.
L’écriture du membre année dans le registre r0 s’effectue par exemple de la manière suivante :
stackpointer:
.word stack+STACK_SIZE @ global variable,
@ which holds actual stack pointer
Le pointeur de pile doit adresser le début de la pile. Du fait que la pile croît dans le sens négatif, ce début est
l’adresse la plus grande de la pile. Le pointeur de pile est par conséquent initialisé avec « stack +
STACK_SIZE ».
Pointeur Adresses
de pile hautes
Variable Adresses
pile basses
Contrairement à la pile décrite au chapitre 6, qui ne contient pas de routine de contrôle pour les dépassements de
capacité, il est possible d’ajouter des fonctionnalités de contrôle, afin de pouvoir déceler les éventuels
dépassements.
Put
2. Element
Get
Dans la pratique, les queues de tampons sont remplacées par les anneaux de tampons. La problématique de
l’engloutissement de la mémoire est ainsi éliminée. Il existe deux types de réalisation :
a) Head et Tail peuvent adresser le même élément de mémoire. Dans ce cas une variable supplémentaire
« size » est également nécessaire, afin de pouvoir stocker le nombre d’éléments contenus dans l’anneau.
Ainsi, size = 0 correspond à un anneau vide.
size = 0 size = 1
n. Element
Figure 103: Les pointeurs Head et Tail dans une queue de tampons
b) Head et Tail ne peuvent pas adresser le même élément de mémoire. Un élément de mémoire est ainsi
sacrifié, mais la variable supplémentaire size n’est plus nécessaire (size = Tail – Head – 1). La
queue peut ainsi être initialisée avec « Head = 1 » et « Tail = 2 ».
Dans les deux cas il faut systématiquement contrôler les dépassements de capacités des pointeurs Head et
Tail. La Figure suivant illustre un exemple avec un dépassement de capacité de 8 à 1.
Dépassement
Head / Tail /
lecture écriture
1 8
2 7
3 6
4 5
Reset
Bootloader
Code de démarrage du système (CA RM E)
10.2 Le reset
Au début fut le reset. Ce dernier peut être généré par les événements suivants :
Allumage
Watchdog
Touche reset
Software
Le reset conduit le processeur dans un état défini. Dans la plupart des contrôleurs le reset est le premier vecteur
d’interruption (souvent à l’adresse 0). Ce dernier contient l’adresse de la première fonction, qui doit être appelée
au démarrage du système. Ce qui est normalement le code de démarrage du système (boot loader), lorsque ce
dernier est disponible, ou le code de démarrage de l’application.
Remarque 1 : Lorsqu’un système d’exploitation est disponible, le boot loader commence par charger ce dernier.
Ensuite, le système d’exploitation charge les applications.
Remarque 2 : Les processeurs Cortex-M4 permettent de déplace le tableau des vecteurs d’interruption dans la
mémoire. Le registre « Vector Table Offset Register » (VTOR à l’adresse : 0xE000ED08) contient l’adresse de
base de ce tableau.
La programmation du processus de démarrage n’est pas requise avec les programmes en C. En effet, le
compilateur lit automatiquement le code de démarrage à partir d’une librairie et l’insère dans le projet.
L’application C démarre alors avec la fonction principale « main ». Cependant il existe de nombreux cas, où le
code de démarrage doit être modifié. Ce qui exige des connaissances en assembleur.
Le code de démarrage des projets CARME C se trouve dans le fichier « crt0.s », qui se trouve dans le sous
directoire « startup ».
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
.word HardFault_Handler
.word MemManage_Handler
.word BusFault_Handler
.word UsageFault_Handler
.word 0
.word 0
.word 0
.word 0
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
/* External Interrupts */
.word WWDG_IRQHandler /* Window WatchDog */
.word PVD_IRQHandler /* PVD through EXTI Line detection */
.word TAMP_STAMP_IRQHandler /* Tamper and TimeStamps through EXTI */
.word RTC_WKUP_IRQHandler /* RTC Wakeup through the EXTI line */
.word FLASH_IRQHandler /* FLASH
…
Initialisation du le piles
Le code de démarrage de l’application doit également initialiser toutes les piles (stack). En particulier, il doit
initialiser le pointeur de pile principale (MSP) avec l’adresse finale du bloc de mémoire alloué pour la pile
correspondante.
Les blocs de mémoire pour les piles sont réservés dans les fichiers script „stm32f4_flash.ld“
respectivement „stm32f4_ram.ld“.
Exemple :
Un extrait du fichier „stm32f4_flash.ld“:
/* Highest address of the user mode stack */
_estack = 0x20020000; /* end of 128K RAM */
L‘adresse de référence _estack se trouve dans le premier vecteur du tableau des vecteurs d’interruption. Tout
au début du démarrage du système, la valeur de ce dernier est chargée automatiquement dans le registre SP.
Es ce que vous vous être déjà poser la question suivante : comment cette variable est-elle initialisée ? Cette
instruction n’est exécutée par aucune fonction durant tout le déroulement du programme. Cette initialisation est
par conséquent une tâche supplémentaire du code de démarrage, qui est réalisée par un processus appelé « le
traitement ROM » (ROM-Processing).
RAM ROM/Flash
_data
.data
/* variable globale
à initialiser */
static int myInt = 5;
_edata
_etext
Valeur d’initialisation
Le code de démarrage copie les valeurs d’initialisation des variables globales, depuis le flash dans la RAM. Cela
peut être programmé de la manière suivante :
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
Ldr r3, =_sidata
ldr r3, [r3, r1]
str r3, [r0, r1]
adds r1, r1, #4
LoopCopyDataInit:
Ldr r0, =_sdata
Ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
La référence _sidata est l’adresse de base du domaine du flash contenant les valeurs d’initialisation. Les
références _sdata et _edata désignent respectivement le début et la fin du bloc .data contenant les
variables globales à initialiser. _sidata, _sdata et _edata sont définies dans les fichiers scripts
stm32f4_flash.ld respectivement stm32f4_ram.ld.
/* Initialized data sections goes into RAM, load LMA copy after code */
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM AT> FLASH
Remarque : Lorsque vous travaillez avec le débuggeur, vous chargez le programme dans la RAM à travers son
interface. Dans ce cas, le débuggeur initialise automatiquement les variables globales. Les systèmes, prêts pour
la commercialisation, ne contiennent plus de débuggeur pour réaliser cette initialisation. Dans ce cas, il faut
absolument programmer un code de démarrage.
LoopFillZerobss:
ldr r3, =_ebss
cmp r2, r3
bcc FillZerobss
L’initialisation du hardware peut être critique en temps lorsque, par exemple, les sorties du processeur doivent
être rapidement fixés à des niveaux prédéfinis (Exemple : contrôle de moteur). Cette initialisation doit
éventuellement être réalisée dans le code de démarrage ou de façon hardware.
Dans un deuxième temps les fonctions des applications sont appelées de façon cyclique, à l’aide d’une boucle
infinie.
Initialisation du hardware
Boucle infinie
...
Exemple :
Lorsque des bytes sont reçus à l’aide d’une interface sérielle, qui génère des interruptions, il faudrait uniquement
stocker les bytes dans un tampon. Le traitement de ces bytes devrait être réalisé en dehors de la routine de
service.
Un point très important est l’échange de données entre les routines de services et les fonctions de l’application.
Ici la communication n’est possible qu’à l’aide de variables globales (à moins que le système possède un RTOS).
Mais il faut toujours faire attention au fait que les routines de service peuvent sur écrire les données, sans que les
fonctions de l’application principale ne le remarque. Cela est surtout critique avec les données structurées. Par
exemple supposons que le programme possède un tampon de réception de 10 bytes, qui sont remplis par une
routine de service et qui sont lus par les fonctions de l’application principale. Admettons que durant la lecture du
cinquième byte, par une des fonctions de l’application principale, une interruption apparaît pour indiquer la
réception de 10 nouveaux bytes. La routine de service sur écrit alors les 10 bytes du tampon de réception. La
fonction de l’application principale va continuer sa lecture avec le sixième byte, comme si rien ne s’était produit.
Finalement l’application va poursuivre son travaille avec des données inconsistantes. Il faut donc introduire des
mécanismes, qui permettent de verrouiller ces opérations mutuellement. C'est-à-dire qu’aucune interruption de
réception ne devrait être générée pendant que les données sont ne train d’être lues. Le tout peut devenir très
complexe, lorsque plusieurs applications sont exécutées simultanément. Si cette complexité n’est pas maîtrisée
(cela est sûrement le cas, lorsque des nouvelles fonctionnalités, qui n’étaient pas planifiées ont été ajoutée par la
suite), le système va chuter immanquablement.
1er fonction de
l’application
2ième fonction
nième
fonction de l’application
de l’application
4ième fonction
de l’application
Variables
globales
Figure 108 : Transfert de données entre les routines de service et les fonctions de l’application
Un RTOS n’est pas nécessaire pour les applications simples. Par contre, un RTOS est recommandé pour les
applications complexes, si les ressources hardware le permettent (CPU, taille de la mémoire etc.). Le module
d’approfondissement « système d’exploitation temps réel » traite ce sujet plus en détail.
11 L’interface C et assembleur
11.1 Introduction
Aujourd’hui la plupart des microcontrôleurs sont programmés à l’aide de langages de programmation évolués,
comme le C et le C++. Toutefois, les parties du code, qui sont critiques soit en temps ou en mémoire, sont
toujours définies en assembleur. Ce qui est particulièrement le cas pour les fonctionnalités suivantes :
Accès optimal au hardware, spécialement pour les driver
Les algorithmes, qui exigent une puissance de calcul élevée, afin de pouvoir utiliser la CPU de façon
optimal (unité de multiplication et d’accumulation)
Les fonctions qui sont souvent traitées, afin d’augmenter la performance du système
Accès aux registres de la CPU, qui ne sont pas accessibles en C
Dans ce cas une attention particulière doit être apportée sur l’interface C/assembleur. Cette interface dépend
technologiquement de la famille des microcontrôleurs et de leur environnement de développement. Les points
suivants sont à considérer :
Appel des sous routine assembleur depuis les fonctions C et inversement
Transmission de paramètres et retour de valeur
Utilisation de variable C en assembleur et inversement
Assembleur en ligne
Les mécanismes décrits dans se chapitres se réfèrent au processeur Cortex-M4 ainsi qu’au compilateur GNU
(gcc) et à assembleur de cette famille de processeur.
Remarque : Dans certain environnement de développement il faut ajouter un « _ » au début du nom symbolique
assembleur. Cela n’est cependant pas le cas avec les compilateurs GNU.
void main(void)
{
...
res = asmsub(par1, par2);
...
}
L’exemple suivant contient une variable globale définie en C (c_var) et une variable globale définie en
assembleur (asm_var) ainsi que les accès sur ces dernières :
Code C :
/* global variables */
int c_var; // reserve memory for word-variable
extern int asm_var; // specified in assembler source
void ctest_var(void){
...
asm_var = 0x22; // modify assembler variable
c_var = 0x55; // modify C variable
}
Code assembleur :
.global asm_var @ give asm_var external linkage
.extern c_var @ specified in C source
...
.data
asm_var:
.space 4,0x11 @ global asm_var,
@ which is initialized with 0x11111111
.text
asmtest_var:
STR lr,[sp, #-4] @ save return address on stack
LDR r0,=c_var @ r0 holds address of variable c_var
MOV r1,#0x55 @ r1 holds value to write to c_var
STR r1,[r0] @ c_var = 0x55
LDR r0,=asm_var @ r0 holds address of variable asm_var
MOV r1,#0x11 @ r1 holds value to write to asm_var
STR r1,[r0] @ asm_var = 0x11
LDR pc,[sp],#4 @ return
L’utilisation de l’assembleur en ligne peut aider de temps en temps. Toutefois, des effets de bord non souhaités
peuvent apparaître ! Il faut par conséquent faire très attention.
L’instruction (statement) « asm » est mise à disposition par le compilateur GNU gcc pour insérer de
l’assembleur en ligne dans le code C. Cette instruction n’est pas définie par le standard ANSI-C. Par conséquent
cette dernière dépend de l’environnement de développement. Les autres compilateurs utilisent des expressions
comme ASM, __asm ou ASMLINE.
Exemple :
asm volatile ("AND %0, %1, #0xFF" : "=r" (result) : "r" (value));
Cette instruction peut paraître un peu compliquée. « volatile » est obligatoire, afin que le compilateur C
n’optimise pas l’instruction asm. L’instruction en soit est composée de quatre colonnes :
1. Le code contient les instructions assembleur, qui ont été définies sous forme de chaîne de caractère :
"AND %0, %1, #0xFF"
2. Output operand list : Les opérandes de sortie, séparées par des virgules, peuvent être définies dans la liste
des opérandes de sortie : "=r" (result). Cette liste des opérandes permet d’accéder également aux
variables C. %0 se réfère au premier élément de la liste. "=r" Signifie que le compilateur peut utiliser un
registre, mais uniquement en mode écriture « write only ».
3. Input operand list : Les opérandes d’entrée, séparées par des virgules, peuvent être définis dans la liste des
opérandes d’entrée : "r" (value). Du fait que ce dernier est le deuxième opérande, %1 permet
d’accéder à la variable value. "r" signifie que le compilateur doit utiliser encore un registre
quelconque.
4. Clobber list : Cette liste informe le compilateur sur les registres, mémoire ou de statut, qui doivent être
modifiés par le code assembleur. La liste clobber est vide dans l’exemple de ci-dessus.
L’exemple le plus simple : Une seule ligne d’assembleur est programmé, sans opérande et sans liste clobber :
asm volatile ("MOV r0, r0"); // NOP (No Operation)
Si une variable C doit être utilisée en tant que paramètre d’entrée, elle peut être définie dans une liste des
opérandes d’entrée. L’exemple suivant ne contient pas de paramètres de sortie, la liste des opérandes de sortie est
ainsi vide. L’exemple illustre également l’accès au registre PSR, ce qui ne serait pas possible en C :
asm volatile("MSR PSR, %0" : : "r" (status)); // load PSR register with
value in status
Le compilateur génère à partir de cette instruction ASM le code objet (désassemblé) suivant :
LDR r3,=status
LDR r3,[r3]
MSR PSR r3
La liste des opérandes d’entrée spécifie l’état "r" pour la variable status. Le compilateur peut ainsi choisir le
un registre quelconque pour exécuter l’instruction. Le registre r3 a été choisi dans l’exemple de ci-dessus.
Il faut informer le compilateur, lorsque l’instruction assembleur change le contenu d’un registre. Le compilateur
ne possède pas les moyens pour remarquer cela. Il dépend donc de cette information, afin qu’il n’y ait pas de
problèmes avec ses propres registres. La déclaration des registres, qui sont modifiés par l’instruction assembleur,
s’effectue dans la liste clobber :
asm volatile("MOV r1, r0" : : : "r1"); // register r1 modified
Ce code possède un paramètre d’entrée, qui est la variable value. value est combiné de façon AND avec la
valeur #0xFF, c'est-à-dire que les 3 bytes de poids plus fort sont effacés. Le résultat de l’opération est déposé
dans la variable result. L’exemple illustre comment l’instruction assembleur peut être étendue et complétée
avec des variables C. Cela n’est par conséquent plus une instruction assembleur pure, mais plutôt un mélange de
C et d’assembleur. Le compilateur génère à partir de cette instruction ASM le code objet (désassemblé) suivant :
LDR r3,=value
LDR r3,[r3]
AND r2,r3,#255
LDR r3,=result
STR r2,[r3]
Du fait que ce code a été généré par le compilateur, il ne faut pas déclarer les registres r2 et r3, qui ont été
modifiées, dans la liste clobber.
Exemple : Il faut définir une variable « led », qui permet d’accéder à la ligne de LED sut le kit de
développement CARME. Ces LED sont accessible à l’adresse 0x0C003000.
La section .LED doit être encore définie dans le script du relieur « stm32f4_flash.ld » respectivement
« stm32f4_ram.ld ». Pour cela deux étapes sont nécessaires.
L’exemple ci-dessus du manuscrit du relieur montre, comment les registres de configuration des différentes
entrées et sorties du kit de développement CARNEIO1 peuvent être rassemblés en sections.
Exemple :
volatile unsigned short reg1 _at 0x1000;
Le bit le plus faible d’un nombre binaire (byte, demi-mot et mot) est qualifié de LSB (Least Significant Bit). Le
bit le plus fort est le MSB (Most Significant Bit).
Un demi-mot est composé de deux bytes : le « lower Byte » (les bits 0 à 7) et le « upper Byte » (les bits 8 à 15).
Un mot est composé de deux demi-mots : « lower Halfword » (bit 0 bis 15) et le « upper Halfword » (bit 16 bis
31).
Complément à deux
Le complément à deux permet de représenter les nombres négatifs dans le système binaire. Des symboles
supplémentaire comme + et – ne sont ainsi plus nécessaire.
Dans le complément à deux, les nombres positifs sont représentés avec un premier bit 0 (le bit de poids le plus
fort qui est également appelé le bit de signe). Les valeurs négatives sont représentées avec un premier bit 1. Ces
dernières sont codées de la manière suivante : tous les bits du nombre positif sont d’abord inversés et la valeur 1
est ensuite additionnée au résultat.
Exemple :
+4(10) = 00000100
-4(10) = 11111011 + 1 = 11111100
-1(10) = 11111111
127(10) = 01111111
-128(10) = 10000000
Le complément est un système de nombres signés, qui ne possède qu’une seule représentation de la valeur nulle.
Les additions et les soustractions peuvent être ainsi être effectuées sans dissociation de cas. Il n’existe qu’une
seule représentation de la valeur nulle, si l’on admet que les bits, résultants des dépassements de capacité, sont
tronqués durant les opérations.
Il existe plusieurs possibilités pour stocker la mantisse et l’exposant dans la mémoire. Par exemple avec la
précision simple (32 bits) ou double (64 bits). Ce chapitre se concentre uniquement sur le format précision
simple. Ce dernier est stocké dans la mémoire de la manière suivante :
31 30 23 22 0
V E + 127 M
1 bit 8 bits 23 bits
Seule la partie fractionnaire de la mantisse est stockée dans la mémoire (hidden bit). Il en résulte la formule
suivante pour la construction des nombres à virgule flottante :
Z = (-1)V M * 2E-127
Annexe D : La documentation
Nom Edition / Auteur Referenz
ARM v7-M Architecture ARM [ARMv7M_ArchRefMan]
Reference Manual
ARM Cortex-M4 Processor ARM [ARM_Cortex-M4_Processor_TechRefMan]
Technical Reference Manual
Cortex-M4 Devices ARM [Cortex-M4_Devices_GenUserGui]
Generic User Guide
STM32F405xx/STM32407xx ST [STM32F407xx_Datasheet]
Datasheet – production data
Samsung Mobile-SDRAM K4S561633 Samsung [Samsung_SDRAM]
The I2C-Bus Specification, V2.1, Jan 2000 Philips [Philips_I2C]
Tableau 52 : Documentation
ARM (Advanced RISC Machines) Thumb ("T" in the core's full name specifies
Thumb. E.g.: ARM7TDMI)
Needs more memory than Thumb, since all are RISC Reduced memory consumption with 16-bit
32-bit instructions instructions (have some 32-bit instructions too)
Totally 18 Registers: R1-R12, SP(R13), LR(R14), Totally 12 Registers: R1-R7, SP, LR, PC, PSR (These
PC(R15), PSR used in exceptions 12 registers have to be used to pass data between
ARM and Thumb state)
T bit = 0 of PSR represents ARM state Totally 12 Registers: R1-R7, SP, LR, PC, CPSR
(These 12 registers have to be used to pass data
between ARM and Thumb state)
Branch instruction (BX or BLX) to the address with Branch instruction (BX or BLX) to the address with
LSB set to 0 enters ARM state. (E.g.: BX 0x80000000 LSB set to 1 enters Thumb state. (E.g.: BX
0x80000001)
When exception occurs, ARM state is entered. -
When return from Exception, if T bit of PSR is set to When return from Exception, if T bit of SPSR is set to
0, returns to ARM 1, returns to Thumb
- No way (instruction) to access status or coprocessor
registers
Load and store instructions of R13 register Has stack mnemonics PUSH, POP
manipulates stack
- Advantages: Reduced memory, 16-bit bus can be used
without compromising with speed.
Index
boucle while ................... 135
. 8
break point ........................... 87
.2byte ............................... 94 8051 ...................................... 28 BSP .................................... 149
.4byte ............................... 94 bus d’adresse ......................... 6
A
.align ............................... 91 bus de contrôle....................... 6
.arm .................................... 90 Ablauf einer bus de données....................... 6
.ascii ............................... 93 Interruptanforderung ...... 127 BX ........................................ 83
.asciz ............................... 93 Acknowledge bit .................. 24 byte .............................. 45, 157
.asm .................................... 58 ADC ................................ 77, 78
C
.balign ............................. 92 ADD ................................ 77, 78
ADR .................................... 103 call by reference ................ 112
.bin .................................... 59
adressage des registres ....... 155 call by value....................... 112
.bss .................................... 96
adresse de retour ................. 108 callee .................................. 107
.byte ................................. 93 caller .................................. 107
adresse physique................... 14
.data ................................. 96 caractéristiques AC .............. 12
adresse virtuelle .................... 14
.elf .................................... 59 ALU ................................. 5, 35 caractéristiques DC .............. 12
.else ............................... 101 amplificateur de sortie .......... 18 carry ..................................... 42
.end .................................. 100 AND ................................ 77, 80 case mémoire ....................... 10
.endif ............................. 101 anneau de tampons ............. 143 cercle de nombre.................. 43
.endm ............................... 103 APCS .......... 109, 112, 114, 152 chaîne de caractère ............ 140
.equ .................................... 95 architecture ............................. 3 champ des étiquettes ............ 52
.extern ............................. 90 architecture ARM ................. 30 chip select .............................. 8
.global ............................. 90 architecture de Harvard .......... 4 choix parmi une liste............ 63
.hword ............................... 94 architecture de stockage ....... 10 CISC .................................... 27
.if .................................... 101 architecture de Von Neumann 3 clobber list ......................... 153
argument ............................. 115 CLZ ...................................... 87
.ifndef ........................... 102
arithmétique des entiers ........ 77 CMN ................................ 77, 78
.include .......................... 95
ARM..................................... 30 CMP ................................ 77, 78
.irp .................................. 105
array ................................... 138 code d’opération .................. 63
.lis .................................... 58
ascending ............................ 109 code de condition ................. 64
.macro ............................. 103
asm .................................... 153 code de démarrage ............. 146
.map .................................... 59 code machine ................. 50, 58
ASR ................................ 68, 81
.o......................................... 58 assemblage conditionnel..... 101 code récursif ...................... 108
.org .................................... 96 assembleur ............................ 58 cœur ..................................... 35
.p2align .......................... 92 assembleur en ligne ............ 153 ColdFire ............................... 28
.rept ............................... 104 assignation des pins .............. 12 commentaire ........................ 54
.s......................................... 58 compilateur .......................... 58
.section .................. 96, 155 B complément à deux .............. 56
.section absolue .................. 155 B 83 complément à un.................. 56
.set .................................... 95 Barrel Shifter .................. 35, 67 compteur de programme . 5, 41,
.space ............................... 94 baud rate ............................... 20 115
.text ................................. 96 BIC ................................ 77, 80 condition .............................. 80
.thumb ............................... 90 big endian ............................. 45 condition d’arrêt .................. 24
.word ................................. 94 binaire................................... 56 condition de démarrage ....... 24
bit ....................................... 157 Condition Flags ................... 42
_ bit C ...................................... 42 Conditional Execution ......... 42
bit d’état ......................... 41, 42 constante ........................ 54, 64
__attribute__ ............ 155
bit N...................................... 42 contexte ............................. 129
_at .................................... 156 control bus ............................. 6
_at_ .................................. 156 bit V...................................... 42
bit Z ...................................... 42 convertisseur A/D ................ 26
= BKPT .................................... 87 convertisseur D/A ................ 27
BL ................................. 83, 108 core ...................................... 35
= 95 Cortex M4............................ 33
BLX ...................................... 83
1 Counter ................................ 25
boot loader .......................... 145
CPSR ................................... 41
1 of X Decoder ....................... 8 boucle ................................. 131
CPU ....................................... 4
boucle d’itération................ 131
6 CPU32 ................................. 28
boucle do while ............. 136
Cross Assembleur ................ 58
68HCxx ................................ 28 boucle for ......................... 136
crt0 ................................. 146
X Z
XOR ...................................... 80 zero ....................................... 42
zone mémoire ......................... 3