Systèmes Embarqués 1: Les Interruptions: Activités Pratiques

Vous aimerez peut-être aussi

Vous êtes sur la page 1sur 25

Systèmes Embarqués 1

Les interruptions : Activités pratiques


Principe de fonctionnement des interruptions

l’Arduino exécutait son programme en séquence, instruction par instruction, dans


l’ordre. Certaines instructions permettent de rompre cet ordre : les instructions
conditionnelles comme le if ... else ou les instructions de boucle comme le for(...).
Mais l’Arduino reste sur ses rails et respecte les instructions qui lui sont données.
Principe de fonctionnement des interruptions

Une interruption, comme son nom l’indique, consiste à interrompre


momentanément le programme que l’Arduino exécute pour qu’il effectue un
autre travail. Quand cet autre travail est terminé, l’Arduino retourne à l’exécution
du programme et reprend à l’endroit exact où il l’avait laissé.
Principe de fonctionnement des interruptions

Il existe plusieurs sources d’interruption, on dit également vecteur


d’interruption, sur l’AVR ATMega 328 qui équipe l’Arduino Uno, 26 au total. On
étudiera 5 interruptions liées au changement de la tension présente sur une
broche numérique qui sont donc ce que l’on appelle des interruptions externes.
Les interruptions liées au timers, des compteurs internes à l’Arduino qui
permettent de compter le temps de manière précise sont également
intéressantes.
Deadlock

Une ISR n’est pas interrompue par une nouvelle interruption. La nouvelle
interruption ne sera prise en compte que lorsque l’ISR en cours se terminera.

Il ne faut pas appeler de fonctions qui se mettent en attente d’une interruption à


partir d’une ISR. Comme l’interruption attendue ne peut pas déclencher une
nouvelle ISR, la fonction attendra indéfiniment et tout le système se bloquera.
C’est ce que l’on appelle un deadlock.
Exemple :
Les fonctions de Serial qui permettent d’afficher, via la ligne

série et l’USB dans le moniteur série font exactement cela.

Leur appel à partir d’une ISR est donc interdit.


Que se passe-t-il si plusieurs interruptions surviennent en même temps ?

Les Interruptions ont chacune une priorité. Par exemple, les interruptions externes
sont plus prioritaires que les interruptions des Timers. L’Arduino exécutera les ISR
dans leur ordre de priorité. L’ordre de priorité est donné dans la table ci-dessous.
La source d’interruption située la plus en haut de la table est la plus prioritaire.

Source Rôle
INT0 Interruption externe sur la broche 2

INT1 Interruption externe sur la broche 3

PCINT0 Interruption externe sur les broches 8 à 13

PCINT1 Interruption externe sur les broches A0 à A5

PCINT3 Interruption externe sur les broches 0 à 7


Les interruptions externes INT0 et INT1
INT0 correspond à la broche 2 des Arduino a base d’AVR ATmega 328 et INT1
correspond à la broche 3. Pour accrocher une routine d’interruption à un état ou
un changement d’état de l’une de ces broches on va utiliser la fonction
attachInterrupt(...).
Cette fonction prend 3 arguments : le numéro d’interruption externe, la fonction à
appeler quand l’interruption survient et enfin la condition selon laquelle
l’interruption survient.
Son prototype est le suivant :

attachInterrupt(numéro, ISR, mode);


Les interruptions externes INT0 et INT1
INT0 correspond à la broche 2 des Arduino a base d’AVR ATmega 328 et INT1
correspond à la broche 3. Pour accrocher une routine d’interruption à un état ou
un changement d’état de l’une de ces broches on va utiliser la fonction
attachInterrupt(...).
Cette fonction prend 3 arguments : le numéro d’interruption externe, la fonction à
appeler quand l’interruption survient et enfin la condition selon laquelle
l’interruption survient.
Son prototype est le suivant :

attachInterrupt(numéro, ISR, mode);


Où numéro est le numéro de l’interruption externe.

Il s’agira de 0 ou 1 sur un Arduino Uno/Nano


Les interruptions externes INT0 et INT1
Mais on préférera l’appeler en utilisant la fonction d’aide digitalPinToInterrupt qui
prend comme argument le numéro de broche et renvoie le numéro
d’interruption. Ceci permet de ne pas avoir à se référer à la documentation pour
retrouver à quelle broche correspond une interruption. L’appel de AttachInterrupt
devient donc :

attachInterrupt(digitalPinToInterrupt(broche), ISR, mode);


•broche est la broche correspondant à l’interruption
externe. C’est à dire 2 ou 3 sur un Arduino Uno/Nano

•ISR est la routine d’interruption qui sera appelée lorsque


l’interruption surviendra. Cette routine d’interruption est
une fonction qui ne renvoit rien et qui ne prend aucun
argument, comme ceci : void maRoutine() { ... }
Les interruptions externes INT0 et INT1
mode indique ce qui va provoquer l’interruption. Les valeurs possibles pour mode sont :
LOW : l’interruption est déclenchée quand la broche concernée est LOW. Comme il s’agit
d’un état et non d’un événement, l’interruption sera déclenchée tant que la broche est
LOW. Par conséquent, dès que l’ISR aura terminé son exécution, elle la recommencera.
Pendant ce temps, loop() ne sera pas exécuté.
CHANGE : l’interruption est déclenchée quand la broche concernée change d’état, c’est à
dire passe de LOW à HIGH ou bien de HIGH à LOW. Il s’agit d’un événement.
RISING : l’interruption est déclenchée quand
la broche concernée passe de LOW à HIGH.
Il s’agit également d’un événement.
FALLING : l’interruption est déclenchée quand
la broche concernée passe de HIGH à LOW.
Il s’agit encore d’un événement.
Exemple code const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
digitalWrite(ledPin, state);
}

void blink() {
state = !state;
}
BOARD INT.0 INT.1 INT.2 INT.3 INT.4 INT.5
Uno, Ethernet 2 3
Mega2560 2 3 21 20 19 18
32u4 based (e.g
3 2 0 1 7
Leonardo, Micro)
Exemple 2

Supposons que l’on veuille provoquer l’exécution de la routine


d’interruption maRoutine lorsque le signal sur la broche 2 passe
de HIGH à LOW. maRoutine est définie comme suit :

/* Change l'état de la LED embarquée sur la carte */


void maRoutine()
{
digitalWrite(LED_BUILTIN, ! digitalRead(LED_BUILTIN));
}
Exemple 2

On appellera attachInterrupt de la même manière :


attachInterrupt(digitalPinToInterrupt(2), maRoutine, FALLING);

Une second fonction detachInterrupt(…) permet de déconnecter


l’ISR de la source d’interruption.

detachInterrupt(numero);

Numero est le numéro d’interruption,


0 et 1 sur Arduino UNO
Exercice
Il s’agit d’un anémomètre qui tourne en activant des ILS, le programme va alors
entrer dans la boucle compteur.

Cependant on cherche un moyen pour compter le nombre de fois que l’ILS va


s’activer pendant une seconde. Pour cela il faut enclencher un timer lorsque le
programme entre dans la boucle compteur et incrémenter jusqu’a ce que le timer
atteigne une seconde.
Le problème étant que à chaque
interruption la boucle recommence
donc le timer se reset à chaque fois.
int anemometre = 3; //pin pour compter le nombre d'impulsion
Exercice int compt = 0; //fonction pour compter le nombre d'impulsion
float vitesse = 0; //vitesse du vent
float valeur = 2.4; //2,4 km/h par activation de l'ILS par seconde
void setup() {
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(3),compteur,RISING); //fonction pour compter le nombre d'interruption
}
void loop() { Serial.println("ca tourne pas"); }

void compteur() {
static unsigned long temps_dernier = 0;
unsigned long temps = millis();
while ((temps - temps_dernier) <= 1000); {
compt++;
temps_dernier = temps; }
affichage(); }

void affichage() {
vitesse = valeur*compt; //calcul de la vitesse du vent
Serial.print("vitesse du vent :");
Serial.println(vitesse);
compt = 0; }
int anemometre = 3; // broche de l'anémometre

solution float vitesse = 0; //vitesse du vent


float valeur = 2.4; //2,4 km/h par interruption
unsigned long temps;
unsigned long temps_maintenant;
int interrupt=0; //fonction pour compter le nombre d'impulsion

void setup() {
pinMode(anemometre, INPUT);
Serial.begin(9600);
temps=millis();
attachInterrupt(digitalPinToInterrupt(3),compteur,RISING); //fonction pour compter le nombre d'interruption
}

void loop() {
if (interrupt != 0) {
temps_maintenant = millis();
while (temps - temps_maintenant <= 1000) {
vitesse = valeur*interrupt; //calcul de la vitesse du vent
Serial.print("vitesse du vent :");
Serial.println(vitesse); }
interrupt = 0; }
Serial.print("Pas de vent"); }

void compteur() {
interrupt++; // on incrémente interrupt
}
Exemple inspiré
int pin = 4;
const byte interruptPin = 2; on voudrais calculer la vitesse d’un train, on a un
volatile byte compteur; capteur à effet hall dessous un wagon et un aimant qui
float Temps;
est sur l’essieu.
unsigned long timeold;
float DP = 3.5;
On calcule le temps entre deux interruptions pour
ensuite calculer la vitesse avec le temps.
void setup() {
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), compte, LOW);
pinMode(pin, INPUT);
Serial.begin(9600);
timeold = 0; }

void loop() {
Temps = (millis() - timeold)*compteur;
Serial.println(Temps); }

void compte ( ) {
compteur = compteur+1; }
Timer
Timer

Un timer est un registre à l’intérieur du microcontrôleur qui s’incrémente (ou se


décrémente) chaque fois qu’il reçoit une impulsion d’un signal d’horloge. Ce signal
d’horloge peut être propre au microcontrôleur ou bien extérieur à celui-ci. Un timer
est donc un compteur capable de compter le temps qui s’écoule, d’où son nom
anglais de timer counter.
Timer
Si le registre du timer comporte 8 bits, il est alors capable de compter de 0 à 255 (en
hexadécimal, de 00 à FF). Lorsqu’il arrive à 255 (FF), un coup d’horloge
supplémentaire devrait le faire passer à 256 (soit 100 en hexadécimal), ce qui n’est
pas possible puisque le registre n’a que 8 bits. Le registre passe donc à 0 ; on dit qu’il
subit un débordement (Overflow en anglais), mais ce débordement entraîne la mise à
1 d’un bit bien particulier dans un registre de contrôle associé au timer. Ce bit est
appelé un flag (drapeau en anglais) et indique que le timer vient de compter jusqu’à
256, ce qui permet d’attirer l’attention du programmeur.
Timer
L’intérêt d’un timer est qu’il compte sans cesse et que pendant ce temps, le
programme peut réaliser autre chose, ce qui n’est pas possible si on utilise la fonction
delay() qui est bloquante et qui ne permet pas de faire autre chose pendant ce temps
d’attente. Le temps que le timer met pour compter 256 coups dépend bien sûr de la
fréquence de l’horloge ; à 16 MHz (fréquence du microcontrôleur utilisé dans les
modules Arduino), c’est très rapide, mais il est possible de diviser cette fréquence
d’horloge grâce à des circuits internes au microcontrôleur appelés prédiviseur
(prescaler en anglais). On peut alors diviser la fréquence de base (16 MHz) par 8, 32,
64, 128, 256 ou 1024 ; pour cela, il faut utiliser intelligemment d’autres registres de
contrôle associés au timer. Par exemple, si on règle de prédiviseur pour diviser la
fréquence par 1024, le timer comptera donc à une fréquence de 15625 Hz.
Timer
Comme pour tout registre, on peut lire la valeur d’un timer ou bien écrire une valeur
particulière dans le timer. Mais ce qui est surtout important, ce sont les registres de
contrôle associés au timer car ce sont eux qui permettent de modifier le
comportement du timer et de contrôler ce qu’il fait. Il faut donc bien les connaître
pour bien savoir les utiliser et la lecture de la documentation liée au microcontrôleur
est souvent indispensable.
Timers du microcontrôleur de l’Arduino
Le module Arduino Uno est construit autour du microcontrôleur AVR ATmega328P
d’Atmel qui possède 3 timers :

• Le timer0, sur 8 bits, utilisé par les fonctions delay(), millis() et micros(). Il
commande également des PWM (Pulse Width Modulation ou Modulation par Largeur
d’Impulsion) sur les broches 5 et 6.

• Le timer1, sur 16 bits, qui compte de 0 à 65535 (0 à FFFF en hexadécimal) et qui


est utilisé par la bibliothèque Servo ou bien pour de la PWM sur les broches 9 et 10.

• Le timer2, sur 8 bits, qui est utilisé par la fonction Tone() ou bien pour de la PWM
sur les broches 3 et 11.
Les registres de contrôle

Timer 0 Timer 1 Timer 2 Rôle


TCNT0 TCNT1L TCNT2 Timer (bit 0 à 7)
- TCNT1H - Timer (bit 8 à 15)
TCCR0A TCCR1A TCCR2A Registre de contrôle
TCCR0B TCCR1B TCCR2B Registre de contrôle
- TCCR1C - Registre de contrôle
OCR0A OCR1AL OCR2A Output Compare (bit 0 à 7)
- OCR1AH - Output Compare (bit 8 à 15)
OCR0B OCR1BL OCR2B Output Compare (bit 0 à 7)
- OCR1BH - Output Compare (bit 8 à 15)
- ICR1L - Input Capture (bit 0 à 7)
- ICR1H - Input Capture (bit 8 à 15)
TIMSK0 TIMSK1 TIMSK2 Interrupt Mask
TIFR0 TIFR1 TIFR2 Interrupt Flag

Vous aimerez peut-être aussi