Condizione di gara del sonno del microcontrollore


11

Dato un microcontrollore che esegue il seguente codice:

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Supponiamo che la if(has_flag)condizione sia falsa e che stiamo per eseguire l'istruzione sleep. Proprio prima di eseguire l'istruzione sleep, riceviamo un interrupt. Dopo aver lasciato l'interruzione, eseguiamo l'istruzione sleep.

Questa sequenza di esecuzione non è desiderabile perché:

  • Il microcontrollore andò a dormire invece di svegliarsi e chiamare process().
  • Il microcontrollore non può mai svegliarsi se in seguito non viene ricevuto alcun interrupt.
  • La chiamata a process()viene posticipata fino al successivo interrupt.

Come può essere scritto il codice per evitare che si verifichi questa condizione di competizione?

modificare

Alcuni microcontrollori, come ad esempio l'ATMega, hanno un bit di abilitazione Sleep che impedisce che si verifichi questa condizione (grazie Kvegaoro per averlo segnalato). JRoberts offre un'implementazione di esempio che esemplifica questo comportamento.

Altri micro, come i PIC18, non hanno questo bit e il problema persiste. Tuttavia, questi micro sono progettati in modo tale che gli interrupt possano comunque riattivare il core indipendentemente dal fatto che sia impostato il bit di abilitazione dell'interrupt globale (grazie supercat per averlo segnalato). Per tali architetture, la soluzione è disabilitare gli interrupt globali prima di andare a dormire. Se un interrupt scatta subito prima di eseguire l'istruzione sleep, il gestore degli interrupt non verrà eseguito, il core si riattiverà e, una volta riattivati ​​gli interrupt globali, verrà eseguito il gestore degli interrupt. In pseudo-codice, l'implementazione sarebbe simile a questa:

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

Questa domanda è pratica o teorica?
AndrejaKo

in teoria usi un timer che ti sveglia una volta ogni (inserisci un valore accettabile) ms e poi torna a dormire se nulla ha bisogno di fare.
Grady Player

1
Vorrei fare interrupt_flagcome int, e incrementarlo ogni volta che c'è interruzione. Quindi modificare la if(has_flag)di while (interrupts_count)sonno e poi. Tuttavia, l'interrupt potrebbe verificarsi dopo essere usciti dal ciclo while. Se questo è un problema, allora l'elaborazione nello stesso interrupt?
angelatlarge,

1
bene dipende da quale micro stai utilizzando. Se fosse un ATmega328 potresti eventualmente disabilitare la modalità sleep sull'interruzione, quindi se la condizione di gara che descrivi dovesse accadere, la funzione sleep verrebbe ignorata, ritorna indietro e processerai l'interruzione con una piccola latenza. Ma anche usare un timer per svegliarti ad un intervallo uguale o inferiore alla tua massima latenza sarebbe un'ottima soluzione
Kvegaoro

1
@TRISAbits: Su PIC 18x, l'approccio che ho descritto nella mia risposta funziona perfettamente (è il mio design normale quando uso quella parte).
supercat

Risposte:


9

Di solito esiste un qualche tipo di supporto hardware per questo caso. Ad esempio, le seiistruzioni degli AVR per abilitare gli interruttori attivano i defer fino al completamento delle seguenti istruzioni. Con esso si può fare:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

In questo caso, l'interrupt che sarebbe stato perso nell'esempio verrebbe trattenuto fino a quando il processore non completa la sua sequenza di sospensione.


Bella risposta! L'algoritmo fornito in realtà funziona davvero bene su un AVR. Grazie per il suggerimento
TRISAbits

3

Su molti microcontrollori, oltre ad essere in grado di abilitare o disabilitare particolari cause di interruzione (in genere all'interno di un modulo controller di interruzione), all'interno del core della CPU è presente un flag master che determina se le richieste di interruzione verranno accettate. Molti microcontrollori usciranno dalla modalità di sospensione se una richiesta di interruzione raggiunge il core, indipendentemente dal fatto che il core sia o meno disposto ad accettarlo effettivamente.

Su un tale progetto, un approccio semplice per ottenere un comportamento del sonno affidabile consiste nel far sì che il controllo del ciclo principale cancelli una bandiera e quindi controlli se conosce qualche motivo per cui il processore dovrebbe essere sveglio. Qualsiasi interruzione che si verifica durante quel periodo che potrebbe influire su uno di questi motivi dovrebbe impostare la bandiera. Se il loop principale non ha trovato alcuna causa per rimanere sveglio e se il flag non è impostato, il loop principale dovrebbe disabilitare gli interrupt e ricontrollare il flag [forse dopo un paio di istruzioni NOP se è possibile che un interrupt diventi in sospeso durante un'istruzione di disabilitazione-interruzione potrebbe essere elaborata dopo che il recupero dell'operando associato alla seguente istruzione è già stato eseguito]. Se la bandiera non è ancora impostata, vai a dormire.

In questo scenario, un interrupt che si verifica prima che il loop principale disabiliti gli interrupt imposterà il flag prima del test finale. Un interrupt che diventa in attesa troppo tardi per essere revisionato prima delle istruzioni di sospensione impedirà al processore di andare in sospensione. Entrambe le situazioni vanno bene.

Sleep-on-exit è talvolta un buon modello da utilizzare, ma non tutte le applicazioni lo "adattano" davvero. Ad esempio, un dispositivo con un display LCD a risparmio energetico potrebbe essere programmato più facilmente con un codice simile a:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

Se non viene premuto alcun pulsante e non succede nient'altro, non c'è motivo per cui il sistema non debba andare in modalità sleep durante l'esecuzione del get_keymetodo. Sebbene sia possibile che i tasti attivino un interrupt e gestiscano tutte le interazioni dell'interfaccia utente tramite una macchina a stati, codice come quello sopra è spesso il modo più logico per gestire i flussi dell'interfaccia utente altamente modali tipici dei piccoli microcontrollori.


Grazie supercat per l'ottima risposta. Disabilitare gli interrupt e poi andare a dormire è una soluzione fantastica a condizione che il core si sveglierà da qualsiasi fonte di interruzione indipendentemente dal fatto che il bit di interruzione globale sia impostato / azzerato. Ho dato un'occhiata allo schema hardware di interrupt PIC18 e questa soluzione avrebbe funzionato.
TRISAbits

1

Programmare il micro per svegliarsi in caso di interruzione.

I dettagli specifici varieranno a seconda del micro che si sta utilizzando.

Quindi modificare la routine main ():

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}

1
L'architettura Wake-on-interrupt è assunta nella domanda. Non credo che la tua risposta risolva la domanda / problema.
angelatlarge,

@angelatlarge Punto accettato. Ho aggiunto un esempio di codice che penso aiuti.
jwygralak67,

@ jwygralak67: Grazie per il suggerimento, ma il codice fornito sposta semplicemente il problema nella routine process (), che ora deve verificare se l'interrupt si è verificato prima di eseguire il corpo process ().
TRISAbits

2
Se l'interrupt non si è verificato, perché siamo svegli?
JRobert

1
@JRobert: Potremmo essere svegli da un precedente interrupt, completare la routine process () e quando finiamo il test if (has_flag) e subito prima del sonno, otteniamo un altro interrupt, che causa il problema che ho descritto nel domanda.
TRISAbits
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.