Mi sto avvicinando al mondo di arduino (ho preso il 2009) e sto tentando di capire come funzionano gli interrupt e in particolare quelli che sfruttano i registri dell'ATMega328 ma ho un po' di confusione. Ho capito che ci sono degli interrupt interni e degli interrupt esterni, poi che l'interrupt di può fare dando il comando attachInterrupt oppure agendo sui registri dell'ATMega328 ma non ho capito se i primi sono esterni e i secondi interni oppure non c'è nesso tra le cose..
Poi ho capito che quando si lancia un interrupt il microcontrollore smette di fare quello che stava facendo e inizia a fare l'interrupt service routine (ISR). Quando quest'ultima è terminata riprende a fare quello che stava facendo.. Però provando con questo codice che ho trovato qui sul forum o in rete ho dei comportamenti che non comprendo:
#include <avr/io.h>
#include <avr/interrupt.h>
int ledPin= 13;
void setup()
{
DDRB |= 1 << PB5; // the same as pinMode(13, OUTPUT);
TCCR1A = 0; // this should fix your problem
TCCR1B = 5; // prescaler to divide clock by 1024 (starts timer counting)
TCNT1=0; // clear timer1 counter
TIFR1 |= (1<<TOV1); // clear the overflow flag
TIMSK1 |= (1<<TOIE1); // enable timer1 overflow interrupts
}
ISR(TIMER1_OVF_vect)
{
// toggle the LED pin to make the LED blink
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
void loop()
{
// nothing to see here, move along
}
Il codice funziona (il led lampeggia) ma perché passa più volte nella ISR? Come fa a incrementare il valore del registro contatore se l'azzeramento avviene nel setup? Il setup non si esegue una volta sola? C'è modo di stampare il contenuto del contatore? Ho provato con Serial.print ma direi che non si fa così oppure non l'ho saputo usare io..
Per il pochissimo che ne so, una volta che tu attivi un registro in modo da far uscire una data frequenza su un pin, quella frequenza resta operativa finché non sostituisci lo sketch o finché non cambi nuovamente il valore del registro.
Ti dico questo solo perché ho usato tempo fa (ma su suggerimento) un registro per generare su un pin una frequenza di 38KHz, tale freuenzacontinuaa ad uscire dal pin perfino dopo aver messo il micro in sleep, oviamente con una modalità che non toccava quel registro
Il codice funziona (il led lampeggia) ma perché passa più volte nella ISR? Come fa a incrementare il valore del registro contatore se l'azzeramento avviene nel setup? Il setup non si esegue una volta sola? C'è modo di stampare il contenuto del contatore?
Il Timer1 ha un registro di conteggio che si incrementa ad ogni "colpo" di clock (nell'esempio 16MHz/1024 = 15626Hz) e quando oltrepassa il massimo conteggio il timer torna a zero(overflow) e viene alzato un bit nel registro degli interrupt per informare il micro che c'è una interruzione da servire (inverti il valore del pin 13). Il timer, intanto, prosegue imperterrito in background il suo conteggio ad oltranza.
In breve, ogni volta che il timer "sbrodola" (overflow) forza il processore a servire la routine di interrupt definita con
ISR(TIMER1_OVF_vect) // Quando il timer1 sbrodola esegue le istruzioni del blocco { }
{
// toggle the LED pin to make the LED blink
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
attachInterrupt è un interrupt esterno, perchè si basa su un segnale che arriva dall'esterno (quindi da un pin)
Il codice funziona (il led lampeggia) ma perché passa più volte nella ISR?
entra nella ISR ogni volta che il registro contatore va in overflow.
C'è modo di stampare il contenuto del contatore?
Serial.println(TCNT1, DEC);
Come fa a incrementare il valore del registro contatore se l'azzeramento avviene nel setup?
appunto, nel setup viene azzerato il registro contatore, però il registro viene incrementato ad ogni overflow del timer. il valore di overflow è settabile tramite un altro registro, quindi è possibile regolare (entro certi limiti eh) il tempo di overflow, e dinque la durata del lampeggio.
Questo è un interrupt interno, perchè viene genarato all'interno del microcontrollore (in questo caso un timer)
Il tutto è scritto nel datasheet del 328 ma anche nelle AN (application note) che trovi sul sito di Atmel.
Quella ISR fa parte di uno dei timer del 328. I timer sono contatori che ricevono il clock di conteggio da: no source, presceler o da esterno pin T0 o T1.
Il prescaler è un'oscillatore libero connesso al clock di sistema (quarzo o oscillatore RC) con tante uscite per divisione (es clock/1, clock/8, clk/64 ecc)
Il timer ad 8 bit andrà in overflow al successivo impulso di clock solo quando tutti i suoi bit sono impostati ad 1. Se si abilità l'interrupt su overflow verra eseguito il salto alla ISR.
Nota che il timer ha molte modalità di funzionamento e riuscire a gestirlo non è cosa semplice, di fatti ho scritto da qualche parte che "il timer è un gatto a nove code".
A proposito il 1284 ha quattro timer 2 a 8 bit e 2 a 16 bit, mentre i prescaler sono 2 proprio come il 328.
Grazie a tutti delle risposte apprezzatissime. Il datasheet del Atmega328 l'ho letto in parte ma non è semplice..
Comunque ora è chiaro che il contatore torna a zero da solo quando va in overflow, (pensavo fosse necessario un intervento manuale) ed è chiaro a cosa ci si riferisce quando si parla di interrupt interni e esterni. La domanda a questo punto è:
volendo realizzare un robot autobilanciante sul modello di quello di gioblu
sarebbe possibile applicare / sfruttare gli interrupt esterni? In particolare sarebbe possibile fare in modo che quando i sensori di prossimità misurano un valore diverso da un valore di riferimento dato venga lanciato un interrupt che eseguendo la routine associata impartisca il comando ai servomotori?
Lo chiedo perché le modalità con cui si lancia l'interrupt sono 4 preconfigurate (LOW, CHANGE, RISING, FALLING)
all'interno della ISR scrivi il codice per gestire cosa sta succendendo, e se necessario ci sono 2 vie:
setti un flag, che poi verrà usata nel loop. Ottima opzione per evitare che l'interrupt duri troppo e incasini timer e PWM etc..
fai la gestione del caso sempre all'interno dell'interrupt, ma devi giostare il fatto che i timer NON avanzano all'interno di un'interrupt, il che può essere problematico.
per esempio: ogni interrupt calcoli il tempo dall'ultimo interrupt (perchè ti salvi il tempo esecuzione dell'ultimo interrupt), se il tempo supera una soglia X allora parte il caso "speciale", e scegli se gestirlo con il metodo 1. o 2.
Consiglio il metodo 1.
Quando dici che i timer non avanzano all'interno delle ISR dici una cosa errata, sempre se consideriamo il timer come qualcosa di molto simile a quello descritto da me. Invece ha raggione nel caso della variabile su cui lavora millis() che viene aggiornata dentro una ISR.
C'è da fare attenzione al fatto che il core ha il TIMERx impegnato e quindi periodicamente viene esguita la ISR preposta, allora nel caso di altri timer abilitati ed in genere altre ISR interne o esterne abilitate c'è da fare attenzione a scrivere codice nella ISR che sia piiù corto possibile perche, quando il puntatore esegue la ISRx le variabili che sono aggionate in ISRy non possono essere eseguite perchè prima deve terminare ISRx.
Se si fa ammeno del core di Arduino e si usano più ISR si deve prestare la stessa attenzione.
Astro ha spiegato brevemente come si compartano di default le IRQ, forse è il caso di ricordarlo, brevemente. Le IRQ vengono notificate e se possibile eseguite, se ci sono troppe IRQ in unità di tempo le IRQ vengono annotate nello stack (che si riempe) terminata una ISR vengono eseguite quelle che sono ancora nello stack. Ogni volta che termina una ISR PC torna ad eseguire almeno fuori da ogni ISR per almeno 3 cicli di clock.
Concordo con Mauro. I timer avanzano e così i contatori che sono gestiti dagli stessi ma le variabili del programma non sono modificate finché la relativa ISR non viene eseguita. Siccome di default avr-gcc compila ogni ISR affinché sia "atomica" (cioè che venga eseguita dall'inizio alla fine senza possibilità di essere interrotta da un'altra chiamata di interrupt), se in una ISR hai una porzione di codice che deve andare avanti in base ad una modifica fatta all'interno di un'altra ISR, quella tua porzione di codice resterà bloccata vita natural durante.