[TUTORIEL] Les interruptions - Partie 1

[TUTORIEL] Les interruptions - Partie 1

Message non lude Laetitia » Mer 12 Aoû 2015 15:16

Bonjour,

Aujourd'hui nous allons plonger un peu plus au cœur du micro-contrôleur et explorer des fonctionnalités un peu avancées ! C'est un tutoriel principalement théorique dont le but est de vous faire comprendre le principe général des interruptions et tester l'une des plus simples à implémenter.

Ce tutoriel est une traduction, l'original est disponible ici.

Qu'est-ce qu'une interruption ?

Basiquement, une interruption est un signal qui interrompt l'activité normale du microcontrôleur. Elle peut être d'origine interne (changement d'état d'une broche) ou interne (compteur, fin de transmission, etc). Lorsqu'elle est déclenchée, une interruption met en pause le programme principal pour exécuter un bloc de code, qu'on appelle une routine d'interruption (en anglais Interrupt Service Routine, ISR). Une fois cette routine effectuée, le programme reprend là où il s'était arrêté.

Routine ? Signaux ? Mais encore ?

Pourquoi se compliquer la vie juste pour répondre à des événements particuliers alors que vous savez déjà vérifier l'état d'une broche et créer vos propres chronomètres ? L'avantage des interruptions est de vous permettre de gérer ces événements de manière asynchrone, c'est-à-dire à n'importe quel moment du programme, indépendamment de son déroulement normal. Ce qui revient à laisser l'ATmega faire le travail tout seul plutôt que de vérifier manuellement si l'événement attendu est bien arrivé.

Pour mieux comprendre, prenons une comparaison avec la vie réelle :

    Imaginons que vous soyez sur votre canapé, à profiter d'une bonne bière et d'un film après une longue journée. Tout va bien. Le seul problème est que vous attendez un colis important, dont vous avez besoin urgemment. Si vous étiez un programme Arduino standard, vous devriez mettre en pause votre film, vous lever et aller vérifier la boîte aux lettres toutes les 5 minutes pour savoir si le colis est arrivé.
    Au lieu de ça, le livreur va sonner à votre porte au moment où il arrive (vecteur d'interruption, en anglais interrupt trigger). Une fois que vous avez la certitude que votre paquet vous attend derrière la porte, vous pouvez mettre en pause votre film et aller récupérer votre colis (ISR). Une fois que vous avez fini vous pouvez reprendre le film là où vous l'aviez laissé, sans perte de temps supplémentaire. Puissant, non ?
Les microcontrôleurs AVR utilisés pour la plupart des Arduinos ne savent pas faire plus d'une chose à la fois. Gérer certains événements de façon asynchrone via les interruptions permet d'optimiser le code, en évitant de gaspiller de précieux cycles d'horloge à faire des lectures répétées ou à attendre que quelque chose arrive. Les interruptions sont aussi utiles pour les applications qui nécessitent un timing précis, parce que l'on est sûr de détecter l'événement au moment où il a lieu, et de ne rien rater.

Types d'interruptions

Il existe deux types d'interruptions :
  • Matérielles, qui font suite à un événement extérieur, par exemple un changement d'état sur une broche d'entrée,
  • Logicielles, qui font suite à une instruction envoyée par le programme.
Les microcontrôleurs AVR 8 bits comme ceux qui sont sur la plupart des cartes Arduino ne sont pas capables d'interruptions logicielles (mais il est possible de les émuler). Nous allons nous concentrer sur les interruptions matérielles pour le moment. La plupart des tutoriels que vous trouverez parlent de celles-ci, en particulier celles liées à un changement d'état d'une broche. Il s'agit du seul type d'interruption que le "langage" Arduino comprend, en utilisant la fonction attachInterrupt(). C'est déjà bien, mais il y a tout une autre catégorie d'interruptions matérielles basées sur les timers de l'AVR, qui sont très utiles aussi. Ici je ne parlerai que des interruptions externes, je reviendrai sur les interruptions liées aux timers dans un prochain tutoriel, je ne voudrais pas vous causer une indigestion ;)

Quand l'interruption fait ce qu'on lui demande - ISR

Chaque microcontrôleur AVR a une liste de vecteurs d'interruptions, c'est-à-dire d'événements qui peuvent déclencher une interruption. Lorsque celles-ci sont autorisées et que l'un des événements en question a lieu, le code va faire un saut à un endroit spécifique de la mémoire : l'adresse du vecteur d'interruption. En écrivant une routine et en la liant à l'adresse mémoire du vecteur d'interruption, on indique à notre programme quel bloc de code spécifique exécuter lorsque l'interruption est appelée.

Prenons un simple exemple : on va détecter un appui sur un bouton, et exécuter une action lors de celui-ci. Tout au long de ce tutoriel, j'utiliserai un Arduino Uno ; si vous utilisez une carte basée sur un autre microcontrôleur il vous faudra aller regarder la documentation technique pour retrouver le bon brochage, mais la méthode reste la même.

    Montage

BP_INT0.png
BP_INT0.png (34.47 Kio) Vu 1046 fois

Implémenter une interruption dans un programme

Pour utiliser correctement une interruption, nous devons faire trois choses :
  1. Activer le bit d'activation globale des interruptions (Global Enable Interrupts) dans le registre de statut (Status Register) de l'AVR
  2. Activer le bit d'activation spécifique à l'interruption qui nous intéresse (chaque vecteur a son propre on/off)
  3. Écrire une routine et l'attribuer à notre vecteur d'interruption cible.
Pour la première étape, on commence par inclure la bibliothèque d'interruptions d'avr-libc, puis on utilise l'une de ses fonctions pour activer le bit d'activation globale. Ensuite, on active l'interruption qui nous intéresse. La plupart des AVR 8 bits comme l'ATmega328 ont deux broches d'interruption matérielle, INT0 et INT1. Si vous utilisez un Arduino standard, elles sont situées sur les broches D2 et D3, respectivement. Activons INT0 pour détecter un changement d'état sur la broche 2 reliée à notre bouton-poussoir.

Code: Tout sélectionner
#include <avr/interrupt.h>

void setup(void)
{
    sei();                    // Activer toutes les interruptions
    EIMSK |= (1 << INT0);     // Activer l'interruption externe INT0


Note : Si l'activation d'INT0 vous donne de l'urticaire, je vous conseille d'aller relire le tutoriel sur les opérations bitwise :ugeek:

Ensuite, nous devons paramétrer le déclencheur de l'interruption. Lorsqu'il s'agit d'un changement d'état sur une broche, nous avons quatre options : front montant (passage de l'état bas à l'état haut), front descendant, changement d'état logique, ou un état bas. Par défaut pour INT0 et INT1, l'interruption se déclenche lorsque la broche est mise à l'état bas, vous pouvez utiliser la documentation technique pour voir comment paramétrer chaque option. Prenons le front descendant pour cet exemple, juste pour voir comment ça marche.

Code: Tout sélectionner
EICRA |= (1 << ISC01);    // Déclencher INT0 sur front descendant

Enfin, nous définissons une routine qui fait ce que l'on veut. Chaque ISR est définie comme suit :

Code: Tout sélectionner
ISR({vector}_vect)
{
    // Code à exécuter
}

{vector} est le nom de l'interruption que nous avons choisi. Encore une fois, le nom de ces vecteurs sont définis dans la fiche technique du processeur ; ici celui qui nous intéresse est EXT_INT0. Pour cet exemple, nous utiliserons la routine pour faire clignoter la LED branchée sur la broche 13.

En rajoutant le code nécessaire pour la LED, voilà à quoi ressemble le code final :

Code: Tout sélectionner
#include <avr/interrupt.h>

void setup(void)
{
   pinMode(2, INPUT);
   pinMode(13, OUTPUT);
   digitalWrite(2, HIGH); // Activation de la résistance de tirage
   sei();
   EIMSK |= (1 << INT0);
   EICRA |= (1 << ISC01);
}

void loop(void)
{
}

ISR(EXT_INT0_vect)
{
   digitalWrite(13, !digitalRead(13));
}


Note : on peut utiliser la loop() du programme pour faire ce que l'on veut, sans affecter le clignotement de la LED.

La fonction attachInterrupt()

Il y a une autre manière d'exploiter INT0 et INT1 en utilisant le "langage" Arduino. Par exemple, nous pouvons utiliser la fonction attachInterrupt() pour activer la même interruption dans le setup() :

Code: Tout sélectionner
setup(void)
{
   attachInterrupt(0, pin2ISR, FALLING);
}

Dans ce cas de figure, nous définissons notre propre ISR (pin2ISR) et la passons comme argument. pin2ISR va simplement prendre la place de ISR(EXT_INT0_vect). C'est souvent plus simple que d'utiliser les fonctions avr-libc directement, et ça permet de s'affranchir des différences entre les différents micro-contrôleurs. Cependant, les méthodes AVR décrites plus tôt peuvent être utilisées avec n'importe quel vecteur d'interruption, alors que attachInterrupt() ne fonctionne qu'avec les interruptions externes.

Tout mettre ensemble

Il reste quelques dernières choses à savoir sur les interruptions.
Tout d'abord, gardez à l'esprit que quelque soit le but de votre interruption, il vaut mieux que la routine soit courte, parce que pendant qu'elle s'exécute, le reste de votre programme est en pause. Si vous avez paramétré beaucoup d'interruptions, ça peut ralentir l'exécution de votre programme. Aussi, "le reste de votre programme", ça veut dire tout le reste : les fonctions Arduino comme millis() ne s'incrémentent plus, et delay() ne fonctionnera pas dans une ISR.
Ensuite, si vous voulez modifier l'état d'une variable à l'intérieur d'une interruption, vous devrez les déclarer comme variables globales et les marquer comme volatile pour que votre ISR puisse y accéder correctement. Par exemple, au lieu de déclarer globalement :

Code: Tout sélectionner
int myInterruptVar;

Vous déclarerez :

Code: Tout sélectionner
volatile int myInterruptVar;

Enfin, les interruptions sont normalement toutes désactivées à l'intérieur d'une ISR (c'est la raison pour laquelle delay() et millis() ne fonctionnent pas). Cela veut dire que si des interruptions doivent être déclenchées alors que vous êtes encore dans une ISR, votre programme ne les verra pas passer. Voilà une autre raison pour laquelle garder les routines aussi courtes que possible.

En conclusion

Voilà pour les bases. Si vous avez envie d'aller plus loin, je vous invite à consulter le tutoriel sur les interruptions basées sur les timers. Il est basé sur les concepts évoqués ici et vous aidera à utiliser les timers de votre micro-contrôleur pour développer des applications plus poussées. En attendant, vous pouvez fouiller dans les documentations Atmel pour découvrir d'autres types de vecteurs d'interruption !

Où trouver le nécessaire ? Documentation technique > Chapitre "Interrupts" > pour l'Arduino Uno, en utilisant la documentation jointe à ce tutoriel :
  • Page 65 la liste des vecteurs d'interruption
  • Page 70 et suivantes la description des interruptions externes et de leur paramétrage
C'est tout pour cette fois ! J'espère que ce tutoriel vous aura plu, et bonne bidouille en attendant le prochain !

Source
Fichiers joints
Datasheet ATmega328P.pdf
(12.52 Mio) Téléchargé 81 fois
"If it's itchy, scratch it !" - "DIY or die"

RTFM (À lire avant de poster) - ANDb (Arduino Noob Database)
Avatar de l’utilisateur
Laetitia
 
Messages: 296
Inscription: Mar 7 Aoû 2012 15:07
Localisation: Toulouse

Retourner vers Logiciel Arduino

Qui est en ligne

Utilisateurs parcourant ce forum: Aucun utilisateur enregistré et 1 invité