Il s’agit d’une implémentation assez simple d’un menu à bouton unique pour Arduino, basé sur des librairies déjà disponibles.

Menu à bouton unique pour Arduino
Il existe plusieurs exemples de menus Arduino disponibles sur le web mais pour mon projet actuel je souhaite réduire la surface utilisée par cette fonction. Il n’est pas nécessaire de créer une ergonomie sophistiquée car il ne sera utilisé que pour choisir quelques paramètres de temps en temps. Je veux aussi obtenir un code fiable, donc j’ai choisi de développer sur la base de librairies qui sont bien connues et qui ont été largement utilisées.
Dans cet exemple de code nous aurons la possibilité de choisir deux paramètres pour allumer des DELs:
- Frequency : 1, 2, 10 (clignotements / seconde)
- Intensity : low, middle, high
Les évènements levés par le bouton sont:
- Click long / pression : entrer ou sortir du menu.
- Simple click: passer à l’élément de menu ou sous-menu suivant.
- Double click : descendre du menu au sous-menu ou sélectionner l’élément de sous-menu courant.
L’objectif est de montrer le fonctionnement du menu. Le code n’implémente pas le clignotement paramétré des DELs mais se contente d’afficher les valeurs de paramètre choisies sur le LCD.
Il y a 3 catégories de fonctions à implémenter
- Déclencheurs d’actions. Ceci est réalisé par la librairie Arduino OneButton Library by Matthias Hertel qui utilise le concept de machine à états finis. Une fonction “button.tick()” est invoquée régulièrement dans la boucle du programme. Elle teste l’état du bouton et détermine en conséquence l’état de la machine selon qu’il a été cliqué une fois, deux fois ou en continu.
- Gestionnaires de menu. La librairie Menu Backend by Orange-Cat permet de créer assez simplement un ensemble d’objets constituant un menu. Ces objets sont mis en relation par des mouvements possibles (vers la droite, la gauche, le bas etc).
- Affichage, basée sur la librairie standard Liquid Crystal library.
Le sketch complet peut être téléchargé en bas de cette page .
Implantation physique
Le schéma de câblage est très simple. Voici une représentation réalisée sur TinkerCAD, qui utilise une carte UNO :

Circuit design of the “One button menu Arduino circuit”
Les connexions de l’écran LCD sont:
- Vss connecté à la masse
- Vdd connecté au 5 volts + un condensateur de lissage de 10 uF (optionnel)
- Vo connecté à la masse à travers une résistance de 510 Ohm
- L’anode au 5 Volts à travers une résistance de 100 Ohm (optionnel, selon le modèle de LCD)
- La cathode à la masse
- 4 lignes de données D4 – PD4(4) / D5 – PD5(5) / D6 – PD6(6) / D7 – PD7(7)
- RS sur PD3(3)
- RW à la masse
- EN sur PB0(8)
Bien sûr les lignes de données et RS, RW, EN peuvent être connectées sur d’autres broches disponibles dans votre montage.
Le bouton poussoir est connecté à la masse d’un côté et sur la broche INT0(2) de l’autre.
Logiciel
Installation des librairies
Si vous avez besoin de savoir comment installer une librairie, référez-vous à https://www.arduino.cc/en/Guide/Libraries
Librairies à installer:
Il faut les lier dans l’entête du sketch
#include <OneButton.h>
#include <MenuBackend.h>
Définition des éléments de menu et de l’arborescence
Il nous faut d’abord créer les éléments de menu. La structure est
Frequency
1
2
10
Intensity
Low
Middle
High
Le menu et les éléments de menu sont des objets définis dans la librairie MenuBackend. Il faut d’abord les instancier:
// Menu items instantiation
MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent);
//beneath is list of menu items needed to build the menu
MenuItem MFrequency = MenuItem(“Frequency > “);
MenuItem MFreq1 = MenuItem(“1 “);
MenuItem MFreq2 = MenuItem(“2 “);
MenuItem MFreq10 = MenuItem(“10 “);
MenuItem MIntensity = MenuItem(“Intensity > “);
MenuItem MIntensityLow = MenuItem(“Low “);
MenuItem MIntensityMiddle = MenuItem(“Middle “);
MenuItem MIntensityHigh = MenuItem(“High “);
Un objet menu est instancié en premier: MenuBackend Menu = MenuBackend(menuUseEvent,menuChangeEvent);
Il sera identifié par menu. Le constructeur utilise deux paramètres qui sont des fonctions gérant les évènement de menu: menuUseEvent and menuChangeEvent.
Les éléments du menu sont ensuite instanciés. Le constructeur utilise un paramètre name qui nous servira pour afficher le menu courant sur l’écran LCD.
Nous complétons les noms pour remplir les 16 caractères de l’écran.
Dans notre implémentation les évènements seront levés par la machine à états finis. Donc nous laissons vide les gestionnaires d’évènements spécifiques au menu.
void menuUseEvent(MenuUseEvent used)
{
// nothing here, events will be handled by oneButton event handlers
}
void menuChangeEvent(MenuChangeEvent changed)
{
// nothing here, events will be handled by oneButton event handlers
}
Nous devons maintenant définir les relations entre les éléments de menu. Ceci définit les déplacements possibles entre éléments de menu.
void menuSetup()
{
menu.getRoot().add(MFrequency);
MFrequency.addRight(MFreq1);
MFreq1.addAfter(MFreq2);
MFreq1.addLeft(MFrequency);
MFreq2.addAfter(MFreq10);
MFreq2.addLeft(MFrequency);
MFreq10.addAfter(MFreq1);
MFreq10.addLeft(MFrequency);
MFrequency.addAfter(MIntensity);
MIntensity.addRight(MIntensityLow);
MIntensityLow.addAfter(MIntensityMiddle);
MIntensityLow.addLeft(MIntensity);
MIntensityMiddle.addAfter(MIntensityHigh);
MIntensityMiddle.addLeft(MIntensity);
MIntensityHigh.addAfter(MIntensityLow);
MIntensityHigh.addLeft(MIntensity);
MIntensity.addAfter(MFrequency);
}
La fonction menuSetup() ajoute toutes les relations entre éléments de menu et de sous-menu. Elle sera invoquée dans la partie setup() du programme.
menu.getRoot().add(MFrequency); ajoute à l’objet menu le premier élément de menu nommé : “MFrequency”. Ceci créé la première entrée de menu.
Nous ajoutons ensuite les déplacements entre les éléments de sous menu associés au menu “MFrequency”
- Nous ajoutons d’abord le premier des sous-menus à la droite de “MFrequency” par la commande MFrequency.addRight(MFreq1);
- On définit les déplacements entre les entrées de sous menu en les liant les uns aux autres par des commandes du type MFreq1.addAfter(MFreq2); Le sous-menu est cyclique puisque l’on relie le dernier élément au premier: MFreq10.addAfter(MFreq1);
- On définit aussi le retour du niveau sous-menu vers le niveau menu par des commandes du type MFreq1.addLeft(MFrequency);
Nous définissons ensuite le déplacement vers le deuxième élément de menu par MFrequency.addAfter(MIntensity); et utilisons le même type de commande que nous venons d’expliquer pour les déplacements dans le sous-menu de “MIntensity”.
On définit enfin le déplacement entre le dernier élément de menu et le premier par MIntensity.addAfter(MFrequency);
C’est fait! Nous avons terminé la structure du menu.
Machine à états finis
Nous définissons en premier un objet “button” dans la partie déclarative du programme:
const char PUSHBUTTON = 2; // PD2/INT0 menu pushbutton pin
OneButton button(PUSHBUTTON, true); // instantiation of a OneButton object identified as “button”
Il nous faut aussi gérer les 3 évènements possibles (click, double click, click long) levés par l’objet button. Il faut attacher les 3 gestionnaires d’évènements dans le setup() du programme. Il est aussi possible de définir quelques paramètres de comportement. setClickTicks définit le délai minimal pour compter un click et setPressTicks définit la durée minimale d’une pression. Nous créons une fonction:
void buttonSetup() {
button.attachClick(buttonClick);
button.attachDoubleClick(buttonDoubleclick);
button.attachLongPressStart(buttonPress);
// these are optional parameters to fine-tune the behavior. Values are in ms. Given values here are default values.
button.setClickTicks(600);
button.setPressTicks(1000);
}
Codons maintenant les 3 gestionnaires d’évènements: buttonClick, buttonDoubleclick and buttonPress.
Nous avons besoin d’une variable booléenne qui sera vraie lorsque nous sommes dans le menu et fausse autrement.
bool inMenu = false;
buttonClick()
Nous avons définit qu’un simple click permet de se déplacer d’une entrée de menu ou sous menu vers la suivante:
void buttonClick() {
if (inMenu) {
menu.moveDown();
printToLCD(menu.getCurrent().getName(),0);
}
}
Lorsqu’un évènement simple click est levé par l’objet button, si nous sommes actuellement en train d’utiliser le menu (inMenu = True) nous utilisons la fonction moveDown() fournie par la librairie pour nous déplacer vers l’élément suivant de menu ou de sous-menu.
Nous obtenons ensuite le nom de l’élément de menu (par exemple “Intensity >”), et l’affichons sur l’écran LCD.
buttonDoubleclick()
Dans la partie déclarative du programme nous définissons les variables de fréquence et d’intensité:
int Frequency = 1;
String Intensity = “Low”;
Nous avons définit le fait qu’un double click permet de descendre du menu au sous menu ou de sélectionner l’élément de sous-menu.
void buttonDoubleclick() {
if (inMenu) {
MenuItem currentMenu = menu.getCurrent();
if (currentMenu == MFrequency) {
menu.moveRight();
}
if (currentMenu == MFreq1) {
Frequency = 1;
menu.moveLeft();
}
if (currentMenu == MFreq2) {
Frequency = 2;
menu.moveLeft();
}
if (currentMenu == MFreq10) {
Frequency = 10;
menu.moveLeft();
}
if (currentMenu == MIntensity) {
menu.moveRight();
}
if (currentMenu == MIntensityLow) {
Intensity = “Low”;
menu.moveLeft();
}
if (currentMenu == MIntensityMiddle) {
Intensity = “Mid”;
menu.moveLeft();
}
if (currentMenu == MIntensityHigh) {
Intensity = “High”;
menu.moveLeft();
}
printToLCD(menu.getCurrent().getName(),0);
}
}
Comme pour le simple click, le double click n’est pris en considération que si nous sommes dans le menu ( if (inMenu)). Autrement une pression accidentelle sur le bouton ne changera aucune variable.
Nous comparons l’élément de menu courant avec les éléments de menu que nous avons définit et ajoutons le code adapté en fonction de chaque cas.
Par exemple si le menu courant est Freq1, nous fixons la variable Frequency à 1, puis nous remontons au niveau menu et affichons le menu “Frequency >”.
ButtonPress()
Nous avons définit qu’une pression longue sur le bouton permet d’entrer ou sortir du menu (après avoir validé la valeur courante de sous-menu)
void buttonPress() {
// buttonPress enters or exists the menu, hence the boolean inMenu value changes
inMenu = !inMenu;
if (inMenu) {
lcd.clear();
// if we just entered the menu, we have to move to the first menu entry
if (menu.getCurrent().getName() == “MenuRoot”) {
menu.moveDown();
}
printToLCD(menu.getCurrent().getName(),0);
} else { // exiting the menu
MenuItem currentMenu = menu.getCurrent();
if (currentMenu == MFreq1) {
Frequency = 1;
}
if (currentMenu == MFreq2) {
Frequency = 2;
}
if (currentMenu == MFreq10) {
Frequency = 10;
}
if (currentMenu == MIntensityLow) {
Intensity = “Low”;
}
if (currentMenu == MIntensityMiddle) {
Intensity = “Mid”;
}
if (currentMenu == MIntensityHigh) {
Intensity = “High”;
}
menu.toRoot();
lcd.clear();
}
}
En premier on bascule la valeur de inMenu, puis, si nous sommes à la racine du menu, nous nous déplaçons vers la première entrée de menu. Si nous sortons du menu, nous fixons la valeur courante de sous-menu comme nous l’avons fait sur le double click. Nous remontons à la racine du menu et réinitialisons le LCD.
Display
Il faut initialiser le LCD dans la partie déclarative du programme:
const int LCDRS = 3; //PD3
const int LCDD4 = 4; //PD4
const int LCDD5 = 5; //PD5
const int LCDD6 = 6; //PD6
const int LCDD7 = 7; //PD7
const int LCDENABLE = 8; //PB0
LiquidCrystal lcd(LCDRS, LCDENABLE, LCDD4, LCDD5, LCDD6, LCDD7);
On créé une fonction pour l’affichage:
void printToLCD(String message, byte row) {
// the code here may depend on the LCD model
// here for a 16*2 LCD that I have
lcd.setCursor(0,row);
lcd.print(message);
}
Setup
void setup() {
lcd.begin(16, 2);
menuSetup();
buttonSetup();
}
Loop
void loop() {
button.tick(); // checks regularly if the button is pressed or not and raises events according to the finite state
if (!inMenu) {
// under normal operation put your code using Frequency and Intensity parameters here.
// in this example we simply display them on the LCD screen
msg = “Frequency: ” + String(Frequency);
printToLCD(msg, 0);
msg = “Intensity: ” + Intensity;
printToLCD(msg, 1);
}
}
Dans la boucle on invoque button.tick(); afin que la machine à états finis évalue si nous somme dans un des états : aucun click, simple click, double click ou pression.
Si nous ne sommes pas actuellement dans le menu nous affichons les valeurs de fréquence et d’intensité qui ont été choisies.
On ajoute ensuite un petit délai afin d’assurer un bon fonctionnement de click et double click. Il peut être nécessaire d’ajuster la valeur du délai en fonction de votre programme.

Objectif atteint!
Quelques photos
Une pression longue sur le bouton permet l’affichage de la première entrée de menu “Frequency >”

Displaying Frequency menu
Un simple click passe à la deuxième entrée de menu “Intensity>”

Displaying Intensity menu
Un double click permet d’atteindre la première entrée du sous menu “Low”. Si nous faisons des simples clicks, nous voyons afficher successivement les autres valeurs “Middle”…”High”…”Low”…

Low sub menu value
Un click long nous fait sortir du menu pour revenir au fonctionnement normal du programme. Dans notre cas on affiche les valeurs courantes des paramètres.

One button menu for Arduino
Télécharger le sketch complet.

Download
.