[Tutoriel] La bibliothèque OneButton

Bonjour,

Ne trouvant pas de tuto clair en Français sur l’utilisation de la bibliothèque OneButton, mais plutôt des infos parsemées sur le forum et sur GitHub, j'ai écrit celui-ci Il se veut le plus simple possible mais n'hésitez pas à créer un topic si vous avez besoin d'aide!

Notez qu'ici quand je dis bibliothèque c'est juste la version française de l'anglicisme "librairie".

1. Pourquoi utiliser une bibliothèque pour des boutons ?

:warning: Gérer un bouton ce n'est pas toujours aussi simple qu'il y parait... En effet on pourrait se dire qu'il suffit de lire une entrée de la carte et de regarder si elle est à 0 ou 1... Dans les faits oui, dans la pratique pas vraiment à cause de ce que l'on appelle des rebonds.

Le bouton ne change pas immédiatement d'état lorsque vous cliquez dessus, c'est un système mécanique avec des imperfections, et celles-ci génère un "bruit" (le signal passe de 0 à 1 plusieurs fois avant de se stabiliser) pendant quelques millisecondes. Pour contrer cela on peut utiliser des anti-rebonds matériels (condensateurs), ou logiciel (et les bibliothèques nous évite d'avoir à les créer nous même :wink:)

OneButton est l'une de ces bibliothèques. Elle inclut un anti-rebonds (pour ceux qui veulent en savoir plus il est basé sur une machine à état décrite ici), ainsi que des méthodes pour gérer des clics simples, doubles, long, etc.

Bien entendu il existe d’autres bibliothèques très bien conçues pour gérer les boutons, comme Toggle de @dlloyd, SimpleBouton de @bricoleau, Button dans easyRun, Bounce2 ou encore EasyButton par exemple... Le choix dépend de vos préférences et de vos besoins, ce tuto parle de OneButton.


2. Manipulation de la bibliothèque OneButton

2.1 Installation

Pour utiliser la bibliothèque OneButton, rien n’est plus simple ! Elle est disponible dans le gestionnaire de bibliothèques, il suffit donc de la chercher et de cliquer sur Installer.

Installation manuelle

Si vous préférez faire cela à la main, tout d’abord téléchargez le fichier .zip sur le GitHub.

Ensuite, décompressez le fichier et ajoutez le à votre code, en cliquant sur Croquis → Inclure une bibliothèque → Ajouter la bibliothèque .ZIP …


2.2 Utilisation basique

Dans votre code, vous incluez la bibliothèque avec la directive de préprocesseur #include

#include <OneButton.h>

Ensuite on veut créer une instance de la classe OneButton, un "objet bouton", et le rattacher à une pin en particulier de notre carte. Pour ce faire, on crée d'abord l'objet, puis dans la fonction setup de notre programme, on le configure.

// on crée l'objet du bouton (sans paramètre)
OneButton bouton;

void setup()
{
  /*
  - On indique que le bouton est sur la pin 2
  Les paramètres suivants sont définis par défaut
  - que cette pin est en mode INPUT_PULLUP
  - que le bp est actif à `LOW` (il retourne `LOW` quand il est pressé)
  */
  bouton.setup(2, INPUT_PULLUP, true);
}

void loop() {}
Pourquoi utiliser la fonction setup sur le bouton

Cette fonction existe depuis qu'un bug à été découvert (cf cette issue sur GitHub) - sur certaines cartes Arduino spéciales comme la NANO ESP32 par exemple, l'utilisation de pinMode dans le constructeur ne fonctionne pas comme espéré (le mode est perdu avant d'arriver au setup()), et donc la pin n'est pas configurée comme voulu pour le bouton.

Pour corriger cela, Matthias Hertel a modifié sa bibliothèque pour y intégrer une fonction appelée setup dans laquelle on peut passer

  1. La pin du bouton,
  2. Le mode (c'est-à-dire si la pin est en INPUT_PULLUP ou non)
  3. Le sens dans lequel le bouton fonctionne (si il est actif à LOW ou à HIGH)

Bon, c'est bien beau, on a un objet bouton attaché à une pin de la carte mais pour l'instant si on clique dessus il ne se passe rien.

On doit maintenant attacher nos fonctions utiles, dites fonctions de callback (en anglais pour fonction de rappel) dans lesquelles on écrit ce qu'on veut faire pour un type de clic. OneButton est bien faite et propose des méthodes pour cela :

void fClicSimple()
{
  // ici le code à exécuter en cas de clic simple
}

void fClicDouble()
{
  // ici le code pour un clic double
}

void setup()
{
  // après la création de l'objet bouton et son setup bien sûr
  bouton.attachClick(fClicSimple);
  bouton.attachDoubleClick(fClicDouble);
}
Cliquez pour la liste des types d'événements

Voici un tableau regroupant tous les événements que la bibliothèque peut gérer sur vos boutons.

Attacher la fonction Description
attachClick Se déclenche dès qu'un seul clic est détecté.
attachDoubleClick Se déclenche dès qu'un double clic est détecté.
attachMultiClick Se déclenche dès que plusieurs clics ont été détectés.
attachLongPressStart Se déclenche dès que le bouton est maintenu enfoncé pendant 800 millisecondes.
attachDuringLongPress Se déclenche périodiquement tant que le bouton est maintenu enfoncé.
attachLongPressStop Se déclenche lorsque le bouton est relâché après un long maintien.

Notez que vous pouvez nommer les fonctions de callback comme vous le voulez! Il faut juste penser à les lier au(x) bouton(x) dans le setup (vous pouvez aussi changer la fonction attachée à un type de clic pendant le déroulement du programme, donc dans la loop)


Parfait, maintenant on peut exécuter une logique pour plusieurs type de clic sur un ou plusieurs boutons. Je lance le programme comme ça ?

Non, car si on lance le code tel quel, rien ne se passera. Pourquoi ? Parce qu'on a besoin que la bibliothèque "lise" régulièrement la pin du bouton pour détecter nos événements (les clics). Pour cela il existe la méthode tick, à appeler pour chaque objet bouton.

Notez que quand je dis régulièrement, c'est le plus souvent possible, donc on évite les codes bloquants au maximum. Si vous ne savez pas faire, renseignez vous sur millis.

void loop()
{
  /*
  C’est cet appel qui déclenchera l’appel de la fonction de callback
  si une action pré enregistrée (donc ici le simple clic ou le
  double clic) est activée
  */
  bouton.tick();
}

Il existe d'autres méthodes dans cette bibliothèque, mais pour gérer un bouton c'est la base.
Pour en savoir plus, notamment pour interroger l'état du bouton ou modifier les timings (d'un clic long, d'un double clic ou même de l'anti-rebonds), rendez-vous sur le GitHub.


2.3 Petite synthèse

Donc, si on résume, pour utiliser un bouton que faut-il ?

Il faut importer la bibliothèque, puis créer un objet pour chaque bouton qu'on veut utiliser, puis dans le setup le configurer (pin, mode, etc.) et attacher aux événements dont on a besoin nos fonctions de callback. Enfin dans la loop, on appelle tick sur chaque objet bouton pour détecter nos événements. En code ça donne ça :

#include <OneButton.h>
OneButton bouton;

void fClicSimple()
{
  Serial.println("Un clic simple a été détecté!");
}

void fClicDouble()
{
  Serial.println("Un double clic a été détecté!");
}

void setup()
{
  Serial.begin(115200);
  bouton.setup(2);

  bouton.attachClick(fClicSimple);
  bouton.attachDoubleClick(fClicDouble);
}

void loop()
{
  bouton.tick();
}
1 Like

3. Un exemple pour bien comprendre...

Bon, on sait théoriquement utiliser la bibliothèque, mais encore faut-il voir ce que ça donne pour l'appliquer dans la pratique. Pour cela, je vous propose un petit montage.

Le but est le suivant : nous avons un bouton poussoir et trois LEDs. Nous voulons que, suivant le type d'appui sur le bouton - simple, double ou long, cela allume ou éteigne une LED - verte, rouge, ou bleue.

Notez que dans la suite, quand je dis BP ça signifie bouton poussoir.

3.1 Câblage (utilisez une breadboard)

Comme ceci :
Pin D10 ==> Bouton ==> GND
Pin D9 ==> R de 220Ω ==> Anode LED verte :bulb: Cathode ==> GND
Pin D8 ==> R de 220Ω ==> Anode LED rouge :bulb: Cathode ==> GND
Pin D7 ==> R de 220Ω ==> Anode LED bleue :bulb: Cathode ==> GND


3.2 Programme

Pour commencer, nous installons la bibliothèque, nous initialisons les pins des LEDs et du BP, et créons un objet bouton :

#include <OneButton.h>

constexpr byte pinBP = 10;
constexpr byte pinLedVerte = 9;
constexpr byte pinLedRouge = 8;
constexpr byte pinLedBleue = 7;

OneButton bouton;

Ensuite, nous créons les fonctions de callbacks :

void simpleClic()
{
  static bool ledVerteAllumee = false;
  ledVerteAllumee =! ledVerteAllumee;
  digitalWrite(pinLedVerte, ledVerteAllumee ? HIGH : LOW);
  digitalWrite(pinLedRouge, LOW);
  digitalWrite(pinLedBleue, LOW);
}

void doubleClic()
{
  static bool etatLedRouge = 0;
  etatLedRouge =! etatLedRouge;
  digitalWrite(pinLedRouge, etatLedRouge ? HIGH : LOW );
  digitalWrite(pinLedVerte, LOW);
  digitalWrite(pinLedBleue, LOW);
}

void clicLong()
{
  static bool ledBleueAllumee = false;
  ledBleueAllumee =! ledBleueAllumee;
  digitalWrite(pinLedBleue, ledBleueAllumee ? HIGH : LOW );
  digitalWrite(pinLedVerte, LOW);
  digitalWrite(pinLedRouge, LOW);
}
Ici, nous introduisons une nouvelle notion, celle de l'opérateur ternaire...

... qui permet en fonction d'une condition de donner une valeur ou une autre.
Ça s'écrit comme ceci : condition ? expression_si_vrai : expression_si_faux. Par exemple, si vous faites x = condition ? 3 : 4;
c'est comme si vous aviez écrit

if (condition)
{
 x = 3;
}
else
{
 x = 4;
}

donc quand je dis ledRougeAllumee ? HIGH : LOW ça veut dire
si ledRougeAllumee est vrai alors prend la valeur HIGH sinon prend la valeur LOW
et c'est cela que j'injecte dans le digitalWrite comme second paramètre.

Pour plus de précisions, je vous laisse lire cet article - page 94 et 95.


Ensuite, nous paramétrons la pin du bouton ainsi que son mode, et nous attachons les fonctions de callbacks avec les bons types d'appuis (long, simple, double), comme ceci :

void setup()
{
  bouton.setup(pinBP, INPUT_PULLUP, true);

  bouton.attachClick(simpleClic); // fonction appelée en cas de clic
  bouton.attachDoubleClick(doubleClic); // fonction appelée en cas de double clic
  bouton.attachLongPressStop(clicLong); // fonction appelée au moment ou le BP est relâché après un appui long
}

Et enfin, dans la loop, on lit en permanence le bouton :

void loop()
{
  bouton.tick(); // on lit en permanence le bouton connecté à la pin D10
}

3.3 Résultat final

Ci-dessous le code final commenté :

#include <OneButton.h> // inclure la bibliothèque OneButton

constexpr byte pinBP = 10; // pin du bouton (D10)
constexpr byte pinLedVerte = 9; // pin de la LED verte - correspondant au clic simple
constexpr byte pinLedRouge = 8; // pin de la LED rouge - correspondant au double clic
constexpr byte pinLedBleue = 7; // pin de la LED bleue - correspondant au clic long

OneButton bouton; // créer une instance (un "objet") bouton.

void simpleClic()
{
  // dans cette fonction (clic simple), on allume la LED verte
  static bool ledVerteAllumee = false; // on définit la variable état de la LED et sa valeur initiale (0 car false = faux)
  ledVerteAllumee =! ledVerteAllumee; // on inverse l'état de la variable de l'état de la LED
  digitalWrite(pinLedVerte, ledVerteAllumee ? HIGH : LOW);
  // On éteint les autres LEDs
  digitalWrite(pinLedRouge, LOW);
  digitalWrite(pinLedBleue, LOW);
}

void doubleClic()
{
  // dans cette fonction (double clic), on allume la LED rouge
  static bool etatLedRouge = 0; // on définit la variable état de la LED
  etatLedRouge =! etatLedRouge; // on inverse l'état de la variable de l'état de la LED
  digitalWrite(pinLedRouge, etatLedRouge ? HIGH : LOW); // on rentre cette variable comme paramètre pour la LED
  // On éteint les autres LEDs
  digitalWrite(pinLedVerte, LOW);
  digitalWrite(pinLedBleue, LOW);
}

void clicLong()
{
  // dans cette fonction (clic long), on allume la LED bleue
  static bool ledBleueAllumee = false; // on définit la variable état de la LED et sa valeur initiale
  ledBleueAllumee =! ledBleueAllumee; // on inverse l'état de la variable de l'état de la LED
  digitalWrite(pinLedBleue, ledBleueAllumee ? HIGH : LOW); // on utilise cette variable comme paramètre pour la LED
  // On éteint les autres LEDs
  digitalWrite(pinLedVerte, LOW);
  digitalWrite(pinLedRouge, LOW);
}

void setup()
{
  bouton.setup(pinBP, INPUT_PULLUP, true); // on paramètre le bouton avec sa pin, etc...

  bouton.attachClick(simpleClic); // fonction appelée en cas de clic
  bouton.attachDoubleClick(doubleClic); // fonction appelée en cas de double clic
  bouton.attachLongPressStop(clicLong); // fonction appelée au moment ou le BP est relâché après un appui long
}

void loop()
{
  bouton.tick(); // on lit en permanence le bouton connecté à la pin D10
}

Le voici sur wokwi :


En espérant que ce petit tuto vous aura éclairci :wink:

Cordialement,
Pandaroux007

1 Like

bravo Rémi !

Je n'y serai pas arrivé sans vous. Merci à vous ! :wink:
Amitiés
Pandaroux007

J'ai modifié le titre.

Machin.h ne fait pas une bibliothèque.

Une bibliothèque, c'est du code et le code se met dans un fichier *.c ou *.cpp

Le fichier *.h est un fichier d'en-têtes => h provient de "header".
C'est le fichier utilitaire qui rassemble toutes les déclarations qui sont à faire dans le programme principal pour que le compilateur puisse compiler le code contenu dans le ou les fichiers *.cpp de la bibliothèque. .

Bonjour Rémi,
Bon, la prochaine fois que j'utilise la librairie, je viens faire un tour ici.

Bon travail.
Philippe.

Bonjour @68tjs
Effectivement, merci pour cette correction.
@philippe86220 Merci ! :wink:

Cordialement
Pandaroux007

Cela peut passer pour du pinaillage, mais on peut attacher les callback n'importe quand, pas seulement dans le setup(). Dans le déroulement du programme on peut changer le comportement d'un bouton.
Par exemple si on fait une gestion de menu avec un bouton.

  • au départ,
    • un appui simple va sélectionner une entrée du menu
    • un appui long, ne fera rien
  • une fois qu'une option est sélectionnée
    • un appui simple va valider l'entrée
    • un appui long va quitter le menu sans valider le changement (en entrant dans ce sous-menu, on a activé le callback pour l'appui long)
    • en quittant le menu on désactive le callback pour l'appui long

Pour désactiver un callback, on donne un pointeur nul en argument à attachDoubleClick() pour la fonction callback.

1 Like

Bonsoir pandaroux007

Bravo!
J'ai l'impression de lire du Locoduino, c'est tout dire :wink:

Entièrement d'accord avec toi, laisses à ce tuto sa simplicité.

Cordialement
jpbbricole

Merci Padaroux,
Super tuto. Je vais essayer cela de suite. J'imagine que pour gerer plusieurs boutons, il suffit de les declarer avec une instance differente et un numero de pin different?
Autre detail, la polarisation du bouton est elle importante ? front montant ou descendant, y a t-il une importance?
Je vais essayer cela de suite.
Merci encore pour ce super tuto, tres belle reussite pour quelqu'un qui pretend ne pas etre pedagogue :slight_smile:

Pour ceux que cela pourrait interesser, cela fonctionne tres bien quelque soit la polarisation du bouton (positif ou negatif commuté).
Aucun probleme egalement avec plusieurs boutons. pour reprendre l'excellent code de Pandaroux007,

#include <OneButton.h> // inclure la librairie OneButton.h

const byte pinBP = 10; // pin du bouton (D10)
const byte pinBP2 = 11;//pin du bouton 2 en D11
const byte pinLedVerte = 9; // pin de la LED verte - correspondant au clique simple
const byte pinLedRouge = 8; // pin de la LED rouge - correspondant au double clique
const byte pinLedBleue = 7; // pin de la LED bleue - correspondant au clique long

OneButton bouton(pinBP); // créer une instance (objet) bouton sur la pin du bouton (D10)
OneButton bouton1(pinBP1); //créer une autre instance (objet) bouton sur la pin du bouton (D11)

void simpleClick() {
  // dans cette fonction, donc le clique simple, c'est la LED verte qui fonctionne
  static bool ledVerteAllumee = false; // on définit la variable état de la LED et sa valeur initiale (0 car false = faux)
  ledVerteAllumee =! ledVerteAllumee; // on inverse l'état de la variable de l'état de la LED
  digitalWrite(pinLedVerte, ledVerteAllumee ? HIGH : LOW);
  ///////////////////// On éteint les autres LEDs /////////////////////////////////
  digitalWrite(pinLedRouge, LOW);
  digitalWrite(pinLedBleue, LOW);
}
void doubleClick() {
  // dans cette fonction, donc le clique double, c'est la LED rouge qui fonctionne
  static bool etatLedRouge = 0; // on définit la variable état de la LED
  etatLedRouge =! etatLedRouge; // on inverse l'état de la variable de l'état de la LED
  digitalWrite(pinLedRouge, etatLedRouge ? HIGH : LOW); // on rentre cette variable comme paramètre pour la LED
  ///////////////////// On éteint les autres LEDs /////////////////////////////////
  digitalWrite(pinLedVerte, LOW);
  digitalWrite(pinLedBleue, LOW);
}

void clickLong() {
  // dans cette fonction, donc le clique long, c'est la LED bleue qui fonctionne
  static bool ledBleueAllumee = false; // on définit la variable état de la LED et sa valeur initiale
  ledBleueAllumee =! ledBleueAllumee; // on inverse l'état de la variable de l'état de la LED
  digitalWrite(pinLedBleue, ledBleueAllumee ? HIGH : LOW); // on utilise cette variable comme paramètre pour la LED
  ///////////////////// On éteint les autres LEDs /////////////////////////////////
  digitalWrite(pinLedVerte, LOW);
  digitalWrite(pinLedRouge, LOW);
}
void(ClickBt2){
   //je vous laisse choisir ce que fera ce second bouton
}
void setup() {
  bouton.attachClick(simpleClick); // fonction appelée en cas de click
  bouton.attachDoubleClick(doubleClick); // fonction appelée en cas de double click
  bouton.attachLongPressStop(clickLong); // fonction appelée au moment ou le BP est relâché après un appui long
bouton1.attachClick(ClickBt2); //fonction appelée en cas de click sur le second bouton
bouton1.attachLongPressStop(ClickBt2); //fonction appelée en cas de click sur le second bouton relie a la meme procedure, preferable en utilisation si pas de besoin specifique
}

void loop() {
  bouton.tick(); // on lit en permanence le bouton connecté à la pin D10
}

J'ai ecrit ce code depuis l'exemple de Pandaroux007 et je ne l'ai pas verifie, j'espere qu'il fonctionne (copie et modifie en live).
Encore une fois un grand merci a Pandaroux007car, ce matin encore, je ne connaissais meme pas l'existance de dette bibliotheque. Ce tuto est reellement excellent et ca fonctionne a merveille.
Bon code a tous.

C'est expliqué ici.

Dans ton programme, tes boutons sont actifs à LOW, programmation par défaut. Ils doivent être câblés à GND.

Cordialement
jpbbricole

Merci JPB, effectivement. Cela fonctionne sans ces parametre mais c'est probablement tres important pour la stabilite du fonctionnement. a retenir donc.
Amicalement

Pas toujours - la bibliothèque dépend du INPUT PULLUP

1 Like

Bonjour @michel-eesr
Merci pour vos remarques positives ! :grin:
Je ne comprends pas votre question :

Vous voulez dire le sens du bouton sur la breadboard ? Si c'est cela, ça n'a effectivement aucune importance.

Exactement. Par exemple :

OneButton bouton1(2); // bouton sur la pin 2 câblage pin - bouton – GND
OneButton bouton2(7); // bouton sur la pin 7 câblage pin - bouton – GND

Il faut juste que le nom de l'instance et le numéro de pin soit différent.
Cordialement
Pandaroux007

Bonjour michel-eesr

Important n'est pas le mot :wink:.

Polariser le bouton au GND (Actif == LOW) est, pour moi, préférable.
Cela permet d'utiliser les résistances de PULL_UP internes le l'Arduino et, surtout, il n'est pas nécessaire d'amener un potentiel (comme le 5V.) au boutons ou autres switches, mais uniquement un GND.

Cordialement
jpbbricole

disons que vous l'emmenez quand même avec la broche de l'Arduino

Bonjour J-M-L

Oui, mais peut-on appeler ça un potentiel vu que c'est une entrée, certes polarisée, mais avec quelques dizaines de kOhms.

Par potentiel, je voulais dire une alimentation comme le +5V ou autre tension du montage.

Si on peut éviter d'amener un tension d'alimentation au "frontal" de son montage, c'est tant mieux.

Cordialement
jpbbricole

oui tout à fait

2 posts were split to a new topic: Question sur bibliothèque OneButton - usage de 2 boutons simultanés