Bonjour tout le monde !
À de nombreuses reprises l’on m’a demandé mon avis sur comment implémenter un menu sous forme de liste avec une carte Arduino et un petit écran LCD 2×16 caractères.
Le sujet est on ne peut plus intéressant, mais je n’avais pas eu le temps de me pencher dessus jusqu’à présent.
Devant répondre une nouvelle fois à cette question par mail je me suis dit qu’il serait temps de prendre une demi-journée pour faire un article sur le sujet 😉
Vous l’aurez compris, le programme du jour est : « comment implémenter un menu sous forme de liste avec une carte Arduino et un écran LCD ».
—
Préface
Avant de partir tête baissée dans de l’explication de code et d’algorithme je souhaitai préciser quelques petites choses.
Premièrement, ce que je vais vous présenter aujourd’hui est un algorithme, une méthode parmi tant d’autres.
Il existe des méthodes bien plus simples (automate fini), ou bien plus compliquées (pattern arbre et parcours).
Pour cet article j’ai choisi une méthode ni trop simple (vous n’auriez pas pu reprendre le code tel quel), ni trop compliqué (il me faudrait plusieurs articles et une vidéo avec un tableau blanc pour expliquer en détail le pattern arbre et l’algo de parcours).
En cherchant sur Google vous trouverez bien d’autre façon de faire, à vous de faire votre choix 😉
Deuxièmement, j’ai choisi de faire un code réutilisable en langage C. Excepté quelques petites notions de C++ tout le code est écrit en C.
Comme je sais que plusieurs personnes m’ont posé le même genre de question pour du code PIC ou ARM (avec un compilateur C uniquement), je me suis dit qu’il serait plus facile de faire tout le code de base en C.
Il est clair que si j’avais du faire un code « propre » j’aurai utilisé des classes C++ et fait un pattern arbre. Mais ce n’est pas mon but pour cet article.
—
Le hardware
Avant de parler du code, je vais vous présenter le montage de test.
J’ai volontairement choisi d’utiliser du matériel tout prêt, facile à trouver et (relativement) fiable.
Mon but n’est pas de vous apprendre à câbler un écran LCD ni de faire du debug toute une après-midi 😉
Pour ce tutoriel, il suffira donc de se munir d’une shield LCD (ici de DFRobots) et d’une carte Arduino UNO.
—
Le résultat escompté
Maintenant que l’on sait sur quoi l’on va travailler, il faut définir ce que l’on souhaite obtenir.
Si on ne sait pas ce que l’on souhaite, on ne risque pas d’y arriver 😉
J’ai choisi de rester très sobre et très classique pour le menu.
La première ligne sera le titre du menu, la seconde ligne le choix en cours.
La touche gauche permettra de revenir au menu précédent (il y aura un sous-menu pour que ce tutoriel soit un minimum réaliste).
La touche droite ou SELECT permettra de sélectionner un choix du menu, ou d’ouvrir un sous-menu.
Les touches haut / bas permettront de naviguer dans la liste des choix.
Pour pouvoir montrer le bon fonctionnement du menu, une fois le choix validé un écran affichera le choix en question.
Dans une véritable application, cet écran serait remplacé par l’action à réaliser en fonction du choix.
—
La théorie et le code
Pour ce tutoriel on va avoir besoin de deux fichiers : tuto_menu_liste.ino et Menu.h.
Le .ino contiendra le code du menu et Menu.h les diverses déclarations liées au menu.
Si l’IDE Arduino n’était pas buggé jusqu’à la moelle on pourrait tout mettre dans un même fichier, mais pour le moment on ne peut pas.
Maintenant réfléchissons à la structure d’un menu.
Un menu est composé :
– d’un titre,
– d’une série de choix (choix pouvant pointer vers un sous-menu),
– d’une fonction à appeler lors de la validation du choix par l’utilisateur.
Traduit en C cela donne la structure suivante :
/* Structure d'un menu */ typedef struct { const char* prompt; // Titre du menu const char** items; // Tableau de choix du menu const uint8_t nbItems; // Nombre de choix possibles void (*callbackFnct)(uint8_t menuItemSelected); // Pointeur sur fonction pour gérer le choix de l'utilisateur } Menu_t;
Avec cette déclaration de type (typedef) on déclare que « Menu_t » est la représentation en mémoire d’un menu, avec : un titre, un tableau de choix, un nombre de choix et une fonction de retour pour la validation.
Je sais, le pointeur sur fonction en fin de structure va faire un scandale parmi les débutants en langage C.
Pour faire simple, c’est un pointeur qui peut pointer vers n’importe quelle fonction de la forme void blabla(byte).
J’aurais aussi pu éviter les chaines de caractères en mémoire RAM en utilisant le mot clef PROGMEM.
Mais si je l’avais fait, j’aurais passer plus de temps à vous expliquer pourquoi utiliser PROGMEM et comment ça marche, que de vous expliquer comment faire un menu 😉
Le résultat final de Menu.h :
#ifndef _MENU_H_ #define _MENU_H_ /* Structure d'un menu */ typedef struct { const char* prompt; // Titre du menu const char** items; // Tableau de choix du menu const uint8_t nbItems; // Nombre de choix possibles void (*callbackFnct)(uint8_t menuItemSelected); // Pointeur sur fonction pour gérer le choix de l'utilisateur } Menu_t; /* Listes des touches de la shield lcd DFrobots */ typedef enum { BP_NONE, // Aucun bouton appuyé BP_SELECT, // Bouton SELECT BP_LEFT, // Bouton gauche BP_RIGTH, // Bouton droite BP_UP, // Bouton haut BP_DOWN // Bouton bas } Button_t; #endif /* _MENU_H_ */
Comme sur ce blog on fait les choses bien, on va ajouter un « include guard » à notre .h (la série de #ifndef #define #endif).
Si vous ne savez pas ce qu’est un include guard ce n’est pas grave, dite vous simplement que c’est une bonne habitude à prendre quand on écrit un fichier .h.
On va aussi ajouter une énumération qui nous sera utile plus tard pour la fonction permettant de lire l’état des boutons de la shield LCD de DFRobots.
—
Maintenant que l’on a notre fichier Menu.h on va passer au code du .ino 😉
/* Inclut la librairie LiquidCrystal pour le LCD */ #include <LiquidCrystal.h> #include "Menu.h" // Fichier d'entête avec les types pour le menu
On va commencer par inclure les fichiers d’entêtes nécessaires à notre code.
Nous aurons ici besoin de la librairie LiquidCrystal pour l’écran LCD, et de notre fichier Menu.h pour la structure d’un menu.
/* Objet LCD sur les broches utilisées par la shield LCD DFrobots */ static LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
On va ensuite instancier un objet de type LiquidCrystal sur les broches adéquates pour pouvoir afficher par la suite du texte sur notre écran LCD.
/* Menu principal */ static const char* MAIN_MENU_ITEMS[] = { "John Doe", "Skywodd", "Dr Freeman", "Nyan Cat", "Obiwan Kenobi" }; static const Menu_t MAIN_MENU = { "Votre choix ?", MAIN_MENU_ITEMS, 5, &doMainMenuAction }; /* Sous menu pour Dr Freeman */ static const char* FREEMAN_MENU_ITEMS[] = { "Gordon Freeman", "Morgan Freeman", "Martin Freeman" }; static const Menu_t FREEMAN_MENU = { "Votre choix ?", FREEMAN_MENU_ITEMS, 3, &doFreemanMenuAction };
Pour finir, on va déclarer deux menus : un menu principal et un sous-menu (pour l’exemple).
Ces menus vous permettront de choisir votre personnage préféré.
(Oui je sais, je n’étais pas franchement inspiré …)
On déclare donc deux menus : un menu principal contenant 5 choix qui sera géré par la fonction « doMainMenuAction ».
Et un sous-menu spécial « Dr Freeman », contenant seulement 3 choix et qui sera géré par la fonction « doFreemanMenuAction ».
Les fonctions « doMainMenuAction » et « doFreemanMenuAction » seront appelées par notre code lorsque l’utilisateur validera son choix.
/** Setup */ void setup() { /* Configuration du LCD */ lcd.begin(16, 2); }
La fonction setup() n’aura dans ce tutoriel pas beaucoup de chose à configurer.
On va uniquement configurer l’écran LCD en mode 2×16 caractères, rien de plus.
/** Programme principal */ void loop() { /* Affiche le menu principal */ displayMenu(MAIN_MENU); /* Démo pour montrer la sortie du menu */ lcd.clear(); lcd.print(F("Menu ferme")); delay(2000); }
On va ensuite passer à la fonction loop() qui elle aussi ne sera pas bien garnie pour ce tutoriel.
La fonction loop() ne fera que deux choses :
– afficher le menu principal,
– afficher un petit message pour montrer la fermeture du menu (avant de le rouvrir 2 secondes plus tard).
/** Fonction retournant le bouton appuyé (s’il y en a un). */ Button_t readPushButton(void) { /* Lecture de l'entrée A0 */ unsigned int val = analogRead(A0); /* Test suivant les fourchettes de valeurs */ if (val > 1000) return BP_NONE; if (val < 50) return BP_RIGTH; if (val < 195) return BP_UP; if (val < 380) return BP_DOWN; if (val < 555) return BP_LEFT; if (val < 790) return BP_SELECT; /* Par défaut aucun bouton n'est appuyé */ return BP_NONE; }
Je passe rapidement sur cette fonction. C’est la fonction de base pour lire les boutons de la shield LCD de DFRobots.
À modifier en conséquence si vous utilisez une autre shield ou un montage maison.
/** Affiche le choix de l'utilisateur */ void doMainMenuAction(byte selectedMenuItem) { /* Cas spécial pour Dr Freeman */ if(selectedMenuItem == 2) { /* Affiche le sous-menu pour Dr Freeman */ displayMenu(FREEMAN_MENU); } else { /* Affiche le choix de l'utilisateur */ displayChoice(selectedMenuItem, MAIN_MENU_ITEMS); } } /** Affiche le choix de l'utilisateur */ void doFreemanMenuAction(byte selectedMenuItem) { /* Affiche le choix de l'utilisateur */ displayChoice(selectedMenuItem, FREEMAN_MENU_ITEMS); } /** Affiche le choix de l'utilisateur */ void displayChoice(byte selectedMenuItem, const char** items) { /* Affiche le choix de l'utilisateur */ lcd.clear(); lcd.print(F("Z'avez choisi :")); lcd.setCursor(0, 1); lcd.print(items[selectedMenuItem]); /* Attend l'appui sur le bouton gauche ou SELECT */ byte buttonPressed; do { buttonPressed = readPushButton(); } while(buttonPressed != BP_LEFT && buttonPressed != BP_SELECT); }
Avant d’entrer en détail dans la fonction d’affichage des menus, on va définir de suite les fonctions de gestion des deux menus.
« doMainMenuAction » utilisera la fonction « displayChoice » pour afficher le choix de l’utilisateur.
La fonction « displayChoice » ne fait qu’afficher le choix de l’utilisateur avant d’attendre que celui-ci appui sur la touche gauche ou SELECT pour revenir au menu précédent.
Dans le cas où l’utilisateur choisit le sous-menu « Dr Freeman », la fonction « doMainMenuAction » lancera l’affichage du sous-menu en question et restera bloqué dans ce sous-menu tant que l’utilisateur ne le quittera pas.
Dans l’hypothèse où l’utilisateur valide un choix dans le sous-menu « Dr Freeman » c’est la fonction « doFreemanMenuAction » qui sera appelée et utilisera la fonction « displayChoice » pour afficher le choix de l’utilisateur.
Dans une véritable application, on utiliserait un switch/case pour gérer chaque choix de l’utilisateur, sous-menu inclus.
Ici ce n’est qu’un exemple bateau donc je reste assez simpliste.
Voici un exemple de fonction plus « réaliste » :
/** Gère le choix de l'utilisateur */ void doMainMenuAction(byte selectedMenuItem) { /* Gère le choix de l'utilisateur */ switch(selectedMenuItem) { case 0: // Code pour le choix 1 break; case 1: // Code pour le choix 2 break; case 2: // Code pour le choix 3 break; // etc ... } }
—
Ça devient sérieux là, passons à la fonction d’affichage d’un menu.
/** Affiche le menu passé en argument */ void displayMenu(const Menu_t &menu) { /* Variable pour le menu */ byte selectedMenuItem = 0; // Choix sélectionné byte shouldExitMenu = false; // Devient true quand l'utilisateur veut quitter le menu Button_t buttonPressed; // Contient le bouton appuyé /* Tant que l'utilisateur ne veut pas quitter pas le menu */ while(!shouldExitMenu) { /* Affiche le menu */ lcd.clear(); lcd.print(menu.prompt); lcd.setCursor(0, 1); lcd.print(menu.items[selectedMenuItem]); /* Attend le relâchement du bouton */ while(readPushButton() != BP_NONE); /* Attend l'appui sur un bouton */ while((buttonPressed = readPushButton()) == BP_NONE); /* Anti rebond pour le bouton */ delay(30); /* Attend le relâchement du bouton */ while(readPushButton() != BP_NONE); /* Gére l'appui sur le bouton */ switch(buttonPressed) { case BP_UP: // Bouton haut = choix précédent /* S'il existe un choix précédent */ if(selectedMenuItem > 0) { /* Passe au choix précédent */ selectedMenuItem--; } break; case BP_DOWN: // Bouton bas = choix suivant /* S'il existe un choix suivant */ if(selectedMenuItem < (menu.nbItems - 1)) { /* Passe au choix suivant */ selectedMenuItem++; } break; case BP_LEFT: // Bouton gauche = sorti du menu shouldExitMenu = true; break; case BP_SELECT: // case BP_RIGTH: // Bouton droit ou SELECT = validation du choix menu.callbackFnct(selectedMenuItem); break; } } }
Le code parait complexe, mais en réalité il est très simple (et largement améliorable / personnalisable).
/* Variable pour le menu */ byte selectedMenuItem = 0; // Choix sélectionné byte shouldExitMenu = false; // Devient true quand l'utilisateur veut quitter le menu Button_t buttonPressed; // Contient le bouton appuyé
On commence par déclarer un certain nombre de variables qui nous seront utiles par la suite.
Comme aucune de ces variables n’est statique, on peut sans problème appeler un sous-menu depuis un menu parent sans que celui-ci ne soit affecté par le sous-menu.
/* Tant que l'utilisateur ne veut pas quitter pas le menu */ while(!shouldExitMenu) { // ... }
Ensuite, toute la magie se trouve encapsuler dans une boucle infinie qui peut être stoppée en plaçant la variable « shouldExitMenu » à true dans la suite du code.
C’est cette boucle et cette variable « shouldExitMenu » qui permettra de quitter le sous-menu lorsque l’on appuiera sur la touche gauche.
/* Affiche le menu */ lcd.clear(); lcd.print(menu.prompt); lcd.setCursor(0, 1); lcd.print(menu.items[selectedMenuItem]);
La première véritable action du code sera d’afficher le titre du menu et le choix courant.
Par défaut le premier choix affiché sera le choix n°1 (indice 0 dans le tableau de choix).
Par la suite le choix affiché sera lié à la valeur présente dans « selectedMenuItem ».
/* Attend le relâchement du bouton */ while(readPushButton() != BP_NONE); /* Attend l'appui sur un bouton */ while((buttonPressed = readPushButton()) == BP_NONE); /* Anti rebond pour le bouton */ delay(30); /* Attend le relâchement du bouton */ while(readPushButton() != BP_NONE);
Une fois le menu affiché il va bien falloir laisser l’utilisateur faire son choix.
Les 4 lignes de code si dessus ont pour but de lire l’état des boutons, tout en évitant qu’un appui prolongé sur un bouton ne fasse partir l’utilisateur dans une dizaine de sous-menus en une fraction de seconde.
Pour éviter ce genre de désagrément (que j’ai pu constater sur certains projets disponibles sur le web) l’algorithme est très simple :
– attendre que le bouton soit relâché,
– attendre que le bouton soit appuyé,
– garder en mémoire le bouton appuyé,
– attendre de nouveau que le bouton soit relâché.
Idéalement avant la dernière attente, il convient d’ajouter un petit délai entre 20ms et 30ms pour éviter les « rebonds » des boutons.
En suivant cette logique, on garantit qu’un seul appui sera traité à chaque appui sur un bouton.
/* Gére l'appui sur le bouton */ switch(buttonPressed) { case BP_UP: // Bouton haut = choix précédent // ... break; case BP_DOWN: // Bouton bas = choix suivant // ... break; case BP_LEFT: // Bouton gauche = sorti du menu // ... break; case BP_SELECT: // case BP_RIGTH: // Bouton droit ou SELECT = validation du choix // ... break; }
Vient ensuite la gestion de l’appui sur ledit bouton.
Pour ce faire, le moyen le plus rapide et le plus efficace est d’utiliser un switch/case.
case BP_UP: // Bouton haut = choix précédent /* Si il existe un choix précédent */ if(selectedMenuItem > 0) { /* Passe au choix précédent */ selectedMenuItem--; } break;
Pour chaque appui sur le bouton haut on décrémentera la valeur de « selectedMenuItem ».
On prendra quand même le temps d’éviter de partir dans des valeurs négatives.
Ce serait quand même dommage de voir le programme planter une fois arrivé à -1 😉
case BP_DOWN: // Bouton bas = choix suivant /* Si il existe un choix suivant */ if(selectedMenuItem < (menu.nbItems - 1)) { /* Passe au choix suivant */ selectedMenuItem++; } break;
Pour chaque appui sur le bouton bas on incrémentera la valeur de « selectedMenuItem ».
Pareil que pour le bouton haut, on évitera de dépasser le nombre maximum de choix possibles.
case BP_LEFT: // Bouton gauche = sorti du menu shouldExitMenu = true; break;
Le bouton gauche permet de sortir du menu, pour ce faire il nous suffit de placer « shouldExitMenu » à true.
La boucle while() en début de code s’arrêtera en sortant du switch/case et on quittera ainsi naturellement la fonction « displayMenu ».
case BP_SELECT: // case BP_RIGTH: // Bouton droit ou SELECT = validation du choix menu.callbackFnct(selectedMenuItem); break;
Le bouton droit et le bouton SELECT sont gérés de la même façon.
Quand on appuiera sur un de ces boutons, la fonction de gestion renseignée dans la structure du menu sera appelée avec en argument le choix de l’utilisateur.
Il est possible que certains d’entre vous aient besoin de quitter le menu une fois l’action choisie réalisée. Dans ce cas il suffit de rajouter la ligne : « shouldExitMenu = true; » juste avant le break.
Comme vous pouvez le voir, une fois décris point par point le code n’est pas si compliqué que ça.
De plus il est générique, vous pouvez copier/coller cette fonction et le fichier Menu.h dans vos projets sans avoir besoin de modifier quoi que ce soit.
—
Le code final
Au final l’on obtient le code suivant :
/* Inclut la librairie LiquidCrystal pour le LCD */ #include <LiquidCrystal.h> #include "Menu.h" // Fichier d'entête avec les types pour le menu /* Objet LCD sur les broches utilisées par la shield LCD DFrobots */ static LiquidCrystal lcd(8, 9, 4, 5, 6, 7); /* Menu principal */ static const char* MAIN_MENU_ITEMS[] = { "John Doe", "Skywodd", "Dr Freeman", "Nyan Cat", "Obiwan Kenobi" }; static const Menu_t MAIN_MENU = { "Votre choix ?", MAIN_MENU_ITEMS, 5, &doMainMenuAction }; /* Sous menu pour Dr Freeman */ static const char* FREEMAN_MENU_ITEMS[] = { "Gordon Freeman", "Morgan Freeman", "Martin Freeman" }; static const Menu_t FREEMAN_MENU = { "Votre choix ?", FREEMAN_MENU_ITEMS, 3, &doFreemanMenuAction }; /** Setup */ void setup() { /* Configuration du LCD */ lcd.begin(16, 2); } /** Programme principal */ void loop() { /* Affiche le menu principal */ displayMenu(MAIN_MENU); /* Démo pour montrer la sortie du menu */ lcd.clear(); lcd.print(F("Menu ferme")); delay(2000); } /** Fonction retournant le bouton appuyé (s’il y en a un). */ Button_t readPushButton(void) { /* Lecture de l'entrée A0 */ unsigned int val = analogRead(A0); /* Test suivant les fourchettes de valeurs */ if (val > 1000) return BP_NONE; if (val < 50) return BP_RIGTH; if (val < 195) return BP_UP; if (val < 380) return BP_DOWN; if (val < 555) return BP_LEFT; if (val < 790) return BP_SELECT; /* Par défaut aucun bouton n'est appuyé */ return BP_NONE; } /** Affiche le menu passé en argument */ void displayMenu(const Menu_t &menu) { /* Variable pour le menu */ byte selectedMenuItem = 0; // Choix selectionné byte shouldExitMenu = false; // Devient true quand l'utilisateur veut quitter le menu Button_t buttonPressed; // Contient le bouton appuyé /* Tant que l'utilisateur ne veut pas quitter pas le menu */ while(!shouldExitMenu) { /* Affiche le menu */ lcd.clear(); lcd.print(menu.prompt); lcd.setCursor(0, 1); lcd.print(menu.items[selectedMenuItem]); /* Attend le relâchement du bouton */ while(readPushButton() != BP_NONE); /* Attend l'appui sur un bouton */ while((buttonPressed = readPushButton()) == BP_NONE); /* Anti rebond pour le bouton */ delay(30); /* Attend le relâchement du bouton */ while(readPushButton() != BP_NONE); /* Gére l'appui sur le bouton */ switch(buttonPressed) { case BP_UP: // Bouton haut = choix précédent /* Si il existe un choix précédent */ if(selectedMenuItem > 0) { /* Passe au choix précédent */ selectedMenuItem--; } break; case BP_DOWN: // Bouton bas = choix suivant /* Si il existe un choix suivant */ if(selectedMenuItem < (menu.nbItems - 1)) { /* Passe au choix suivant */ selectedMenuItem++; } break; case BP_LEFT: // Bouton gauche = sorti du menu shouldExitMenu = true; break; case BP_SELECT: // case BP_RIGTH: // Bouton droit ou SELECT = validation du choix menu.callbackFnct(selectedMenuItem); break; } } } /** Affiche le choix de l'utilisateur */ void doMainMenuAction(byte selectedMenuItem) { /* Cas spécial pour Dr Freeman */ if(selectedMenuItem == 2) { /* Affiche le sous-menu pour Dr Freeman */ displayMenu(FREEMAN_MENU); } else { /* Affiche le choix de l'utilisateur */ displayChoice(selectedMenuItem, MAIN_MENU_ITEMS); } } /** Affiche le choix de l'utilisateur */ void doFreemanMenuAction(byte selectedMenuItem) { /* Affiche le choix de l'utilisateur */ displayChoice(selectedMenuItem, FREEMAN_MENU_ITEMS); } /** Affiche le choix de l'utilisateur */ void displayChoice(byte selectedMenuItem, const char** items) { /* Affiche le choix de l'utilisateur */ lcd.clear(); lcd.print(F("Z'avez choisi :")); lcd.setCursor(0, 1); lcd.print(items[selectedMenuItem]); /* Attend l'appui sur le bouton gauche ou SELECT */ byte buttonPressed; do { buttonPressed = readPushButton(); } while(buttonPressed != BP_LEFT && buttonPressed != BP_SELECT); }
—
Bonne fin de WE à toutes et à tous !
super comme toujours! un grand merci
Très intéressante ! Merci 🙂
Merci pour ton article je vais etudier ca de suite…
Tu es un as de la prog
A bientot
Question tu es de quelle endroit en france ? Moi a cote de clermont fd… 63
Bonjour,
Très bon taff comme d’habitude.
il y a une petite erreur de casse.
Tu déclare LCD et ensuite tu utiliser lcd.***
ce qui fait une erreur de compilation avec l’IDE Arduino
Hop, corrigé, merci d’avoir prévenu 😉
(pour les curieux : je test tout mes codes avant publication. Ici c’est le correcteur ortho que j’utilise qui c’est mis en tête de mettre LCD en majuscule pour cette ligne … allez savoir pourquoi)
Merci le correcteur.
Je te suis depuis très longtemps avec ton test de la stml32 discovery.
A l’heure actuelle j’implemente un RTOS (Trampoline) sur l’arduino.
Et cela fonctionne très bien 😀
chez moi ca compile pas….
Sans plus de précision concernant l’erreur de compilation, je ne peux pas faire de miracle …
Bonjour,
Juste une question:
Pour rajouter un sous menu à un sous menu, comment fait-on?
Merci de me répondre par mail.
Suffit d’ajouter un niveau en plus avec une fonction de gestion.
Exemple :
bonjour, merci d’avoir publié ce code, cela semble limpide maintenant pour moi.
J’ai un petit souci: j’ai créé un onglet .h afin d’y mettre le premier bout de code, copié le programme dans le .ino et à l’essai de compilation j’ai ce message: ‘doMainMenuAction’ was not declared in this scope.
Me manque t il une librairie?
cordialement, Arnaud
Le fichier s’appelle Menu.h (avec la majuscule) ?
D’après l’erreur il semblerait que le compilateur ne trouve pas le fichier .h.
Bonjour et merci d’avoir répondu à ma question.
J’ai fait plusieurs essais avec : Menu.h , MENU.h et menu.h et rien ne se passe, aucune différence.
J’avoue être perplexe.
Une petite capture d’écran de l’erreur et de l’ide (avec les différents onglets), c’est possible ?
A mon avis c’est juste une erreur de #include, ce sera vite résolu si c’est la cas.
Merci pour ce code j’ ai un problème de compilation avec les erreurs suivante :
This report would have more information with
« Show verbose output during compilation »
enabled in File > Preferences.
Arduino: 1.0.6 (Windows 7), Board: « Arduino Uno »
Teste_menu:7: error: expected ‘,’ or ‘…’ before ‘&’ token
Teste_menu:7: error: ISO C++ forbids declaration of ‘Menu_t’ with no type
Teste_menu:16: error: expected `}’ before ‘;’ token
Teste_menu:16: error: cannot convert ‘const char**’ to ‘const char*’ in initialization
Teste_menu:16: error: uninitialized const member ‘Menu_t::nbItems’
Teste_menu:17: error: expected unqualified-id before numeric constant
Teste_menu:19: error: expected declaration before ‘}’ token
pouvez vous m’ apporter votre aide
merci par avance
La structure Menu_t n’ai pas reconnu. Causes possibles : pas de fichier Menu.h, include manquant, erreur de copier/coller de la déclaration de la structutre.
Bonjour skywodd, votre topic est parfait pour mon projet, néanmoins j’ai une erreur également avec le menu :
C:\Users\juju\AppData\Local\Temp\arduino_bcb36d786c28b6123d4ee68919c15263\sketch_dec11a.ino:3:67: fatal error: Menu.h: No such file or directory
#include « Menu.h » // Fichier d’entete avec les types pour le menu
^
compilation terminated.
exit status 1
Erreur lors de la compilation.
es-ce en rapport avec la librairie?
version d’arduino : 1.6.6
Le code est en plusieurs parties.
Une partie du code de l’article va dans le .ino et une autre dans le fichier Menu.h.
Bonjour Skywood, merci pour ton code. Je l’ai adapté pour une petite appli et la en le reprenant avec la version 1.6.6 ca merde sur le doMainMenuAction’ comme pour les autres. Avez vous solutionné la chose? Si oui comment? Merci
De même, il ne me prend plus LiquidCrytal pourtant copié dans le répertoire??
Non c’est bon les .h sont bien intégré maintenant, mais il replante sur doMainMenuAction… je vais tenter de revenir à la 1.5.7
Bonjour, avec la 1.57 ça marche. S’il y a des balaises parmis vous merci de faire le necessaire pour tansmettre l’info.
Toute la branche 1.6.x de l’ide Arduino est buggé jusqu’à la moelle. Pour mes tuto arduino en cours de rédaction, j’ai dû rester en version 1.5.x. Impossible de faire marcher correctement la nouvelle version …
Salut,
Bon, je code depuis deux mois (mesures dht22, liquide cristal, (sorties relais et sms par sim900) et le uno en pète ! J ai taillé dans la masse, mais cela ne suffit pas. Avant de virer le lcd, j ai tenté de passer en progmem mais ça m… On fait const char* const PROGMEM []={string1, string2,….} ?ou pas?
PS merci pour ton code pour dht.
Génial ce tuto, exactement ce qu’il me fallais… +1 pour BROLENSKY, le soucis vient de la version de l’IDE arduino… n’hesitez pas a revenir aux séries 1.5.X!
Bonjour, dans le cadre de mon projet je dois faire un menu de configuration comparable au vôtre mais j’ai un petit problème je ne comprends pas comment créer ma propre bibliothèque … une petite (ou grande..) aide ne serait pas de refus 🙂
j’imagine que tu es en terminale, je me trompe ?
dans tout les cas, pour le créer, il suffit de copier ce code dans ton logiciel arduino, une fois enregistré, il suffit de renommer le nom de ton fichier en enlevant le .ino et en le remplaçant en .h
une fois fait tu place ton fichier Menu.h dans un dossier nommé Menu_h
et tu copie/colle ce fichier dans la librairie de ton logiciel en suivant ce chemin là normalement C:\Program Files (x86)\Arduino\libraries
:
#ifndef _MENU_H_
#define _MENU_H_
/* Structure d’un menu */
typedef struct {
const char* prompt; // Titre du menu
const char** items; // Tableau de choix du menu
const uint8_t nbItems; // Nombre de choix possibles
void (*callbackFnct)(uint8_t menuItemSelected); // Pointeur sur fonction pour gérer le choix de l’utilisateur
} Menu_t;
/* Listes des touches de la shield lcd DFrobots */
typedef enum {
BP_NONE, // Aucun bouton appuyé
BP_SELECT, // Bouton SELECT
BP_LEFT, // Bouton gauche
BP_RIGTH, // Bouton droite
BP_UP, // Bouton haut
BP_DOWN // Bouton bas
} Button_t;
#endif /* _MENU_H_ */
Bonjour j’ai essayé de compiler votre code mais il me retourne ca comme erreur :
Ecran_de_selection:20: error: ‘doMainMenuAction’ was not declared in this scope
&doMainMenuAction
^
Ecran_de_selection:33: error: ‘doFreemanMenuAction’ was not declared in this scope
&doFreemanMenuAction
^
exit status 1
‘doMainMenuAction’ was not declared in this scope
Un petit peu d’aide ? 🙂
bonjour à tous et à toutes,
tout d’abord un grand merci à notre yoda numérique j’ai nommé Sky(non pas walker)Wood.
@LEJEUNE SIMON :
as-tu régler ton problème de compilation ? si oui j’aimerais savoir comment s’il te plait ? car mon IDE (Xcode)
me renvoie la meme issue et j’ai essayé de declarer : void doMainMenuAction(); mais ca n’as pas l’air de lui plaire !
merci par avance à tous ceux qui prendront quelques seconde précieuse pour répondre 🙂
Bonjour,
Je ne suis pas (encore) guru sur la compilation Arduino, mais, étant tombé sur le même problème, je l’ai résolu comme je le faisais en langage C à l’époque de ma jeunesse. J’ai ajouté les prototypes de fonctions en haut du fichier INO :
#include
#include « menu.h »
void doMainMenuAction(byte selectedMenuItem);
void doFreemanMenuAction(byte selectedMenuItem);
void doSubMainMenuAction(byte selectedMenuItem);
Cela a résolu le problème de compilation hez moi
Bonjour.
comment implanter une scrutation par un appui bouton select par exemple, pour quitter une des fonction du menu item une fois sélectionner, pour eu retour dans le choix des menu item
Salut,
il faut appeller un sous menu ou fonction dans lequel on defini Continuer =true
Puis faire un do while (Continuer).
Enfin a l’interieur de cette boucle on met Continuer = false si readpuschbutton==BP_Select
Pour parfaire il faiudrait pas entrer dans la boucle tant qu’un appuye bouton est en cours, donc ajouter
while( readpushbutton() ! = BP_NONE)
, juste à l’entré de ce sous menu/fcontion
A++
Merci pour ces explications, mais J’ai un peu de mal à suivre.
(Continuer =true ou continue = true )
Continuer est une variable booléan que tu définie en début de routine
Bonsoir
Je viens de découvrir votre code. Un grand merci, c’est exactement ce que je cherchais.
Bonne continuation et merci pour le partage.
Bonjour,
Comme beaucoup de commentaires, je découvre ce code qui va certainement me servir énormément !!!
Donc, un grand merci d’avance !
Ceci étant dit, une chose non explorée qui devrait m’être très utile est de pouvoir ajouter ou supprimer des entrées dans les sous menus par l’utilisateur… Est-ce possible ?
Merci d’avance.
Bonjour,
tout d’abord merci pour ce partage,je débute dans la programation,ce menu est exactement ce que je cherche ,j’ai essayer de faire quelque modification mais mais bouton ne fonctionne pas,j’ai un lcd simple,j’ai juste rajouter 4 boutons poussoir que j’ai mis en analogique,je ne sais pas si c,est bon
/* Inclut la librairie LiquidCrystal pour le LCD */
#include
#include « Menu.h » // Fichier d’entête avec les types pour le menu
void doMainMenuAction(byte selectedMenuItem);
void doForetMenuAction(byte selectedMenuItem);
void doSubMainMenuAction(byte selectedMenuItem);
/* Objet LCD sur les broches utilisées par la shield LCD DFrobots */
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
/* Menu principal */
static const char* MAIN_MENU_ITEMS[] = {
« taille foret »,
« manuel »,
};
static const Menu_t MAIN_MENU = {
« Votre choix ? »,
MAIN_MENU_ITEMS,
2,
&doMainMenuAction
};
/* Sous menu pour Dr Freeman */
static const char* Foret_MENU_ITEMS[] = {
« Gordon Freeman »,
« Morgan Freeman »,
« Martin Freeman »
};
static const Menu_t Foret_MENU = {
« Votre choix ? »,
Foret_MENU_ITEMS,
3,
&doForetMenuAction
};
/** Setup */
void setup() {
/* Configuration du LCD */
lcd.begin(16, 2);
}
/** Programme principal */
void loop() {
/* Affiche le menu principal */
displayMenu(MAIN_MENU);
/* Démo pour montrer la sortie du menu */
lcd.clear();
lcd.print(F(« Menu ferme »));
delay(2000);
}
/** Fonction retournant le bouton appuyé (s’il y en a un). */
Button_t readPushButton(void) {
/* Lecture de l’entrée des boutons */
unsigned int bg = analogRead(A0);// bouton bas
unsigned int bd = analogRead(A1);// bouton haut
unsigned int select = analogRead(A2);
unsigned int retour = analogRead(A3);// bouton retour
/* Test suivant les fourchettes de valeurs */
if (bd) return BP_UP;
if (bg) return BP_DOWN;
if (select) return BP_SELECT;
if (retour) return BP_NONE;
/* Par défaut aucun bouton n’est appuyé */
}
/** Affiche le menu passé en argument */
void displayMenu(const Menu_t &menu) {
/* Variable pour le menu */
byte selectedMenuItem = 0; // Choix selectionné
byte shouldExitMenu = false; // Devient true quand l’utilisateur veut quitter le menu
Button_t buttonPressed; // Contient le bouton appuyé
/* Tant que l’utilisateur ne veut pas quitter pas le menu */
while(!shouldExitMenu) {
/* Affiche le menu */
lcd.clear();
lcd.print(menu.prompt);
lcd.setCursor(0, 1);
lcd.print(menu.items[selectedMenuItem]);
/* Attend le relâchement du bouton */
while(readPushButton() != BP_NONE);
/* Attend l’appui sur un bouton */
while((buttonPressed = readPushButton()) == BP_NONE);
/* Anti rebond pour le bouton */
delay(30);
/* Attend le relâchement du bouton */
while(readPushButton() != BP_NONE);
/* Gére l’appui sur le bouton */
switch(buttonPressed) {
case BP_UP: // Bouton haut = choix précédent
/* Si il existe un choix précédent */
if(selectedMenuItem > 0) {
/* Passe au choix précédent */
selectedMenuItem–;
}
break;
case BP_DOWN: // Bouton bas = choix suivant
/* Si il existe un choix suivant */
if(selectedMenuItem < (menu.nbItems – 1)) {
/* Passe au choix suivant */
selectedMenuItem++;
}
break;
case BP_LEFT: // Bouton gauche = sorti du menu
shouldExitMenu = true;
break;
case BP_SELECT: // Bouton droit ou SELECT = validation du choix
menu.callbackFnct(selectedMenuItem);
break;
}
}
}
/** Affiche le choix de l'utilisateur */
void doMainMenuAction(byte selectedMenuItem) {
/* Cas spécial pour Dr Freeman */
if(selectedMenuItem == 2) {
/* Affiche le sous-menu pour Dr Freeman */
displayMenu(Foret_MENU);
} else {
/* Affiche le choix de l'utilisateur */
displayChoice(selectedMenuItem, MAIN_MENU_ITEMS);
}
}
/** Affiche le choix de l'utilisateur */
void doForetMenuAction(byte selectedMenuItem) {
/* Affiche le choix de l'utilisateur */
displayChoice(selectedMenuItem, Foret_MENU_ITEMS);
}
/** Affiche le choix de l'utilisateur */
void displayChoice(byte selectedMenuItem, const char** items) {
/* Affiche le choix de l'utilisateur */
lcd.clear();
lcd.print(F("Z'avez choisi :"));
lcd.setCursor(0, 1);
lcd.print(items[selectedMenuItem]);
/* Attend l'appui sur le bouton gauche ou SELECT */
byte buttonPressed;
do {
buttonPressed = readPushButton();
}
while(buttonPressed != BP_SELECT);
}
Bonjour, Trop trop trop fort, merci beaucoup pour ce partage et ses explications plus que claire, je voulais gerer la température avec arduino mega, dht22, shield dfrobot avec boutons et relais 2 canaux (temp. et humiditer) mais je ne trouvais pas comment inserer un sous menu, je vais lire et relire merci encore grâce à vous je reprends espoir, tres bon week end.
Bonjour,
Alors je sais que ce post date d’il y a un bon moment mais je tente quand même ma chance ! J’ai essayé de reproduire ton code, étape par étape pour essayer de le comprendre au mieux. Lorsque j’essaie de lancer la vérification, j’ai ce message d’erreur qui apparaît : Arduino : 1.8.6 (Windows 10), Carte : « Arduino/Genuino Uno »
Menu_deroulant:19:14: error: ‘Menu_t’ does not name a type
static const Menu_t MAIN_MENU = {
^
Menu_deroulant:32:15: error: ‘FREEMAN_MENU’ does not name a type
static const FREEMAN_MENU = {
^
exit status 1
‘Menu_t’ does not name a type
Pourtant, je suis convaincu d’avoir repris ton code puisque j’ai réessayé plusieurs fois en repartant à zéro. Est-ce que tu pourrais m’aider ?
Bonne soirée !