Utilizzo di millis () e micros () all'interno di una routine di interrupt


13

La documentazione per attachInterrupt()dice:

... millis()conta sugli interrupt per contare, quindi non aumenterà mai all'interno di un ISR. Poiché delay()richiede l'interruzione per funzionare, non funzionerà se chiamato all'interno di un ISR. micros()funziona inizialmente, ma inizierà a comportarsi in modo irregolare dopo 1-2 ms. ...

In cosa micros()differisce millis()(tranne ovviamente per la loro precisione)? L'avvertimento di cui sopra significa che l'utilizzo micros()all'interno di una routine di interrupt è sempre una cattiva idea?

Contesto: voglio misurare l'occupazione di impulsi bassi , quindi devo attivare la mia routine quando il segnale di ingresso cambia e registrare l'ora corrente.

Risposte:


16

Le altre risposte sono molto buone, ma voglio approfondire come micros()funziona. E ' sempre legge il timer hardware attuale (eventualmente TCNT0) che viene costantemente aggiornato dall'hardware (in realtà, ogni 4 ms a causa del prescaler di 64). Quindi aggiunge il conteggio di overflow del timer 0, che viene aggiornato da un interrupt di overflow del timer (moltiplicato per 256).

Pertanto, anche all'interno di un ISR, puoi fare affidamento micros()sull'aggiornamento. Tuttavia, se aspetti troppo a lungo , perdi l'aggiornamento di overflow e il risultato restituito diminuirà (ovvero otterrai 253, 254, 255, 0, 1, 2, 3 ecc.)

Questo è micros()- leggermente semplificato per rimuovere le definizioni per altri processori:

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t;
    cli();
    m = timer0_overflow_count;
    t = TCNT0;
    if ((TIFR0 & _BV(TOV0)) && (t < 255))
        m++;
    SREG = oldSREG;
    return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

Il codice sopra consente l'overflow (controlla il bit TOV0) in modo che possa gestire l'overflow mentre gli interrupt sono disattivati ​​ma solo una volta - non è prevista la gestione di due overflow.


TLDR;

  • Non fare ritardi all'interno di un ISR
  • Se devi farlo, puoi passare il tempo con micros()ma non millis(). Inoltre delayMicroseconds()è una possibilità.
  • Non ritardare più di 500 µs circa o perderai un overflow del timer.
  • Anche brevi ritardi potrebbero farti perdere i dati seriali in entrata (a 115200 baud otterrai un nuovo carattere ogni 87 µs).

Non è in grado di comprendere l'affermazione fornita di seguito che viene utilizzata in micros (). Potresti per favore elaborare? Da quando è stato scritto ISR, il flag TOV0 verrà cancellato non appena viene inserito ISR e quindi la condizione sottostante potrebbe non diventare realtà !. if ((TIFR0 & _BV (TOV0)) && (t <255)) m ++;
Rajesh,

micros()non è un ISR. È una funzione normale. Il flag TOV0 consente di testare l'hardware per vedere se si è verificato l'overflow del timer (ma non è stato ancora elaborato).
Nick Gammon

Poiché il test viene eseguito con interruzioni disattivate, la bandiera non cambierà durante il test.
Nick Gammon

Quindi vuoi dire dopo che la funzione cli () all'interno di micros (), se si verifica un overflow, deve essere verificata? Questo ha senso. Altrimenti anche se micros non è una funzione, TIMER0_OVF_vect ISR cancellerà TOV0 in TIFR0 è quello che il mio dubbio era.
Rajesh,

Inoltre, perché TCNT0 viene confrontato con 255 per vedere se è inferiore a 255? Dopo cli () se TCNT0 raggiunge 255 cosa accadrà?
Rajesh,

8

Non è sbagliato usare millis()o micros()all'interno di una routine di interrupt.

Si è sbagliato usarli in modo non corretto.

La cosa principale qui è che mentre ci si trova in una routine di interruzione "il tempo non passa". millis()e micros()non cambierà (beh, micros()inizialmente, ma una volta passato quel magico millisecondo in cui è richiesto un millisecondo, tutto cade a pezzi).

Quindi puoi sicuramente chiamare millis()o micros()scoprire l'ora corrente all'interno del tuo ISR, ma non aspettarti che l'ora cambi.

È quella mancanza di cambiamento nel tempo che viene avvertito nella citazione che fornisci. delay()si affida al millis()cambiamento per sapere quanto tempo è passato. Dal momento che non cambia non delay()può mai finire.

Quindi, essenzialmente millis()e micros()ti dirò l'ora in cui il tuo ISR è stato chiamato, non importa quando nel tuo ISR li usi.


3
No, micros()aggiornamenti. Legge sempre il registro del timer hardware.
Nick Gammon

4

La frase citata non è un avvertimento, è semplicemente una dichiarazione su come funzionano le cose.

Non c'è nulla di intrinsecamente sbagliato nell'uso millis()o micros()all'interno di una routine di interruzione scritta correttamente.

D'altra parte, fare qualsiasi cosa all'interno di una routine di interruzione scritta in modo improprio è per definizione sbagliato.

Una routine di interruzione che impiega più di qualche microsecondo per fare il suo lavoro è, con ogni probabilità, scritta in modo improprio.

In breve: una routine di interruzione scritta correttamente non causerà o incontrerà problemi con millis()o micros().

Modifica: per quanto riguarda "perché micros ()" inizia a comportarsi in modo irregolare "", come spiegato in una pagina web " esame della funzione Arduino micros ", il micros()codice su uno Uno ordinario è funzionalmente equivalente a

unsigned long micros() {
  return((timer0_overflow_count << 8) + TCNT0)*(64/16);
}

Ciò restituisce un lungo senza segno a quattro byte composto dai tre byte più bassi da timer0_overflow_counte un byte dal registro di conteggio timer-0.

Il gestore di interrupt timer0_overflow_countincrementa circa una volta al millisecondo TIMER0_OVF_vect, come spiegato in un esame della pagina Web della funzione arduino millis .

Prima che inizi un gestore di interrupt, l'hardware AVR disabilita gli interrupt. Se (per esempio) un gestore di interrupt dovesse funzionare per cinque millisecondi con gli interrupt ancora disabilitati, almeno quattro overflow del timer 0 verrebbero persi. [Gli interrupt scritti in codice C nel sistema Arduino non sono rientranti (in grado di gestire correttamente più esecuzioni sovrapposte all'interno dello stesso gestore) ma si potrebbe scrivere un gestore di linguaggio di assemblaggio rientrante che riattivi gli interrupt prima che inizi un processo che richiede tempo.]

In altre parole, gli overflow del timer non si "impilano"; ogni volta che si verifica un overflow prima che l'interrupt del precedente overflow sia stato gestito, il millis()contatore perde un millisecondo e la discrepanza timer0_overflow_counta sua volta si micros()sbaglia anche di un millisecondo.

Considerando "meno di 500 μs" come limite di tempo superiore per l'elaborazione degli interrupt, "per evitare di bloccare l'interrupt del timer per troppo tempo", si potrebbe arrivare a poco meno di 1024 μs (ad esempio 1020 μs) e funzionerebbe millis()ancora, la maggior parte dei tempo. Tuttavia, considero un gestore di interrupt che prende più di 5 μs come pigro, più di 10 μs come pigro, più di 20 μs come una lumaca.


Potresti per favore approfondire "come funzionano le cose", in particolare perché micros()"iniziare a comportarsi in modo irregolare"? E cosa intendi con "routine di interruzione scritta correttamente"? Presumo significhi "più corto di 500us" (per evitare di bloccare l'interruzione del timer per troppo tempo), "usare variabili volatili per la comunicazione" e "non chiamare il codice della biblioteca" il più possibile, c'è qualcos'altro?
Petr Pudlák,

@ PetrPudlák, vedi modifica
James Waldby - jwpat7,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.