Aller au contenu

Interruptions

Dans le cours “Architecture des ordinateurs”, nous avons vu comment fonctionnent les interruptions sur un Cortex-M. Nous avons vu que nous pouvions réagir à un évènement généré par différentes sources (timer, UART, GPIO, etc…) avec une routine de traitement d’interruptions ou “Interrupt Service Routine (ISR)”. Avec ” LibOpenCM3”, cette routine devait avoir un nom bien précis, défini par la bibliothèque.

Classe InterruptIn et Callback

Avec Mbed OS, les routines de traitement d’interruptions sont “attachées” à des objets sous la forme de fonctions de rappel (callback). Par exemple, pour réagir à la pression d’un bouton, on attache une routine sur le flanc montant du signal du bouton :

Bouton

Pression d’un bouton

Le code ci-dessous montre comment on peut faire :

int_simple.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include "mbed.h"

constexpr int kLedOn  = 0;
constexpr int kLedOff = 1;

DigitalOut led(PE_0, kLedOff);

void handler()
{
    led = !led;
}

int main()
{
    InterruptIn button(PA_0, PullDown);
    button.rise(&handler);
    while (true) {
        asm("nop");
    }
}

À la ligne 15, on crée une instance de InterruptIn pour le bouton et on y attache la routine handler sur l’évènement “rise” (flanc montant) à la ligne 16. Dans cet exemple on a choisi d’appeler cette routine handler, mais on aurait put choisir n’importe quel nom.

Pour plus de détails concernant la classe InterruptIn, consultez la documentation de Mbed OS.

Notez que l’utilisation d’interruptions pour traiter la pression d’un bouton peut poser des problèmes si le bouton produit des rebonds. Pour éviter cela, nous pouvons faire du “polling” périodique, ou nous pouvons désactiver les interruptions pendant le traitement :

int_debounce.cpp
#include "mbed.h"

constexpr int kLedOn  = 0;
constexpr int kLedOff = 1;

DigitalOut led(PE_0, kLedOff);
InterruptIn button(PA_0, PullDown);

void handler()
{
    button.disable_irq();
    led = !led;
    // add delay if needed
    button.enable_irq();
}

int main()
{
    button.rise(&handler);
    while (true) {
        asm("nop");
    }
}

Dans les exemples ci-dessus, la routine de traitement ne prend pas de paramètre. On souhaite agir sur la LED et nous avons défini la LED comme un objet global. Ça serait cependant plus propre si on pouvait passer la LED comme paramètre à la routine de traitement. Ça permettrait aussi d’utiliser la même routine pour gérer d’autres boutons et d’autres LEDs. On pourrait passer un argument de type void* à la routine de traitement, et c’est d’ailleurs ce que nous faisons en C, mais en C++ on peut faire mieux. L’interface de la méthode rise est :

void rise(Callback<void()> func);

La classe Callback utilise les templates1 (la manière de faire des classes et des méthodes génériques en C++) pour permettre plusieurs scénarios.

Le code ci-dessous montre comment passer un argument à la routine de traitement :

int_callback.cpp
#include "mbed.h"

constexpr int kLedOn  = 0;
constexpr int kLedOff = 1;

void handler(DigitalOut* led)
{
    *led = !*led;
}

int main()
{
    DigitalOut led(PE_0, kLedOff);
    button.rise(callback(handler, &led));
    while (true) {
        asm("nop");
    }
}

Si on souhaite que le bouton connecté sur PG_1 contrôle la LED sur PE_0 et le bouton sur PG_0 contrôle la LED PE_1, on peut facilement réutiliser la routine de traitement :

int_callback_2.cpp
#include "mbed.h"

constexpr int kLedOn  = 0;
constexpr int kLedOff = 1;

void handler(DigitalOut* led)
{
    *led = !*led;
}

int main()
{
    DigitalOut led1(PE_0, kLedOff);
    InterruptIn button1(PG_1, PullDown);
    button1.rise(callback(handler, &led1));

    DigitalOut led2(PE_1, kLedOff);
    InterruptIn button2(PG_0, PullDown);
    button2.rise(callback(handler, &led2));

    while (true) {
        asm("nop");
    }
}

Callback avec un objet

Le Callback permet aussi d’utiliser la méthode d’un objet comme argument. Supposons que la classe MyDevice représente la LED que nous souhaitons contrôler. Le code ci-dessous crée une instance de MyDevice et attache la méthode handler à l’évènement “rise” du bouton :

int_object_callback.cpp
 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
26
#include "mbed.h"

constexpr int kLedOn  = 0;
constexpr int kLedOff = 1;

class MyDevice {
   public:
    MyDevice(PinName led) : led_(led, kLedOff), ...
    void handler()
    {
        ...
    }

   private:
    DigitalOut led_;
};

int main()
{
    InterruptIn button(PA_0, PullDown);
    MyDevice device(PE_0);
    button.rise(callback(&device, &MyDevice::handler));
    while (true) {
        asm("nop");
    }
}

L’instance est créée à la ligne 21 et à la ligne 22, on utilise un callback avec le premier argument qui est un pointeur sur l’objet (&device) et le second argument qui est la méthode à appeler (&MyDevice::handler).

L’intérêt de cette méthode est de permettre à la routine d’interruption de conserver un “état” en plus du code à exécuter. Il est important de comprendre l’importance de véhiculer un objet avec son état dans le mécanisme des routines d’interruptions. La section “The importance of state{target=blank}” de la documentation de _Mbed OS explique ce concept et l’illustre avec un exemple.

Pour plus de détails concernant les callbacks, consultez les liens ci-dessous:

Exercice

Exercice Interruptions/1

Implémentez la classe MyDevice pour que la LED soit inversée à chaque deuxième pression du bouton.

Solution
int_object_callback.cpp (solution)
class MyDevice {
   public:
    MyDevice(PinName led) : led_(led, kLedOff), counter_{0} {};
    void handler()
    {
        counter_++;
        if (counter_ >= 2) {
            led_     = !led_;
            counter_ = 0;
        }
    }

   private:
    DigitalOut led_;
    int counter_;
};

Remarque importante sur le thread safety

Comme tout RTOS, l’API de Mbed OS permet de développer des applications multi-tâches basées sur plusieurs entités d’exécution (threads). Nous reviendrons en détail sur ces possibilités.

A ce stade, il est important de comprendre que Mbed OS offre des primitives de synchronisation qui permettent aux développeurs de programmer de telles applications en tenant compte des problèmes de concurrence. En utilisant de telles primitives, les développeurs doivent comprendre le niveau de thread safety qui est réalisé par chaque primitive. Ceci est particulièrement important pour le code exécuté dans un contexte ISR (donc dans une routine servant une interruption). Ce code doit être conçu avec soin afin d’éviter les problèmes de concurrence et de thread safety du système. Un développeur doit donc bien comprendre le niveau de thread safety réalisé par chaque composant de l’API utilisé. En particulier, les méthodes appelées dans une routine de service d’interruption doivent être garanties interrupt safe, ce qui signifie que ces méthodes peuvent être utilisées depuis des threads différents et depuis une routine d’interruption. Par exemple, il n’est pas possible d’utiliser un mutex dans une routine ISR (comme documenté dans Mutex).

Le concept de thread safety sera traité plus en détail dans le chapitre Modèles de programmation. A ce stade, vous pouvez trouver plus d’informations sur Thread safety.


  1. Vous trouverez aussi un tutoriel sur les templates sur www.cplusplus.com