Polling e interrupt
I microcontrollori hanno come obiettivo principale quello di prendere efficacemente le informazioni presenti nelle periferiche (pin digitali, ingressi analogici, porte di comunicazione, timer, ecc.) ed elaborarle allo scopo di generare automaticamente degli output. Ci si concentrerà ora sul meccanismo che permette di far capire alla CPU se una periferica ha un dato a disposizione o no.
In termini più generici e corretti si parlerà del meccanismo di comunicazione tra periferiche, che richiedono un "servizio" e CPU che fornisce il servizio richiesto.
Il problema può essere affrontato in due modi:
- Tramite polling
- Tramite interrupt
Il meccanismo del polling prevede che la CPU interroghi in successione le periferiche "chiedendo" se c'è bisogno di un servizio e in caso affermativo fornendolo.
Il meccanismo dell'interrupt invece lascia l'iniziativa alle periferiche che in caso di bisogno "avvertorno" la CPU che è necessario un servizio. La CPU cercherà la periferica che ha lanciato l'interrupt e fornirà il servizio richiesto.
N.B.: Si parla in generale di "servizio" e non di dati perché in alcune circostanze non è presente un dato. Per esempio nel caso dei timer, l'informazione è relativa al fatto che questi si sono azzerati e quindi che è passato un certo tempo prefissato, non ci sono quindi valori da prelevare. Un altro esempio potrebbe essere l'esito della spedizione di un byte tramite la porta seriale (UART). L'informazione è che la porta seriale è pronta ad inviare un altro byte e anche in questo caso non deve essere prelevato alcun dato.
Il polling
Con questo meccanismo la CPU interroga ciclicamente le periferiche per vedere se queste hanno bisogno di un servizio.
Il meccanismo è semplice da realizzare, perché gestito interamente via software, e non necessita di elettronica aggiuntiva.
Per contro il meccanismo è poco efficiente perché si usano cicli istruzione per interrogare le periferiche che nella maggiorparte delle volte non avranno bisogno di un servizio.
Gli interrupt
Se viene implementato il meccanismo degli interrupt è la periferica che richiede alla CPU di essere servita inviando un segnale di interrupt. In altre parole la periferica chiede alla CPU un servizio. La CPU cercherà quale periferica ha inviato l'interrupt e una volta individuata fornirà il servizio corretto.
Questo meccanismo è molto efficiente perché l'esecuzione del programma viene interrotta solo in caso di necessità.
Il sistema di gestione degli interrupt nella CPU opera dunque in 3 passi:
- Riconosce la presenza di un segnale di interrupt da una periferica
- Identifica la periferica e avvia per questa il servizio di interrupt corretto
- Riprende a svolgere le operazioni interrotte a causa dell'interrupt
Per permettere la fornitura dei servizi di interrupt corretti è quindi necessario che vi sia dell'elettronica aggiuntiva che permette la gestione dei
segnali di interrupt.
Si nota inoltre come la gestione di un interrupt comporti che la CPU interrompa le operazioni che stava svolgendo in qualsiasi momento. È necessario quindi implementare un meccanismo per cui alla fine della gestione degli interrupt la CPU possa tornare a svolgere le operazioni interrotte.
Poiché gli interrupt modificano il flusso dei programmi e quindi il tempo di esecuzione, possono essere disabilitati e riabilitati più volte via software all'interno di un programma.
Esempio
Per comprendere meglio la differenza si può ricorrere a un esempio.
Si voglia attivare un led tramite la pressione di un pulsante. Alla pressione del pulsante il led si accende, alla pressione successiva si spegne.
Soluzione tramite polling:
Si nota come la sequenza di polling venga eseguita in continuazione. Nel caso il tasto venga premuto, l'esecuzione del programma si arresta fino a quando questo non viene rilasciato, altrimenti il led continuerebbe ad accendersi e spegnersi in successione ad ogni ciclo (ad una velocità molto elevata non percepibile dall'occhio).
Viene comunque fatta l'ipotesi che il pulsante sia ideale, quindi senza rimbalzo.
La codifica per Arduino dell'algoritmo è la seguente:
#define ledpin 13
#define buttonpin 2
bool buttonState = LOW;
bool statoLed = LOW;
void setup()
{
pinMode(buttonpin, INPUT);
pinMode(ledpin, OUTPUT);
digitalWrite(ledpin, LOW);
}
void loop()
{
// inizio sequenza di polling
buttonState = digitalRead(buttonpin);
if (buttonState == HIGH) {
if (statoLed == LOW) {
digitalWrite(ledpin, HIGH); // accendi il led
statoLed = HIGH;
} else {
digitalWrite(ledpin, LOW); // spegni il led
statoLed = LOW;
}
while (digitalRead(buttonpin) == HIGH); // attende che il pulsante venga rilasciato
}
// fine sequenza di polling
}
Soluzione tramite interrupt:
L'algoritmo si compone di due parti:
- Il programma principale
- La routine di interrupt che viene eseguita quando avviene una richiesta di interrupt tramite un segnale di interrupt
La codifica per Arduino viene qui sotto riportata. Si nota come la gestione degli interrupt preveda la possibilità di controllare anche le singole transizioni:
- RISING: l'interrupt viene attivato quando il livello logico del pin passa da LOW a HIGH
- FALLING: l'interrupt viene attivato quando il livello logico del pin passa da HIGH a LOW
- CHANGE: l'interrupt viene attivato in corrispondenza ad un cambio di livello logico qualsiasi
- LOW: l'interrupt viene attivato quando il livello logico è LOW
e quindi non è necessario attendere il rilascio del pulsante (anche in questo caso si fa l'ipotesi che il pulsante sia ideale, quindi privo di rimbalzi)
Il programma prevede anche l'attivazione degli interrupt della fase di setup. Per approfondimenti sull'implementazione con Arduino è possibile consultare la guida online all'indirizzo https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/
#define ledpin 13
#define buttonpin 2
/* Le variabili utilizzate all'interno delle routine di interrupt vanno
* dichiarate di tipo volatile affinché il compilatore le tratti
* nel modo corretto
*/
volatile bool buttonState = LOW;
volatile bool statoLed = LOW;
void setup()
{
pinMode(buttonpin, INPUT);
pinMode(ledpin, OUTPUT);
digitalWrite(ledpin, LOW);
/* usiamo l'interrupt associato al pin digitale 2
* (non tutti i pin di Arduino permettono gli interrupt)
* attachInterrupt chiamerà la funzione "interruptRoutine"
* il modo per la rilevazione dell'interrupt è RISING,
* cioè l'interrupt viene eseguito quando avviene
* un cambiamento di stato da LOW a HIGH sul pin
*/
attachInterrupt(digitalPinToInterrupt(buttonpin), interruptRoutine, RISING);
}
void loop()
{
// Nel loop non viene fatto nulla
}
void interruptRoutine(){
if (statoLed == LOW) {
digitalWrite(ledpin, HIGH); // accendi il led
statoLed = HIGH;
} else {
digitalWrite(ledpin, LOW); // spegni il led
statoLed = LOW;
}
}