Domanda:
Qual è il tempo massimo necessario per eseguire una routine di servizio di interruzione su ATmega328P?
thebear8
2019-07-11 17:44:36 UTC
view on stackexchange narkive permalink

Ho un ATmega328P che controlla se un pulsante è stato premuto tramite interrupt di cambio pin.Ora voglio accendere un LED per 200 ms. Posso semplicemente accendere il LED, aspettare 200 ms e spegnerlo nuovamente nell'ISR come nel codice seguente?

  ISR (PCINT1_vect)
{
    if (PINB & 0b1)
    {
         PORT = 0b10;
         _delay_ms (200);
         PORTA = 0;
    }
}
 

In alcuni post del forum su AVR Freaks, ho letto che non dovresti passare molto tempo in un ISR, ma non ho mai visto numeri esatti.Purtroppo non riesco più a trovare quei post, quindi non posso collegarli.Per quanto posso ricordare, tutti dicevano che se passavi troppo tempo nell'ISR, il microcontrollore poteva bloccarsi. È vero?E se è così, c'è un limite di tempo esatto dopo che ciò potrebbe accadere?

Cinque risposte:
Neil_UK
2019-07-11 17:52:02 UTC
view on stackexchange narkive permalink

Se nient'altro è in esecuzione nell'MCU, sei libero di impiegare tutto il tempo che desideri nell'ISR. Ma , questa è una cattiva abitudine da prendere e significa che se vuoi fare qualcos'altro, dovrai probabilmente rielaborare il codice.

Un caso particolare è se l'MCU utilizza una libreria seriale, che si aspetta che gli interrupt funzionino abbastanza spesso da servire i singoli caratteri ricevuti.A 115.200 baud (un'elevata velocità seriale spesso utilizzata per ridurre al minimo il tempo di download), ci sono meno di 100 µs tra i caratteri.Se blocchi le interruzioni più a lungo, rischi di perdere i caratteri di input.

Come regola generale, fai il minimo assoluto in un ISR.Nella tua applicazione, un progetto ragionevole sarebbe quello di avere un interrupt ogni ms, che incrementa e controlla un valore del contatore.Sono sicuro che puoi elaborare una logica adatta per impostare e testare il contatore per ottenere 200 ms tra gli eventi di accensione e spegnimento.

Dave Tweed
2019-07-11 17:49:14 UTC
view on stackexchange narkive permalink

Nel peggiore dei casi, un ISR può essere eseguito fino a quando non si verifica nuovamente l'interrupt successivo dello stesso tipo.

Ma in generale, è una cattiva pratica di progettazione passare più tempo in un ISR di quanto sia assolutamente necessario, perché impedisce a tutti gli altri codici di funzionare.Questo è un grosso problema per qualsiasi cosa diversa dai programmi banali.

Direi che nel peggiore dei casi un ISR può essere eseguito indefinitamente, impedendo il ripetersi dell'IRQ successivo.
@DmitryGrigoryev: In questo caso, intendevo "caso peggiore" in termini di NON avere il sistema in errore immediatamente.
Brethlosze
2019-07-11 17:51:50 UTC
view on stackexchange narkive permalink

Sebbene la pratica sia quella di allocare i cicli di esecuzione minimi possibili all'interno di un'interruzione e, oltre ad altre specifiche hardware generali, non ci sono limitazioni tecniche per aumentarli, se non ci sono altre interruzioni da eseguire.

In attachInterrupt () Arduino Reference:

In generale, un ISR dovrebbe essere il più breve e veloce possibile. Se tuo sketch utilizza più ISR, solo uno può essere eseguito alla volta, altro gli interrupt verranno eseguiti dopo che quello corrente termina in un ordine dipende dalla priorità che hanno. millis () si basa sugli interrupt contare, quindi non aumenterà mai all'interno di un ISR. Dal ritardo () richiede interruzioni per funzionare, non funzionerà se chiamato all'interno di un ISR. micros () funziona inizialmente ma inizierà a comportarsi in modo irregolare dopo 1-2 SM. delayMicroseconds () non usa alcun contatore, quindi funzionerà come normale.

Avendo 25 possibili interruzioni in questa famiglia di processori, si consiglia di gestirle come eventi puntuali, per consentire che si verifichino altre interruzioni.

Sebbene il ritardo in un ISR sia generalmente una cattiva idea, la domanda usa quello che sembra essere `_delay_ms ()` di avr-gcc, che è basato sui cicli a differenza della funzione `delay ()` di Arduino che si basa sull'interruzione del timer.
Dmitry Grigoryev
2019-07-12 13:45:51 UTC
view on stackexchange narkive permalink

Tecnicamente, non è vietato nemmeno avviare un ciclo infinito all'interno di un interrupt, quindi non c'è limite massimo al tempo di esecuzione dell'ISR. Tuttavia, gli interrupt sono più utili quando vuoi, beh, interrompere il normale flusso del programma per eseguire un'azione breve che deve essere eseguita immediatamente e le qualificazioni "brevi" e "immediatamente" sono naturalmente correlati: se il tuo ISR più lungo dura 1 ms, un interrupt in entrata con la stessa priorità avrà un tempo di risposta di 1 ms. Quindi, in sostanza, il tempo di esecuzione di ISR è limitato dal tempo di risposta IRQ desiderato .

Se il tuo programma trascorre molto tempo in attesa in un interrupt, potrebbe essere più semplice eliminare completamente gli interrupt e utilizzare il polling, il che rende la programmazione sostanzialmente più semplice.

Ci sono casi in cui puoi abusare degli interrupt per eseguire codice più o meno regolare. Un esempio potrebbe essere l'implementazione della priorità del task: un task a bassa priorità viene avviato dal loop principale, mentre un task a priorità più alta è un interrupt del timer attivato periodicamente. Questo di solito viene fatto su MCU con diversi livelli di priorità IRQ, in modo che il sistema possa ancora avere ISR regolari quando necessario.

AaronD
2019-07-12 03:21:12 UTC
view on stackexchange narkive permalink

Hai una CPU single-core, single-threaded. Ciò significa che in qualsiasi momento sta facendo esattamente una cosa. Se l'applicazione richiede che esegua diverse operazioni, il codice deve essere progettato per passare da una all'altra. Può essere banale o complesso a piacere.

Normalmente, la CPU gira intorno al ciclo principale, facendo tutto quello che c'è dentro, e va bene fino a quando non si verifica un interrupt. L'hardware di interrupt fondamentalmente forza una chiamata di funzione all'ISR appropriato, anche se non ci sono istruzioni di chiamata per esso nel ciclo principale. Questa imprevedibilità è l'origine della maggior parte delle regole per scrivere ISR.

Il tempo che trascorri in un ISR è il tempo in cui il ciclo principale viene messo in pausa, in attesa del ritorno dell'ISR. Se il ciclo principale è l'unico a reimpostare un timer watchdog attivo (ottima pratica), il watchdog non verrà reimpostato durante quel periodo. Se il watchdog va in timeout, ti dà un hard reset. Proprio come il reset esterno, ma con flag diversi che puoi controllare all'avvio. Questo è probabilmente il "crash" di cui hai sentito parlare.


È una buona pratica utilizzare il watchdog e reimpostarlo solo una volta per ogni viaggio attorno al loop principale. Questo ti costringe a scrivere codice che rimanga reattivo. Se hai bisogno di aspettare qualcosa, puoi impostare un evento (timer finito, personaggio successivo ricevuto, ecc.) E andare avanti. Controlla periodicamente quell'evento o imposta un altro interrupt per il suo completamento, quindi torna ad esso. Nel frattempo, continui con qualsiasi altra cosa stavi facendo.

La mia struttura principale è tipicamente qualcosa del genere:

  #include "module1.h"
#include "module2.h"

void main (void)
{
    //complessivamente
    //patata fritta
    //impostare

    mod1_init ();
    mod2_init ();

    // cancella i flag di interrupt
    // abilitazione dell'interrupt globale

    mentre (1)
    {
        // clear watchdog

        mod1_run ();
        mod2_run ();
    }
}
 

E i miei moduli sono così:

  void modX_init (void)
{
// inizializzazione hardware e variabile solo per questo modulo
    // non usare interrupt se il polling è abbastanza buono
}

void modX_run (void)
{
    if (POLLED_INTERRUPT_FLAG)
    {
        POLLED_INTERRUPT_FLAG = 0;

        // codice "ISR" non bloccante
    }
}

void ISR modX_ISR (void)
{
    // ok, questo richiede una risposta * immediata *
    // trascorri qui il tempo minimo assoluto e vattene
}
 

Le firme delle funzioni non devono essere void , ma la maggior parte di esse lo sono. A volte avrò una tempistica ampia in un modulo che è usato anche da un altro, ed è utile usare il valore di ritorno di un modX_run () e gli argomenti di un altro (o una logica di base) per fare quella connessione. Ad esempio:

  if (DMX_run ()) // include la propria temporizzazione e restituisce true all'inizio di ogni intervallo di 30 Hz, altrimenti false
{
    I2C_start (); // I frame I2C vengono sincronizzati con DMX
}
I2C_run (); // una volta avviato, un frame I2C gira liberamente fino al termine
 

Se studi la scheda tecnica, potresti anche scoprire che le periferiche hardware possono essere massaggiate per fare ciò che desideri senza alcun intervento da parte della CPU.

La generazione di impulsi in uscita, ad esempio, è comune. Accendilo, imposta la periferica per spegnerlo qualche tempo dopo e dimenticalo. Di solito si trova nella stessa area generale del PWM.



Questa domanda e risposta è stata tradotta automaticamente dalla lingua inglese. Il contenuto originale è disponibile su stackexchange, che ringraziamo per la licenza cc by-sa 4.0 con cui è distribuito.
Loading...