Aiutate un povero niubbo

Salve.
Ho trovato Arduino ad una fiera dell'elettronica e da appassionato ho subito comprato lo starter kit.
Ho svolto tutti gli esercizi del libro allegato divertendomi e cercando di.imparare.
Il problema è nato quando ho provato a costruire qualcosa di testa mia.
Volevo creare una mia versione del timer del libro ma riscritto da zero così da poter sperimentare.

In breve ho impostato l'idea così:
Un tasto accende un led verde e il buzzer facendo un beep, il "dispositivo" è attivo in attesa di essere impostato.
Poi con due tasti si seleziona il tempo, attivando uno dei quattro timer a scatti di 15 minuti, ognuno con i relativi led rossi accesi.
Una volta impostato si preme il pulsante di prima per attivare il conto alla rovescia, mentre il led rosso lampeggia e il led rossi ai accendono di conseguenza agli scatti di 15 minuti e alla fine una suoneria segnala il termine del conteggio.

Magari per voi è molto semplice ma io mi sono scontrato con:
il problema del debounce, cercando di risolverlo sia hardware che software ma senza trovare una cera soluzione.
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.
Il delay che blocca tutto e il millis che non ho capito come gestire.
Il velocissimo ciclo di loop che mi crea cambi di variabili quasi imprevedibili.

Provengo da una formazione "in porta logiche e stati" e non riesco a trovare un metodo di ragionamento per programmare Arduino.

Non sei il primo ad incontrare queste difficoltà. Per il debounce puoi cercare l’argomento sul forum poiché trovi sia schemi elettrici che suggerimenti software. Per la gestione tasti io uso la libreria JC_Button.

Ovviamente “delay” non deve essere presente nello sketch e devi gestire gli stati tramite switch case realizzando ciò che viene conosciuto come “macchina a stati finiti” argomento ampiamente trattato nel forum.

Ti lascio il solito link: https://programmersqtcpp.blogspot.com/2022/03/applicazione-accesso-parcheggio.html

Troverai come usare millis e come funziona una macchina a stati finiti.

Ciao.

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.

Proprio barbaro! :slight_smile: Come principio, bisogna evitare di rallentare il ciclo.
Per risolvere brillantemente il problema basta mettere un condensatore da 100 o 220nF in parallelo al pulsante.

Io di solito metto sempre anche una resistenzadi basso valore (47 o 100 ohm) in serie al pulsante, ed il condensatore collegato direttamente al pin.

Di solito quando uso pulsantini piccoli e delicati o micro contatti reed, che non reggono piu di tanta corrente, anche se ormai li metto cosi per tutto.

Solo perche’ anche se il condensatore e’ di basso valore, si carica comunque con la pullup, e chiuderlo ripetutamente su un contattino che e’ pensato per pochi mA, alla lunga potrebbe rovinare il contatto … vero, non e’ indispensabile, specie con pulsanti o contatti ad alta portata, ma tanto male non fa :grin:

C'è anche la possibilità che la piccola scarica tenga puliti i contatti...
E' sempre bene, invece, mettere il condensatore in parallelo al pulsante, piuttosto che in parallelo alla resistenza di pull-up, per non mandare in giro impulsi di corrente che potrebbero azzerare per un istante l'alimentazione e bloccare tutto.

scusate per il ritardo nelle risposte.
io metto una resistenza di 100 k tra il pin e la massa, cosi ho imparato dal libro e ho anche notato che senza resistenza il segnale in ingresso sbarella.
ho provato a mettere il condensatore in parallelo, ad aggiungere una seconda variabile e a ritardare con delay ma senza risultato.
la soluzione che ho adottato, quella che mi ha dato finalmente un risultato accettabile e' la combinazione di un delay di 1/4 di secondo e una variabile.
vi posto il codice di questo semplice esperimento.

const int lv = 2;
const int lr = 3;
const int p1 = 4;

bool pstate = 0;

void setup() {
 pinMode(lv, OUTPUT);
 pinMode(lr, OUTPUT);
 pinMode(p1, INPUT);
}

void loop() {
  if(digitalRead(p1) == 1 & pstate == 0){
    digitalWrite(lr, 1);
    digitalWrite(lv, 0);
    pstate = 1;
    delay(250);
  }

  if(digitalRead(p1) == 1 & pstate == 1){
    digitalWrite(lr, 0);
    digitalWrite(lv, 1);
    pstate = 0;
    delay(250);
  }
}

P.S. ho messo 0 e 1 invece di LOW e HIGH perché l'ide mi diceva che HIGH non era una variabile valida per lo scope all'interno degli if

Lo schema per il debouncing hardware è uno dei seguenti (a seconda del caso):

... e FUNZIONA, senza ma e senza se ... al limite, aggiustare leggermente il valore del condensatore.

Guglielmo

Ho provato senza successo a riprodurre l'errore. Deve esserci qualcosa "di strano" nelle tue impostazioni o nel modo in cui scrivevi qualcosa.

Per il resto: un errore di logica, un errore di semantica, due dettagli di design. Vediamoli.

Logica: la condizione del secondo if dovrebbe controllare se la lettura attuale è tornata a 0, non se è ancora 1. Una volta corretto questo la logica di riconoscimento dei fronti è corretta.

Semantica: hai usato & invece che &&, cioè un'operazione aritmetica logica a livello di bit (bitwise) invece che l'operatore logico and && (che valuta verità/falsità dei termini).
Funziona lo stesso solo perché per questo specifico caso:

  1. entrambi i confronti generano solamente valori 0 o 1
  2. la precedenza dei confronti è maggiore di quella di &
  3. La mancanza della valutazione short-circuit dei termini garantita da && non causa alcun problema

Design: Se è vera la prima condizione, inutile cercare di valutare la seconda, porta via solo tempo di elaborazione e, come nel tuo caso, può produrre effetti collaterali (LED che lampeggiano tenendo premuto il pulsante). Invece di due if separati meglio if / else if.
Seconda cosa, in generale la lettura di in ingresso sarebbe meglio farla in un punto solo, prima delle altre operazioni, e usare il valore ottenuto salvato in una variabile. Questo evita la possibilità che durante un ciclo di elaborazione, in presenza di rapide variazioni dell'ingresso, in diversi punti del programma l'ingresso risulti avere stati differenti.
Una "giornata di lavoro" tipica per il micro è: leggere gli ingressi, poi elaborare, poi aggiornare le uscite (modello IPO).

Il pulluo interno va attivato?
Ho usato il primo schema ma senza condensatore.
Non ho poi 22nf, forse qualcosa che si avvicina...
Proverò. Grazie della dritta.

Proverò con queste correzioni.

Dipende dal tuo codice e se usi resistenze esterne di pull-up o pull-down.

Per il condensatore eventualmente cerca qualche cosa con capacità leggermete superiore.

Guglielmo

dopo vari tentativi, sono riuscito ad avere u risultato credo accettabile.

ho attivato il pullup interno e ho usato le resistenze ma non il condensatore.
non avevo a disposizione un condensatore di valore opportuno e ho dovuto applicare un delay che funziona solo a 250ms.
con valori più alti i led lampeggiano.

questo il codice, sicuramente migliorabile. non sono riuscito ad utilizzare una variabile per memorizzare il risultato del tasto.
accetto suggerimenti.

void setup() {
 pinMode(2, OUTPUT);
 pinMode(3, OUTPUT);
 pinMode(4, INPUT_PULLUP);
}

bool pstate = LOW;

void loop(){
    
  if(digitalRead(4)){
    pstate = !pstate;
    delay(250);
  }
  
  if(pstate){
    digitalWrite(2, HIGH);
    digitalWrite(3, LOW);
  }
    
  else{
    digitalWrite(2, LOW);
    digitalWrite(3, HIGH);
  }

}

È accettabile SE fa esattamente quello che volevi fargli fare.

Il collegamento del pulsante non è chiaro, serve schema, perché il pull-up interno potrebbe anche non andare bene. Il principio è:

  • una resistenza di pull (up o down, interna o esterna) serve a stabilizzare/imporre il livello logico che deve essere letto dall'ingresso quando un pulsante normalmente aperto non è premuto.
astate = digitalRead(4);

Solo che adesso la logica è completamente diversa. Nel primo codice cambiavi stato alla variabile pstate nel momento della pressione e nel momento del rilascio. Qui invece realizzi una logica relé passo-passo che commuta nel momento della pressione E continua a commutare ogni 250 ms finché il pulsante rimane premuto. È meglio chiarire che cosa vuoi che faccia il pulsante.

250ms di delay per un debounce sono un'enormità... almeno un ordine di grandezza più di quanto dovrebbe essere sufficiente.

Ci sono dei problemi di cablaggio, oppure hai dei pulsanti di cattiva qualità che non hanno un contatto stabile.

Mettiamola così, io voglio comprendere il meccanismo di base per fare il debounce di un tasto.
Chiedo degli esercizi di codice.
Per quanto riguarda il collegamento, Arduino ha un pullup interno?
Mi fare di poterlo attivare via codice.
È il caso di farlo comunque via hardware?

Il collegamento è: positivo diretto ad un PIN del pulsante, uscita del pulsante verso il PIN di Arduino e in parallelo resistenza di 10k Verso Massa.
Non dispogno al momento di condensatore be altre resistenze

La resistenza da 10k verso gnd consiste in una pull-down esterna quindi NON serve e NON si deve attivare la pull-up interna

La connessione con il tasto è corretta