[Tutoriel] La bibliothèque OneButton

Introduction

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, je me suis dit que j’allais en faire un.

Ce tuto se veut le plus simple possible, mais n'étant pas très pédagogue, il est probable que des précisions ou modifications soient apportées au fur et à mesure.

:warning: Gérer un bouton ce n'est pas toujours aussi simple qu'il y parait, car il y a ce que l'on appelle des rebonds. Pour ne pas avoir à créer soit même son anti-rebonds, utiliser une bibliothèque simplifie la vie :warning: L'avantage c'est que OneButton inclut un anti-rebonds (basé sur une machine à état décrite ici).

graphique montrant le signal d'un bouton pressé avec ses rebonds

Bien entendu il existe d’autres bibliothèque très simples et bien conçues pour gérer les boutons, comme Toggle ou SimpleBouton, par exemple... Je vous laisse les découvrir, ce tuto parle de OneButton.


Manipulation de la bibliothèque OneButton

Installation

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

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

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


Initialisation

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

#include <OneButton.h>

Puis vous définissez une instance de la classe - un objet - bouton rattachée par exemple ici à la pin n°2 de votre carte Arduino :

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

Attention, 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)

Pour l'utiliser, il vous suffit d'ajouter dans le setup, avant d'attacher vos fonctions de callback aux actions possibles sur le bouton (click, etc...), l'instance du bouton suivie de la fonction et de ses paramètres. Par exemple :

// on crée l'instance du bouton, sans lui passer la pin en paramètre
OneButton bouton;
...
void setup()
{
  /*
  On indique que le bouton est sur la pin 2, que cette pin est
  en INPUT_PULLUP, et que le bp est actif à `LOW` (il retourne `LOW` 
  quand il est pressé) :
  */
  bouton.setup(2, INPUT_PULLUP, true);
  // la suite du code...
}

Pour que votre programme soit fiable sur toutes les cartes (+ de portabilité) - et de manière général parce que c'est une bonne pratique d'utiliser des fonctions qui sont sûr de marcher sans "tomber en marche" - il est préférable d'utiliser cette fonction setup.


Ensuite vous pouvez définir quelles fonctions appeler lors du click, doubleclick ou click long. Ces fonctions sont appelées callback (en anglais pour fonction de rappel) – voir réponse de @J-M-L

void fonctionSimpleClick() {...}
void fonctionDoubleClick() {...}

Vous pouvez les appeler comme vous le voulez ! Il faut juste que ce soit ces fonctions que vous attachiez au(x) bouton(s) dans le setup !


Utilisation

Dans le setup, vous liez les fonctions aux objets boutons que vous avez créés ( qui correspondent aux boutons physiques ) :

void setup()
{
  bouton.attachClick(fonctionSimpleClick);
  bouton.attachDoubleClick(fonctionDoubleClick);
}

Enfin dans la loop vous demandez au bouton de vérifier si quelque chose s’est passé par l’appel de tick() sur le bouton. Il faut le faire assez souvent pour ne pas rater d’événements donc il ne faut pas que la loop ou les callbacks soient bloquants bien sûr (ce qui exlue l'utilisation des delay - mieux vaut utiliser millis!). C’est cet appel qui déclenchera l’appel de la fonction de callback si une action pré enregistrée (donc ici le simple click ou le double click) est activée

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

Il existe d'autres méthodes dans cette bibliothèque, mais pour gérer un bouton c'est la base.

Voici un tableau regroupant toutes les fonctions utiles gérants des événements avec cette bibliothèque :

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.

"Les événements valides se produisent lorsque tick() est appelé après un nombre spécifié de millisecondes. Vous pouvez utiliser les fonctions suivantes pour modifier la synchronisation.

Remarque : Attacher un double-clic augmentera le délai de détection d'un simple clic. Si un événement de double-clic n'est pas attaché, la bibliothèque assumera un simple clic valide après une durée d'un clic, sinon elle doit attendre que le délai d'expiration du double-clic passe. En effet, un appel du callback pour un seul clic ne doit pas être déclenché en cas d'événement de double clic.

Fonction Défaut Description
setDebounceMs(int) 50 msec Période de temps pendant laquelle ignorer les changements de niveau supplémentaires.
setClickMs(int) 400 msec Délai d'attente utilisé pour distinguer les clics simples des doubles clics.
setPressMs(int) 800 msec Durée de maintien d'un bouton pour déclencher un appui long.

Vous pouvez modifier ces valeurs par défaut, mais sachez que lorsque vous spécifiez des durées trop courtes, il est difficile de cliquer deux fois ou vous créerez une pression au lieu d'un clic.

Fonctions supplémentaires

OneButton fournit également quelques fonctions supplémentaires à utiliser pour interroger l'état des boutons :

Fonction Description
bool isLongPressed() Détecter si oui ou non le bouton est actuellement à l'intérieur d'un appui long.
int getPressedMs() Obtenir le nombre actuel de millisecondes pendant lesquelles le bouton a été maintenu enfoncé.
int pin() Obtenez la broche OneButton
int state() Obtenir l'état OneButton
int debouncedValue() Obtenir la valeur anti-rebond OneButton

tick() et reset()

Vous pouvez spécifier un niveau logique lors de l'appel de tick(bool) , ce qui sautera la lecture de la broche et utilisera ce niveau à la place. Si vous souhaitez réinitialiser l'état interne de vos boutons, appelez reset()"

(Source - infos tirées de GitHub)

1 Like

Petit exemple pour bien comprendre...

Bon, c'est bien beau tout cela, mais encore faut il un moyen de mettre en pratique les fonctions de cette bibliothèque. Pour cela, je vous propose un petit montage.

Un peu de câblage ?

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. Pour cela, faites le montage suivant :

schéma

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


Créons le code...

Pour commencer, nous installons la bibliothèque, nous initialisons les pins des LEDs et du BP, et créons une instance 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 simpleClick()
{
  static bool ledVerteAllumee = false;
  ledVerteAllumee =! ledVerteAllumee;
  digitalWrite(pinLedVerte, ledVerteAllumee ? HIGH : LOW);
  digitalWrite(pinLedRouge, LOW);
  digitalWrite(pinLedBleue, LOW);
}

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

void clickLong()
{
  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(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
}

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
}

Et voilà le travail!

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 clique simple
constexpr byte pinLedRouge = 8; // pin de la LED rouge - correspondant au double clique
constexpr byte pinLedBleue = 7; // pin de la LED bleue - correspondant au clique long

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

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 setup()
{
  bouton.setup(pinBP, INPUT_PULLUP, true); // on paramètre le bouton avec sa pin, etc...

  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
}

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