Outils pour utilisateurs

Outils du site


msp430-interruption

Les interruptions sur MSP430

Auteur: Bjonnh

Licence: Creative Commons BY SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/fr/

Principe

Une interruption est un dispositif permettant d'interrompre, suite à un évènement, le fonctionnement d'un programme pour exécuter une fonction spécifique à cet évènement. C'est ce qui permet d'éviter d'avoir à faire ce qu'on appelle du “polling”, consistant à vérifier en boucle dans le programme si une valeur à changé, ralentissant d'autant le code, évitant au microcontrolleur de se mettre en veille et présentant le risque de rater un évènement si le programme était en train de faire autre chose.

Un endroit spécifique de la mémoire contient ce que l'on appelle un vecteur d'interruption contenant l'adresse de la fonction quoi doit être exécutée quand l'interruption se présente.

Il existe 3 types d'interruptions sur les MSP430:

  • Reset
  • Interruption non masquable (NMI)
  • Interruption masquable

Le reset est une interruption spéciale, qui a une priorité sur toutes les autres. Par défaut le reset pointe vers l'adresse de départ du programme, mais il est possible de changer cela afin d'effectuer une action avant le reset effectif. Ou d'utiliser le bouton reset pour faire autre chose.

Les NMI ne sont pas gérées par le même système d'interruption que les autres. Elles sont toutes désactivées automatiquement après l'exécution d'une d'entre elles et doivent être explicitement réactivées par l'utilisateur. Le vecteur d'interruption est situé à l'adresse 0xFFFC. Il existe trois sources d'interruptions :

  • Une impulsion sur la broche !RST/NMI (!RST signifie que l'entrée est active sur niveau bas) si elle est configurée ainsi. Son bit d'activation est NMIIE.
  • Un problème d'oscillateur. Son bit d'activation est OFIE
  • Un problème d'accès à la mémoire flash. Son bit d'activation est ACCVIE.

Les interruptions masquables sont produites par les différents périphériques internes et externes du MSP430. Ce sont celles qui nous intéressent ici.

Contrairement à d'autres microcontrolleurs tels que les ATMEGA 328, les MSP430 disposent d'interruptions programmables sur chaque broche de PORT1 et PORT2. Ils sont configurés par 3 registres, PxIFG, PxIE et PxIES (avec x étant le numéro de port).

Les différents vecteurs

Les différents vecteurs sont définis à partir de l'adresse 0xFFE0. Attention les pointeurs de fonction sont définis sur un mot (deux octets).

InterruptionNom en CDécalagePosition réelle
Port 1 PORT1_VECTOR 0x0004 0xFFE4
Port 2 PORT2_VECTOR 0x0006 0xFFE6
Convertisseur A/D ADC10_VECTOR 0x000A 0xFFEA
USCI A0/B0 Transmission USCIAB0TX_VECTOR 0x000C 0xFFEC
USCI A0/B0 Réception USCIAB0RX_VECTOR 0x000E 0xFFEE
Timer0)A CC1, TA0 TIMER0_A1_VECTOR 0x0010 0xFFF0
Timer0_A CC0 TIMER0_A0_VECTOR 0x0012 0xFFF2
Timer du Watchdog WDT_VECTOR 0x0014 0xFFF4
Comparateur A COMPARATORA_VECTOR 0x0016 0xFFF6
Timer1_A CC1-4, TA1 TIMER1_VECTOR 0x0018 0xFFF8
Timer1_A CC0 TIMER1_A0_VECTOR 0x001A 0xFFFA
NMI NMI_VECTOR 0x001C 0xFFFC
Reset [Priorité maximale] RESET_VECTOR 0x001E 0xFFFE

On n'a pas besoin de s'embêter avec la position réelle, la macro « interrupt(VECTOR) » contenue dans legacymsp430.h permet de faire le calcul automatiquement. Ainsi, on définit une fonction pour une interruption sur le PORT1, par :

interrupt (PORT1_VECTOR) Nomdelafonction(void)
{
// Corps de la fonction
}

Les registres concernés

PxIFG

Ce registre est un registre drapeau, le bit correspondant à l'entrée du port ayant déclenché une interruption est mis à 1 par le moteur d'interruptions. Il peut aussi servir à déclencher des interruptions logicielles, c'est à dire que c'est l'utilisateur qui place un bit à 1 dans ce registre pour déclencher l'interruption.

Attention, pensez à remettre le bit correspondant à votre évènement de ce registre à 0 durant la fonction d'interruption, sinon elle ne sera plus jamais déclenchée.

Attention, le fait de changer le mode (entrée/sortie) de la broche peut déclencher une interruption, il peut être plus sage de la désactiver avant.

PxIES

Ce registre contrôle sur quel front est déclenchée l'interruption. Si le bit est à 0, c'est une transition bas vers haut qui déclenche, et inversement pour 1.

Changer ce registre peut conduire à déclencher une interruption !

PxIESxPxINxPxIFGx
0→10Peut être
0→11Non changé
1→00Non changé
1→01Peut être

PxIE

Ce registre sert à contrôler si l'on souhaite utiliser les interruptions sur la broche correspondante.

Réalisation

#include <msp430g2553.h>
#include <legacymsp430.h> // nécessaire pour les interruptions
 
 
interrupt (PORT1_VECTOR) Port_1(void) // Façon de coder une fonction d'interruption sur le PORT1
{
  volatile int i;
  for (i = 0; i < 0x60; i++); // Petite pause pour l'anti-rebond
  while (! (P1IN & BIT3)); // On attend que l'utilisateur lache le bouton
  P1OUT ^= BIT6; // On inverse la led verte
  P1IFG &= ~BIT3; // On efface le drapeau pour permettre à l'interruption d'arriver à nouveau
}
 
int main(void) {
  volatile int i;
 
  // Arrêt du Watchdog
  WDTCTL = WDTPW | WDTHOLD;
 
  // P1.0 et P1.6 en tant que sorties
  P1DIR = BIT0 | BIT6;
  // P1.0=0 et P1.6=1 
  P1OUT = BIT3; // Il est nécessaire de mettre le registre de l'entrée à 1
  P1REN = BIT3; // Et de mettre la résistance de pull-down
 
  // Règle l'interruption sur P1.3
  P1IE |= BIT3; // On souhaite surveiller P1.3
  P1IES |= BIT3; // Sur un front descendant
  P1IFG &= ~BIT3; // Effacer le registre drapeau
  _BIS_SR(GIE); // Lancer le module des interruptions
 
  for (;;) { // Boucle infinie
    // inversion de la led rouge
     P1OUT ^= BIT0;
    // pause pour le clignotement
    for (i = 0; i < 0x6000; i++);
  }
}

Qu'est-ce qu'il y a sous le capot

Reprenons la définition de fonction d'interruption:

interrupt (PORT1_VECTOR) Nomdelafonction(void)
{
// Corps de la fonction
}

Le code précédent est en fait transformé par le préprocesseur en :

void __attribute__((interrupt ((0x0004)))) Nomdelafonction(void)
{
// Corps de la fonction
}

attribute est une fonction spéciale de gcc servant à donner au compilateur des informations sur la fonction. On peut indiquer beaucoup de choses, mais dans ce cas précis, on lui indique que cette fonction doit être placée au vecteur d'interruption ayant un offset de 0x0004.

À vérifier: Le compilateur connaît la position des vecteurs d'interruption grâce au fichier memory.x correspondant à l'architecture. C'est pourquoi il est important de spécifier l'option -mmcu de gcc correspondant exactement au composant utilisé.

Le code assembleur généré par gcc (option -S) devient alors

.global	Port_1
	.type	Port_1,@function
/***********************
 * Interrupt Vector 2 Service Routine `Port_1' 
 ***********************/
Port_1:
.global	__isr_2
__isr_2:
 // Reste de la fonction

Ceci indique au compilateur que la fonction est située à l'adresse __isr_2.

Ce code une fois compilé donne quand on utilise msp430-objdump -d une fonction située à l'adresse 0xc04c et une section .vector située à partir de l'adresse 0xffe0, contenant à l'adresse 0xffe4 (celle du vecteur d'interruption de PORT1 vu dans le tableau précédent) la fonction 0xc04c.

C'est ici l'avantage du format ELF, permettant d'indiquer la position d'une section en mémoire sans avoir à définir l'intégralité de la mémoire.

Ainsi le fichier avec des fonctions vides (main et Port1) sans informations de debug et compilé avec -Os fait 492 octets, alors que si il avait fallu définir la mémoire entière, il aurait fait 64K (Je donnerai plus de détails sur cette histoire de mémoire dans un autre article, en particulier pour expliquer comment une petite bête avec 16K de mémoire contient des adresses qui s'étendent sur 64K).

Il est possible de voir les différentes sections d'un fichier ELF avec l'outil readelf -a. Qui nous donne la taille de chacune de ces sections. Ainsi on voit que même si le fichier fait 492 octets, finalement, la majorité des données est constituée par les métadonnées de ELF et les sections spéciales telles que les vecteurs. On ne peut donc pas juger de la taille réelle d'un programme par sa taille sur le disque.

_BIS_SR(GIE) ???

Cette fonction est une macro, remplacée par :

__bis_status_register((0x0008));

Elle sert en fait à mettre à 1 dans le registre SR (Status register) les bits correspondants à son argument.

La fonction __bic_status_register (ou _BIC_SR) sert à mettre cette valeur à 0.

Le bit 3 du registre SR (16 bits) est appellé GIE (Global Interrupt Enable) et sert à lancer le moteur d'interruptions.

Sources

msp430-interruption.txt · Dernière modification: 2015/01/28 03:09 (modification externe)