[résolu]Pointeurs sur structure, tableaux, Pointeurs de fonctions

Bonjour,

Comme beaucoup d’ utilisateurs récents, jusqu’à maintenant j’ai peu utilisé les pointeurs.

Récemment, un petit programme qui devait beaucoup grandir m’a interpellé sur l’optimisation de l’encombrement mémoire et l’exécution du code.

Je propose de soumettre à votre expertise mes digressions sur le sujet avec un petit exemple.

Pour la 1ere partie,(A) comme j’avais un type structure dont je que souhaitais permuter des éléments ou des champs ; j’avais deux solutions :

  • 1 Recopier une structure dans l’autre
  • 2 Poser un pointeur sur les deux structures et permuter les pointeurs .
//
enum numeros {Premiere,Deuxieme,Troisieme};
struct Entree
{ byte numero;
  char nom[6];
  byte numerovar1;
  byte numerovar2;
};
Entree Entree1={Premiere,"Entr1",0,0};
Entree Entree2={Deuxieme,"Entr2",0,0};
Entree Choix_entree=Entree1;
Entree *pEntree=&Entree1;

void setup()
 {  if( Choix_entree.numero<Entree2.numero)//version A
    Choix_entree=Entree2;

   if( pEntree->numero<Entree2.numero)//version B
      pEntree=&Entree2;
  
  }

 void loop()
  {  }

Pour moi :

  • la version A recopie une structure (Entrée1) dans la srtucture Choix_entree
  • la version B change le pointeur d’entrée pEntrée; Il pointe alors sur Entree2

Qu’en pensez- vous ?

Serge .D

Voila la suite :

char Phrase[17][17] ={"Me1 Me2 Me3 Me4 ","Mea-Meb-Mec-Med-"};
char Table[5]={"ABCD"};

void setup() 
	{ }

void loop()
	{ AFFICHER(Table,0);
          while();
	}

void AFFICHER(char *pTableau,byte posY)
  { lcd.setCursor(0,posY);
    lcd.print(pTableau[0]);lcd.print('-');lcd.print(pTableau);//ligne A
    lcd.setCursor(0,1);
    lcd.print(Phrase[1]);//ligne B
  }

Pour moi,
La ligne A affiche : A-ABCD
La ligne B affiche : Mea-Meb-Mec-Med-

Serge .D

C’est quoi la question? Bouger des pointeurs c’est effectivement toujours plus efficace que de recopier des structures de données (si vous avez plus de 2 octets dans la structure)

J-M-L:
C’est quoi la question? ....

Voila :

En fait, ma question n'est pas la pertinence de l'usage de pointeurs; mais une validation de la syntaxe employée.
Ce qui me gêne, c'est surtout la syntaxe utilisée; particulièrement à propos des tableaux. C'est pourquoi je ne suis pas toujours sur que mon écriture est correcte.

D'où ma demande :

A propos des tableaux, si j'ai bien compris :

  • char Table[5]={"ABCD"};
    Dans ce cas

  • Table[5] est un tableau de 5 éléments, les 4 caractères ABCD et le zéro terminal de la chaine.

  • Table est le pointeur correspondant

  • pTableau est un pointeur "container" qui permet de passer le paramètre à la fonction AFFICHER

Si on écrit : lcd.print(pTableau[0]); Alors on affiche le premier élément du Tableau Table[5]

Si on écrit : lcd.print(pTableau); Alors on devrait afficher pTableau c'est à dire Table qui est un pointeur; pour moi j'aurais attendu un affichage de l'entier qui est l'adresse de ce pointeurTable~~[5]~~

Je ne sais pas si ma question est pertinente car je ne suis pas encore très familier de ce genre d'écriture.

Merci par avance de m'éclairer.

Serge .D

Bonjour,

Dans ton exemple pTableau est un pointeur de type char *
char * pTableau=Table

C'est comme ça que la fonction print sait quelle doit afficher une chaine et non la valeur de pTableau. Quand on lui passe un char *, la fonction print affiche la chaine pointée par l'argument

Grand merci à tous pour les précisions d’une syntaxe qui ne m’était pas familière.

J’en ferai bon usage, je pense.

Serge .D

Voila, je viens de faire usage de ce que je viens de consolider grâce à vous :

Il s’agit d’un petit menu de sélection avec 6 choix possibles, ce qui permet d’orienter vers 6 fonctions possibles.

Les rubriques à sélectionner sont provisoirement repérées :
Men1Men2Men3*
ou bien
Mena-Menb-Menc-

Avec un afficheur LCD basique contenant des lignes avec au moins 16 caractères

4 boutons poussoirs pour la navigation. (Commutation de résistances sur une entrée analogique)
la rubrique sélectionnée clignote.

Dans la première fenêtre de code quelques déclarations le programme principal et la fonction selectionner.
Dans la deuxième quelques fonctions.

enum Boutons{BpSuite,BpChange,BpHorz,BpVert};
int ValeursBp[BpVert+1]={140,340,490,730};byte Intervalle = 100;

enum Selection{Choix1,Choix2,Choix3};
char Phrase[16][16] ={"Men1*Men2*Men3*","Mena-Menb-Menc-"};
byte Select_Menu0=Choix1;byte Hauteur=0;

void loop()
{ Select_Menu0 = SELECTIONNER(Select_Menu0,Phrase[0],3);
  delay(200);lcd.clear();
  EXECUTER();
}

byte SELECTIONNER(byte numero_Choix,char *pTableau,byte MaxChoix)
  { byte i=0; 
    while (!TOUCHE(BpSuite))
       {lcd.setCursor(0,0);lcd.print(pTableau+16*Hauteur);     
        if (TOUCHE(BpVert))
           {BUZZER(50);
            if (Hauteur<1)Hauteur++; else Hauteur=0; 
            delay(200);
           }
        if (TOUCHE(BpHorz))
           {BUZZER(10);if (numero_Choix<(MaxChoix-1)) numero_Choix++; else numero_Choix=Choix1;
            delay(200);
           }    
        if (i%2==0)// clignotement de la sélection une fois sur 2
           {lcd.setCursor(numero_Choix*5,0);
            delay(100);lcd.print("     ");delay(80);
           }
        i++;   
       }      
    return numero_Choix;  
  }
boolean TOUCHE(byte Bouton)
   {int Tension;
    Tension=analogRead(ClavierPin);  
    return((Tension>ValeursBp[Bouton])&&(Tension<ValeursBp[Bouton]+Intervalle));
   }


void EXECUTER()
{ BUZZER(50);
  switch(Select_Menu0)
      { case Choix1 : 
          {if (Hauteur==0)ME1();
             else MEA();
           break;
          }
        case Choix2 : 
          {if (Hauteur==0)ME2();
             else MEB();
           break;
          }        
        case Choix3 :
         {if (Hauteur==0)ME3();
             else MEC();
           break;
         }
        default     :  break;     
      }
  BUZZER(50);delay(500);
}

void ME1()
  {
   lcd.print("ME1");
   while (!TOUCHE(BpSuite))
       {lcd.setCursor(0,1);lcd.print(0.01*analogRead(A1));lcd.print("V  ");
        delay(50);
       }
  }

Serge .D

Re bonjour,

J'ai profité des posts qui viennent d'être publiés sur ce forum pour modifier la fonction EXECUTER().

Auparavant selon les positions horizontales (Select_Menu0 variant de 0 à 2 et Hauteur variant de 0 à1 je faisait une sélection avec un switch(Select_Menu0) pour appeler la fonction adéquate.
Ce travail était fait dans la fonction EXECUTER().

Maintenant je déclare deux tableaux de pointeurs sur des fonctions, :

// libellés des menus :
char Phrase[16][16] ={"Men1*Men2*Men3*","Menua-Menb-Menc"};
// Déclarations des fonctions et pointeurs :
void ME1();void ME2();void ME3();
void MEA();void MEB();void MEC();
void (*pFonctionTab0[3])()={ME1,ME2,ME3};// tableau pour la ligne 0 de menu
void (*pFonctionTab1[3])()={MEA,MEB,MEC};//tableau pour la ligne 1 de menu

Et la fonction EXECUTER() est remplacée par la fonction P_EXECUTER()

// Appel de la fonction repérée par son pointeur
void P_EXECUTER()
  {if (Hauteur==0)
     pFonctionTab0[Select_Menu0]();
       else pFonctionTab1[Select_Menu0]();
   BUZZER(50);delay(500);
  }

Il me reste à éventuellement encore compacter cela en ne déclarant qu'un tableau de pointeurs de fonctions mais a deux dimensions, de ce fait je pense réduire P_EXECUTER() à une seule ligne

Ce code fonctionne pour un menu contenant deux lignes de 4 sous-menus; je viens de l'adapter pour publier sur le forum en deux lignes de 3 sous-menus de façon à ce qu'il soit cohérent avec les posts précédents.

Je considère cela juste comme un exercice me permettant de m'habituer à l'usage des pointeurs.

N'hésitez pas à signaler les éventuels erreurs, problèmes....

Serge .D

// libellés des menus :
char Phrase[[color=red]16[/color]][16] ={"Men1*Men2*Men3*","Menua-Menb-Menc"};

Si vous n'avez que 2 entrées ça fait bcp de mémoire gaspillée - où en prévoyez vous 16 pour plus tard?

A mon avis il serait plus simple de déclarer Phrase comme cela:const char * Phrase[] = {"Men1*Men2*Men3*","Menua-Menb-Menc"}; comme cela vous n'avez pas à vous soucier des tailles, (en supposant que le contenu ne change pas cependant)

N'hésitez pas à appuyer sur ctrl-T souvent dans l'IDE pour indenter votre texte - le code sera plus lisible

Merci J-M-L,

  • 1 Concernant l'occupation mémoire :

Je ne suis pas certain d'avoir tout bien compris :

// libellés des menus :
char Phrase[16][16] ={"Men1Men2Men3*","Menua-Menb-Menc"};

Si vous n'avez que 2 entrées ça fait bcp de mémoire gaspillée - où en prévoyez vous 16 pour plus tard?

Effectivement en déclarant un tableau de 2 chaines de 15 caractères on "gaspille" les deux caractères de fin de chaine par rapport à un tableau de 15 caractères .... mais cela facilite grandement le reste de l'écriture du programme, notamment pour l'affichage sur le LCD.

Mais je suppose que ce n'est pas cela, il s'agit peut-être de la déclaration comme variable qui mobiliserait la RAM précieuse et peu abondante par rapport à une déclaration en constante; donc gérable par le compilateur et implantable en mémoire programme. (Flash)

Bien sur, la simple modification par l'ajout de const devant ma déclaration ne fonctionne pas, le compilateur "jette", d'où votre proposition :

const char * Phrase = {"Men1Men2Men3*","Menua-Menb-Menc"};

Mais la j'hésite un peu pour l'accès à ce tableau dans le reste du programme.

  • 2 Pour l'indentation, je suppose que cela concerne mon habitude d'indentation du "if... else"

{if (Hauteur==0)
pFonctionTab0Select_Menu0;
else pFonctionTab1Select_Menu0;
... qui m'est confortable car j'en ai pris l'habitude mais gênante pour ceux qui le découvrent.

Donc à éviter si je dois publier ....

Serge .D

Effectivement en déclarant un tableau de 2 chaines de 15 caractères

Non non... mon point c’est que vous avez déclaré 16 tableaux de 16 caractères et n’en utilisez que 2... il y en a 14 qui sont alloués et ne servent à rien.

const char * Phrase = {"Men1Men2Men3*","Menua-Menb-Menc"};
Mais la j'hésite un peu pour l'accès à ce tableau dans le reste du programme.

??... Phrase[ 0 ] c’est la première chaîne et Phrase[ 1 ] c’est la deuxième chaîne - ou est la difficulté?

Pour l’indentation autant profiter de ce que ctrl-T fait pour vous, non?

Non non… mon point c’est que vous avez déclaré 16 tableaux de 16 caractères et n’en utilisez que 2… il y en a 14 qui sont alloués et ne servent à rien.

Ouiiiiiiii, mais c’est bien sur ! C’était sous mon grand nez et je ne voyait rien.

Tout va bien, merci beaucoup.

Il est vrai que j’ai une très mauvaise vue … sans doute aussi un privilège de l’âge! … d’où mes indentations exotiques !

Merci aussi à pepe pour son post#5 qui précise bien les choses.

Encore Merci.

Serge .D

:slight_smile:

Bonsoir,

Voila, grâce aux conseils avisés des uns et des autres je pense avoir bien avancé le code :
(Je suis passé à 2lignes de menus et 4 rubriques de menus par ligne)

  • La fonction EXECUTER() disparaît, elle est remplacée par une seule ligne dans loop
  • L’ensemble est encore plus paramétrable : on peut facilement changer le nombre de lignes de sélection de menus; on peut changer le nombre de rubriques sélectionnées; on peut changer la position d’affichage.
  • L’ensemble est bien plus flexible car la même fonction SELECTIONNER sera facilement réutilisable dans un même programme pour d’éventuels sous-menus

Le code me parait plus lisible et l’occupation mémoire est plus réduite.

.......
enum Selection{Choix1,Choix2,Choix3,Choix4};
//Tableau des libellés des menus
char Phrases[2][20] ={"Men1*Men2*Men3*Men4","Tram-Menb-Menc-Mend"};
byte Select_Menu0=Choix1;byte Hauteur=0;
//Fonctions associées à chaque rubrique de menu
void ME1();void ME2();void ME3();void ME4();
void MEA();void MEB();void MEC();void MED();
// Tableau des pointeurs associés
void (*pFonctionTab[2][4])()={{ME1,ME2,ME3,ME4},{MEA,MEB,MEC,MED}};
byte SELECTIONNER(byte N_Menus_ligne,byte y_menu,byte numero_Choix,char *pTableau,byte MaxChoix);

void setup() 
  {Init();}

void loop()
{ // Sélection de la rubrique de menu  Vert => Hauteur , Horizontal => Select_Menu0
  Select_Menu0 = SELECTIONNER(2,3,Select_Menu0,Phrases[0],4);
  delay(200);lcd.clear();
  // executer la fonction désignée par le tableau de pointeurs de fonctions
  pFonctionTab[Hauteur][Select_Menu0]();
  BUZZER(50);delay(500);
}

byte SELECTIONNER(byte N_Menus_ligne,byte y_menu,byte numero_Choix,char* pTableau,byte MaxChoix)
  { byte i=0; 
    while (!TOUCHE(BpSuite))
       {lcd.setCursor(0,y_menu);lcd.print(pTableau+20*Hauteur);     
        if (TOUCHE(BpVert))
           {BUZZER(50);
            if (Hauteur<N_Menus_ligne-1) Hauteur++; else Hauteur=0; 
            delay(200);
           }
        if (TOUCHE(BpHorz))
           {BUZZER(10);if (numero_Choix<(MaxChoix-1)) numero_Choix++; else numero_Choix=Choix1;
            delay(200);
           }    
        if (i%2==0)// clignotement de la sélection une fois sur 2
           {lcd.setCursor(numero_Choix*(9-MaxChoix),y_menu);
            delay(100);lcd.print("     ");delay(80);
           }
        i++;  
       }      
    return numero_Choix;  
  }

Bonne soirée.

Serge .D

Vous partez vraiment sur l'idée que vos menus font tous exactement 20 caractères et ont 4 sous sections de 5 caractères. Toutes vos formules rendent cela très statique. (ou le code, par exemple le lcd.print("    "); ne fera clignoter que 5 caractères, toujours).

Quand je lis ça
lcd.print(pTableau + 20 * Hauteur); en fait je me dis que ce que vous vouliez faire c'estlcd.print(pTableau[Hauteur]); n'est pas?

Pour cela vous pouvez prendre ma définition avec des const char * (pas obligé)

char Phrases[][20] = {
   "Men1*Men2*Men3*Men4",  // en notant comme cela on peut mettre des commentaires
    "Tram-Menb-Menc-Mend"  // à côté des menus pour expliquer à quoi ils servent
};

// on peut même calculer combien on a de menus 
const byte nbMenus = sizeof(Phrases) / sizeof(Phrases[0]);

La signature de la fonction devient alorsbyte SELECTIONNER(byte N_Menus_ligne, byte y_menu, byte numero_Choix, const char *pTableau[], byte MaxChoix);
et vous l'appelez avec   Select_Menu0 = SELECTIONNER(2, 3, Select_Menu0, Phrases, 4);

  return numero_Choix; c'est pas très propre au niveau programmation de modifier un des paramètres pour s'en servir comme variable locale. Autant le passer par référence, comme valeur par défaut et le code le modifie. Comme ça vous pouvez utiliser le return pour passer une autre information, genre - l'utilisateur a annuler

Bonjour,

Comme je suis actuellement en déplacement ( mais quand même avec une petite carte arduino Uno pour des petites manips), je vais répondre par petits morceaux.

Merci beaucoup pour "l'assistance" qui va très exactement ou je suis en recherche.

Vous partez vraiment sur l'idée que vos menus font tous exactement 20 caractères et ont 4 sous sections de 5 caractères. Toutes vos formules rendent cela très statique. (ou le code, par exemple le lcd.print(" "); ne fera clignoter que 5 caractères, toujours).

Oui, c'est mon support actuel, pour l'instant limité par les afficheurs LCD à ma disposition (LCD 2 lignes de 16 ou 4 lignes de 20)
Donc ces menus peuvent se caler sur 3x5 ou 4x5

c'est pas très propre au niveau programmation de modifier un des paramètres pour s'en servir comme variable locale. Autant le passer par référence, comme valeur par défaut et le code le modifie. Comme ça vous pouvez utiliser le return pour passer une autre information, genre - l'utilisateur a annuler

Justement, mon arrivée (récente) au C s'est faite en venant (comme amateur) du Pascal (TurboPascal puis Delphi)

Le passage de paramètre aux procédures par valeur ou par référence était facile et bien protégé, en C j'ai eu des difficulté pour m'adapter (en particulier le passage par référence est, au début, moins limpide pour moi).... D'où les acrobaties ou je peux me perdre pour m'y repérer.
J'apprend lentement les méandres et la syntaxe que je crains car je la connais mal.

Au passage j'ai localisé l'origine de ma bourde sur les tableaux à deux dimensions : j'usqu'alors je n'avais utilisé que des tableaux à une dimension, j'ai pris un Tuto ou j'avais comme exemple :
TEXTES[5][5]={"AAAA","BBBB"}; :confused: :confused:

lcd.print(pTableau + 20 * Hauteur); => lcd.print(pTableau[Hauteur]);

Mais oui, cela c'est caractéristique de ma progression (lente) dans les arcanes de la compréhension de ces passages de paramètres.

Bon, merci pour tout, je vais revenir pour déblayer une par une les remarques.

Serge .D

Je tâcherai de retrouver un petit bout de code que j'avais écrit il y a quelques temps sur ce même sujet
l'approche était un peu différente mais avec le même objectif, mais je gérais les taille variables de chaîne de caractère parce que c'est quand meme plus cool d'avoir un peu de flexibilité

Tenez j’ai retrouvé le code - C’est assez commenté - ça devrait se lire sans trop de soucis.

L’idée de base c’est de définir une structure d’un élément de menu (un choix possible) qui contient un libellé, une fonction a appeler, et une valeur à passer en paramètre à la fonction lors de l’appel (ce qui permet d’avoir plusieurs menus appelant la même fonction mais ayant un comportement différent en fonction de la valeur reçue).

L’autre point important de cette approche c’est qu’elle était non bloquante, la loop() peut continuer à tourner en attendant que l’utilisateur choisisse quelque chose dans un menu. on peut donc continuer à faire d’autres choses (écouter le port série, faire clignoter des LEDs ou ce que vous voulez du moment que ce n’est pas trop lent sinon on ratera des appuis sur les boutons du menu)

Pour faire simple j’avais utilisé la librairie OneButton pour les boutons. On en a 3, un pour aller à droite, un pour aller à Gauche un pour valider. Ils seront en INPUT_PULLUP donc câblage Pin <–> bouton <–> GND

/* ************************************ */
/*       Gestion Menu sur un LCD        */
/* ************************************ */
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// adresse LDC 0x3F  20 caractères, 4 lignes
LiquidCrystal_I2C lcd(0x3F, 20, 4);

// les bouton pour la gestion du choix dans le menu

// On importe une librairie de gestion des boutons (vous pouvez bien sûr gérer cela vous même à la main)
#include <OneButton.h>    // https://github.com/mathertel/OneButton

const byte aDroitePin = 5; // bouton pour aller à droite dans les menus
OneButton aDroite_b(aDroitePin, true); // true pour le mettre en INPUT_PULLUP

const byte aGauchePin = 6; // bouton pour aller à gauche dans les menus
OneButton aGauche_b(aGauchePin, true);

const byte validePin = 7;// bouton pour valider une entrée dans les menus
OneButton valide_b(validePin, true);

//========================================

const char separateur = ' ';
const char vide = ' ';

enum etat_t : byte {normal, attenteMenu} etatMenu = normal;

struct menu_t {
  const char * etiquette;
  byte valeur;
  void (* fonction)(byte valeur);
};

struct {
  unsigned long chrono;
  menu_t *menu;
  byte nbChoix;
  byte xMenu;
  byte yMenu;
  byte choix;
  byte element;
} menusEnCours;


//========================================
// On pré-déclare les fonctions pour qu'elles 
// soient utilisables dans la définition des menus
//========================================
void fonctionMenuA1(byte valeur);
void fonctionMenuA2(byte valeur);
void fonctionMenuB(byte valeur);

//========================================
// On crée nos menus
//========================================
menu_t menusA[] = {
  {"Vide", 10, fonctionMenuA1},
  {"Transport ?", 30, fonctionMenuA2}
};
const byte nbElementsMenuA = sizeof(menusA) / sizeof( menusA[0]);

menu_t menusB[] = {
  {"Bus", 22, fonctionMenuB},
  {"Voiture", 33, fonctionMenuB},
  {"Avion", 77, fonctionMenuB}
};
const byte nbElementsMenuB = sizeof(menusB) / sizeof( menusB[0]);

//========================================
// On implémente les fonctions callback
//========================================
void fonctionMenuA1(byte valeur)
{
  Serial.print(F("Menu A1 - valeur = "));
  Serial.println(valeur);
}

void fonctionMenuA2(byte valeur)
{
  Serial.print(F("Menu A3 - valeur = "));
  Serial.println(valeur);
  afficherMenu(0, 1, nbElementsMenuB, 0, menusB);
}

void fonctionMenuB(byte valeur)
{
  Serial.print(F("Le prix du Transport est de = "));
  Serial.println(valeur);
  lcd.setCursor(0, 2);
  lcd.print(F("Prix = ")); lcd.print(valeur);  lcd.print(F(" euros"));
  delay(2000);
}



//========================================
// gestion de l'interaction dans le menu
//========================================
void afficherEnCours(boolean estVisible)
{
  // on cherche le début de la chaîne en cours
  byte x = menusEnCours.xMenu + menusEnCours.choix; // on démarre avec le nombre d'espaces
  for (byte i = 0; i < menusEnCours.choix; i++) x += strlen(menusEnCours.menu[i].etiquette); // on ajoute les longueurs

  lcd.setCursor(x, menusEnCours.yMenu);    // on s'y positionne

  if (estVisible) {
    for (byte i = 0; i < strlen(menusEnCours.menu[menusEnCours.choix].etiquette); i++)
      lcd.write(vide); // on met le bon nombre de case vide pour cacher cette étiquette de menu 
  } else {
    lcd.print(menusEnCours.menu[menusEnCours.choix].etiquette); // on affiche l'étiquette
  }
}

void clignote()
{
  static boolean estVisible = true;
  const unsigned long demiPeriode = 500;

  if (millis() - menusEnCours.chrono >= demiPeriode) {
    afficherEnCours(estVisible);
    estVisible = !estVisible;
    menusEnCours.chrono += demiPeriode;
  }
}


//========================================
// la fonction a appeler pour afficher
// un menu sur le LCD
// x et y sont la colonne et ligne du LCD
// nbChoix le nombre de choix dans le menu
// choixDefaut celui qui clignotte
// monMenu est un tableau de menus_t
//========================================

void afficherMenu(byte x, byte y, byte nbChoix, byte choixDefaut, menu_t monMenu[])
{
  lcd.setCursor(x, y);
  for (byte i = 0; i < nbChoix; i++) {
    lcd.print(monMenu[i].etiquette);
    if (i != nbChoix - 1) lcd.write(separateur);
  }

  menusEnCours.chrono = millis();
  menusEnCours.menu = monMenu;
  menusEnCours.nbChoix = nbChoix;
  menusEnCours.xMenu = x;
  menusEnCours.yMenu = y;
  if (choixDefaut < nbChoix) menusEnCours.choix = choixDefaut;
  else menusEnCours.choix = 0;
  menusEnCours.element = 0;

  etatMenu = attenteMenu;
}

//========================================
// la gestion des boutons du menu
//========================================
void aDroite()
{
  afficherEnCours(false);  // s'assurer que le texte n'est pas éteint
  if (menusEnCours.choix < menusEnCours.nbChoix - 1) menusEnCours.choix++;  // passer au suivant si possible
}

void aGauche()
{
  afficherEnCours(false);  // s'assurer que le texte n'est pas éteint
  if (menusEnCours.choix > 0) menusEnCours.choix--;  // passer au suivant si possible
}

void valider()
{
  afficherEnCours(false);  // s'assurer que le texte n'est pas éteint
  etatMenu = normal; // on quitte le mode menu

  // on déclenche la fonction call back en passant un paramètre
  menusEnCours.menu[menusEnCours.choix].fonction(menusEnCours.menu[menusEnCours.choix].valeur);
}

//========================================

void verifierBoutons()
{
  // on vérifie si un des boutons est appuyé, si oui ça déclenche automatiquement le call back du bouton
  aDroite_b.tick(); // appellera la fonction aDroite() si appui
  aGauche_b.tick(); // appellera la fonction aGauche() si appui
  valide_b.tick();  // appellera la fonction valider() si appui
}

//========================================

void setup() {
  Serial.begin(115200);
  lcd.begin();
  lcd.backlight();

  // on attache les fonctions appelées par les boutons en cas de click
  aDroite_b.attachClick(aDroite);
  aGauche_b.attachClick(aGauche);
  valide_b.attachClick(valider);

  lcd.clear();
  afficherMenu(0, 0, nbElementsMenuA, 0, menusA);
}

void loop() {

  // avec cette approche, si on est en mode sélection de menu la loop() écoute les boutons
  // mais ne bloque pas le programme globalement en attente du choix d'un bouton
  // on permet ainsi de traiter de manière asynchrone une validation de menu
  if (etatMenu == attenteMenu) {
    clignote();
    verifierBoutons(); // ceci déclenche l'appel des callback si un bouton est appuyé

    if (etatMenu == normal) {
      // si on arrive ici en étant sorti du mode menu c'est que l'utilsateur a validé son entrée
      // et n'a pas déclenché d'autre (sous) menu
      // la fonction finale associée au menu a été exécutée
      // la structure menusEnCours contient dans <.choix> la selection effectuée
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(F("Action accomplie"));
      delay(2000);
      // pour notre exemple on relance le menu
      lcd.clear();
      afficherMenu(0, 0, nbElementsMenuA, 0, menusA);
    }
  }

  // ici on peut faire autre-chose du moment que c'est rapide si on ne veut pas rater des clicks sur les boutons en mode menu

}

voilà en espérant que ça vous donne peut être des idées pour être plus souple!

Merci beaucoup pour le partage,

Je vais dès que possible (pas avant la semaine prochaine) m'atteler à décortiquer tout cela qui à l'air bien riche.
Je reniflerai aussi de près les indentations proposées :slight_smile:

(Je viens d'y jeter très un rapide coup-d'œil )

Au flair, je dirais que tout est déjà organisé en prévision de la mise en place de l'encapsulation dans un objet .... avec éventuellement des fonctions virtuelles pour envisager des héritages.
.... je me trompe ? :slight_smile:

Je profiterai aussi du modèle pour améliorer mon propre programme : Grâce aux conseils il s'est déjà beaucoup compacté et flexibilisé. Je souhaite obtenir un programme "moyennement flexible" mais très compact pour servir de socle aux petites applications que je développe à partir de cartes nanos et LCD 2x16.
Sur ces cartes, je transmettais par RS232 les paramètres a mon PC (Ou réside un programme Hôte anciennement développé en Delphi pour visualiser les résultats)
je suis aussi attaché à maintenant faire la transmission RS232 par Bluetooth. Du point de vue logiciel cela fonctionne bien, il me reste seulement à améliorer la partie électrique (répartition de masses et câblage soigné des alimentations DC) pour éviter les petites perturbations électriques qui dégradent légèrement la précision.
Mais cela, en principe je sais faire.

Serge .D

Good !