Affichage_Atrylec_com

Vous n'êtes pas seuls dans l'Univers !

Transistor_100

STM32 : Interruptions

STM32 : Interruptions.

Je veux remonter ! | Liste

1) Définitions et utilités

Les interruptions consistent à mettre en pause l'exécution d'un programme, par un microcontrôleur, pour traiter une tâche qui est prioritaire devant cette exécution de programme.
Cela est très utilie, par exemple, pour éviter de mobiliser les ressources du composant pour produire un délai.
Par exemple, si l'on souhaite faire clignoter une LED, on peut programmer le microcontrôleur de manière à ce que son programme principal soit interrompu à intervalles réguliers, plutôt que de le faire compter en permanence.
Dans le premier cas de figure, lorsqu'une interruption surviendra, le système décidera s'il doit allumer ou éteindre la LED, puis il pourra continuer ce qu'il était en train de faire auparavant.
Dans le second cas de figure, la LED sera allumée ou éteinte à chaque fois que le comptage atteindra la limite qui lui aura été fixée, mais le microprocesseur ne pourra rien faire d'autre que de compter et de changer l'état de la LED qui lui sera raccordée : le comptage quasi-permanent s'appelle une attente active.

Voici un exemple du clignotement d'une LED, qui utilise une attente active :

Code source : Exemple - Faire clignoter une LED avec une attente active



#include <stm32f10x_lib.h>

void Delai(int t)
{
	int i;

	for(i = 0; i < (100 * t); i++);
}

void ConfigPortC()
{
	RCC->APB2ENR |= (1 << 4);

	GPIOC->CRH = ((GPIOC->CRH & 0x00000000) | (0x00000033));
}

int main(void)
{
	ConfigPortC();
	while(1)   /* Boucle infinie */
	{
		GPIOC->ODR |= (1 << 9); /* Allumage de la LED verte, située sur la broche PC9 */
		Delai(1000);
		GPIOC->ODR &= ~(1 << 9); /* Extinction de la LED verte, située sur la broche PC9 */
		Delai(1000);
	}
}

void SystemInit (void)
{ 

}


Pour donner une analogie entre la notion d'interruptions et le quotidien, imaginons que vous rentriez chez vous, que vous avez très faim, et que vous n'ayez pas envie de cuisiner.
Par chance, vous vous rappelez qu'il vous reste une pizza surgelée dans votre congélateur.
Vous la mettez à réchauffer pendant une dix de minutes dans votre four.
Là, deux solutions s'offrent à vous :

Le premier cas constitue une attente active, car vous ne pouvez pas faire autre chose de bien intéressant pendant votre comptage des dix minutes - vous risqueriez d'être déconcentré - e dans votre affaire.
Il s'agit d'une attente active.
Le second cas, quant à lui, vous permet d'aller faire autre chose pendant le déroulement de l'opération de cuisson.
En effet, celle-ci sera automatiquement arrêtée par le four, qui ne manquera pas, au passage, de sonner pour vous interrompre dans l'activité que vous seriez en train de faire au moment où les dix minutes se seront écoulées.
Un dispositif supplémentaire a été nécessaire pour cela : il s'agit de la minuterie et de la sonnette de votre four.

Ces notions de cuisinier affamé qui peut faire autre chose que de compter en regardant sa pizza se réchauffer, de minuterie et de sonnette se retrouvent au niveau des microcontrôleurs.

Au niveau d'un microprocesseur, le cuisinier peut être représenté par le programme principal du microcontrôleur, la minuterie s'apparente aux « timers », qui sont des périphériques capables de compter et qui sont inclus dans la plupart des microcontrôleurs, et la sonnette correspond au fait que les timers sont capables d'interrompre le programme principal, quand ils ont atteint ou dépassé une certaine valeur de comptage.

Pour un microcontrôleur, une interruption est donc un événement qui donne lieu à :

Les interruptions sont des événements qui arrivent de façon asynchrone par rapport à l'horloge qui cadence le microcontrôleur.
Elles peuvent être provoquées par un élément matériel qui entoure le composant, par exemple par le dépassement d'une certaine valeur par un timer, par l'appui sur un bouton-poussior, ou éventuellement par la réception d'une donnée par un périphérique capable de faire une telle chose.
Elles peuvent également être générées par le microcontrôleur, par exemple dans le cas d'une division par zéro, d'une erreur détectée sur un périphérique, ou bien alors si la mémoire vive du microcontrôleur est complètement remplie.
Dans ce cas précis, on parle alors d' « interruption système », ou encore d' « exception ».

Je veux remonter ! | Liste

2) Les interruptions et les exceptions du STM32F100RB

Dans le STM32F100RB, et plus généralement dans tous les STM32, les interruptions sont associées à un vecteur d'interruption, qui est codé sur 32 bits.
Chacun de ces vecteurs d'interruption contient l'adresse de la routine de service de l'interruption, cette adresse indique l'emplacement du code qui doit être traité lorsque l'interruption correspondante au vecteur d'interruption survient.
L'ensemble des vecteurs d'interruption est contenu dans une table, qui est relogeable, ce qui signifie que cette table peut être déplacée dans la mémoire du programme du STM32F100RB.
Par défaut, cette table se situe à l'adresse 0x0000 0000, dasn la mémoire flash du STM32.
Tout vecteur d'interruption est donc localisé par l'adresse du début de la table, à laquelle s'ajoute un déplacement qui est propre à chaque vecteur d'interruption.
Le registre qui permet de modifier l'adresse du début s'appelle « SCB_VTOR ».
Chacun de ces vecteurs occupe 32 bits.

Cette table de vecteurs contient, pour ses 16 premières positions, les adresses des exceptions, puis celles des 60 interruptions.

Si la table n'a pas été déplacée, la mémoire du STM32 se présente alors comme ceci :

Adresses Contenu de la mémoire (donnée de 32 bits) Signification pour le STM32
0x0000 0000 Réservé Emplacement de la mémoire réservé
0x0000 0004 Vecteur de l'exception 0 Reset
0x0000 0008 Vecteur de l'exception 1 NMI
0x0000 000C Vecteur de l'exception 2 HardFault (Détection d'une erreur au niveau matériel)
... ... ...
0x0000 0040 Vecteur de l'interruption 0 Window Watchdog interrupt (Chien de garde)
0x0000 0044 Vecteur de l'interruption 1

PVD through EXTI Line detection
interrupt (Surveillance de la tension d’alimentation)

0x0000 0048 Vecteur de l'interruption 2

Tamper and TimeStamp through
EXTI line interrupts (Modification sur la broche TAMPER)

... ... ...
0x0000 0058 Vecteur de l'interruption 6 EXTI Line0 interrupt (Détection d’une modification sur « EXTI0 »)
... ... ...
0x0000 0130 Vecteur de l'interruption 60 DMA2 Channel5 global interrupt (DMA2 canal 5)

La progression dans les adressages se fait de 4 bits en 4 bits, car le contenu de la mémoire du STM32 est codé sur 32 bits, qui occupent 4 octets.

Pour obtenir l'adresse du vecteur d'une interruption, connaissant son numéro et l'adresse du début de la table des vecteurs, il faut suivre la formule suivante :

Adresse du vecteur = Adresse du début de la table des vecteurs + (4 x 16) + (4 x Numéro de l'interruption).

Il faut, en effet, tenir compte du fait que les 16 premiers vecteurs sont ceux qui correspondent aux 16 exceptions.

Par exemple, le vecteur de l'interruption qui correspond à la détection d’une modification sur « EXTI0 », qui sera utilisé dans l'un des exemples présentés ci-après, porte le numéro 6.
Cela signifie que, si la table des vecteurs n'a pas été déplacée, l'adresse du vecteur recherché est :
Adresse = 0 + (4 * 16) + (4 x 6),
Adresse = 0 + 64 + 24,
Adresse = 88, soit 0x0000 00058, en notation hexadécimale.

Les interruptions ont des priorités qui sont réglables, sauf pour les 4 premières exceptions, dont le niveau de priorité est fixe.
La priorité est un nombre qui est lié à chaque interruption.
Plus ce nombre est faible, plus l'interruption correspondante est prioritaire.

Une interruption peut survenir alors qu'une autre interruption est déjà en cours de traitement.
Si la nouvelle interruption est plus prioritaire que celle qui est exécutée, alors cette dernière sera mise en pause et laissera la nouvelle interruption se dérouler, jusqu'à ce qu'elle soit terminée.

L'interruption précédente reprendra ensuite.

Si la nouvelle interruption n'est pas prioritaire, l'interruption en cours de traitement continuera d'être exécutée sans aucune modification de son déroulement.

Les niveaux de priorité des exceptions pouvant être modifiés peuvent l'être grâce aux registres nommés « SCB_SHPR1 », « SCB_SHPR2 » et « SCB_SHPR3 ».

Les niveaux de priorité des interruptions peuvent l'être grâce aux registres nommés « NVIC_IPR0 » à « NVIC_IPR20 ».

Capture_interruptions_1
Les quatre premières exceptions des STM32F100

Capture_interruptions_2
Les 12 dernières exceptions et les 20 premières interruptions des STM32F100

Capture_interruptions_3
Les interruptions numéro 23 à 53 des STM32F100

Capture_interruptions_4
Les 7 dernières interruptions des STM32F100

Par défaut, les interruptions ne sont pas activées.
On dit qu'elles sont alors « masquées ».

Dans de nombreux environnements de développement intégrés, tels que « Keil µVision 4 », il faut passer par une variable de type « tableau » pour activer ou non les interruptions.
NVIC->ISER[0] permet de configurer les interruptions noméro 0 à 31 et
NVIC->ISER[1] permet de configurer les interruptions noméro 32 à 63.

Par exemple, pour activer l'interruption de la ligne « EXT0 », qui a le rang numéro 6, il convient d'écrire :

NVIC->ISER[0] |= (1 << 6);.

Pour désactiver cette même interruption, il faut cette fois écrire :

NVIC->ISER[0] &= ~(1 << 6);.

Des registres spéciaux permettent également de masquer toutes les interruptions, ou de masquer les interruptions à partir d'un certain rang.
Masquer toutes les interruptions peut s'avérer utilie lorsque le STM32 s'initialise, en effet, il ne faudrait pas qu'un événement externe ne déclenche un interruption lorsque le système n'est pas encore totalement initialisé, car il pourrait alors avoir un comportement erratique.
De plus, masquer tout un groupe d'interruptions est utilie lors de l'accomplissement d'une tâche critique, ne devant pas être mise en pause.

Je veux remonter ! | Liste

3) Exemples de programmes qui mettent en œuvre les interruptions

3-1) Mettre en œuvre l'interruption provoquée par la broche « PA0 »

L'exemple de code source suivant permet de faire s'allumer la LED bleue de la carte STM32VLDISCOVERY lorsque l'on appuie sur le bouton-poussoir bleu, et de la faire s'éteindre lorsque l'on rappuie sur ce même bouton-poussouir.

Code source : Exemple 1 - Mettre en œuvre l'interruption provoquée par la broche « PA0 »



#include <stm32f10x_lib.h>

unsigned char Etat = 0;   /* Variable globale qui représente l'état de la LED bleue */

void Delai(int t)   /* Fonction qui sert à faire perdre son temps au STM32F100RB */
{
	int i;

	for(i = 0; i < (10 * t); i++);
}

void ConfigurerPortA()
{
	RCC->APB2ENR |= (1 << 2);   /* Activitation de l'horloge du port A */

	GPIOA->CRL = ((GPIOA->CRL & 0x00000000) | (0x00000004));
}

void ConfigurerPortC()
{
	RCC->APB2ENR |= (1 << 4);   /* Activitation de l'horloge du port C */

	GPIOC->CRH = ((GPIOC->CRH & 0x00000000) | (0x00000003));
}

void EXTI0_IRQHandler(void) __irq   /* Fonction appelée lorsque l'interruption « EXTI0 » survient */
{   
	if(Etat == 0)
	{
		GPIOC->ODR |= (1 << 8);   /* Allumage de la LED bleue, située sur la broche PC8 */
		Etat = 1;
	}

	else
	{
		GPIOC->ODR &= ~(1 << 8);   /* Extinction de la LED bleue, située sur la broche PC8 */
		Etat = 0;
	}

	Delai(30000);   /* Méchant délai anti-rebond */

	EXTI->PR |= (1 << 0);   /* Réarmement de l'interruption « EXTI0 » */
}

void initITBouton()
{
	EXTI->IMR |= 1;   /* Autorisation de l'interruption de la ligne 0, correspondant à la broche « PA0 » */
	EXTI->RTSR |= 1;   /* Activation du déclenchement de l'interruption sur un front montant de la broche « PA0 » */   
	AFIO->EXTICR[1] &= ~(0x0000000F);   /* L'interruption « EXTI0 » doit être provoquée par une modification
d'état de la broche « PA0 » */

	NVIC->ISER[0] |= (1 << (EXTI0_IRQChannel  & 0x1F));   /* Autorisation de l'interruption « EXTI0 »,
qui porte le numéro 6,
contenu dans la variable « EXTI0_IRQChannel » */

}

int main(void)
{
	ConfigurerPortA();   /* Configuration de la broche relié au bouton-poussoir */   
	ConfigurerPortC();   /* Configuration des broches reliées à la LED verte et à la LED bleue */   

	initITBouton();   /* Mise en place de l'interruption « EXTI0 »,
de façon à ce qu'elle soit déclenchée
par l'appui sur le bouton-poussoir bleu */ 

	while(1)   /* Boucle infinie */
	{

	}
}

void SystemInit(void)
{

}


3-2) Faire clignoter une LED avec une interruption

Le code source qui suit permet de configurer le timer 2, de manière à ce qu'il génère des interruptions de façon périodique, le nombre de ces interruptions nécessaire pour faire changer d'état la LED verte est de (DELAIS / 2), soit de 500, dans le cas présenté ici.

Code source : Exemple 2 - Faire clignoter une LED avec une interruption



#include <stm32f10x_lib.h>

#define DELAIS 1000

/* Variables   globales */

int compteur = 0;

void ConfigPortC()
{
	RCC->APB2ENR |= (1 << 4);

	GPIOC->CRH = ((GPIOC->CRH & 0x00000000) | (0x00000033));
}

void initTimer2()
{            
	RCC->APB1ENR |= 0x1; /* Activation du timer 2 */
	
	TIM2->CR1 = 0x00000095;   /* Le timer 2 utilise l'horloge interne, sans division.
Il recharge automatiquement la valeur contenue dans le registre « TIM2->ARR »,
lorsque le comptage est arrivé à son terme.
Le timer 2 va fonctionner en mode "Edge-aligned".
Le timer 2 fonctionnera en mode de décomptage.
Cela signifie donc qu'à chaque fois que le timer 2 arrivera en fin de cycle,
une interruption sera générée.
Le timer 2 fonctionnera de manière cyclique, et non pas une seule fois. */

	TIM2->ARR = 0x00000FA0;

	TIM2->DIER = TIM2->DIER | (1 << 0);   /* Permettre la mise à jour sur une interruption */
}

void TIM2_IRQHandler(void) __irq 
{
	compteur++;

	if(compteur == (DELAIS / 2))
	{
		GPIOC->ODR |= (1 << 9); /* Allumage de la LED verte, située sur la broche PC9 */
	}

	if(compteur > DELAIS)
	{
		GPIOC->ODR &= ~(1 << 9); /* Extinction de la LED verte, située sur la broche PC9 */

		compteur = 0;
	}

	TIM2->SR &= (~1); /* Ceci doit toujours être la dernière ligne de l'interruption.
On réarme l'interruption du timer 2 */
} void initITTimer2() { /* Activation de l'interruption */ NVIC->ISER[0] |= (1 << 28); /* L'interruption qui correspond au timer 2 est située en position 28,
dans la table des vecteurs */
} int main(void) { ConfigPortC(); initTimer2(); initITTimer2(); while(1) /* Boucle infinie */ { } } void SystemInit (void) { }

3-3) Faire clignoter une LED avec une interruption,
avec relocalisation de la table des vecteurs

Code source : Exemple 3 - Faire clignoter une LED avec une interruption, avec relocalisation



#include <stm32f10x_lib.h>

#define DELAIS 1000

/* Variables   globales */
int compteur = 0;


void ConfigPortC()
{
	RCC->APB2ENR |= (1 << 4);

	GPIOC->CRH = ((GPIOC->CRH & 0x00000000) | (0x00000033));
}

void initTimer2()
{            
	RCC->APB1ENR |= 0x1; /* Activation du timer 2 */

	TIM2->CR1 = 0x00000095;   /* Le timer 2 utilise l'horloge interne, sans division.
Il recharge automatiquement la valeur contenue dans le registre « TIM2->ARR »,
lorsque le comptage est arrivé à son terme.
Le timer 2 va fonctionner en mode "Edge-aligned".
Le timer 2 fonctionnera en mode de décomptage.
Cela signifie donc qu'à chaque fois que le timer 2 arrivera en fin de cycle,
une interruption sera générée.
Le timer 2 fonctionnera de manière cyclique, et non pas une seule fois. */

	TIM2->ARR = 0x00000FA0;

	TIM2->DIER = TIM2->DIER | (1 << 0);   /* Permettre la mise à jour sur une interruption */
}

void it_timer2(void) __irq 
{
	compteur++;

	if(compteur == (DELAIS / 2))
	{
		GPIOC->ODR |= (1 << 9); /* Allumage de la LED verte, située sur la broche PC9 */
	}

	if(compteur > DELAIS)
	{
		GPIOC->ODR &= ~(1 << 9); /* Extinction de la LED verte, située sur la broche PC9 */

		compteur = 0;
	}

	TIM2->SR &= (~1); /* Ceci doit toujours être la dernière ligne de l'interruption.
On réarme l'interruption du compteur 2 */
} void initITTimer2() { int adrExcept; int * pt; /* Déclaration d'un pointeur sur un nombre entier */ pt = (int*)0x200001B0; /* On veut que la nouvelle adresse
du début de la table des vecteurs soit de 0x20000100, et le déplacement du vecteur de l'interruption du Timer 2
par rapport au début de cette table est de 0x000000B0, donc 0x20000100 + 0x000000B0 = 0x200001B0 */
/* 1) Nouvelle adresse du début de la table des vecteurs */ SCB->VTOR = 0x20000100; /* 2) Ecriture du vecteur correspondant au Timer 2, dans la table des vecteurs */ adrExcept = (int)it_timer2; /* Récupération de l'adresse qui correspond au début
de la fonction « it_timer2 » */
*pt = adrExcept; /* Ecriture de cette adresse à l'endroit que pointe adrExecpt, c'est-à-dire écriture de la valeur de l'adresse qui correspond
au début de la fonction « it_timer2 », dans la case mémoire numéro 0x200001B0 */
/* 3) Activation de l'interruption */ NVIC->ISER[0] |= (1 << 28); /* L'interruption qui correspond au Timer 2 est située en position 28,
dans la table des vecteurs */
} int main(void) { ConfigPortC(); initTimer2(); initITTimer2(); while(1) /* Boucle infinie */ { } } void SystemInit (void) { }

Commentaires (2)

Atrylec

Le vendredi 13 juin 2014 à 9:22

Bonjour,

Je vous remercie pour cette critique positive !

Relocaliser la table des vecteurs d'interruptions est une opération qui peut être nécessaire lorsque vous utilisez un « bootloader » ou un système d'exploitation, qui utilisent les adresses mémoires des vecteurs d'interruptions !
Dans le cas contraire, votre microcontrôleur ne fera pas ce que vous souhaiterez !

Si vous avez d'autres questions, n'hésitez pas à me les poser !

eaflip

Le jeudi 12 juin 2014 à 22:17

Bonjour, super ton site mais je ne comprend pas a quoi sert de relocaliser la table des vecteurs, peut tu m'expliquer?
merci

Ajouter un commentaire

Votre pseudo :

Votre commentaire :

Je veux remonter !