Creazione di un Menù funzionale su un display LCD 20x4

Buongiorno a tutti,
premetto che come vedrete nel programma ho utilizzato un display senza modulo IC2 visto che non ne disponevo uno.
vi pongo il mio problema, vorrei realizzare un menù "interattivo". Questo menù avrà come input 2 pulsanti, uno di selezione e uno di cambio modalità.
nel momento in cui viene premuto il tasto "p_mode" lui cambia la modalità passando dalla verde alla gialla e infine alla rossa, per ogni cambio è necessario premere il pulsante ogni volta.
quando si è fermi su una modalità premendo "p_select" accenderà un led e cambierà schermata, poi comincierà a farlo lampeggiare all'infinito (processo che verrà poi sostituito).
tutto questo attualmente funziona, ora la mia domanda è una volta entrato in questo loop infinito necessito di uscirne soltanto premendo il pulsante "p_mode", e tornare nel void principale. siccome il processo all'interno del loop sarà più complesso in futuro di un led che lampeggia, non posso uscirne normalmente terminando le istruzioni nel void secondario (in questo caso void green/yellow/red), quindi ho pensato di utilizzare un interrupt che funziona soltanto nel void secondario e che quando il pulsante "p_mode" viene premuto (dando "0") entri nel void reset che spenga i led, e che tramite un goto mi riporti nel void principale facendolo tornare alla pagina delle modalità.
soltanto che a quanto ho capito il goto funziona soltanto nel void in cui è chiamato.

ora sperando di avervi fornito una spiegazione comprensibile, cosa ne pensate? cosa potrei fare?
grazie in anticipo,
buon pomeriggio

#include <LiquidCrystal.h> 
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);  

#define p_mode 8
#define p_select 9
int mode=0;
int lv=12;
int lg=11;
int lr=10;
  
void setup(){  
  Serial.begin(9600); 
  pinMode(p_mode,INPUT);
  pinMode(p_select,INPUT);
  pinMode(lv,OUTPUT);
  pinMode(lg,OUTPUT);
  pinMode(lr,OUTPUT);
  attachInterrupt(0,reset,FALLING);
  lcd.begin(20, 4);  
  lcd.setCursor(8,0);
  lcd.print("Menu");
  lcd.setCursor(0,3);
  lcd.print("Select          Mode");
}  
  
void loop(){
  noInterrupts();
  header:
  Serial.println(mode);
  if(digitalRead(p_mode)==LOW){
    mode=mode+1;
    while(digitalRead(p_mode)==LOW){
      delayMicroseconds(1);
    }
    lcd.clear();
  }
  if(mode==1){
    verde();
  }
  else if(mode==2){
    giallo();
  }
  else if(mode==3){
    rosso();
  }
  else if(mode>=4){
    mode=1;
  }
}

void verde(){
  interrupts();
  lcd.setCursor(8,0);
  lcd.print("Menu");
  lcd.setCursor(0,1);
  lcd.print("Green Mode");
  if(digitalRead(p_select)==LOW){
    digitalWrite(lv,HIGH);
    digitalWrite(lg,LOW);
    digitalWrite(lr,LOW);
    lcd.clear();
    lcd.setCursor(5,4);
    lcd.print("GREEN MODE");
    lcd.setCursor(16,3);
    lcd.print("Home");
    int led1=0;
    while(led1==0){
      digitalWrite(lv,LOW);
      digitalWrite(lv,HIGH);
    }  
  }
  noInterrupts();
}

void giallo(){
  interrupts();
  lcd.setCursor(8,0);
  lcd.print("Menu"); 
  lcd.setCursor(4,4);
  lcd.print("Yellow  Mode");
  if(digitalRead(p_select)==LOW){
    digitalWrite(lv,LOW);
    digitalWrite(lg,HIGH);
    digitalWrite(lr,LOW); 
    lcd.clear();
    lcd.setCursor(4,1);
    lcd.print("YELLOW  MODE");
    lcd.setCursor(16,3);
    lcd.print("Home");
    int led2=0;
    while(led2==0){
      digitalWrite(lg,LOW);
      digitalWrite(lg,HIGH);
    } 
  }
  noInterrupts();
}

void rosso(){
  interrupts();
  lcd.setCursor(8,0);
  lcd.print("Menu"); 
  lcd.setCursor(6,4);
  lcd.print("Red Mode");
  if(digitalRead(p_select)==LOW){
    digitalWrite(lv,LOW);
    digitalWrite(lg,LOW);
    digitalWrite(lr,HIGH);
    lcd.clear();
    lcd.setCursor(6,4);
    lcd.print("RED MODE");
    lcd.setCursor(16,3);
    lcd.print("Home");
    int led3=0;
    while(led3==0){
      digitalWrite(lr,LOW);
      digitalWrite(lr,HIGH);
    }   
  }
  noInterrupts();
}

void reset(){
  digitalWrite(lv,LOW);
  digitalWrite(lg,LOW);
  digitalWrite(lr,LOW);
  lcd.clear();
  lcd.setCursor(8,0);
  lcd.print("Menu");
  lcd.setCursor(0,3);
  lcd.print("Select          Mode");
  goto header;
}

Tralasciando tutto quanto c'e' da dire sull'uso del GOTO, etc etc
Tu hai impostato il pin 2 (attachInterrupt( 0.... ) corrisponde al pin 2 ) e il pulsante è collegato sul pin 8

Si ma son collegati insieme tramite un ponte, un po', inutile ora che ci penso in effetti...

Se ti può essere utile, tempo fa ho realizzato un menù simile al tuo su LCD 20x4 (sta ancora funzionando) utilizzando solo switch - case, senza interrupts e goto.
All' epoca avevo preso spunto da un esempio di pubblico dominio.
Domattina lo cerco e ti posto il link, se ti serve.

Si mi sarebbe sicuramente utile!
Grazie mille

Il video esplicativo lo trovi QUI

Il codice di esempio è QUI

No purtroppo non vanno bene perchè io ho bisogno che quando premo il pulsante accada subito qualcosa, e siccome quello che sarà all'interno del menù sarà corposo, non avrei l'immediatezza che cerco, come potrei avere nel caso dell'interrupt

Credo che tu abbia le idee un pochino confuse su cosa sia un interrupt, quando va realmente usato, e quelli che sono i tempi di reazione umana e quelli di un micro.
Un essere umano, nella migliore delle ipotesi riesce a reagire ad uno stimolo dopo un decimo di secondo, ovvero ti rendi conto di un ritardo solo se quando premi un pulsante e succede qualcosa passa di più di un decimo di secondo.
In un decimo di secondo un Atmega 328P riesce ad eseguire diverse centinaia di migliaia di istruzioni assembly, ovvero dal punto di vista di un micro un essere umano appare immobile mentre lui ti "corre" attorno.
Non esiste che per un menù su un display si usano gli interrupt, vanno bene pure le "lentissime" digital read dentro la loop, con relativa cascata di if o switch, per gestire gli eventi in tempo reale con latenza zero dal punto di vista di un essere umano.

So a cosa servono e come funzionano gli interrupt, purtroppo però gli ho studiati soltanto per quanto riguarda il loro funzionamento in assembler, specifico su programmazione per lo Z80 e quindi non so come applicarli in quanto a stesura del software sull'ide di arduino.
Siccome nel programma finito (che sto ancora elaborando) ci saranno la gestione in lettura e scrittura di una decina di digitalPin e 2 analogPin, inoltre dovrò comandare dei motori, un modulo bluetooth e un modulo gsm. Siccome già la rapidità di tutta l'esecuzione di questo void non è proprio elevata, cercavo un comando che una volta premuto questo pulsante mi garantisse di fermare il tutto, tornando al menù principale, magari anche con qualche secondo di ritardo.
Se questo non avvenisse significherebbe che dovrei tenere il pulsante premuto fino al termine delle operazioni che sta eseguendo, che nel migliore dei casi sono pochi millisecondi, se non di meno; ma nel peggiore dei casi, si potrebbe parlare di 2, esagero, 3-4 secondi di ritardo, e siccome questo prototipo si muove abbastanza rapidamente, vedrei un po' scomodo dovergli correre dietro per qualche secondo per disattivarlo.

Gli interrutp sono funzionalità hardware e non sono dipendenti dal linguaggio usato, ovvero funzionano allo stesso identico modo sia se lavori in assembly che in C.
Un interrupt non fa altro che eseguire un salto incondizionato ad una ben precisa locazione di ram dove è contenuta la jump table degli interrupt, in questa locazione si trova l'address fisico della ISR (Interrupt Service Routine) che si occupa della gestione dell'evento, vale per tutti i micro di questo mondo.
Per quanto riguarda la gestione dei pulsanti sono eventi talmente lenti, dal punto di vista del micro, che non ha alcun senso gestirli con interrupt su un pin, si gestiscono con un apposito polling contenuto all'interno della loop, parlando di come si scrive il codice per Arduino, o in una apposita funzione richiamata ad ogni ciclo della loop.
Va da se che è indispensabile rispettare la prima regola del bravo programmatore, il ciclo main, o la loop di Arduino, deve essere eseguito il massimo numero possibile di volte ogni secondo, quanto dipende dalla reattività richiesta del sistema, questo comporta il rispetto della seconda regola, le funzioni richiamate dalla main()/loop() non devono mai essere bloccanti e devono durare il minimo tempo possibile.
Quanto sopra vuol dire che usare istruzioni come la delay(), o cicli di attesa senza un timeout o una condizione di uscita in caso di errore, è un pessimo modo per scrivere il software. :slight_smile:
In linea di massima, salvo casi più unici che rari, e il tuo non mi sembra rientrare in questa categoria, su Arduino, anche senza ricorrere a tecniche di programmazione avanzate, è sempre possibile scrivere il codice in modo che la loop esegua almeno 10 iterazioni al secondo, più che sufficienti per garantire la reattività ad un evento pressione pulsante.
Dai un'occhiata qui, è un modo semplice, quasi zero risorse impegnate, per eseguire vari compiti ad intervalli predeterminati sfruttando il watchdog come timer per lo scheduler, dato che il tutto è gestito tramite interrupt per ottenere temporizzazioni precise l'esecuzione di una eventuale funzione per il polling dei pulsanti, a intervalli regolari, è garantita qualunque cosa stia facendo il micro.

Intendevo che non so che comandi software usare perchè non so bene il linguaggio di arduino, infatti i comandi che ho usato non credo siano giusti perchè non funziona :sweat_smile: comunque grazie.
Per quanto riguarda questo qRTOS di cui parli nell'altro topic, faccio fatica a capirlo, trovo complicato già solo leggere lo sketch nel primo post, non sono decisamente a quei livelli, se hai tempo di provare a rispegarla te ne sarei grato, comunque io nel mentre continuo a leggere l'altro topic.
grazie in ogni caso

Per fare un esempio, io ho questo programma

while(header==0){
    digitalWrite(lg,LOW);
    delay(500);
    digitalWrite(lg,HIGH);
    delay(500);
    if(digitalRead(p_mode)==LOW){
      header=1;
    }
}

in questo caso quando premo il pulsante, se io lo premo durante un delay o un'altra istruzione, e non lo premo quando me lo legge, è come se non lo avessi premuto. la mia domanda è, come posso rendere immediata questa operazione?

Risposta 1, di tipo generale

Evitando le delay e schedulando le varie operazioni

Risposta 2, per casi particolari
Se la via 1 non va bene, interrupt

Risposta 3, se nemmeno questo basta
Un bel fungo rosso

beh potrei optare per un bel fungo rosso... ahah :slight_smile:
A parte le battute, come potrei schedulare tutto questo? non so cosa intendi

alessandro-arduino:
durante un delay

La delay() non devi proprio usarla, è bloccante.

e quindi serve quella famosa "millis()" ?

Esatto

ok quindi, giusto per capire, mi riuscireste a scrivere come fare nel programmino che ho mandato prima dei led, così ho un esempio da seguire, ve ne sarei grato.
grazie

Prova a pensare qualcosa di più semplice, magari non so se nel tuo caso specifico possa rivelarsi utile, ma un ciclo loop che controlla periodicamente i pin in ingresso dei tastini e che ne verifica lo stato, se questi vengono premuti entri dentro un'altro ciclo che ti cambia lo stato dei led o ti cambia la pagina, e poi ritorna al loop di controllo.

PS il goto non usarlo, evitalo in tutti i modi, c'è sempre il modo.