Il y a plusieurs façon d’aborder le problème.
1ere approche : méthode débutant
Puisque le test du boutton se fait dans la fonction appui(), si tu souhaites qu’un appui sur le bouton sorte de la boucle, il faut donc appeller appui() dans la boucle afin de retester et mettre à jour la variable bouton
Exemple (simplifié à une seule boucle et avec une indentation correcte):
if (bp1 == true) {
lcd.setCursor(1,1);
lcd.print("En cours");
for (byte i=10; i<15; i++) {
appui();
if (bouton == 2) break;
lcd.setCursor(i,1);
lcd.print("*");
delay(200);
}
}
L’inconvénient de cette approche c’est que si elle marche encore bien alors que ton code reste simple (tu ne gère que 2 choses en même temps : le bouton et le LCD), elle va devenir infernale dès que tu va vouloir ajouter plus de tâches.
Notamment, le fait d’utiliser une boucle avec un delay(200) monopolise le CPU dans cette boucle.
Imagine que maintenant il faille rajouter :
- gérer la liaison série,
- contrôler un moteur,
…
Il faut donc découper chaque tâche en modules élémentaires non bloquants qui vont pouvoir être appelés dans loop() sans empêcher loop() de tourner en continue pour passer la main successivement à chaque tache.
En gros, tu devrait pouvoir écrire :
loop()
{
gere_bouton();
gere_lcd();
}
Dans chaque tâche, tu ne dois pas rester bloqué. Tu rentre dans la fonction, tu regarde s’il y a quelque chose a faire. Si oui, tu le fais, si non tu ressort immédiatement. Jamais tu n’attends!
Il faut donc définir chaque tâche comme une machine d’états qui va passer d’un état à un autre en fonction d’évènements. Si l’évènement ne se produit pas, la fonction retourne pour passer la main à une autre tâche.
Par exemple, essayons de définir la tâche qui va gérer le bouton.
Je choisit le fonctionnement suivant :
La tache à 2 états :
- Aucun bouton n’a été pressé
- Un bouton à été pressé
Les évènements :
Ax) Un bouton x est pressé
B) Le bouton a été acquitté par une autre tache
Le déroulement:
- Si dans l’état 1 et évènement Ax alors je passe dans l’état 2, signalant à l’extérieur qu’un bouton est pressé
- Si dans l’état 2 et évènement Ax (même bouton), je ne change rien (le but est qu’un appui long ou plusieurs appuis sur le même bouton ne sont pas pris en compte plusieurs fois)
- Si dans l’état 2 et évènement Ay (autre bouton), je change la signalisation du bouton
Une tâche extérieure peut remettre l’état 1) pour confirmer qu’elle a pris en compte le bouton et qu’un nouvel appui sera un nouvel évènement.
On peut écrire comme cela :
enum { ATTENTE_BOUTON, BOUTON_APPUYE } etat_bouton = ATTENTE_BOUTON;
int bouton = 0;
void gere_bouton( void )
{
switch( etat_bouton )
{
case ATTENTE_BOUTON:
int b = appui(); // modier appui() pour retourner une valeur plutôt que modifier la var. globale
if ( b > 0 )
{
bouton = b;
etat_bouton = BOUTON_APPUYE;
}
break;
case BOUTON_APPUYE:
int b = appui(); // modier appui() pour retourner une valeur plutôt que modifier la var. globale
if ( b > 0 )
{
bouton = b;
}
break;
}
}
// fonction pour une autre tâche afin de savoir si un bouton a été pressé, si oui récupérer sa valeur et acquitter.
int lit_bouton( void )
{
int b;
switch( etat_bouton )
{
case ATTENTE_BOUTON:
// pas de bouton pressé depuis la dernière fois, je retourne 0
b = 0;
break;
case BOUTON_APPUYE:
// je prend la valeur du bouton pour la renvoyer
b = bouton;
// et je remet l'automate en attente de bouton
bouton = 0;
etat_bouton = ATTENTE_BOUTON;
break;
}
return b;
}
Dans la tache gere_xxxx() tu appelle lit_bouton() pour savoir si un bouton a été appellé depuis la dernière fois.
Ok, d’accord, c’est un marteau pilon pour écraser une mouche.
Mais c’est plus facile d’introduire le concept sur un exemple simple pour le comprendre.
En pratique même moi je n’écrirait pas quelque chose d’aussi compliqué pour cela.
Le code suivant est aussi efficace mais reste propre:
int bouton = 0;
void gere_bouton( void )
{
int b = appui(); // modier appui() pour retourner une valeur plutôt que modifier la var. globale
// Le test est important. Je ne remet pas a zéro bouton si un nouvel appui n'a pas eu lieu.
if ( b > 0 )
bouton = b;
}
// fonction pour une autre tâche afin de savoir si un bouton a été pressé, si oui récupérer sa valeur et acquitter.
int lit_bouton( void )
{
int b = bouton;
bouton = 0;
return b;
}
Maintenant vient le plus gros morceau : l’automate de gestion du LCD.
Prend un papier et un crayon, et essaye de découper la tâche du LCD en un certains nombre d’états.
Quels sont les évènements à considérer :
- bouton appuyé
- temps écoulé
Savoir qu’un bouton a été pressé, c’est la fonction lit_bouton().
Savoir qu’un temps a été écoulé.
Pour le temps, il faut bannir la fonction delay() sauf quand tu as des temps très court et critiques à gérer de manière atomique (atomique signifie qu’on n’a pas le droit de découper, sans interruption).
Je te propose le code suivant :
unsigned long timer_debut;
void timer_start()
{
timer_debut = millis();
}
bool timer_ecoule( unsigned long duree_ms )
{
if ( (millis() - timer_debut) > duree_ms )
return true;
return false;
}
Conseil : tu n’est pas forcement obligé de considérer des états distincts pour les différentes étapes de la boucle d’animation. Tu peut avoir un état ANIMATION_1 et un compteur. Peut être te faudra t’il 2 états pa animation … 
A ton crayon, relevé des copies dans quelques heures 