Interruzione hardware.
Interrupt - un processo avviato in un certo modo che commuta temporaneamente il microprocessore all'esecuzione di un altro programma, seguito dalla ripresa del programma interrotto.
Interrupt in Arduino può essere descritto esattamente allo stesso modo.
Gli interrupt sono diversi, o meglio le loro cause: un interrupt può causare un ADC, un timer o letteralmente un pin del microcontrollore. Tali interrupt sono chiamati interrupt hardware esterni.
External hardware interrupt - è un interrupt causato da una variazione di tensione su un pin del microcontrollore. Il punto principale è che il core di sistema del microcontrollore non esegue il polling del pin e non perde tempo su di esso. Ma non appena la tensione sul pin cambia (segnale digitale), il microcontrollore riceve un segnale, chiude tutto, elabora l'interruzione e torna indietro.
Perché è necessario? Molto spesso, gli interrupt vengono utilizzati per rilevare eventi brevi - impulsi o persino per contare il loro numero, senza caricare il codice principale. Un interrupt hardware può rilevare una breve pressione di un pulsante o un trigger di sensore durante calcoli lunghi complessi o ritardi nel codice, ovvero il pin viene interrogato in parallelo con il codice principale. Inoltre gli interrupt possono riattivare l'microcontrollore dalle modalità di risparmio energetico, quando quasi tutte le periferiche sono generalmente spente. Vediamo come lavorare con gli interrupt hardware nell'IDE di Arduino.
Interrupts in Arduino.
Il microcontrollore ha la capacità di ricevere interrupt da qualsiasi pin, tali interrupt sono chiamati PCINT e puoi lavorare con loro solo utilizzando librerie di terze parti o manualmente. Qui parleremo di interrupt ordinari, che sono chiamati INT, perché il framework Arduino standard può funzionare solo con loro. Ci sono pochissimi di questi interrupt e i loro pin corrispondenti:
Numero di interrupt INT0 INT1 INT2 INT3 INT4 INT5
ATmega 328/168 (Nano, UNO, Mini) D2 D3 - - - -
ATmega 32U4 (Leonardo, Micro) D3 D2 D0 D1 D7 -
ATmega 2560 (Mega) D2 D3 D21 D20 D19 D18
Come si hai capito dalla tabella, gli interrupt hanno il loro numero, che è diverso dal numero pin. A proposito, esiste una comoda funzione digitalPinToInterrupt(pin), che prende il numero di pin e restituisce il numero di interrupt.
Wemos Mini (esp8266)
Su esp8266, l'interrupt può essere configurato utilizzando strumenti standard su qualsiasi pin.
Gestire le interrupt.
Per prima cosa devi dichiarare una funzione di gestione degli interrupt, questa funzione verrà eseguita quando viene attivato l'interruzione:
- Per Arduino AVR, questa è una funzione del modulo void name(){}.
- Per ESP8266, la funzione viene creata con l'attributo IRAM_ATTR: IRAM_ATTR void name(){}.
Ci sono alcuni requisiti per il codice all'interno di questa funzione:
- Le variabili che cambiano il loro valore in un interrupt devono essere dichiarate con lo specificatore volatile. Esempio: volatile byte val;
- delay() non funziona.
- Non cambia il suo valore con millis() e micros().
- L'output sulla porta Serial.print() non funziona correttamente.
Devi fare il minor numero di calcoli possibile e in generale azioni "lunghe": questo rallenterà il lavoro dell'microcontrollore
con frequenti interruzioni:
- Calcoli con float.
- Lavorare con la memoria dinamica (funzioni new(), malloc(), realloc() e altre).
- Lavorare con le stringhe.
Collegamento di interrupt.
Un interrupt è connesso usando la funzione attachInterrupt(pin, handler, mode):
pin - pin interrupt.
- Per Arduino AVR, questo è il numero di interrupt (vedi tabella sopra).
- Per ESP8266, questo è il numero GPIO o D-pin sulla scheda.
handler - è il nome della funzione di gestione degli interrupt che abbiamo creato.
mode - modalità operativa di interruzione:
- RISING (crescita) - attivato quando il segnale cambia da LOW ad HIGH.
- FALLING (caduta) - attivato quando il segnale cambia da HIGH a LOW.
- CHANGE (cambia) - attivato quando il segnale cambia (da LOW ad HIGH e viceversa).
- LOW (basso) - Attivato costantemente quando viene segnalato LOW (non supportato su ESP8266).
L'interruzione può essere disabilitata utilizzando la funzione detachInterrupt(pin).
Puoi disabilitare globalmente gli interrupt con noInterrupts() e riattivarli con interrupts(). Stai attento con loro! noInterrupts() interromperà anche gli interrupt del timer e interromperai tutte le funzioni di temporizzazione e la generazione di PWM.
Esempio.
Consideriamo un esempio in cui le pressioni dei pulsanti vengono conteggiate nell'interrupt e nel ciclo principale vengono emesse con un ritardo di 1 secondo:
volatile int counter = 0; // variabile contatore.
void setup()
{
Serial.begin(9600); // aprire la porta di comunicazione.
// collegato il pulsante a D2 e GND
pinMode(2, INPUT_PULLUP);
// FALLING - quando premi il pulsante, ci sarà un segnale 0.
attachInterrupt(0, btnIsr, FALLING);
}
void btnIsr()
{
counter++; // premere
}
void loop()
{
Serial.println(counter); // dedurre
delay(1000); // aspettiamo
}
Catturare un evento.
Se l'interruzione rileva un breve evento che non deve essere elaborato immediatamente, è meglio utilizzare il seguente algoritmo di gestione dell'interruzione:
- Nel gestore degli interrupt, alza semplicemente il flag (volatile bool variabile).
- Nel ciclo principale del programma, controlliamo il flag, se è sollevato, lo resettiamo ed eseguiamo le azioni necessarie.
volatile bool intFlag = false; // flag
void setup()
{
Serial.begin(9600); // aprire la porta di comunicazione.
// collegato il pulsante a D2 e GND
pinMode(2, INPUT_PULLUP);
attachInterrupt(0, buttonTick, FALLING);
}
void buttonTick()
{
intFlag = true; // flag true
}
void loop()
{
if (intFlag)
{
intFlag = false; // flag false
// fare qualcosa
Serial.println("Interrupt!" );
}
}
Il prossimo scenario possibile: dobbiamo catturare il segnale dal "sensore" e rispondere immediatamente una volta fino a quando appare il segnale successivo. Se il sensore è un pulsante, ci aspetta il rimbalzo dei contatti. È meglio affrontare il rimbalzo nell'hardware, ma puoi risolvere il problema in modo programmatico: ricorda il momento della pressione e ignora le operazioni successive. Si consideri un esempio in cui l'interrupt verrà impostato su cambia (CHANGE):
void setup()
{
// interrupt su D2 (UNO/NANO)
attachInterrupt(0, isr, CHANGE);
}
volatile uint32_t debounce;
void isr()
{
// lascia un timeout di 100 ms per il rimbalzo.
// CHANGE non fornisce lo stato del pin.
// devi trovarlo con digitalRead.
if (millis() - debounce >= 100 && digitalRead(2))
{
debounce = millis();
// il tuo codice su interrupt su un segnale alto
}
}
void loop()
{
}
Dirai: ma millis() non cambia il valore nell'interrupt! Sì, non lo fa, ma cambia tra gli interrupt!