rév 2015/12/13
Un système à microcontrôleur est généralement pourvu d’entrées et de sorties. Le but premier du programme est souvent de réagir correctement aux changements d’état des entrées, en agissant en conséquence sur les sorties.
Les enseignes et afficheurs à LED sont plutôt une exception dans ce domaine. Beaucoup d’enseignes ou d’afficheurs n’ont aucune entrée et ne font que faire évoluer les sorties selon un ordre prédéfini.
Il existe toutefois des cas où une enseigne ou un afficheur doit réagir à des entrées. Par exemple, une télécommande peut être utilisée pour allumer et éteindre un afficheur, changer sa luminosité ou le texte qu’il doit afficher. Un autre cas où le système doit réagir à un événement est la gestion du temps dans un afficheur multiplexé : à des instants précis, il faut envoyer de nouvelles valeurs sur les LED.
On appelle interruption dans un système informatique l’arrêt temporaire d’un programme au profit d’un autre programme, jugé à cet instant plus important. L’interruption correspond au sens qu’on donne à ce mot dans nos vies courantes. Prenons un exemple : je suis en train de travailler à mon bureau. Le téléphone sonne. Je vais répondre au téléphone. Après la conversation, je reprends mon travail là où je l’avais laissé.
C’est toujours un événement qui va produire une interruption. Cet événement a un caractère imprévisible, le programme ne sais pas quand il va se produire.
Pour utiliser les interruptions, il n’est pas indispensable de comprendre en détail le mécanisme qui les rend possibles. C’est comme les procédures ou les fonctions, que nous avons l’habitude d’utiliser sans forcément connaître les mécanismes matériels qui les rendent possibles.
Toutefois, nous allons ici faire une petite incursion dans le monde de la programmation en assembleur, pour mieux comprendre les interruptions. La figure ci-dessous montre un programme, dont les instructions successives sont notées en bleu. Dans le cas où une fraction du programme doit s’exécuter plusieurs fois, on a l’habitude de grouper ses instructions, notées ici en vert. On appelle ce morceau de programme une routine ou sous-routine. Ce concept correspond aux procédures et aux fonctions dans les langages évolués comme le C.
L’appel de la routine se fait par une instruction Call dans le programme principal. A la fin de l’exécution de la routine, une instruction Ret (return = retour) permet de revenir au programme appelant, juste après l’instruction Call. La routine peut être appelée plusieurs fois dans le programme principal.
Notons que l’adresse de retour doit être mémorisée pour que le retour soit possible. C’est une pile (stack) en mémoire vive qui est utilisée à cette fin. Nous ne détaillerons pas son mécanisme ici.
Regardons maintenant la figure suivante. Un nouvelle routine, appelée Routine d’interruption est représentée en rouge. On voit qu’elle va aussi s’exécuter. Mais son exécution n’est pas la conséquence d’une instruction Call.
Rien dans le programme principal ne permet de savoir que cette routine va s’exécuter. C’est un événement qui est la cause de son exécution. C’est ce qu’on appelle un interruption.
La routine d’interruption se termine aussi par une instruction de retour, appelée Reti (return from interrupt). En plus d’effectuer le retour au programme interrompu, elle rétablit le mode de sensibilité aux interruptions qui prévalait avant l’interruption.
Quels sont ces événements qui vont produire une interruption ? Il en existe principalement deux sortes :
Dans cette catégorie des interruptions intérieures au microcontrôleur, les plus importantes sont celle liées aux Timers. Ce sujet sera abordé dans un chapitre séparé, vu son importance pour la commande des enseignes et afficheurs à LED.
Il existe généralement plusieurs sources interruptions sur un microcontrôleur. Lorsqu’une interruption se produit, le système doit être capable d’en savoir la source. Si rien n’est prévu au niveau matériel, la routine d’interruption doit consulter les registres pour chaque interruption, pour connaître celle qui a été activée.
Les vecteurs d’interruption (interrupt vectors) permettent d’être plus efficace : une adresse différente est réservée pour le début de la routine de chaque interruption.
Souvent ces deux mécanismes vont être utilisés successivement, comme nous le verrons plus bas lors d’une interruption produite par une entrée sur un MSP430.
Voici la table résumée des vecteurs d’interruption pour un MSP430G, y compris l’adresse pour le Reset :
Les adresses se trouvent en mémoire flash, ce sont les dernières adresses de l’espace d’adressage de 16 bits.
Plusieurs sources d’interruptions nécessitent la scrutation pour déterminer la cause exacte de l’interruption. C’est le cas par exemple des interruptions sur les ports : chaque bit peut produire une interruption. C’est aussi le cas d’une des interruptions des Timers : les registre de comparaison 1 et 2, ainsi que l’interruption générale du Timer sont regroupés sur un vecteur unique.
Plusieurs étapes sont nécessaire pour mettre en œuvre une routine d’interruption :
Le schéma logique ci-dessous montre la logique qui permet de générer les interruptions et les fanions qu’il faut ajuster.
On y trouve :
Le langage C ne définit pas la syntaxe des routines d’interruptions. Plusieurs notations sont utilisées, dépendant des compilateurs. Nous présenterons ici une des syntaxe supportée par les compilateurs GCC.
#pragma vector=NUMERO_DU_VECTEUR
__interrupt void Nom_de_la_routine (void) {
...
}
La première ligne indique au compilateur à quel vecteur d’interruption la routine sera associée. La seconde ligne est une déclaration de procédure presque habituelle, avec un nom et aucun paramètres d’entrée (void)
. L’indication __interrupt
permet au compilateur d’utiliser les instructions correspondant à une routine d’interruption, en particulier le Reti final.
Sur les microcontrôleurs MSP430, plusieurs registres permettent de définir la manière dont une broche d’entrée-sortie est utilisée. Ils sont associés à un Port, composé de 8 broches. Sur le MSP430G2553 du Launchpad, deux ports sont disponibles : P1 et P2. On connaît déjà les registres suivant :
P1DIR
: détermine le rôle de la broche (entrée ou sortie)P1OUT
: donne la valeur pour les broches de sortieP1IN
: permet de lire la valeur des entréesP1REN
: permet d’enclencher une résistance de tirage (pull-up ou pull-down, selon l’état de bit de P1OUT correspondantPour mettre en œuvre les interruptions sur des broches du port P1, trois registres supplémentaires sont disponibles :
P1IE
: (Interrupt Enable) permet l’enclenchement de l’interruption pour chaque bit. L’usage habituel est d’écrire dans ce registre pour choisir quels bits vont causer une interruption.P1IES
: (Interrupt Edge Select) permet de choisir pour chaque bit le flanc qui va produire l’interruption. Lorsque le bit est à 0, l’interruption va se produire lors d’une transition de 0 vers 1 (low-to-high transition). L’usage habituel est d’écrire dans ce registre pour choisir quel flanc va causer une interruption, pour chaque bit.P1IFG
: (Interrupt FlaG) les fanions d’interruption. Lorsque qu’un transition telle qu’elle est spécifiée dans un bit de P1E
, le bit correspondant s’active dans P1IFG. C’est son activation qui produit l’interruption elle-même.Voici un programme qui met en œuvre une interruption sur l’entrée P1.3
(le poussoir du Launchpad) et qui change d’état P1.6
(la LED verte) à chaque flanc descendant.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
La première remarque, c’est que la boucle principale while(1)...
ne fait rien ! En plus des initialisations classique des entrées et des sorties, trois instructions ont été ajoutées, correspondant aux étapes de mise en œuvre d’une interruption :
P1IES
sélectionne le flanc descendantP1IE
autorise l’interruption sur l’entrée P1.3
__enable_interrupt()
autorise globalement les interruptions sélectionnéesLa mise à 0 du bit 3 dans le registre des fanions d’interruption P1IFG
évite qu’un flanc sur l’entrée P1.3
pouvant s’être produit avant l’autorisation des interruptions ne cause une interruption.
La routine d’interruption a été placée à la suite du programme principal, alors que nous avons l’habitude de placer les procédures avant le programme principal. Cette procédure n’est en effet jamais appelée explicitement.
Dans notre exemple, seul le bit 3 est concerné par les interruptions. Dans la routine d’interruption, il n’y a donc pas besoin de regarder quel bit a produit l’interruption et c’est systématiquement le fanion 3 qui est remis à 0.
Lorsque l’interruption peut provenir de plusieurs entrées, il est alors nécessaire de scruter le registre pour connaître le bit qui a causé l’interruption, comme le montre cet exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Voici un autre exemple d’interruption, où l’événement est interne au microcontrôleur. L’interruption doit se produire lorsque le convertisseur Analogique-Numérique (ADC) termine sa conversion.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|