Archivi tag: interrupt

Arduino – Interrupts – lezione 2

Ormai sono passati diversi anni da quando scrissi il primo post sull’uso degli interrupt e durante gli anni a scuola puntualmente svolgo diversi esercizi, pertanto ho deciso di riprendere ed ampliare quanto detto nel precedente post e sicuramente più avanti ci saranno occasioni per aggiungere ulteriori esempi.

La maggior parte dei microcontrollori permette la gestione degli interrupt. Possiamo pensare all’intterrupt come a un sistema che ci permette di rispondere agli eventi “esterni” mentre facciamo qualcos’altro.

Riprendo quindi il post: Appunti su Arduino: interrupts da cui partire per comprendere cosa sono e come usare gli interrupts con Arduino UNO e con questa lezione voglio proporre alcuni esempi in modo che l’argomento spero possa essere più chiaro.

Per comprendere meglio cos’è un interrupt immaginate la seguente situazione:

tornate a casa dopo la scuola e desiderate cucinarvi un piatto di spaghetti. Fate bollire l’acqua ed inserite gli spaghetti in pentola fissando un tempo di 8 minuti, aspettate che gli spaghetti cucinino e poi mangiate. Possiamo paragonare questa azione all’esecuzione del codice nel loop() di Arduino.

In modo diverso potreste avviare un timer che scade ad 8 minuti e in questo intervallo di tempo potreste guardate il notiziario in TV. Quando il timer suona interromperà (interrupt) la vostra visione che vi ricorderà che dovrete porre attenzione alla pasta in pentola. Quindi verrà eseguito un interrupt (il suono del timer) che permetterà di eseguire il programma: “scola la pasta”.

In altro modo, riprendendo ciò che avevo scritto in passato:

Supponete di dover rilevare lo stato di alcuni sensori esterni. All’interno del loop() tutte le istruzioni sono eseguite in modo sequenziale e quindi anche la rilevazione del cambiamento di stato dei sensori collegati ad Arduino avviene in modo sequenziale. Supponete di dover eseguire il controllo della variazione di stato di 3 sensori, il vostro sketch eseguirà il controllo sul primo sensore, sul secondo e poi sul terzo.

Supponete che tutti i sensori si trovino al medesimo stato iniziale che chiameremo S1 e che il controllo di Arduino sia in un determinato istante sul secondo sensore, potrebbe capitare nello stesso istante una variazione repentina di stato sul primo sensore che passa da S1 a S2 e poi a S1, Arduino non si accorgerà di nulla perché la sua attenzione è sul secondo sensore; ecco che in questa situazione potrebbe essere utile l’utilizzo degli interrupt.

In ambito elettronico possiamo avere due tipi di interrupt:

  • Interrupt hardware: si verificano in risposta ad un evento esterno, come un pin che assume uno stato HIGH o LOW
  • Interrupt software: si verificano in risposta a un’istruzione software.

In questo post ci concentreremo sugli interrupt hardware.

Sulla scheda Arduino UNO, Nano, Mini e altre schede basate sul microcontrollore ATmega328 due sono i pin su cui realizzare un interrupt hardware:

pin digitale 2: interrupt 0
pin digitale 3: interrupt 1

Mentre per altre schede Arduino:

  • Uno WiFi Rev.2, Nano Every
    tutti i pin sono utilizzabili per l’interrupt
  • Mega, Mega2560, MegaADK
    2, 3, 18, 19, 20, 21
  • Micro, Leonardo e schede basate sull’ATmega32u4
    0, 1, 2, 3, 7
  • Zero
    tutti i pin eccetto il 4
  • Tutta la famiglia MKR
    0, 1, 4, 5, 6, 7, 8, 9, A1, A2
  • Nano 33 IoT
    2, 3, 9, 10, 11, 13, 15, A5, A7
  • Nano 33 BLE, Nano 33 BLE Sense
    tutti i pin
  • Due
    tutti i pin
  • 101
    tutti i pin e solo i pin 2, 5, 7, 8, 10, 11, 12, 13 lavorano in modalità CHANGE

Ma come funziona un interrupt?
In estrema sintesi quando si verifica l’interrupt (interruzione), il microcontrollore salva il suo stato di esecuzione attuale ed esegue una piccola porzione di codice che l’utente desidera venga eseguita in presenza di un interrupt, questa porzione di codice prende anche il nome di: interrupt handler o interrupt service routine (in italiano gestore di interrupt o routine di servizio interrupt).

Il programmatore quindi definisce il codice del gestore di interrupt che deve essere eseguito quando si verifica un particolare interrupt all’interno del programma stesso e per fare ciò in Arduino, come indicato nella mia precedente lezione, utilizziamo una funzione chiamata attachInterrupt():

attachInterrupt(digitalPinToInterrupt(PIN), ISR, modo);
  • digitalPinToInterrupt(PIN)
    è la funzione che consente di convertire il numero del pin su cui effettuare l’interrupt con il numero dell’interrupt quindi nel caso di Arduino UNO si avrà:
    digitalPinToInterrupt (2) > 0
    digitalPinToInterrupt (3) > 1
    PIN è quindi il pin abilitato all’interrupt, che indica al microcontrollore qual è il PIN da monitorare e come indicato nell’elenco sopra Il PIN dipende dal microcontrollore utilizzato.
  • ISR è la porzione di codice che deve essere eseguito se l’interrupt viene attivato
  • modo
    è il tipo di trigger che attiva l’interrupt che, come indicato nella precedente lezione, può essere di 4 tipi:

    • LOW l’interrupt viene eseguito quando il livello del segnale è basso
    • CHANGE l’interrupt viene eseguito quando avviene un cambiamento di stato sul pin
    • RISING l’interrupt viene eseguito quando si passa da un livello LOW ad un livello HIGH
    • FALLING l’interrupt viene eseguito quando si passa da un livello HIGH ad un livello LOW

Nota: all’interno della funzione utilizzata in attachInterrupt:

  • delay() non funziona;
  • il valore restituito dalla funzione millis() non verrà incrementato.
  • i dati seriali ricevuti durante l’esecuzione della funzione di interrupt possono essere sbagliati.
  • qualsiasi variabile modificabile all’interno della funzione attached (chiamata all’interno attachInterrupt) devono essere dichiarare come volatili.
  • le funzione non può avere parametri di ingresso

Esempio 01

Realizziamo uno sketch che al verificarsi di un interrupt sul pin 2 cambia lo stato in cui si trova il LED sulla scheda di Arduino

Circuito

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 01

*/

int pinLed = 13;
volatile int stato = LOW;
int pulsante = 2;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  pinMode(pinLed, OUTPUT);       // definiamo pin output
  pinMode(pulsante, INPUT);      // pulsante collegato al pin 2
  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);

  // usiamo l'interrupt 0 che è associato al pin digitale 2
  // attachInterrupt chiamerà la funzione collegata, cambiaStato
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l’interrupt viene eseguito quando si passa
  // da un livello HIGH ad un livello LOW

}

void loop()
{
  digitalWrite(pinLed, stato);
  // il pin digitale 13 viene impostato a "state"
  // che può essere LOW o HIGH
  // all'avvio di Arduino il LED sarà spento
}

void cambiaStato()
// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW

{
  stato = !stato;
}

quindi il cambiamento di stato avviene solo e soltanto se cambiamo lo stato sul pin 2.

Non si confonda l’azione del cambiamento di stato del LED con l’azione che si potrebbe avere con un semplice controllo sul pin 2 fatto con un’istruzione IF: “se il pulsante è premuto allora cambia stato al LED”.
In questo caso il funzionamento è totalmente diverso, non è presente un’istruzione di controllo di flusso, ma solamente il cambiamento di stato su un pin di interrupt, quando presente questo cambiamento viene invocata la funzione cambiaStato().

Si noti che le variabili utilizzate nella routine di servizio interrupt devono sempre essere globali e volatili.
Nel nostro caso la variabile “stato” utilizzate nella routine di servizio interrupt che abbiamo chiamato cambiaStato() è una variabile di tipo globale e volatile.

Ma cosa vuol dire dichiarare una variabile volatile? Come si può notare la variabile “stato” non è presente all’interno del loop(). Se il compilatore si accorge che sono presenti variabili non usate nel setup() e nel loop(), allora in fase di compilazione, per risparmiare memoria queste variabili vengono cancellate.

Dichiarare quindi “stato” volatile garantisce che il compilatori non elimini questa variabile perchè ci servirà all’interno della routine di servizio interrupt che abbiamo chiamato cambiaStato().
Una variabile volatile indicherà al compilatore di non memorizzare il contenuto della variabile in uno dei registri del microcontrollore, ma di leggerlo quando necessario dalla memoria. Attenzione però che questa modalità di azione rallenterà l’elaborazione del programma, pertanto non bisogna mai rendere volatile ogni variabile, ma l’operazione è da fare solamente quando necessario.

In generale una variabile dovrebbe essere dichiarata come volatile solamente se è utilizzata sia all’interno dell’ISR (interrupt service routine, in italiano gestore di interrupt) che all’esterno dell’ISR.

Esempio 02

Per comprendere meglio quanto scritto nell’esempio precedente facciamo alcune modifiche allo sketch precedente, il circuito rimane invariato.

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 02

*/

int pin = 13;
volatile int stato = LOW;
int pulsante = 2;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  pinMode(pin, OUTPUT);       // definiamo pin output
  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);

  // usiamo l'interrupt 0 che è associato al pin digitale 2
  // attachInterrupt chiamerà la funzione collegata, cambiaStato()
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l’interrupt viene eseguito quando si passa
  // da un livello HIGH ad un livello LOW

}

void loop()
{
  delay(5000); // intervallo di 5 secondi
}

void cambiaStato()
// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW

{
  stato = !stato;
  digitalWrite(pin, stato);
  // il pin digitale 13 viene impostato a "stato"
  // che può essere LOW o HIGH
  // all'avvio di Arduino il LED sarà spento
}

L’unica variazione al codice è stato l’inserimento di:

delay(5000); // intervallo di 5 secondi

che interrompe l’esecuzione del loop per 5 secondi.

Si noti però che non appena premiamo il pulsante avviene una modifica istantanea del codice indipendentemente dal delay di 5 secondi

Attenzione che il LED rosso poteva essere controllato internamente al loop, ma in questo caso il codice, eseguito sequenzialmente, avrebbe prima eseguito il blink del LED verde e successivamente controlla se il pulsante è premuto per poter accendere il LED verde, ciò non accade con l’uso dell’interrupt.

Esempio 03

Nell’esempio che segue potrete notare il ritardo con cui si manifesta la variazione di stato se il controllo viene effettuato all’interno del loop().

Circuito

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: controllo di stato subordinato dai delay

   Esempio 03

*/

int pinRosso = 5; // pin a cui è connesso il LED rosso
int pinVerde = 4; // pin a cui è connesso il LED verde
int pulsante = 2;
int stato = LOW;

void setup()
{
  pinMode(pinRosso, OUTPUT);
  pinMode(pinVerde, OUTPUT);
  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
}

void loop()
{ 
  digitalWrite(pinVerde, HIGH);
  delay(3000);
  digitalWrite(pinVerde, LOW);
  delay(3000);

  if (digitalRead(pulsante)) {
    digitalWrite(pinRosso, !stato);
    stato = !stato; 
  }
}

// Il controllo sul LED rosso potrà avvenire solamente dopo 6 secondi
// La pressione ripetuta del pulsante nei primi 6 secondi non modifica
// lo stato del LED fino a quando il controllo non giunge all'IF.

Esempio 04

Realizziamo ora un quarto sketch in cui abbiamo un LED verde che lampeggia controllato dal codice all’interno del loop ed un secondo LED rosso che cambia stato se premiamo il pulsante connesso al pin 2. Noterete che la pressione del pulsante, che varia lo stato del LED rosso, non influirà sulla normale esecuzione del loop().

Il circuito è il medesimo dell’esercizio precedente.

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 04

*/

int pinRosso = 5;
int pinVerde = 4;
int pulsante = 2;
volatile int stato = LOW;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  pinMode(pinRosso, OUTPUT);
  pinMode(pinVerde, OUTPUT); 
  pinMode(pulsante, INPUT);   // pulsante collegato al pin 2
  
  attachInterrupt(digitalPinToInterrupt(2), cambiaStato, FALLING);

  // usiamo l'interrupt 0 che è associato al pin digitale 2
  // attachInterrupt chiamerà la funzione collegata, cambiaStato()
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l’interrupt viene eseguito quando si passa
  // da un livello HIGH ad un livello LOW

}

void loop()
{
  digitalWrite(pinVerde, HIGH);
  delay(3000);
  digitalWrite(pinVerde, LOW);
  delay(3000);
}

// Durante i 6 secondi di accensione e spegnimento del LED verde
// non saremo bloccati dai delay presenti nel loop, potremo modificare
// lo stato del LED rosso ogni volta nei 6 secondi di pausa del loop

void cambiaStato()
// la funzione cambiaStato() esegue la funzione NOT di "stato" cioè
// se stato = LOW viene cambiato in HIGH, se stato = HIGH viene cambiato in LOW

{
  stato = !stato;
  digitalWrite(pinRosso, stato);
  // il pin digitale 5 viene impostato a "stato"
  // che può essere LOW o HIGH
  // all'avvio di Arduino il LED sarà spento
}

Esempio 05

Vediamo ora un’altro esempio dove con due pulsanti controlliamo l’incremento e il decremento di una variabile che viene stampata sulla Serial Monitor.
Notate all’interno del loop() che viene implementato un blink lento e ll’incremento e il decremento della variabile non è influenzata dai delay() del blink, ma risulta immediata.

Circuito

/*
   Prof. Michele Maffucci
   Data 03.03.2021

   Oggetto: utilizzo degli interrupt

   Esempio 05

*/

volatile int valore = 10;

int pulsanteIncrementa = 2;
int pulsanteDecrementa = 3;

/* dichiariamo volatile la variabile
  state usata nella funzione usata all'interno di attachInterrupt */

void setup()
{
  Serial.begin(9600);
  
  pinMode(pulsanteIncrementa, INPUT);   // pulsante collegato al pin 2
  pinMode(pulsanteDecrementa, INPUT);   // pulsante collegato al pin 3 
   
  attachInterrupt(digitalPinToInterrupt(2), incrementa, FALLING);
  attachInterrupt(digitalPinToInterrupt(3), decrementa, FALLING);

  // usiamo l'interrupt 0 e 1 che sono associati ai pin digitali 2 e 3
  // attachInterrupt chiamerà la funzione collegata, cambiaStato
  // il modo per la rilevazione del cambiamento di stato
  // sarà di tipo: FALLING
  // cioè l'interrupt viene eseguito quando vi è un passaggio da HIGH a LOW

}

void loop()
{
  Serial.println(valore);
}

void incrementa() {
  valore++;
}

void decrementa() {
  valore--;
}

Attenzione che durante le prove potreste notare qualche problema nella gestione dei pulsanti su cui dovrebbe essere effettuato un controllo di antirimbalzo, in questo caso però potrà essere realizzato solamente in modo hardware (in modo elettronico) e non per via software in cui viene richiesto un controllo del tempo di pressione del pulsante mediante il delay() o di calcoli di intervalli di tempo e tutto ciò non è permesso all’interno dell’interrupt service routine .

Buon coding a tutti 🙂

Appunti su Arduino: interrupts


Dopo l’estenuante periodo di fine anno scolastico passato tra interrogazioni, recuperi, esami di qualifica, esami di alternanza scuola lavoro, preparazioni tesine e svariate riunioni/scrutini… riesco a rimettere mani sui miei appunti realizzati durante l’anno scolastico e questa volta colgo l’occasione di rispondere al commento di Raffaele che vuole avere ulteriori informazioni sull’uso degli interrupt in Arduino:

salve sono un vecchio appassionato di elettronica (analogica )- Essendo che ci siamo dovuti convertire sui microcontrollori solo 5 mesi fà sono riuscito finalmente dopo aver tentato con i PIC ,sono passato ad Arduino
duemilaenove e finalmente ho incominciato a capirlo e sviluppare tutti i tipi di esempi che dava nel softwere, dal led ai motori passo passo
e i servo perchè il tipo di programmazzione e soprattutto molti esempi erano spiegati
pure in video e come dicevo sono riuscito a capirci qualcosa.Ovviamente nel mio caso
riuscivo a modificare quelli già presenti ma in sostanza ero abbastanza sodisfatto perchè su di uno sviluppavo le modifiche creandone dei nuovi. Adesso però mi sono fermato un pò perchè ho una richiesta da farle.Pur avendo letto e capito l’istruzione
interrupt cosa significa avendola vista in altri programmi qualè il suo scopo non riesco a capire come inserirla avrei bisogno quindi se possibile di qualche esempio o modo di capire. Ci conto molto

A risentirci e Grazie

Di seguito vi riporto la parte (provvisoria) del manuale che ho realizzato per i miei studenti che si riferisce agli interrupt (sto realizzando alcuni esempi pratici che chiarificano questa funzionalità), sono tratte dal reference on-line di Arduino.cc con alcune mie rielaborazioni.

Con il termine interrupt (interruzione) intendiamo un segnale asincrono che indica la necessità di “attenzione” da parte di una periferica collegata ad Arduino.
L’interrupt viene generato quando si verifica una variazione di stato su uno dei piedini di Arduino. Normalmente il microcontrollore esegue all’interno del loop() in modo sequenziale e ripetitivo le istruzioni in esso inserite, ma quando si verifica un interrupt viene interrotto il flusso delle istruzioni all’interno del loop() ed invocate altre routine (create dall’utente). Quando le routine terminano il flusso del programma prosegue normalmente.
L’utilizzo dell’interrupt è particolarmente utile quando abbiamo la necessità di gestitire in background alcune routine ovvero eseguire istantaneamente un’operazione nel caso si manifesti un evento asincrono esterno.

Capita spesso di avere la necessità di controllare lo stato di diversi pin di input, in questa situazione potrebbe capitare che il programma non si accorga del cambiamento di stato su uno dei pin, utilizzando gli interrupt evitiamo questo tipo di errore.

Continua a leggere