il problema del debounce, cercando di risolverlo sia hardware che software
Il "debounce barbaro minimo" è mettere un piccolo delay di qualche 10 millisecondi all'inizio o alla fine del loop, in modo da rallentarne il ciclo. Piuttosto: non è che hai lasciato gli ingressi flottanti senza uno stato stabile? Senza vedere i collegamenti nulla si può dire.
Il problema del cambio velocissimo e imprevedibile di alcune variabili per gestire lo switch "di stato" e il continuo beep che nonostante fosse diverso per ogni cambio di stato suonava continuamente con un ritmo fastidioso.
Può essere che tu stia valutando "i livelli" dei segnali ad ogni giro di loop, piuttosto che le variazioni? In genere di un pulsante di selezione interessa gestire il momento in cui viene premuto, non eseguire qualcosa continuamente finché rimane premuto. Senza vedere il codice nulla si può dire.
Il delay che blocca tutto e il millis che non ho capito come gestire.
millis restituisce l'ora di sistema in millisecondi, salvi il valore nel momento da cui ti interessa misurare il tempo, e periodicamente calcoli quanto è trascorso da quel momento (ora attuale meno ora salvata).
Provengo da una formazione "in porta logiche e stati" e non riesco a trovare un metodo di ragionamento per programmare Arduino.
Molto del mondo porte logiche si può portare paro paro nel mondo software sotto forma di espressioni logiche. Anzi questo semplifica molto alcune parti di programma rispetto ad una soluzione completamente algoritmica.
Il metodo di ragionamento richiederà tempo, per ora è normale che sembri una montagna verticale. Più che "programmare Arduino" il discorso è "scrivere un procedimento in linguaggio C++" che realizzi una procedura, e come progettare/schematizzare bene quella procedura. È chiaro che la progettazione dell'idea, e la conoscenza del linguaggio di programmazione in cui poi codificarla, sono due cose che si sostengono a vicenda: conoscendo le peculiarità del linguaggio si progetta le logica tenendo conto di quelle peculiarità, e conoscendo la logica da realizzare si possono scegliere le caratteristiche più adatte del linguaggio.
In senso generale l'approccio "macchina a stati" o "programmazione a stati" è quello più proficuo: ogni giro di loop è una nuova giornata di lavoro, il programma deve leggere "gli appunti" lasciati il giorno prima per sapere dove era arrivato e continuare da lì. Appunti uguale variabili di stato. Continuare da lì uguale struttura decisionale if/else if/else (oppure equivalente costrutto switch) controllata dalle variabili di stato (ad ogni giro di loop ci si occupa solo dello stato corrente).
In ogni stato ci si domanda se stiamo riscontrando un certo evento (che in quella condizione di funzionamento ci interessa), e se si facciamo qualcosa (compreso eventualmente cambiare di stato).
Nella descrizione vedo almeno tre macro stati: riposo, impostazione, conteggio. Non hai specificato lo stato a cui tornare a fine conteggio.
Altra cosa, all'inizio è normale cercare di scrivere una logica in un unico intricatissimo monoblocco di codice formato da tantissime if annidate (difficilissimo da leggere, modificare o correggere).
Anche in informatica c'è il concetto di "sottosistema", parti funzionali più piccole con ingressi e uscite ben precisi, che si collegano alle successive; la separazione dei compiti riduce drammaticamente la complessità. In C++ per fare questo abbiamo le funzioni (e più avanti gli oggetti).
Userei un solo pulsante di selezione tempi: arrivato al massimo ritorna al minimo, tanto sono solo quattro step.