Interrompere la gestione in microcontrollori ed esempio FSM


9

Domanda iniziale

Ho una domanda generale sulla gestione degli interrupt nei microcontrollori. Sto usando MSP430, ma penso che la domanda possa essere estesa ad altri UC. Vorrei sapere se è o meno una buona pratica abilitare / disabilitare gli interrupt frequentemente lungo il codice. Voglio dire, se ho una parte di codice che non sarà sensibile agli interrupt (o peggio ancora, non devo ascoltare gli interrupt, per qualsiasi motivo), è meglio:

  • Disabilitare gli interrupt prima e quindi riattivarli dopo la sezione critica.
  • Inserisci un flag all'interno del rispettivo ISR e (invece di disabilitare l'interrupt), imposta il flag su false prima della sezione critica e ripristinandolo su true, subito dopo. Per impedire l'esecuzione del codice ISR.
  • Nessuno dei due, quindi i suggerimenti sono benvenuti!

Aggiornamento: interrupt e grafici di stato

Fornirò una situazione specifica. Supponiamo di voler implementare un diagramma di stato, che è composto da 4 blocchi:

  1. Transizioni / Effect.
  2. Condizioni d'uscita
  3. Attività di ingresso.
  4. Fai attività.

Questo è ciò che un professore ci ha insegnato all'università. Probabilmente, non il modo migliore per farlo è seguendo questo schema:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Supponiamo anche che, per esempio STATE_A, voglio essere sensibile a un interrupt proveniente da una serie di pulsanti (con sistema di debouce, ecc. Ecc.). Quando qualcuno preme uno di questi pulsanti, viene generato un interrupt e il flag relativo alla porta di input viene copiato in una variabile buttonPressed. Se il debounce è impostato in qualche modo su 200 ms (timer del watchdog, timer, contatore, ...) siamo sicuri che buttonPressednon può essere aggiornato con un nuovo valore prima di 200 ms. Questo è quello che ti sto chiedendo (e io stesso :) ovviamente)

Devo abilitare l'interruzione nell'attività DO STATE_Ae disabilitarla prima di partire?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

In un modo in cui sono sicuro che la prossima volta che eseguo il blocco 1 (transizione / effetti) alla prossima iterazione, sono sicuro che le condizioni verificate lungo le transizioni non provengono da un interrupt successivo che ha sovrascritto il valore precedente di buttonPressedquello I necessario (anche se è impossibile che ciò accada perché devono trascorrere 250 ms).


3
È difficile dare una raccomandazione senza sapere di più sulla tua situazione. Ma a volte è necessario disabilitare gli interrupt nei sistemi integrati. È preferibile mantenere le interruzioni disabilitate solo per brevi periodi di tempo in modo da non perdere le interruzioni. Potrebbe essere possibile disabilitare solo determinati interrupt anziché tutti gli interrupt. Non ricordo di aver mai usato la tecnica flag-inside-ISR come hai descritto, quindi sono scettico sul fatto che sia la soluzione migliore.
kkrambo,

Risposte:


17

La prima tattica è quella di progettare il firmware generale in modo che sia possibile che si verifichino interruzioni in qualsiasi momento. Dover disattivare gli interrupt in modo che il codice di primo piano possa eseguire una sequenza atomica dovrebbe essere fatto con parsimonia. C'è spesso un modo architettonico attorno ad esso.

Tuttavia, la macchina è lì per servirti, non viceversa. Le regole generali sono solo per impedire ai programmatori malvagi di scrivere codici davvero cattivi. È molto meglio capire esattamente come funziona la macchina e quindi progettare un buon modo per sfruttare quelle capacità per eseguire l'attività desiderata.

Tieni presente che, a meno che tu non sia molto attento a cicli o posizioni di memoria (può certamente succedere), che altrimenti desideri ottimizzare per chiarezza e manutenibilità del codice. Ad esempio, se si dispone di una macchina a 16 bit che aggiorna un contatore a 32 bit in un'interruzione di tick di clock, è necessario assicurarsi che quando il codice di primo piano legge il contatore che le due metà di esso siano coerenti. Un modo è disattivare gli interrupt, leggere le due parole e quindi riattivare gli interrupt. Se la latenza di interruzione non è critica, allora è perfettamente accettabile.

Nel caso in cui sia necessario avere una bassa latenza di interruzione, è possibile, ad esempio, leggere la parola alta, leggere la parola bassa, leggere di nuovo la parola alta e ripetere se è cambiata. Questo rallenta un po 'il codice di primo piano, ma non aggiunge alcuna latenza di interruzione. Ci sono vari piccoli trucchi. Un altro potrebbe essere quello di impostare un flag nella routine di interrupt che indica che il contatore deve essere incrementato, quindi farlo nel loop degli eventi principali nel codice di primo piano. Funziona bene se la frequenza di interruzione del contatore è abbastanza lenta in modo che il ciclo di eventi esegua l'incremento prima che il flag venga nuovamente impostato.

Oppure, anziché una bandiera, utilizzare un contatore di una parola. Il codice di primo piano mantiene una variabile separata che contiene l'ultimo contatore a cui ha aggiornato il sistema. Esegue una sottrazione senza segno del contatore attivo meno il valore salvato per determinare il numero di tick per volta che deve gestire. Ciò consente al codice di primo piano di perdere fino a 2 eventi N -1 alla volta, dove N è il numero di bit in una parola nativa che l'ALU può gestire atomicamente.

Ogni metodo ha i suoi vantaggi e svantaggi. Non esiste una sola risposta corretta. Ancora una volta, capisci come funziona la macchina, quindi non avrai bisogno di regole pratiche.


7

Se è necessaria una sezione critica, è necessario assicurarsi che l'operazione di protezione della sezione critica sia atomica e non possa essere interrotta.

Quindi disabilitare gli interrupt, che è più comunemente gestito da una singola istruzione del processore (e chiamato usando una funzione intrinseca del compilatore), è una delle scommesse più sicure che puoi fare.

A seconda del sistema in uso, potrebbero esserci alcuni problemi, ad esempio un interrupt potrebbe perdersi. Alcuni microcontrollori impostano i flag indipendentemente dallo stato dell'abilitazione dell'interrupt globale e, dopo aver lasciato la sezione critica, gli interrupt vengono eseguiti e vengono semplicemente ritardati. Ma se si dispone di un interrupt che si verifica a una velocità elevata, è possibile perdere una seconda volta che si è verificato se si bloccano gli interrupt per un tempo troppo lungo.

Se la sezione critica richiede che non venga eseguito un solo interrupt, ma che l'altro debba essere eseguito, l'altro approccio sembra fattibile.

Mi ritrovo a programmare le routine del servizio di interruzione il più breve possibile. Quindi hanno appena impostato un flag che viene quindi verificato durante le normali routine del programma. Ma se lo fai, fai attenzione alle condizioni di gara mentre aspetti che venga impostata quella bandiera.

Ci sono molte opzioni e sicuramente non una sola risposta corretta a questo, questo è un argomento che richiede un'attenta progettazione e merita un po 'più di pensiero rispetto ad altre cose.


5

Se hai stabilito che una sezione di codice deve essere eseguita ininterrottamente, tranne in circostanze insolite, dovresti disabilitare gli interrupt per la durata minima possibile per completare l'attività e riattivarli dopo.

Inserisci un flag all'interno del rispettivo ISR e (invece di disabilitare l'interrupt), imposta il flag su false prima della sezione critica e ripristinandolo su true, subito dopo. Per impedire l'esecuzione del codice ISR.

Ciò consentirebbe comunque che si verifichi un interrupt, un salto di codice, un controllo, quindi un ritorno. Se il tuo codice è in grado di gestire questa interruzione, allora probabilmente dovresti semplicemente progettare l'ISR per impostare un flag piuttosto che eseguire il controllo - sarebbe più breve - e gestire il flag nella tua normale routine di codice. Sembra che qualcuno abbia inserito troppo codice nell'interrupt e che stiano usando l'interrupt per eseguire azioni più lunghe che dovrebbero avvenire nel codice normale.

Se hai a che fare con codice in cui gli interrupt sono lunghi, un flag come suggerisci potrebbe risolvere il problema, ma sarebbe comunque meglio disabilitare gli interrupt se non riesci a ricodificare il codice per eliminare il codice eccessivo nell'interrupt .

Il problema principale nel farlo in modo bandiera è che non si esegue affatto l'interruzione, che potrebbe avere ripercussioni in seguito. La maggior parte dei microcontroller monitorerà i flag di interruzione anche quando gli interrupt sono disabilitati a livello globale, quindi eseguirà l'interruzione quando si riattivano gli interrupt:

  • Se durante la sezione critica non si sono verificati interruzioni, non ne viene eseguita nessuna dopo.
  • Se si verifica un interrupt durante la sezione critica, ne viene eseguito uno dopo.
  • Se si verificano più interrupt durante la sezione critica, solo uno viene eseguito dopo.

Se il tuo sistema è complesso e deve tracciare gli interrupt in modo più completo, dovrai progettare un sistema più complicato per tracciare gli interrupt e operare di conseguenza.

Tuttavia, se si progettano sempre gli interrupt per eseguire il lavoro minimo necessario per raggiungere la loro funzione e ritardare tutto il resto all'elaborazione regolare, gli interrupt avranno raramente un impatto negativo sull'altro codice. Avere l'acquisizione di interruzione o rilasciare i dati, se necessario, o impostare / reimpostare le uscite, ecc., Se necessario, quindi fare in modo che il percorso del codice principale presti attenzione ai flag, ai buffer e alle variabili che influiscono sull'interruzione in modo che l'elaborazione lunga possa essere eseguita nel ciclo principale, piuttosto che l'interruzione.

Questo dovrebbe eliminare tutte le situazioni, tranne pochissime, in cui potresti aver bisogno di una sezione di codice ininterrotta.


Ho aggiornato il post per spiegare meglio la situazione in cui sto lavorando :)
Umberto D.

1
Nel tuo codice di esempio, prenderei in considerazione la disabilitazione del pulsante di interruzione specifico quando non necessario e lo abilito quando necessario. Fare questo spesso non è un problema di progettazione. Lascia gli interrupt globali attivi in ​​modo da poter aggiungere altri interrupt al codice in seguito, se necessario. In alternativa, reimposta il flag quando vai allo stato A e altrimenti lo ignori. Se il pulsante viene premuto e la bandiera impostata, a chi importa? Ignoralo finché non torni allo stato A.
Adam Davis,

Sì! Potrebbe essere una soluzione perché nella progettazione attuale vado spesso in LPM3 (MSP430) con gli interrupt globali abilitati e esco da LPM3, riprendendo l'esecuzione, non appena viene rilevato un interrupt. Quindi una soluzione è quella che hai presentato che penso sia riportata nella seconda parte del codice: abilita gli interrupt non appena inizio a svolgere l'attività do di uno stato che ne ha bisogno e disabilita prima di passare al blocco delle transizioni. Un'altra possibile soluzione potrebbe essere quella di disabilitare l'interrupt appena prima di lasciare il "Blocco attività" e riattivare dopo (quando?)?
Umberto D.

1

Mettere una bandiera all'interno dell'ISR come descrivi probabilmente non funzionerà poiché stai praticamente ignorando l'evento che ha innescato l'interruzione. Disabilitare gli interrupt a livello globale è di solito una scelta migliore. Come altri hanno già detto, non dovresti farlo molto spesso. Tieni presente che qualsiasi lettura o scrittura eseguita tramite una singola istruzione non deve essere protetta poiché l'istruzione viene eseguita o no.

Molto dipende dal tipo di risorse che stai cercando di condividere. Se stai fornendo dati dall'ISR al programma principale (o viceversa), potresti implementare qualcosa come un buffer FIFO. L'unica operazione atomica sarebbe l'aggiornamento dei puntatori di lettura e scrittura, il che riduce al minimo il tempo impiegato con gli interrupt disabilitati.


0

C'è una sottile differenza che devi prendere in considerazione. È possibile scegliere di "ritardare" la gestione di un interrupt o "ignorare" e interrompere.

Spesso nel codice si dice che disabilitiamo l'interrupt. Ciò che probabilmente accadrà, a causa delle funzioni hardware, è che una volta attivati ​​gli interrupt si innescherà. Questo in un certo senso sta ritardando l'interruzione. Affinché il sistema funzioni in modo affidabile, è necessario conoscere il tempo massimo in cui possiamo ritardare la gestione di questi interrupt. E quindi assicurarsi che tutti i casi in cui gli interrupt sono disabilitati saranno finiti in un tempo più breve.

A volte vogliamo ignorare gli interrupt. Il modo migliore potrebbe essere quello di bloccare l'interrupt a livello hardware. C'è spesso un controller di interrupt o simile in cui potremmo dire quali input dovrebbero generare interrupt.

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.