Comment fonctionne la librairie simpleBouton de bricoleau ?

Bonjour,

Voici le lien qui pointe vers la librairie en question :

Voici les explications concernant mon approche :

Objectif : Comprendre la librairie de @bricoleau et ne porter un regard que sur la gestion d'un simple clic sur des boutons-poussoirs et leurs rebonds. Le montage des boutons est en INPUT_PULLUP et la gestion se fait à l’aide d’un octet :

  • le bit 7 ne sert à rien (c'est une modification de ma part);
  • le bit 6 permet d’indiquer l’état du dernier digitalRead() ;
  • les bits 0 à 5 permettent de mémoriser les numéros de pin (jusqu’à 64, c’est pas mal !).

Dans le header on trouve la classe simpleBouton de bricoleau sous une forme minimaliste :

  • un constructeur qui prend en paramètre le numéro de pin et le délai anti-rebond (implémenté dans le CPP bien sûr) ;
simpleBouton(uint8_t pin, uint8_t delai_debounce_ms = 20);
  • Les méthodes actualiser() et estEnfonce() toutes deux implémentées dans le CPP ;
bool actualiser(); //retourne true si l'état du bouton a changé
//Informations de statut
bool estEnfonce() const;
  • la signature d'une méthode permettant de récupérer le numéro de la pin de l'instance en cours
uint8_t pin() const;
  • Une méthode qui renvoie un booléen et en particulier TRUE si les méthodes actualiser() et estEnfonce() sont vraies ! (seule méthode implémentée directement dans le .h)
operator bool() {return actualiser() && estEnfonce();}
  • Les attributs _statut (c’est l’octet utilisé avec les bits comme décrits dans l’objectif plus haut), _delai_debounce_ms (délai anti-rebond) et _millis_etat_actuel (participe au calcul du délai anti-rebond).
uint8_t  _statut, _delai_debounce_ms;
uint32_t _millis_etat_actuel;

Voici le header dans sa totalité :

//Librairie Arduino pour la gestion de boutons poussoirs
//Version 4.2
//
//Cablage : pin---BP---GND
//
//Bricoleau 2020

#ifndef simpleBouton_h
#define simpleBouton_h

#include <Arduino.h>

class simpleBouton
{
  public :
    //Constructeur
    simpleBouton(uint8_t pin, uint8_t delai_debounce_ms = 20); // delai anti-rebond par défaut à 20 millisecondes

    //Lecture hardware et mise à jour des variables internes
    //Idéalement, doit être appelée à chaque début de loop()
    bool actualiser(); //retourne true si l'état du bouton a changé

    //Informations de statut
    bool estEnfonce() const;
    uint8_t pin() const;

    //Et pour un usage minimaliste :
    operator bool() {return actualiser() && estEnfonce();} // si les deux conditions sont remplies alors le bouton est enfoncé avec le délai par défaut ou defini par l'utilisateur

  private :
    uint8_t  _statut, _delai_debounce_ms;
    uint32_t _millis_etat_actuel;
};


#endif

Le CPP implémente bien sûr le constructeur et les méthodes du header :

  • Dans le constructeur tout d’abord, bricoleau construit l’octet utilisé pour une instance de la classe et l’affecte à l’attribut _statut. Dans un premier temps le numéro de la pin (bits 0 à 5) est inclu puis on ajoute son état à HIGH (bouton relâché aux fins d’initialisation (bit 6)). Ensuite l’attribut _delai_debounce_ms prend la valeur par défaut du constructeur ou celle fixée par l’utilisateur. Puis l’attribut _millis_etat_actuel est initialisé avec millis(). Enfin il initialise la pin choisie par l'utilisateur en INPUT_PULLUP.
//Constructeur
simpleBouton::simpleBouton(uint8_t pin, uint8_t delai_debounce_ms)
{
  //Initialisation des variables internes
  pin &= masque_pin_simpleBouton; // permet de fixer le nmr de pin en binaire ex : pin 7 = 0b00000111
  this->_statut = masque_etat_simpleBouton | pin; // rajoute à la variable le bit d'etat = 0b01000111 : HIGH = bouton relâché
  this->_delai_debounce_ms = delai_debounce_ms; 
  this->_millis_etat_actuel = millis();

  //Initialisation hardware
  pinMode(pin, INPUT_PULLUP);
}

  • la méthode de type booléenne : estEnfonce() permet de retourner vrai ou faux suivant que le bouton de l’instance en cours est enfoncé ou non ;
bool simpleBouton::estEnfonce() const {return (this->_statut & masque_etat_simpleBouton) == 0;}
  • la méthode de type uint8_t : pin() permet de retourner le numéro de la pin de l’instance en cours qui est notamment utilisé pour déterminer la variable locale etat_courant avec un digitalRead() (dans la méthode booléenne : actualiser()) ;
uint8_t simpleBouton::pin() const     {return  this->_statut & masque_pin_simpleBouton;}

Voici maintenant la méthode la plus importante, bool simpleBouton::actualiser() :

Explications :

1/ Elle effectue un test du changement d’état de l’instance en cours d’un bouton (enfoncé LOW- relâché HIGH) :

  • la variable etat_precedent récupère le statut précédent de l’instance en cours du bouton ;
uint8_t etat_precedent = this->_statut & masque_etat_simpleBouton; // 0b010000000 (HIGH) ou 0b00000000 (LOW)
  • puis la variable etat_courant récupère son statut actuel.
uint8_t etat_courant = (digitalRead(this->pin()) == HIGH) ? masque_etat_simpleBouton : 0; // si appui : 0b00000000 - si relaché : 0b01000000

2/ - premier test : si etat_courant est différent de etat_precedent et que le délai de temporisation fixé par défaut à 20 millisecondes ou par l’utilisateur n’est pas atteint alors etat_courant reste égal à etat_precedent donc rien à changé ;

uint32_t maintenant = millis();
  if (etat_courant != etat_precedent) // en cas de changement d'etat bouton enfoncé ou pas : LOW ou HIGH
  {
    uint32_t delai = maintenant - this->_millis_etat_actuel;
    if (delai < this->_delai_debounce_ms)
    {
      etat_courant = etat_precedent; // si delai est inférieur au temps défini pour filtrer les rebonds (par défaut 20 millisecondes)
    }
  }
  • deuxième test : par contre si le bouton a été enfoncé ou relâché et que le délai de temporisation est atteint ou dépassé alors il y a changement et on met à jour le bit d’état (6) ainsi que l’argument _millis_etat_actuel avec millis().
bool changement = (etat_courant != etat_precedent); 
  if (changement) // si delai est supérieur au temps défini pour filtrer les rebonds (par défaut 20 millisecondes)
  {
    this->_statut ^= masque_etat_simpleBouton; //inversion du bit d'état
    this->_millis_etat_actuel = maintenant;
  }
  //Serial.println(this->_statut, BIN);

3/ enfin la méthode actualiser() retourne la variable changement : 1 ou 0.

return changement;

Conclusion : cette méthode booléenne permet à l’aide de l’argument _statut et du 6ème bit de l’octet qui le constitue de comparer l’état du dernier digitalRead() avec le digitalRead() actuel. Si les deux bits sont différents et que le délai de temporisation fixé par l’utilisateur ou par défaut est atteint ou dépassé alors l’argument _statut est mis à jour en conséquence ainsi que l’argument _millis_etat_actuel qui permet l’évaluation du temps passé dans un état. La variable booléenne « changement » (d’état) est retournée par la méthode.

Voici le CPP :

//Librairie Arduino pour la gestion de boutons poussoirs simples
#include "simpleBouton.h"

//Cuisine interne - décomposition du statut pour tout gérer sur un octet :
//bit 6 = état du dernier digitalRead : 0 <=> LOW (bouton enfoncé) et 1 <=> HIGH (bouton relâché)
//bits 0 à 5 = numéro de pin, de 0 à 63

const uint8_t masque_etat_simpleBouton =  0b01000000;  
const uint8_t masque_pin_simpleBouton  =  0b00111111;


//Constructeur
simpleBouton::simpleBouton(uint8_t pin, uint8_t delai_debounce_ms)
{
  //Initialisation des variables internes
  pin &= masque_pin_simpleBouton; // permet de fixer le nmr de pin en binaire ex : pin 7 = 0b00000111
  this->_statut = masque_etat_simpleBouton | pin; // rajoute à la variable le bit d'etat = 0b01000111 : HIGH = bouton relâché
  this->_delai_debounce_ms = delai_debounce_ms; 
  this->_millis_etat_actuel = millis();

  //Initialisation hardware
  pinMode(pin, INPUT_PULLUP);
}

//Méthodes constantes non développées dans le .h (pour masquer la cuisine interne)
bool simpleBouton::estEnfonce() const {return (this->_statut & masque_etat_simpleBouton) == 0;}
uint8_t simpleBouton::pin() const     {return  this->_statut & masque_pin_simpleBouton;}

//Méthode principale
bool simpleBouton::actualiser()
{
  uint8_t etat_precedent = this->_statut & masque_etat_simpleBouton; // 0b010000000 (HIGH) ou 0b00000000 (LOW)

  //Lecture physique
  uint8_t etat_courant = (digitalRead(this->pin()) == HIGH) ? masque_etat_simpleBouton : 0; // si appui : 0b00000000 - si relaché : 0b01000000

  //Filtrage temporel
  uint32_t maintenant = millis();
  if (etat_courant != etat_precedent) // en cas de changement d'etat bouton enfoncé ou pas : LOW ou HIGH
  {
    uint32_t delai = maintenant - this->_millis_etat_actuel;
    if (delai < this->_delai_debounce_ms)
    {
      etat_courant = etat_precedent; // si delai est inférieur au temps défini pour filtrer les rebonds (par défaut 20 millisecondes)
    }
  }

  //Mise à jour des variables internes
  bool changement = (etat_courant != etat_precedent); 
  if (changement) // si delai est supérieur au temps défini pour filtrer les rebonds (par défaut 20 millisecondes)
  {
    this->_statut ^= masque_etat_simpleBouton; //inversion du bit d'état
    this->_millis_etat_actuel = maintenant;
  }
  //Serial.println(this->_statut, BIN);
  return changement;
}

et enfin le fichier INO :

#include "simpleBouton.h"
simpleBouton boutonTest1(7);
simpleBouton boutonTest2(9);
simpleBouton boutonTest3(10);

void setup() {
  Serial.begin(115200);
}

void loop() {
  if (boutonTest1) Serial.println("Detection appui bouton 1");


  if (boutonTest2) Serial.println("Detection appui bouton 2");


  if (boutonTest3) Serial.println("Detection appui bouton 3");


}

Enfin, pour comprendre cette librairie il faut maîtriser l’utilisation des masques et des opérateurs binaires &, | et ^. Il s’agit respectivement des opérateurs ET, OU inclusif et OU exclusif. Voici les masques binaires définis au tout début du CPP :

const uint8_t masque_etat_simpleBouton =  0b01000000;  
const uint8_t masque_pin_simpleBouton  =  0b00111111;

Voici les tables de vérité concernant les 3 opérateurs binaires :

Je prends un exemple de code qui se trouve dans le constructeur :

pin &= masque_pin_simpleBouton;
this->_statut = masque_etat_simpleBouton | pin;

On commence par : pin &= masque_pin_simpleBouton;

==> Je décompose : pin = pin & masque_pin_simpleBouton;

Supposons que j’instancie par exemple la PIN 7 :

En binaire cela donne 0b00000111, masque_pin_simpleBouton = 0b00111111

Donc votre calcul se fait comme ceci :
00000111
& 00111111
= 00000111

Pour this->_statut = masque_etat_simpleBouton | pin;

01000000
| 00000111
= 01000111

On retrouve dans l’attribut _statut à la fois les bits qui constituent le numéro de la pin mais également le bit d’état.

Bonne journée à tous.

PS : Je ne suis pas du tout pédagogue, j'espère être suffisamment clair et surtout ne pas m'être trompé dans mon interprétation ?
Un grand merci à bricoleau pour son excellente librairie...
L'explication porte uniquement sur une partie de la classe simpleBouton (la classe boutonAction n’en fait pas parti) .

La suppression d'un certain nombre de fonctions ou méthodes d'une librairie n'apporte rien.
En effet quand une application utilise certaines méthodes ou variables, lors de la phase d'édition de liens seules ces méthodes et variables sont incluses dans le binaire final, et non pas la librairie entière.

Bonjour @hbachetti
Merci pour cette information :wink:
Ceci étant c’est plus simple de donner une explication sur ce qu’on utilise en enlevant ce qui n’est pas utilisé :wink:
Merci
Bonne journée

Il y a un autre inconvénient. Tes fichiers portent les mêmes noms que les fichiers originaux, ce qui interdit d'installer les deux librairies simultanément dans un environnement ARDUINO, car il y a conflit, à moins d'installer chaque librairie en local dans chaque projet.

Je pense que l'on connaît tous le problème des multiples librairies LiquidCrystal ou LiquidCrystal_I2C existantes, qui portent toutes le même nom, et les ennuis que cela peut occasionner.

Par exemple celle d'AdaFruit s'appelle Adafruit_LiquidCrystal, ce qui permet d'éviter la confusion.

C’est vrai,


Ceux qui voudront l’utiliser n’auront qu’à la renommer.
Pour ma part je l’utilise en local car c’est une partie de ma librairie finale qui va contenir la classe simpleBouton et son implémentation plus une autre classe :wink:
Merci pour ton esprit critique qui est bénéfique. C’est ce genre de remarques que je recherche mais peut-être un peu plus portées sur mon interprétation du code que j’ai présenté (celui de bricoleau)
Ma librairie finale portera un autre nom :wink:

Tu seras probablement le seul utilisateur.
Quand on débute, on a forcément tendance à réinventer la poudre.
Le développeur expérimenté raisonne différemment :

  • utiliser le code des autres (le fameux CDA)
  • comprendre le CDA : la partie la plus difficile
  • minimiser les développements personnels

Ce n'est qu'une question de fainéantise ... mais aussi d'efficacité.

1 Like

Très certainement je pense mais le but est de conforter ma compréhension de la librairie. Mettre des mots sur des idées m'aide à accentuer mon éventuelle clairvoyance. D'autre part si ça peut aider certains à comprendre également...

Sur ce point nous sommes en désaccord total car :
je ne cherche pas à réinventer la roue puisque ce n'est pas mon code, je souhaite l'expliquer pour mieux comprendre et pour le partage de mon éventuelle bonne compréhension.

Au contraire je cherche seul dans mon coin à apprendre et c'est en essayant de compredre, en décortiquant le code des autres, leurs tutos ... que je m'imprègne de leur approche dans leur façon de coder. Ce qui me permet de coder moi-même :wink:
Décortiquer le code des autres pour le comprendre c'est aussi mettre la main à la patte :wink:

Toi tu te situes dans le camps des expérimentés alors tu peux te permettre d'être fainéant et moi j'apprends, et pour mieux comprendre il faut que je réinvente la roue pour savoir dans quel sens elle tourne et être efficace ...

1 Like

Mon propos va simplement dans le sens suivant :
Aux amateurs qui seraient tentés de reprendre une librairie existante pour en éliminer les fonctions superflues, vous faites fausse route. Cela n'apporte rien, y compris en terme de taille de code.
La bonne démarche est tout simplement d'utiliser la librairie originale, sans chercher à la dégraisser.
Si par contre la librairie en question manque de fonctionnalités, il suffit de créer une classe dérivée et d'ajouter les méthodes manquantes.

Etant donné les multiples remarques et éloges dont a bénéficié la librairie de Bricoleau, je pense qu'il n'y a pas photo, elle a l'air simple à utiliser et son fonctionnement n'est pas à mettre en doute.

Rien ne t'empêche de continuer ainsi, dans ton coin, sans chercher à partager une librairie qui est un clone diminué d'une librairie reconnue.

En bref, mon conseil : utiliser plutôt la librairie de bricoleau.

Il n’y a aucune fonction superflue dans la librairie de bricoleau :wink:

La bonne démarche est pour moi de la comprendre.

Ou tout simplement d’ajouter une classe fille à la classe mère avec le concept de l’héritage :wink:

Alors là tu dis n’importe quoi car je ne remets en aucun cas en doute son efficacité :joy:

Alors là excuse-moi mais tu tombes dans le pipi caca :joy:

Je n’ai pas la prétention d’avoir fait mieux que bricoleau. J’ai juste voulu la dégraisser afin de créer une autre librairie à des fins personnelles et surtout j’ai voulu expliquer le code que j’ai extrait en premier lieu pour savoir si j’avais bien compris et peut-être permettre à d’autres de comprendre également :wink:

Ce n’est pas du tout mon objectif :wink:
Tout ça devient ridicule :wink:

Le problème est que tu la publies alors que tu devrais la garder pour ton usage personnel.

Tout à fait, ta démarche est celle d'un débutant.

Ton principal problème est que tu ne supportes pas la contradiction, ce qui n'est pas du tout dans l'esprit d'un forum.
J'exprime l'opinion d'un professionnel, 40 ans d'expérience dans l'informatique embarquée. A toi, mais surtout aux lecteurs de faire la part des choses.
Ciao, pour moi c'est clos : sourdine.

je pense qu'il a bien expliqué que son objectif n'était pas vraiment de publier la bibliothèque mais plutôt présenter son analyse du code de bricoleau. non ?

1 Like

Dans un forum on discute, on échange. À partir du moment où les échanges restent courtois, on n’enfreint pas les règles :wink:

Merci @J-M-L vous avez compris l’esprit de ma démarche.

Bonne soirée.

cette explication n'est pas précise:

ce n'est pas une méthode (vous remarquez qu'il n'y a pas de nom de fonction) mais c'est la surcharge de l'opérateur de vérité, c'est ce qui permet d'écrire

Sans surcharge de l'opérateur, le test porterait sur le fait que boutonTest1 est évalué à zéro ou pas et pour une instance le compilateur ne sait pas faire la promotion de ce type vers une valeur de vérité (alors que pour un pointeur il regarderait si c'est le pointeur nul) et donc la compilation ne fonctionnerait pas.

La surcharge des opérateurs c'est magique :slight_smile:

C’est vrai @J-M-L vous avez entièrement raison.
Bonne soirée

Philippe, la prochaine fois, tu publieras "philippe_simple_bouton.h".
C'est une boutade, mais pas que.......

Je te comprends parfaitement pour avoir, en bon débutant, fait de même et je comprends parfaitement Henri quand il fait remarquer que les bibliothèques qui portent le même nom en n'ayant pas le même contenu sont une vraie plaie. Il faut avoir en tête que ce que tu fais peut-être compris de travers.

Le foutoir dans les noms de bibliothèque est la principale raison qui m'a fait passer à l'IDE PlatformIO sur Vscode. Par défaut, les bibliothèques n'y sont pas globales et sont enregistrées projet par projet.
Au début je m'en servais comme l'IDE Arduino, c'est parfaitement faisable, puis à mon rythme, je l'utilise un peu plus finement.

Un point un peu hors sujet : malgré la grande réputation d'Adafruit cela fait pour trois composants de suite que je trouve les versions Sparkfun plus claires.

PS :
CDA : je comprends en gros le sens, mais ça veut dire quoi exactement ?

1 Like

Bonjour @68tjs

Oui je l’entends mais mon objectif n’était pas de créer une bibliothèque du même nom, j’ai travaillé en local et au final ma bibliothèque portera un autre nom avec une partie du code de bricoleau et une autre classe qui est entièrement de moi :wink:
J’ai simplement présenté le code à ma façon et donné une explication le concernant en espérant qu’elle soit la bonne… enfin décortiquer une librairie ça aidera peut-être quelqu’un qui comme moi cherche à comprendre :wink:

Il nomme ainsi le « code des autres »

Je pars voir mes amis les animaux en espérant pouvoir les photographier :wink:

Bonne journée à toi.

C'est pourtant ce que tu as fais et publié.
Tu aurais pu dans l'éventualité ou quelqu'un reprenne ce que tu as publié, faire un "remplacer tout".

Personnellement par rapport à ta démarche, ou il me semble que tu accepte de "perdre"/prendre du/le temps, j'aurais plutôt essayé de refaire la librairie en y jetant un coup d'œil mais sans la décortiquer.
C'est à dire en prennent comme postulat que le code n'était pas une évidence pour toi et qu'il utilisait des notions que tu n'avais pas forcément encore appliqué.
Puis dans un second temps, j'aurais essayé de comparer mon code avec celui de la librairie, pour améliorer mon code.

C'est un avis surement très personnel, mais je trouve que l'on apprends beaucoup plus en faisant, qu'en lisant, même si tu ne t'es pas contenter de lire :slight_smile: .
C'est pour ça que je ne suis pas du tout de l'avis de @hbachetti, un débutant dont l'objectif est d'apprendre ne devrait pas utiliser de librairie, mais refaire la roue.

1 Like

Au final c’est plus le titre de ce fil de discussion qui n’est pas adapté

Il ne s’agit pas de publier une bibliothèque

1 Like

Non, le titre est bon : comment fonctionne la librairie simple bouton.
Ce sont bien des explications que l'on trouve.

Ah si une modification : s'il te plait Philippe : en français on dit bibliothèque.
La bibliothèque est l'endroit où l'on range les fichiers.
La librairie est l'endroit où on trouve des bibliothèques à acheter ou à récupérer.
:grinning:

Ce n'est pas une erreur de ne pas avoir changé le nom du fichier, c'est une maladresse............de débutant (un peu beaucoup confirmé quand même).