Interruptions multiples sur Arduino

Written by Sebastien Lambot on . Posted in Electronique

Voici un guide sur le fonctionnement des interruptions sur Arduino. Nous allons d’abord étudier un circuit simple pour bien comprendre les mécanismes d’interruption, et nous céerons ensuite un circuit capable de gérer de multiples interruptions.

Les interruptions permettent à certaines tâches importantes de survenir à tout moment, et permettent donc de résoudre certains soucis de temporisation. Dès qu’une interruption est détectée par le processeur, celui-ci sauve son état d’exécution, exécute une portion de code liée à l’interruption (Interrupt Service Routine), et revient ensuite à son état avant l’interruption pour continuer ce qu’il faisait.

Sur Arduino, la création d’une interruption commence avec la fonction attachInterrupt(p1, p2, p3) qui prend 3 paramètres:

  • p1: le numéro de l’interruption. La plupart des arduinos ont 2 interruptions: l’interruption n°0 qui est liée à la pin 2 et l’interruption n°1 qui est liée à la broche 3. L’arduino Mega en possède 4 de plus: l’interruption n°2 sur la broche 21, la n°3 sur la broche 20, la n°4 sur la broche 19, et la n°5 sur la broche 18.
  • p2: la fonction ou l’ISR. La fonction ne doit pas prendre de paramètre et ne doit rien renvoyer. Comme le processeur est « gelé » à ce moment-là, assurer vous d’avoir une fonction la plus courte possible. Au lieu d’appeler d’autres fonctions lourdes, activez un flag qui sera lu dans votre code général et sortez de l’interruption.
  • p3: le mode ou par quoi est-ce que l’interruption sera déclenchée. LOW lorsque la pin est à l’état bas (et de manière répétée tant que la pin est au niveau bas), CHANGE lorsque son état change, et RISING ou FALLING si un flanc montant ou descendant est détecté.
Attention, à l’intérieur de la routine d’interruption, certaines fonctions sont inutilisables (delay ne fonctionne pas, millis renverra toujours la même valeur, les données séries reçues seront perdues et les signaux PWM sont affectés).
Toute valeur modifiée à l’intérieur de la routine d’interruption devra être déclarée comme volatile, afin que le processeur aille chercher la valeur en mémoire et ne se fie pas à ce qui se trouve dans ses registres qui étaient gelés au moment de l’interruption.

Si une fonction attachInterrupts assigne une routine à une pin, celle-ci écrase la configuration précédente.

La fonction detachInterrupt(pin) permet de retirer l’assignation d’une interruption à une pin.

Les fonctions interrupts() et noInterrupts() permettent respectivement d’activer ou de désactiver les interruptions. Celles-ci sont activées par défaut.

Interruption simple

Pour ce premier montage, un bouton poussoir va déclencher l’interruption.

int pin = 13;
volatile int state = LOW; // déclartion d’une variable volatile

void setup()
{
pinMode(pin, OUTPUT);
attachInterrupt(0, blink, CHANGE); // attache l’interruption externe n°0 à la fonction blink
}

void loop()
{
digitalWrite(pin, state); // la LED reflète l’état de la variable
}

void blink() // la fonction appelée par l’interruption externe n°0
{
state = !state; // inverse l’état de la variable
}

int_simpleInterruption évoluée

Après avoir câblé le tout et uploadé votre code, vous remarquerez que celui-ci ne fonctionne pas exactement comme il devrait. Ceci est dû au bouton qui crée un effet « bounce » lorsqu’il est pressé.
butbouncPour contrer cet effet, il existe plusieurs méthodes: hardware (avec une capa en parallèle du bouton pour le transformer en circuit RC) ou software.

Pour minimiser le nombre de composants utilisés, nous allons opter pour la méthode software, mais rien ne vous empêche d’ajouter une capa et de comparer.

Nous allons introduire un temps de debounce, ce qui nous permet, suite à une interruption, d’ignorer les interruptions suivantes si elles se trouvent dans cette fenêtre de temps. J’ai pris pour l’exemple un temps de 250ms, ce qui permet de contrer l’effet bounce de la majorité des boutons. Vous pouvez essayer avec différents boutons de diminuer ce délai jusqu’à ce que les effets néfastes réapparaissent et vous remarquerez que les boutons ne sont pas tous égaux, certains étant plus performants (et généralement plus bruyants).

Avec un bouton poussoir standard, j’ai un comportement correct jusqu’à un debounce d’environ 150ms. En-dessous, il se déclenche plusieurs fois.

int pin = 13;

volatile unsigned long button_time = 0;
volatile unsigned long last_button_time = 0;
int debounce = 250; // debounce latency in ms

volatile int state = LOW; // déclartion d’une variable volatile

void setup()
{
pinMode(pin, OUTPUT);
attachInterrupt(0, blink, FALLING); // attache l’interruption externe n°1 à la fonction blink
}

void loop()
{
digitalWrite(pin, state); // la LED reflète l’état de la variable
}

void blink() // la fonction appelée par l’interruption externe n°0
{
button_time = millis();

if (button_time – last_button_time > debounce)
{
state = !state; // inverse l’état de la variable
last_button_time = button_time;
}

}

Interruptions multiples

Si vous utilisez un arduino standard avec plusieurs boutons déclenchant des interruptions, vous risquez d’être limité par les 2 seules interruptions disponibles.

Grâce à quelques diodes et un code un peu plus élaboré, nous allons pouvoir étendre nos possibilités:

multi_int

int_multi2

#include <lib_serial.h>

#define inter 2 //pull-up IN
#define button1 3 //pull-up IN
#define button2 4 //pull-up IN
#define button3 5 //pull-up IN

volatile unsigned long button_time = 0;
int debounce = 50; // debounce latency in ms

volatile int trigger = 0;
int total = 0;
int inta = 0;
int intb = 0;
int intc = 0;

void setup() {
initPins();
initSER();
Serial.println("Starting…");

attachInterrupt(0, detect, FALLING);
}

void loop() {
if (trigger != 0){
total++;
if (trigger == 1) inta++;
if (trigger == 2) intb++;
if (trigger == 3) intc++;
Serial.print(total);
Serial.print(" || ");
Serial.print(inta);
Serial.print(" | ");
Serial.print(intb);
Serial.print(" | ");
Serial.println(intc);
trigger = 0;
}
}

void detect(){
if (button_time > millis()) return;
delayMicroseconds(32000);
if (!digitalRead(button1)) trigger=1;
if (!digitalRead(button2)) trigger=2;
if (!digitalRead(button3)) trigger=3;

button_time = millis() + debounce;
}
// —————–
void initPins(){
pinMode(button1, INPUT);
pinMode(button2, INPUT);
pinMode(button3, INPUT);
pinMode(inter, INPUT);
pinMode(led, OUTPUT);
digitalWrite(button1, HIGH);
digitalWrite(button2, HIGH);
digitalWrite(button3, HIGH);
digitalWrite(inter, HIGH);
digitalWrite(led, LOW);
}

J’ai stocké les procédures d’initialisation dans un tab séparé

void initSER(){
Serial.begin(19200);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
}

Si vous êtes attentifs, vous verrez que la routine d’interruption a été légèrement modifiée, et qu’elle incorpore un delayMicroseconds().

Contrairement au delay(), la fonction delayMicroseconds peut être utilisée dans une routine d’interruption car elle ne se base pas sur des interruptions mais sur des NOP (1 cycle de processeur pendant lequel il ne fait rien). Ce qui nous permet d’ajouter 0,030 seconde (la fonction prend un int en paramètre, ce qui nous limite sur un arduino uno à environ 32.000, arrondi à 30.000). Bien que négligeable, il s’avère que ce délai est d’une aide précieuse pour la lecture et l’identification de la pin qui a déclenché l’interruption. Avant de l’utiliser, mon interruption configurée sur FALLING, donc sur flanc descendant, se déclenchait lorsque j’appuyais sur le bouton, mais également lorsque je le relâchais. Ce qui n’est plus le cas avec ce micro-délai. Par ailleurs, le debounce s’en retrouve amélioré et le debounce time peut être diminué de manière très conséquente. Dans le cadre de mon application, j’ai même pu supprimer les mécanismes de debounce pour ne laisser que le delayMicroseconds().

Tags: ,

Trackback from your site.

Comments (13)

  • yvohero

    |

    Salut,

    Pas mal l’astuce pour la multi-interruption. Je suis sur un projet dont je voudrais faire intervenir une interruption mise en veille pour que mon appareil consomme moins. Est il possible de le mettre en veille automatiquement au bout d’un certain temps. par exemple, une minute et qu’il se réactive lors d’une impulsion sur un bouton poussoir à l’aide du système multi-interruption décrit au dessus ?

    Reply

    • Sebastien Lambot

      |

      Dans ce cas, on parle de sleep_mode. Il en existe 4 en tout (SLEEP_MODE_IDLE – the least power savings, SLEEP_MODE_ADC, SLEEP_MODE_PWR_SAVE, SLEEP_MODE_STANDBY, SLEEP_MODE_PWR_DOWN – the most power savings). Chaque sleep_mode désactive différent composants internes (timers, watchdogs, etc.).
      Tu trouveras un tutoriel très complet sur le site arduino playground, et une mise en pratique sur le site de Donal Morrissey.
      Bon courage!

      Reply

  • maxkcirtap

    |

    Bonjour,
    Voilà une astuce bien utile, car la gestion des interruptions n’est pas simple, surtout pour un débutant… Merci donc !

    Sur la Mega il y effectivement 4 interruptions sur les broches 18 à 21 (interruption 3 à 0). Mais les broches 20 et 21 sont aussi des broches SDA et SCL. Si SDA et SLC sont utilisées par exemple pour un LCD I2c et un RTC 1307, je n’arrive pas à utiliser les interruptions 0 ou 1 sur ces broches pour la gestion d’un capteur de pluie à auget.

    Est-ce normal ?
    Cordialement.

    Reply

    • Sebastien Lambot

      |

      Je ne pourrais malheureusement pas vous répondre car je n’ai pas de Mega sous la main. Mieux vaut poser la question sur le forum arduino.
      A bientôt!

      Reply

  • Bestel

    |

    Bonjour,
    D’abord merci pour cet article.
    Le montage des interruptions multiples m’intéresse, mais pour des raisons esthétiques, je voudrais utiliser des capteurs ttp223.
    Est-ce qu’ils peuvent fonctionner en remplacement des poussoirs, et quelles seraient les modifications à apporter au circuit et/ou au code ?
    Merci d’avance de votre réponse.
    Cordialement,

    Reply

    • Sebastien Lambot

      |

      Les TTP223 ne devraient pas poser problème. Vu qu’ils renvoient une valeur analogique, il n’y a pas de « debounce » à faire, je conseillerais plutot de mettre un trigger de schmidt entre le TTP223 et l’entrée d’interruption pour éviter les effets de flapping (variations autour de la valeur de déclenchement).

      Reply

  • Valery01

    |

    Bonjour,
    Une chose m’interroge dans les explications vous dites: Attention à l’intérieur de la routine d’interruption la fonction Delay ne fonctionne pas et la fonction Millis renvoi toujours la mémé valeur. Or dans l’exemple la routine d’interruption blink contient la fonction Millis !??

    Reply

    • Valery01

      |

      Je crois que je viens de comprendre : en fait la valeur millis est neutralisée à la dernière valeur juste avant de rentrer ou juste en rentrant dans la routine d’interruption mais le compteur hard correspondant à mollis continue à tourner. Ce qui veut dire que si on test 30 milli-secondes dans la routine pd’interruption si on demande la différence entre millis au début de la routine et en fin de routine d’interruption on aura 0 car millis est neutralisé mais une fois sorti de la routine d’interruption si on fait la différence entre la valeur mémorisée dans la routine d’interruption par une variable volatile et la valeur millis une fois sorti de la routine d’interruption on aura bien nos 30 d’écart

      Reply

      • Sebastien Lambot

        |

        exact 🙂

        Reply

  • gwest

    |

    Bonjour,
    J’essaye de mettre en place la lecture de deux interrupteurs sur arduino nano. Mais j’ai toujours le problème des rebonds, si je met en place le code anti rebond avec la librairie « Bounce2 » il n’y a plus le problème des rebonds, cependant si je veux (quand l’intérrupteur est fermé ) changer d’état l’allumage de la sortie (led) cela ne fonctionne pas. Alors qu’avec le code simple sans bounce 2 cela fonctionne :
    http://www.locoduino.org/spip.php?article74

    Comment puis-je faire?

    Reply

  • Matth

    |

    Bonjour je suis projet de fin d’année et j’aimerai savoir, si lors dune interruption il est possible de relever une valeur (par exemple dans mon cas d’un anémomètre) ?
    Merci d’avance
    Cordialement

    Reply

    • Sebastien Lambot

      |

      Le mieux est de créer une variable qui servira de flag, par exemple « check_anemo » et de la mettre à 1 lors du passage dans la routine d’interruption. Ensuite, dans le code principal, il faut prévoir de consulter le contenu de ce flag et lorsqu’il est à 1, passer dans une procédure qui prendra la mesure de la vitesse du vent.
      Les routines d’interruption doivent être les plus courtes possibles et en général on évite de toucher aux I/O pendant cete période car certaines fonctionnalités internes sont désactivées.

      Reply

  • Matth

    |

    Merci beaucoup maintenant que ça marche grâce a vous je suis enfin débloqué et je peux continué. Encore merci

    Reply

Leave a comment

You must be logged in to post a comment.