Commande LEDs par keypad

Bonjour

Je cherche à commander un bandeau de LEDs WS2812 ave un keypad 4x4 : l'idée est de programmer diverses configurations d'allumage des 60 LEDs du bandeau et de les choisir en appuyant sur les touches.

Pour cela, j'utilise les librairies Keypad et Adafruit_Neopixel. J'arrive à faire fonctionner chaque librairie indépendamment : le keypad me renvoie bien la touche sur laquelle j'appuie, et les LEDs font de jolies animations.

Vient le moment d'intégrer les deux. J'ai choisi d'utiliser un Switch - Case. Je 'Switch' la variable contenant le caractère correspondant à la touche appuyée et je lance l'animation associée avec le 'Case'.

Là, je me pose une question. J'aimerais que l'appui sur une touche pendant que les LEDs sont en train de jouer une animation arrête cette animation et lance celle qui est sélectionnée.

Je ne vois pas comment faire. La librairie Keypad propose bien une gestion d’événements via la commande KeypadEvent qui appelle une fonction, à la manière d'une interruption. Cette fonction ne ferait à mon sens que recevoir le caractère de la touche enfoncée.

https://playground.arduino.cc/KeypadTutorial/EventKeypad

Mais il faut qu'après avoir exécuté cette fonction, je revienne au début de mon Switch, pour lancer une nouvelle animation.

Est-ce possible ? Ou bien il y a une autre méthode ?
Merci de votre aide...

L'animation visuelle que vous utilisez est sans doute en un seul bloc, utilisant des délais, donc tant que l'animation n'est pas finie vous ne pouvez pas lire les touches....

Il faut re-écrire toutes les animations pour virer les délais et gérer l'animation comme l'exemple "blink without delay"

OK, je comprends qu'utiliser millis() permet de ne pas louper l'appui sur une touche.
Mais mon problème n'est pas là.

Supposons déjà que je n'aie plus de delay(). Si j'appuie sur une touche pendant une animation, la commande EventKeyPad va lancer une fonction (qui me permettra par exemple de savoir quelle touche a été pressée). Lorsque cette fonction sera terminée, le programme reprendra là où il était avant l'appui c'est à dire dans l'animation en cours. Exact ?

Or, je désire stopper cette animation immédiatement pour lancer celle qui a été sélectionnée. Comment faire ?

il faut découper l’animation en morceaux avec la technique de millis() et laisser la loop() tourner

j’avais donné un exemple de décomposition de code dans ce post (réponse 17)

Merci de vos réponses

@pepe : J'avais imaginé un truc comme ça
@J-M-L : J'ai lu ce post qui est pertinent pour ma question, en effet. Par contre, c'est assez complexe à mettre en oeuvre, j'imagine que je vais devoir reprendre mes animations pour les adapter...

Bon, j'ai du boulot alors...

Oui c'est du boulot, il faut décortiquer les animations pour gérer le timing différemment - mais ça vous donnera un temps de réponse quasi instantané à l'appui du bouton.

autre option - votre bouton fait un reset de l'arduino qui dans le setup vous donne un peu de temps pour choisir la bonne animation... moins propre...

oui mais dans ce cas il faut tester la valeur retourné par delay() à chaque appel pour voir si on doit interrompre l’animation en cours, ce n’est pas juste un remplacement direct, il faudra modifier un peu le code. Par exemple

void animation() {
   petit_bout_animation1();
   delay(5);
   petit_bout_animation2();
   delay(5);
   petit_bout_animation3();
}

pourra devenir

int mon_delay(const int32_t duree)
{
  uint32_t t0 = millis();
  while ( millis()-t0 < duree ) { // duree est la valeur du délai à réaliser
    int touche = litToucheClavier();
    if ( touche ... ) // ici les tests de sortie
      return touche; // valeur de la touche ayant provoqué la sortie, pour traitement par l'appelant
  }
  return 0;
}

int animation() {
   int touche;
    petit_bout_animation1();
    if (touche = mon_delay(5)) return touche; // on interrompt la séquence et retourne la touche appuyée
    petit_bout_animation2();
    if (touche = mon_delay(5)) return touche; // on interrompt la séquence et retourne la touche appuyée
    petit_bout_animation3();
    return -1; // fin sans avoir été interrompu
}

et il faut que le déclenchement de l’animation s’attende à relire une touche en retour. si l’animation n’a pas été interrompue alors on retourne -1 par exemple et l’appelant saura qu’aucune touche n’a été appuyée

(adapter le type de touche à ce que vous gérez, un char ou int si keypad est OK, ou un uint32_t par exemple si c’est un code de télécommande à IR)

Oui avec un #define vous pouvez cacher alors le return - par rapport à une fonction ça se fait au prix d'une augmentation de la taille du code si vous avez bcp de idelay() car duplication et c'est pas super explicite que idelay() peut interrompre la fonction en cours mais ça fait le job (pas sûr qu'il y ait besoin du ; dans le define)

oui ou au lieu d'appeler cela idelay() on pourrait appeler le #define attendreEtInterrompreSiActiviteClavier() :slight_smile:

Je suis impressionné par vos réponses, j'ai besoin de temps pour assimiler tout ça...

Mais je pense que vous avez laissé de côté deux aspects (ou c'est moi qui me trompe) :

  • Le clavier, c'est un keypad 4x4 avec la librairie associée
  • Les animations sont faites principalement avec des boucles (numéro de LED varie par exemple de 1 à 60), puis viennent un 'strip.show' et le 'delay'

Les animations sont assez fluides donc les arguments du delay sont assez faibles, typiquement inférieurs à 50.

Avant de re-concevoir le code en entier, j'avais une idée peut-être plus simple : insérer dans la boucle le test d'appui d'une touche avec un return si une touche a été enfoncée.

Je voyais donc le code comme ceci :

Dans le setup :
- les initialisations (notamment le traitement d'interruption de cas de touche enfoncée) et l'attente d'une première touche 

Dans la loop:
- Le switch pour lancer l'animation choisie avec un return si une touche a été enfoncée

Ensuite :
- Les fonctions des animations
- La fonction qui est appelée en cas d'appui sur une touche : elle ne fait que modifier la variable globale correspondant à la touche, et un booléen pour dire qu'une touche est appuyée

Est-ce que ça vous parait fonctionner ?

lesept:
Mais je pense que vous avez laissé de côté deux aspects (ou c'est moi qui me trompe) :

  • Le clavier, c'est un keypad 4x4 avec la librairie associée
  • Les animations sont faites principalement avec des boucles (numéro de LED varie par exemple de 1 à 60), puis viennent un 'strip.show' et le 'delay'

Les animations sont assez fluides donc les arguments du delay sont assez faibles, typiquement inférieurs à 50.

Non non on a bien compris :slight_smile:

la boucle qui calcule la valeur des LEDs tournera vite, le show sera aussi assez rapide et ensuite vous attendez.
50ms c'est une éternité pour votre micro-contrôleur et c'est inséré sans doute dans une autre boucle qui fait que le tout dure un moment

Le soucis de votre approche c'est que votre interruption (si vous parlez d'interruption hardware) va bien être prise en compte dans l'ISR vous allez noter la valeur de la touche appuyée, mais ensuite vous allez retourner dans votre code d'affichage (pas dans la loop()) et donc si vous ne testez pas en permanence dans ces boucles d'affichages si une touche a été enfoncée, votre code ne s'interrompra pas....