Les interruptions

Nos programmes dans le chapitre précèdent fonctionnent très bien, mais ils ont un gros défaut, ils passent la grande majorité de leur temps à attendre. Ils consomment les ressources du Raspberry pour rien alors qu’on pourrait faire pleins d’autres choses.

En plus, la lecture des ports ne se fait que toutes les 0.5 s, on est donc prévenu que l’état d’une entrée a changé avec un certain retard. La plupart du temps ce n’est pas grave, mais si par exemple une voiture doit éviter un obstacle et que les freins commandés par notre Raspberry répondent avec 0.5s de retard… Afficher l'image d'origine

On voudrait donc être prévenu tout de suite qu’une entrée de notre Raspberry a changé d’état. C’est là que les interruptions entre en jeux, elles vont déclencher une fonction dès que l’état d’une broche va changer.


 

1.1        Programme en C++

#include <iostream>   // Include pour affichage et clavier

#include <signal.h>   // signal de fin de programme

#include <string.h>   // Gestion des String d'erreur

#include <errno.h>    // Gestion des numéros d'erreur

#include <stdlib.h>   // La librairie standard

#include <wiringPi.h> // La wiringPi

 

using namespace std;

 

const int gpio20 = 20; // Regular LED - Broadcom pin 20, P1 pin 38

 

volatile int eventCounter = 0; // Le compteur d'appui sur le bouton

 

// Fonction de fin déclenchée par CTRL-C

void fin(int sig)

{

       // Désactive les résistances

       pullUpDnControl(gpio20, PUD_OFF);

       cout << "FIN signal: " << sig << endl;

       exit(0);

}

 

// -----------------------

// Fonction d'interruption

// -----------------------

void myInterrupt(void) {

       eventCounter++;

       cout << "Nb: " << eventCounter << " temps: " << clock() << endl;

}

 

// ----

// main

// ----

int main(void) {

       // Ecoute du CTRL-C avec fonction à lancer

       signal(SIGINT, fin);

      

       // Setup wiringPi library

       if (wiringPiSetupGpio () < 0) {

             cerr << "Erreur d'initialisation de wiringPi: " <<  strerror (errno) << endl;

             exit(1);

       }

 

       pinMode(gpio20, INPUT);

       pullUpDnControl(gpio20, PUD_UP);

 

       // Programmation de l'interruption si la GPIO20 passe de 1 à 0

       if ( wiringPiISR (gpio20, INT_EDGE_FALLING, &myInterrupt) < 0 ) {

           cerr << "Erreur d'initialisation de l'interruption: " <<  strerror (errno) << endl;

           exit(2);

       }

 

       // On boucle, mais on pourrait faire plein d'autres choses...

       while ( 1 ) {

       }

}

 

Il y a déjà plein d’explications dans les commentaires, mais il y a quelques nouveautés.

-          Les fonctions d’initialisation sont contrôlés et renvoi des messages d’erreur si il y a un problème.

-          L’eventCounter est volatile, car comme il est responsable du comptage des évènements, il ne doit y avoir aucune optimisation.

-          On utilise la fonction clock() qui donne le nombre de tick d’horloge depuis le début du programme. Le tick est un truc bizarre qui s’incrémente toutes les micros secondes, il y a donc 1 millions de tick par seconde.
Attention : les fonctions delay ou sleep suspendent l’exécution d’un programme, le temps de ses fonctions n’est donc pas comptabilisé dans le nombre de tick. Cela peut faire des erreurs si on compte un temps d’exécution par exemple (voir plus loin l’anti-rebond).

-          Les paramètres pour programmer l’interruption dans la méthode wiringPiISR sont  INT_EDGE_RISING (0 à 1), INT_EDGE_FALLING (1 à 0) ou INT_EDGE_BOTH (les deux).

On compile on exécute et on appui une fois sur notre petit bouton câblé sur la GPIO20.


 

Argggg… qu’est-ce qu’il se passe, j’appui une fois et il envoie 78 interruptions alors qu’il ne devrait y en avoir qu’une.

En fait, notre programme marche très bien, c’est le bouton qui fait des sienne, on ne le voit pas, mais un bouton est constitué d’une petite lame métallique qui a tendance à rebondir quand on l’active, comme si vous tapiez avec une grande plaque métallique, c’est pareil à l’échelle du bouton poussoir.

Ici, lorsque que j’ai appuyé sur le bouton, il a rebondit 66 fois, générant les évènements 1 à 66. Ensuite, lorsque j’ai relâché, il a rebondit encore 12 fois générant les évènements 67 à 78.

Pour éviter ça, il y a deux solutions, soit on temporise dans le programme pour ne générer qu’une interruption, soit on temporise par de l’électronique.


 

1.1.1        Anti rebond logicielle

Il va falloir expliquer à notre programme qu’on ne veut pas d’interruption trop rapproché dans le temps.

On voudra par exemple qu’il se soit écoulé 500000 cycle soit une demie seconde entre deux interruptions. Cette valeur est empirique, il faudra vous en choisir une en fonction de la capacité à rebondir de vos boutons et en fonction de l’utilisation de votre programme.

On va modifier notre programme comme ceci

# define TEMPO 500000

 

using namespace std;

 

const int gpio20 = 20; // Regular LED - Broadcom pin 20, P1 pin 38

 

// Le compteur d'appui sur le bouton

volatile int eventCounter = 0;

clock_t svgClock = 0;

 

// Fonction de fin déclenchée par CTRL-C

void fin(int sig)

{

       // Désactive les résistances

       pullUpDnControl(gpio20, PUD_OFF);

       cout << "FIN signal: " << sig << endl;

       exit(0);

}

 

// -----------------------

// Fonction d'interruption

// -----------------------

void myInterrupt(void) {

       if(clock() > svgClock + TEMPO) {

             eventCounter++;

             svgClock = clock();

             cout << "Nb: " << eventCounter << " temps: " << svgClock << endl;

       }

}

 

Les parties include et main restent identiques.

On a ajouté :

-          Un « #define » qui définit notre fameuse constante.

-          On a modifié la méthode myInterrupt pour ne compter que les valeurs pas trop rapproché.

Attention à changer le nom du programme source pour ne pas perdre le précédant qui va nous servir à tester l’anti-rebond électronique.

Ça marche beaucoup mieux, si on va assez vite, tout va bien. Il reste un problème, si on appui sur le bouton et qu’on relâche longtemps après, il compte deux évènements car il détecte les rebonds du relâchement comme un nouvel évènement. Et là, pas de solution.

Attention : cette solution ne doit être appliquée que pour les boutons ou les relais qui génèreront des interruptions parasites. Si vous voulez par exemple générer cycliquement des interruptions via un signal carré parfaitement propre, aucune temporisation ne doit être appliquée.

Afficher l'image d'origine


 

1.1.2        Anti rebond électronique

 Si nous voulons faire un anti rebond électronique, il nous faut quelques composants afin de filtrer les rebonds parasites.

Il faudra deux résistances de 100 kΩ et une capa de 10 nF

Voici le schéma

Il faudra ensuite enlever la résistance de pull up interne dans le programme car nos résistances la remplacent.

On garde le même programme que notre version d’origine qui occasionnait les rebonds en changeant juste la ligne  pullUpDnControl(gpio20, PUD_UP); par pullUpDnControl(gpio20, PUD_OFF);

(Faite une copie en la nommant interrupt_sans_r.cpp pour bien garder les deux versions)

On compile et on lance

Ça va beaucoup mieux

PS : même avec l’anti rebond électronique, j’ai dû changer mon bouton poussoir, il avait plusieurs dizaine d’années et devait commencer à s’oxyder, il rebondissait vraiment beaucoup ou faisait des faux contacts. Et pour une raison que je ne comprends pas, la première fois où j’appuie sur le bouton, il y a deux interruptions, ensuite une seule à chaque fois.

Et en prime, une petite photo de mon super montage, toujours en wrapping.


1.2        Programme en python

import RPi.GPIO as GPIO    # utilisation GPIO

import time                       # utilisation time

 

GPIO.setmode(GPIO.BCM)     # numérotation BCM

GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP)  # GPIO20 en entrée avec résistance pull up 

 

eventCounter = 0

 

def my_interrupt(channel):

    global eventCounter

    eventCounter += 1

    print "Nb: ", eventCounter, " temps: ", time.time()

 

GPIO.add_event_detect(20, GPIO.FALLING, callback=my_interrupt)

      

try: 

    while True:       # Jusqu'à CTRL+C

        pass

 

finally:              # Bloc de fin aprés CTRL-C

    GPIO.cleanup()    # Nettoyage des GPIO (force les GPIO utilisés en entrée sans résistance)

    print "FIN"

 

Encore une fois, le programme en Python est beaucoup plus petit.

Quelques explications :

-         On utilise une variable global eventCounter. Pour l’utiliser, on indique à la fonction my_interrupt que c’est une variable global. Cela permet de compter le nombre d’interruption sans remise à zéro dans la fonction.

-         GPIO.add_event_detect déclare la broche, le front et la méthode d’interruption. Les fronts sont GPIO.FALLING (1 à 0), GPIO.RISING (0 à 1) et GPIO.BOTH (les 2)

-         La commande pass, qui ne fait rien mais qui est indispensable car une boucle while ne peut pas être vide

 

Voici la trace d’exécution avec un appui sur notre bouton en GPIO20.

Il y a toujours les rebonds (voir chapitre sur le C++), mais notre programme n’en détecte que 4 (2 à l’appui et 2 au relâchement d’après l’horloge) au lieu des 78 du C++, pourtant, le bouton poussoir est absolument le même, il n’a pas bougé (c’est le très mauvais qui rebondit beaucoup).

On s’aperçoit que le Python est largement plus lent que le C++. C’est pour cela que je vous avais dit qu’à mon avis, les langages compilés sont très supérieur aux langages interprétés au niveau du temps réel, en voici la preuve.


 

1.2.1        Anti rebond logicielle

Notre python est tellement lent qu’on a quasiment un anti rebond de base, mais on va quand même l’améliorer.

import RPi.GPIO as GPIO    # utilisation GPIO

import time                       # utilisation time

 

GPIO.setmode(GPIO.BCM)     # numérotation BCM

GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP)   # GPIO20 en entrée avec résistance pull up 

 

eventCounter = 0

time_stamp = time.time()

 

def my_interrupt(channel):

    global eventCounter

    global time_stamp

    time_now = time.time()

    if(time_now - time_stamp) >= 0.4:

        eventCounter += 1

        print "Nb: ", eventCounter, " temps: ", time.time()

    time_stamp = time_now

 

GPIO.add_event_detect(20, GPIO.FALLING, callback=my_interrupt)

      

try: 

    while True:            # Jusqu'à CTRL+C

        pass

 

finally:             # Bloc de fin aprés CTRL-C

    GPIO.cleanup()   # Nettoyage des GPIO (force les GPIO utilisés en entrée sans résistance)

    print "FIN"

 

On ajoute une petite temporisation qui indique qu’il faut 0.4 seconde entre chaque interruption.

Et là, tout va bien.


 

1.2.2        Anti rebond électronique

Pour la partie matérielle et câblage, voir le chapitre du même nom pour le C++

Pour le programme en python, on remplace la ligne GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP) par GPIO.setup(20, GPIO.IN)

Ça fonctionne, même si des fois il y a un petit temps d’attente.


 

1.3        Programme en vrai C++

Juste pour le fun, mais aussi parce que ça va nous servir pour plus tard, on va refaire le programme de gestion des interruptions avec rebond en vrai C++.

En vrai C++, ça veut dire quoi ?

On va créer une classe qui va gérer nos ports d’entrées/sorties et notre programme fera appel à cette classe.

En premier, le fichier header de la classe avec les déclarations des méthodes et les attributs.

/*

 * GestionPinGPIO.h

 *

 *  Created on: 1 août 2016

 *      Author: totof

 */

 

#ifndef GESTIONPINGPIO_H_

#define GESTIONPINGPIO_H_

 

typedef void (*interrupt)(void);

 

class GestionPinGPIO {

public:

       GestionPinGPIO(int pPinNumber);

       void pinModePI(int pDirection);

       void pullUpDnControlPI(int pNiveau);

       int wiringPiISRPI(int pFront, interrupt intr);

       virtual ~GestionPinGPIO();

 

private:

       int pinNumber;

};

 

#endif /* GESTIONPINGPIO_H_ */

 

Les méthodes auront le même nom que dans la bibliothèque WiringPi postfixé par « PI »

La ligne typedef est pour le pointeur de fonction pour la fonction d’interruption.

Dans les classes, il y a bien sur le constructeur et le destructeur.

Le seul attribut étant le numéro de port.

Ensuite, la librairie elle-même.

/*

 * GestionPinGPIO.cpp

 *

 *  Created on: 1 août 2016

 *      Author: totof

 */

 

#include "GestionPinGPIO.h"

#include "wiringPi.h"

 

GestionPinGPIO::GestionPinGPIO(int pPinNumber) {

       pinNumber = pPinNumber;

}

 

void GestionPinGPIO::pinModePI(int pDirection) {

       pinMode(pinNumber, pDirection);

}

 

void GestionPinGPIO::pullUpDnControlPI(int pNiveau) {

       pullUpDnControl(pinNumber, pNiveau);

}

 

int GestionPinGPIO::wiringPiISRPI(int pFront, interrupt intr) {

       return wiringPiISR(pinNumber, pFront, *intr);

}

 

GestionPinGPIO::~GestionPinGPIO() {

       pullUpDnControlPI(PUD_OFF);

       pinModePI(INPUT);

}

 

La petite nouveauté, c’est le destructeur (la méthode qui commence par le ~), c’est elle qui fera implicitement le ménage, plus besoin d’appel spécifique.

Puis le programme lui-même.

//============================================================================

// Name        : interruptClasse.cpp

// Author      : totof

// Version     :

// Copyright   : Free

// Description : Hello World in C++, Ansi-style

//============================================================================

 

#include <iostream>

#include <signal.h>   // signal de fin de programme

#include <stdlib.h>   // La librairie standard

#include <time.h>     // l'horloge

#include <errno.h>    // Gestion des numéros d'erreur

#include <string.h>   // Gestion des String d'erreur

#include <wiringPi.h> // La wiringPi

#include "GestionPinGPIO.h"

 

using namespace std;

 

volatile int eventCounter = 0; // Le compteur d'appui sur le bouton

 

GestionPinGPIO gpio20(20);

 

// Fonction de fin déclenchée par CTRL-C

void fin(int sig)

{

       // Désactive les résistances

       gpio20.~GestionPinGPIO();

       cout << "FIN signal: " << sig << endl;

       exit(0);

}

 

// Fonction d'interruption

void myInterrupt(void) {

       eventCounter++;

       cout << "Nb: " << eventCounter << " temps: " << clock() << endl;

}

 

int main() {

       cout << "Gestion interruption en vrai C++ " << endl;

 

       // Ecoute du CTRL-C avec fonction à lancer

       signal(SIGINT, fin);

 

       // Setup wiringPi library

       if (wiringPiSetupGpio() < 0) {

             cerr << "Erreur d'initialisation de wiringPi: " <<  strerror (errno) << endl;

             exit(1);

       }

 

       gpio20.pinModePI(INPUT);

       gpio20.pullUpDnControlPI(PUD_UP);

       gpio20.wiringPiISRPI(INT_EDGE_FALLING, &myInterrupt);

 

       while ( 1 ) {

       }

}

 

Il ressemble à celui d’avant, sauf qu’on fait appel à notre classe GestionPinGPIO.

La compilation « g++ -o interruptClasse interruptClasse.cpp GestionPinGPIO.cpp –l wiringPi » attention à bien mettre les deux fichiers cpp.

 


 

1.4        Liste des programmes

https://github.com/montotof123/raspberry/blob/master/050_Int/GestionPinGPIO.cpp

https://github.com/montotof123/raspberry/blob/master/050_Int/GestionPinGPIO.h

https://github.com/montotof123/raspberry/blob/master/050_Int/interrupt.cpp

https://github.com/montotof123/raspberry/blob/master/050_Int/interrupt.py

https://github.com/montotof123/raspberry/blob/master/050_Int/interruptClasse.cpp

https://github.com/montotof123/raspberry/blob/master/050_Int/interrupt_anti_rebond.cpp

https://github.com/montotof123/raspberry/blob/master/050_Int/interrupt_anti_rebond.py

https://github.com/montotof123/raspberry/blob/master/050_Int/interrupt_sans_r.cpp

https://github.com/montotof123/raspberry/blob/master/050_Int/interrupt_sans_r.py

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Lien vers la base du site.