Profil M
Les processeurs du profil M implémentent une gestion des interruptions au niveau du CPU permettant un traitement logiciel réalisé en langage C/C++. Sur ces µP, il n’est pas nécessaire de réaliser des routines de bas niveau en assembleur. Le CPU se charge de sauvegarder le contexte du programme en cours avant d’appeler la routine de traitement de l’événement et de le restaurer après traitement. Ce choix simplifie énormément la réalisation des applications logicielles.
Note
Les exemples de ce chapitre se basent sur le processeur ARM
Cortex-M4F et plus spécialement le µC de ST STM32F412 et utilisent
la bibliothèque libopencm3 sous l’environnement platformio.
Sources d’interruptions
Les µC du profil M connaissent 15 sources d’interruptions internes et de multiples sources d’interruptions externes. Toutes ces sources d’interruptions sont connectées et gérées par le contrôleur d’interruptions (NVIC - Nested Vectored Interrupt Controller).
Sur les 15 sources internes, 10 sont actuellement allouées1:
- Reset : l’exception est levée lorsque le µC est mis sous tension ou lors d’un reset local du µC par logiciel. Elle génère la réinitialisation du CPU et de ses périphériques.
- NMI : l’interruption (Non-Maskable Interrupt) est l’interruption la plus élevée après le reset et ne peut pas être bloquée. Elle sert généralement à des composants matériels pour signaler des erreurs exigeant un traitement immédiat ne pouvant être retardé.
- HardFault : l’exception signale des fautes génériques ne trouvant aucune autre exception offrant un traitement permettant de récupérer la situation d’erreur. Elle est généralement le résultat d’un enchaînement de fautes, dues à un non-traitement d’autres fautes, telles
MemManage,BusFaultouUsageFault. - MemManage : l’exception signale une violation de la protection de la mémoire détectée par la MPU (Memory Protection Unit).
- BusFault : l’exception signale des erreurs lors de transfert de données ou d’instructions sur les bus système.
- UsageFault : l’exception signale des erreurs causées par l’exécution d’instructions, mais non liées à la mémoire, par exemple des instructions non définies, des accès non alignés, des divisions par zéro, des accès à des coprocesseurs non existants ou déclenchés, etc.
- DebugMonitor : l’exception signale des événements de debugging.
- SVCall : l’exception est levée suite l’appel de l’instruction
SVC. Les systèmes d’exploitation l’utilisent pour les appels système (syscall). - PendSV : l’exception (Pendable Service Call) est levée par logiciel pour des appels système asynchrones.
- SysTick :l’interruption est levée par l’horloge interne au µC. Les systèmes d’exploitation l’utilisent pour générer l’horloge système.
Le contrôleur NVIC est capable en principe de gérer jusqu’à 496 sources d’interruptions externes. Cependant le nombre exact de sources dépend de la réalisation spécifique du fabricant du µC.
Table des vecteurs d’interruptions
La conception de la table des vecteurs est très simple à utiliser avec
le langage C/C++. Elle contient l’adresse initiale du pointeur de pile
MSP (Main Stack Pointer) ainsi que les adresses des routines de
traitement des interruptions. Comme le montre le pointeur de fonction
vector_table_entry_t, ces routines sont des fonctions sans paramètre
ni valeur de retour.
#define NVIC_IRQ_COUNT 96
typedef void (*vector_table_entry_t)(void);
typedef struct { // vector number
unsigned int *initial_sp_value; // MSP
vector_table_entry_t reset; // 1
vector_table_entry_t nmi; // 2
vector_table_entry_t hard_fault; // 3
vector_table_entry_t memory_manage_fault; // 4
vector_table_entry_t bus_fault; // 5
vector_table_entry_t usage_fault; // 6
vector_table_entry_t reserved_x001c[4]; // (7-10)
vector_table_entry_t sv_call; // 11
vector_table_entry_t debug_monitor; // 12
vector_table_entry_t reserved_x0034; //(13)
vector_table_entry_t pend_sv; // 14
vector_table_entry_t systick; // 15
vector_table_entry_t irq[NVIC_IRQ_COUNT];
} vector_table_t;
__attribute__ ((section(".vectors"))) vector_table_t vector_table={};
La table des vecteurs (vector_table) peut être placée librement dans
la mémoire SRAM ou Flash (Code) du processeur à l’aide du registre
VTOR contenu dans le bloc de contrôle du système (SCB - System
Control Block)2. Par défaut et grâce à la section .vectors,
l’éditeur de liens (Linker) place la table des vecteurs dans les
premiers blocs de la Flash à l’offset 0.
Avec la bibliothèque libopencm3, cette table est initialisée dans le
module vector.c. Quant aux routines de traitement par défaut, elles
sont définies dans le fichier nvic.h de la famille du µC. Pour
attacher une routine spécifique à l’application (ISR - Interrupt
Service Routine) au système de traitement des interruptions, il suffit
de surcharger la routine par défaut en implémentant une routine
correspondante. Celle-ci doit respecter le nom par défaut, par exemple
pour le “timer 2” la routine doit impérativement être nommée void
tim2_isr(void). Son numéro de vecteur est NVIC_TIM2_IRQ.
Au démarrage, le processeur initialise le registre MSP avec la valeur
de la première entrée de la table des vecteurs (initial_sp_value).
Cette valeur initiale est générée par l’éditeur de lien et pointe sur le
sommet de la SRAM (adresse la plus haute). Le processeur exécute ensuite
la routine attachée à la source Reset.
Traitement de l’interruption par le CPU
Lors de la levée d’une interruption autorisée, c’est-à-dire une interruption ayant une priorité suffisante, le CPU sauve le contexte du programme en cours d’exécution avant d’appeler la routine de traitement correspondante et de le restaurer une fois l’interruption servie. Cette technique décharge le logiciel des opérations à effectuer au niveau du CPU et lui permet ainsi de s’occuper directement et seulement du traitement de l’événement.
Pour sauver le contexte, le CPU bascule dans le mode “Thread”. Il sauve ensuite les registres qui ne sont pas sauvés lors de l’appel de fonction (Scratch Registers), le registre de liens (LR - Link Register), l’adresse de retour (Return Address) ainsi que le registre de statut du programme (xPSR - Program Status Register) selon le standard AAPCS3. Si nécessaire et afin de respecter la convention d’alignement de la pile sur 8 octets, le CPU réserve un mot supplémentaire sur la pile.
Si le µC dispose d’une unité de calcul à virgule flottante (FPU -
Floating Point Unit), le CPU peut également sauver les registres de
cette unité. Cette sauvegarde ne s’effectue que si le CPU est configuré
pour l’effectuer. Cette configuration s’effectue via le registre FPCCR
contenu dans les registres de contrôle de la FPU4.
Il charge ensuite dans le registre LR un code lui permettant de restaurer l’état du CPU après traitement.
Priorité et préemption
La notion de priorité des interruptions et exceptions prend tout son sens lorsque celles-ci surviennent simultanément. Les µC du profil M utilisent un système de priorité des événements où plus la valeur de la priorité est faible, plus le niveau de priorité de l’événement est élevé.
Les sources internes Reset, NMI et HardFault s’exécutent avec des
priorités fixes de -3, -2 et -1 respectivement. Le logiciel peut définir
la priorité (valeur entre 0 et 255) pour toutes les autres sources. La
priorité des sources internes, sources avec un numéro IRQ négatif, se
configure via les registres SHPR1, SHPR2 et SHPR3 contenu dans le
SCB2, tandis que les sources externes se configurent via les
registres du contrôleur d’interruptions (NVIC - Nested Vector Interrupt
Controler)5. Le niveau de priorité d’une source se laisse
configurer avec la fonction nvic_set_priority du module nvic.h.
Le µC traite les exceptions ou interruptions séquentiellement selon leur
priorité en débutant par la plus prioritaire. Lorsque plusieurs
événements se lèvent simultanément et ont la même priorité, celui ayant
le numéro le plus bas est prioritaire. Seul un événement avec une
priorité plus élevée peut le préempter. Afin d’affiner le contrôle des
priorités et du système de préemption, chaque priorité se compose de
deux champs, un groupe de priorité et une sous-priorité. Les bits de
poids fort définissent le groupe et les bits de poids faible la
sous-priorité. Le registre AIRCR contenu dans le SCB permet de
définir le nombre de bits déterminant la taille du groupe de priorité.
La fonction scb_set_priority_grouping du module scb.h permet de
configurer la taille du groupe de priorité.
Lors de la levée d’une interruption ou d’une exception, seul le groupe de priorité détermine la préemption d’un traitement en cours. Si de multiples interruptions arrivent dans le même groupe de priorité, alors la sous-priorité sert à la précédence du traitement.
Gestion de la levée des interruptions
Le logiciel peut gérer la levée des interruptions et d’exceptions au niveau du CPU sur trois niveaux différents :
-
Au premier niveau, le registre spécial
BASEPRIpermet au logiciel de restreindre la levée d’interruptions aux sources ayant une priorité plus élevée qu’une certaine valeur.movs r0, #0x80 // choix du niveau de priorité (p.ex. 0x80) msr basepri, r0 // assignation du niveau -
Au deuxième niveau, le registre spécial
PRIMASKpermet au logiciel de bloquer la levée de toutes les interruptions ayant un niveau de priorité inférieur ou égal à 0.cpsid i // désactivation cpsie i // autorisation -
Au troisième niveau, le registre spécial
FAULTMASKpermet au logiciel de désactiver la levée de toutes les interruptions ayant un niveau de priorité inférieur ou égal à -1.cpsid f // désactivation cpsie f // autorisation
Les fonctions cm_enable_interrupts, cm_disable_interrupts,
cm_enable_faults et cm_disable_faults du module cortex.h
permettent de gérer l’activation et le blocage des interruptions au
niveau du CPU.
Contrôleur d’interruptions
Le contrôleur d’interruptions NVIC est capable de multiplexer, selon les
réalisations, jusqu’à 496 sources d’interruptions. Le registre ICTR
contenu dans l’espace de contrôle du système (SCS - System Control
Space)6 indique le nombre spécifique à l’implémentation
déployée sur le µC. Chacune de ces sources correspond à une ligne de
requêtes d’interruptions d’un périphérique interne au processeur.
Pour gérer les requêtes de ces sources, le NVIC dispose de six sets de
registres7. Le module nvic.h de la bibliothèque libopencm3
offre des fonctions pour manipuler ces registres.
- “
ISER” (Interrupt Set-Enable Registers), ce registre permet d’autoriser la levée d’interruptions pour les différentes lignes de requêtes (fonctionnvic_enable_irq). - “
ICER” (Interrupt Clear-Enable Registers), ce registre permet de bloquer la levée d’interruptions pour les différentes lignes de requêtes (fonctionnvic_disable_irq). - “
ISPR” (Interrupt Set-Pending Registers), ce registre indique si une ou plusieurs interruptions sont en attente de traitement. Il permet également de générer, de simuler, par logiciel, la levée d’interruptions pour les lignes de requêtes données (fonctionsnvic_get_pending_irqetnvic_set_pending_irq). - “
ICPR” (Interrupt Clear-Pending Registers), ce registre permet de quittancer une requête en attente pour les différentes lignes de requêtes (fonctionnvic_clear_pending_irq). - “
IABR” (Interrupt Active Bit Registers), ce registre indique si la requête d’une source d’interruption est en cours de traitement (fonctionnvic_get_active_irq). - “
IPR” (Interrupt Priority Registers), ce registre permet de configurer la priorité de la ligne de requêtes donnée lors de la levée d’interruptions (fonctionnvic_set_priority).
Le registre STIR contenu dans le SCS permet également de générer, de
simuler, la levée d’interruption pour une source donnée (fonction
nvic_generate_software_interrupt). La levée d’interruptions n’est
possible que si les requêtes pour une ligne d’interruptions donnée ont
préalablement été autorisées.
Unité de gestion des entrées/sorties
L’unité de gestion des entrées/sorties (GPIO - General Purpose Input
Output) sert à piloter 16 broches (Pin) numériques. En utilisant les
fonctions du module gpio.h, chaque broche peut se configurer aussi
bien en entrée qu’en sortie.
Configurés en entrée, des périphériques externes au µP, tels des
boutons-poussoirs, peuvent les utiliser comme ligne de requêtes
d’interruptions via le contrôleur EXTI (External Interrupt/Event
Controller). Ce contrôleur multiplexe 8 GPIO. La broche servant de
ligne de requête est choisie par les registres syscfg_exticr1 à
syscfg_exticr4 ou en utilisant la fonction exti_select_source du
module exti.h.
Chaque broche peut de générer une interruption si elle détecte :
- Flanc descendant (Falling Edge)
- Flanc montant (Rising Edge)
La fonction exti_set_trigger du module exti.h sert à configurer le trigger.
Lors de la levée d’une interruption sur une broche, l’unité EXTI la
propage vers le contrôleur NVIC. Afin que cette interruption atteigne le
CPU, il faut premièrement l’autoriser sur le contrôleur EXTI ainsi que
sur le contrôleur NVIC. Les fonctions exti_enable_request et
exti_disable_request permettent gérer la levée d’interruption sur le
contrôleur EXTI.
La levée d’une interruption externe doit être quittancée sans quoi elle
reste active. Ce quittancement doit s’effectuer durant le traitement de
l’interruption avec la fonction exti_reset_request du module
exti.h.