Problema interrupt, delay nel loop

Salve a tutti,
come specificato dal titolo, non riesco a capire perchè la funzione delay(2000); va a bloccarmi l’interrupt pur essendo nel void loop().
Mi spiego meglio.
Nell’esempio della guida ufficiale arduino, che spiega il funzionamento dell’interrupt tramite la pressione di un pulsante e l’accensione di un led, vado ad aggiungere l’istruzione delay(2000); nel void loop().
Non c’è un senso a tutto ciò ma solo una mia curiosità.
Ecco il codice:

const byte ledPin = 13;
const byte interruptPin = 3;
volatile byte state = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
  delay(2000);
}

void blink() {
  state = !state;
}

Ebbene l’istruzione delay non viene interrotta dall’interrupt, ma viene completata e solo dopo viene presa in considerazione l’interrupt. Questo si vede aumentando sensibilmente i secondi di ritardo.
In termini concreti testando il circuito per accendere il led devo premere “miliardi di volte” per intercettare secondo me la fine dell’istruzione delay.
Da qui la domanda: “L’istruzione delay non portava problemi solo se utilizzata nella chiamata ISR? perchè anche nel loop? e se si devo utilizzare l’istruzione mills() per risolvere?”

Mi scuso per la lunghezza del post, ma volevo essere sicuro di essermi spiegato :stuck_out_tongue:
e mi scuso anche per la domanda magari può risultare banale per alcuni parte di voi.
Per sentito dire questo forum è popolato da cervelloni tanto vale approfittarne mi son detto :slight_smile:

Vi ringrazio anticipatamente per la risposta,
Remake

Secondo me il problema non è quello che pensi tu. Essendo impostata come interrupt handler, blink() viene chiamata nonappena premi il pulsante, anche se il loop() sta eseguendo la delay().

Tuttavia il led cambierà stato solo quando la delay() è terminata, visto che solo allora il loop() verrà ripetuto e quindi la digitalWrite() potrà fare il suo lavoro.

Credo però che questo ti sia chiaro. Quello che credo ti sfugga è che gestire un pulsante reale con un interrupt è sconveniente, a causa del cosiddetto bouncing: ogni volta che lo premi, generi in realtà decine di pressioni e rilasci (per ognuno dei quali viene scatenato l'interrupt), dovuti al funzionamento meccanico del pulsante. Idem quando lo rilasci. Per cui quello che può succedere è che nel momento in cui termina il loop(), il bouncing è ancora in corso e il pulsante in qual momento è nello stesso stato in cui era prima, per cui il led non cambia stato.

Spero di essermi spiegato. @gbp01 ha un'immagine molto esplicativa in merito, speriamo la posti :).

Proprio per questo motivo è fortemente sconsigliato gestire i pulsanti in interrupt, e si raccomanda di farlo in polling. In questo modo si possono mettere in atto apposite tecniche di debouncing, per le quali ti rimando al relativo esempio nell'IDE.

Il motivo per cui hai l'impressione che la delay blocca gli interrupt è un tuo errore nel codice, se modifichi lo stato del pin led dentro la loop, per ovvi motivi, questo avviene solo quando la delay() termina, innescando un nuovo ciclo, e non quando premi il pulsante.
Cambia lo stato del pin led, oltre che della variabile di controllo, all'interno dell'interrupt e vedrai che funziona correttamente, al netto dei problemi di debouncing se non hai previsto la cosa a livello hardware.

SukkoPera:
Proprio per questo motivo è fortemente sconsigliato gestire i pulsanti in interrupt,

Direi proprio di no, anche se dipende molto dalle condizioni operative, puoi benissimo gestire i pulsanti da interrupt, non è assolutamente sconsigliato, anche perché i problemi legati ai rimbalzi li hai pure con il polling, però è necessario gestire, hardware e/o software, il debouncing sempre e comunque.
Esempio pratico, devi gestire dei fine corsa su un sistema in movimento, in questo caso è praticamente obbligatorio usare l'interrupt per gli switch, o barriere ottiche, in quanto il polling può introdurre ritardi eccessivi con conseguenti, possibili, danni meccanici.

Mi sono espresso male: diciamo che è decisamente più comodo fare il debouncing in hardware se si vuole gestire i pulsanti tramite interrupt.

Un finecorsa però è un esempio un po' particolare: appena lo rilevi "premuto" blocchi tutto, non è una cosa di cui devi poter gestire pressioni multiple consecutive.

Vi ringrazio per le risposte. ;D

Comunque Astrobeed è vero funziona il tutto, facendo come hai detto tu.

Non ho capito bene come.
Mi spiegheresti tecnicamente il perchè però?

A rigor di logica non era del tutto sbagliato il codice, o meglio quando il program counter(PC) arrivava all'istruzione digitalWrite(ledPin, state); la eseguiva (la variabile state è false considerando sia il primo ciclo) e poi andava avanti con il delay(2000); ricominciando il ciclo, se premevo il pulsante richiamavo l'interrupt andando all' ISR modificavo il valore della variabile state e continuavo il ciclo.

Non capisco come lavora il PC in questo caso?

Scusate la mia ignoranza :blush:

remake:
A rigor di logica non era del tutto sbagliato il codice,

La tua logica è fallace :slight_smile:
Dentro la loop() il codice viene eseguito una riga per volta, questo vuol dire che dopo aver eseguito "digitalWrite(ledPin, state);", aggiornando lo stato del pin, poi viene eseguita "delay(2000);" con la relativa attesa di due secondi, lo stato del pin non viene aggiornato per tutto questo tempo indipendentemente da cosa succede nell'interrupt, che viene regolarmente eseguito, ecco perché devi aggiornare il pin del led dentro la ISR.
Vale sempre la regola che la ISR deve essere il più veloce possibile, evitare qualunque attesa, massimo ammesso pochi us, e delegare le funzioni che richiedono tempo per essere eseguite al main loop tramite dei flag, che è quello che stai facendo col tuo codice, però se nel main loop metti una delay di 2 secondi questo comporta che tutte le operazioni in sospeso vengono eseguite ogni due secondi e non immediatamente.

Non capisco come lavora il PC in questo caso?

Tirare in ballo il registro PC in questo caso ti porta fuori strada, infatti la funzione delay non impiega un solo ciclo di clock per essere eseguita ma tanti, quanti non lo so. Supponi che la funzione "delay" è composta da 50 (numero a caso) istruzioni assembly, la quale ognuna richiede un ciclo di clock per essere eseguita, quindi possiamo dire che il contatore PC cambierà valore 50 volte mentre viene eseguita la funzione "delay".

Quando una richiesta di interrupt (IRQ) viene sollevata il valore di PC viene salvato, si esegue un salto (modificando il valore di PC) alla routine ISR e poco prima di terminare si scrive in PC il valore salvato in precedenza + 1.

Ora una IRQ può essere sollevata alla prima istruzione assemby appartenente alla funzione "delay", e chiaro che sono necessari 50 - 1 = 49 cicli per completare la funzione "delay"

Ora purtroppo la funzione delay è di tipo bloccante, più o meno la puoi immaginare come una istruzione assembly che richiede un numeri cicli cpu variabile in base al valore specificato come argomento. Con questa visione è naturale immaginare la cpu impegnata a svolgere un compito che dura tot secondi.

Per fortuna la funzione "delay" non è atomica e pertanto una IRQ la può interrompere, diversamente rendendola atomica diventa non interrompibile in assoluto.

Ciao.

La delay() è piuttosto semplice come codice ...

void delay(unsigned long ms)
{
	uint32_t start = micros();

	while (ms > 0) {
		yield();
		while ( ms > 0 && (micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

... in pratica altro non è che una while() che gira sino a quando un contatore non è arrivato a zero. Ovviamnete la MCU è impegnata a fare quello e NON fa altro sino a quando non esce da quel while(), da cui il concetto di bloccante :wink:

Guglielmo

Vi ringrazio tanto!
Siete stati illuminanti! :wink:

gpb01:
La delay() è piuttosto semplice come codice ...

void delay(unsigned long ms)

{
uint32_t start = micros();

while (ms > 0) {
	yield();
	while ( ms > 0 && (micros() - start) >= 1000) {
		ms--;
		start += 1000;
	}
}

}

questa e' la nuova versione di delay() quella di @vbextreme che ha risolto un bug presente dalla notte dei tempi in arduino :slight_smile:

@MauroTec, grande intervento :slight_smile:

Interessante... quale bug? Quella di prima come era?

dovresti trovare la versione precedente andando a ravanare nelle commit di github.
Non ho capito se c'e' un modo per vedere le commit su un singolo file

Azz... speravo lo avessi a portata di mano.
Nel week-end andrò a dare una ravanata per togliermi questa curiosità!

Ecco la VECCHIA delay() ... così ti togli la soddisfazione :smiley: :smiley: :smiley:

void delay(unsigned long ms)
{
	uint16_t start = (uint16_t)micros();

	while (ms > 0) {
		if (((uint16_t)micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

Guglielmo

Grazie! Curiosità soddisfatta!

il problema della vecchia delay era che dava per scontato che il ciclo durasse meno di un millisecondo, il codice postato è ancora più vecchio di quello in cui ho rilevato il bug.
In linea di massina se non si avevano interrupt da millisecondi non c’era problema, era piu facile incappare nel bug se si usava uno scheduler.