Il miglior modello per i microcontroller WMI (wait-for-interrupt) su Cortex (ARM)


18

Sto cercando di sviluppare software alimentato a batteria utilizzando i controller Gekko EFM (http://energymicro.com/) e vorrei che il controller si addormentasse ogni volta che non c'è nulla di utile da fare. L'istruzione WFI (Wait For Interrupt) viene utilizzata per questo scopo; metterà il processore in standby fino a quando si verifica un interrupt.

Se il sonno fosse impegnato memorizzando qualcosa da qualche parte, si potrebbero usare operazioni di esclusione del carico / esclusive del negozio per fare qualcosa del tipo:

  // dont_sleep viene caricato con 2 ogni volta che succede qualcosa
  // dovrebbe forzare il ciclo principale a scorrere almeno una volta. Se un interrupt
  // si verifica che provoca il ripristino su 2 durante la seguente istruzione,
  // il comportamento sarà come se l'interruzione avvenisse dopo di esso.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  while (! dont_sleep)
  {
    // Se si verifica l'interruzione tra la prossima istruzione e store_exclusive, non dormire
    load_exclusive (SLEEP_TRIGGER);
    if (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Se dovesse verificarsi un interrupt tra le operazioni load_exclusive e store_exclusive, l'effetto sarebbe quello di saltare store_exclusive, facendo in modo che il sistema esegua il ciclo ancora una volta (per vedere se l'interrupt aveva impostato dont_sleep). Sfortunatamente, Gekko utilizza un'istruzione WFI anziché un indirizzo di scrittura per attivare la modalità di sospensione; scrivere codice come

  if (! dont_sleep)
    WFI ();

correrebbe il rischio che si verifichi un'interruzione tra 'if' e 'wfi' e imposti dont_sleep, ma il wfi andrebbe avanti ed eseguirà comunque. Qual è il modello migliore per impedirlo? Impostare PRIMASK su 1 per impedire agli interrupt di interrompere il processore prima di eseguire WFI e cancellarlo immediatamente dopo? O c'è qualche trucco migliore?

MODIFICARE

Mi chiedo il bit dell'evento. Secondo la descrizione generale, vorrebbe che fosse destinato al supporto di più processori, ma si chiedeva se potesse funzionare qualcosa di simile al seguente:

  if (dont_sleep)
    SEV (); / * Renderanno il seguente flag di evento WFE chiaro ma non inattivo * /
  WFE ();

Ogni interrupt che imposta Don't_sleep dovrebbe anche eseguire un'istruzione SEV, quindi se l'interrupt si verifica dopo il test "if", il WFE cancellerebbe il flag di evento ma non andrebbe in sospensione. Suona come un buon paradigma?


1
L'istruzione WFI non mette in standby il core se la sua condizione di veglia è vera quando l'istruzione viene eseguita. Ad esempio se c'è un IRQ non chiarito quando viene eseguito WFI, esso agisce come NOP.
Segna il

@Mark: il problema sarebbe che se viene preso un interrupt tra "if (! Dont_sleep)" e "WFI", la condizione di interruzione non sarebbe più in sospeso quando il WFI viene eseguito, ma l'interrupt potrebbe aver impostato dont_sleep perché fatto qualcosa che giustificherebbe il ciclo principale eseguendo un'altra iterazione. Su una mia applicazione PSOC di Cypress, qualsiasi interruzione che dovrebbe causare un risveglio prolungato farebbe impallidire lo stack se il codice della linea principale stava per dormire, ma sembra piuttosto malato e capisco che ARM scoraggia tali manipolazioni dello stack.
supercat

@supercat L'interrupt può o non può essere cancellato quando WFI viene eseguito. Sta a te e quando / dove scegli di cancellare l'interruzione. Sbarazzati della variabile dont_sleep e usa un interrupt mascherato per indicare quando vuoi rimanere sveglio o dormire. Puoi semplicemente eliminare l'istruzione if tutti insieme e lasciare WFI alla fine del ciclo principale. Se hai soddisfatto tutte le richieste, cancella l'IRQ in modo da poter dormire. Se è necessario rimanere svegli, attivare l'IRQ, è mascherato in modo che non accada nulla, ma quando WFI tenta di eseguirlo, NOP.
Segna il

2
@supercat A un livello più fondamentale, sembra che tu stia cercando di mescolare un design guidato da interrupt con un design 'big main loop', che di solito non è critico in termini di tempo, spesso basato su polling e ha interruzioni minime. Mescolare questi può diventare piuttosto brutto piuttosto velocemente. Se possibile, scegliere un paradigma di progettazione o l'altro da utilizzare. Ricorda che con i moderni controller di interrupt ottieni sostanzialmente il multitasking preventivo tra gli interrupt e ciò che equivale alle code delle attività (servi un interrupt, quindi la priorità più alta successiva, ecc.). Usalo a tuo vantaggio.
Segna il

@Mark: ho sviluppato un sistema che utilizzava un PIC 18x abbastanza bene in un'applicazione alimentata a batteria; a causa delle limitazioni dello stack, non poteva gestire troppo all'interno di un interrupt, quindi la stragrande maggioranza delle cose viene gestita nel ciclo principale su una base altrettanto conveniente. Funziona principalmente abbastanza bene, anche se ci sono un paio di punti in cui le cose vengono bloccate per un secondo o due a causa di operazioni di lunga durata. Se eseguo la migrazione a un ARM, posso utilizzare un semplice RTOS per semplificare la suddivisione delle operazioni di lunga durata, ma non sono sicuro se utilizzare il multitasking preventivo o cooperativo.
supercat

Risposte:


3

Non ho capito appieno la dont_sleepcosa, ma una cosa che potresti provare è fare il "lavoro principale" nel gestore PendSV, impostato sulla priorità più bassa. Quindi pianifica un PendSV da altri gestori ogni volta che hai bisogno di fare qualcosa. Vedi qui come farlo (è per M1 ma M3 non è troppo diverso).

Un'altra cosa che potresti usare (forse insieme all'approccio precedente) è la funzione Sleep-on-exit. Se lo abiliti, il processore andrà in sospensione dopo essere uscito dall'ultimo gestore ISR, senza che tu debba chiamare WFI. Vedi alcuni esempi qui .


5
l'istruzione WFI non richiede che gli interrupt siano abilitati per riattivare il processore, i bit F e I in CPSR vengono ignorati.
Segna il

1
@Mark Devo aver perso quel bit nei documenti, hai qualche link / puntatore a riguardo? Cosa succede al segnale di interruzione che ha svegliato il core? Rimane in sospeso fino a quando gli interrupt non vengono nuovamente abilitati?
Igor Skochinsky,

Il manuale di riferimento ASM è qui: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… informazioni più specifiche per la corteccia-m3 sono qui: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a /… in breve quando un interrupt mascherato diventa in attesa il core si sveglia e continua l'operazione dopo l'istruzione WFI. Se si tentasse di emettere un altro WFI senza cancellare l'interruzione in sospeso, il WFI avrebbe funzionato come NOP (il core non avrebbe dormito poiché la condizione di riattivazione per WFI è vera).
Segna il

@Mark: Una cosa che stavo prendendo in considerazione sarebbe quella di avere un gestore di interrupt che imposta dont_sleep anche eseguire un'istruzione SEV ("Set Event"), e quindi usare WFE ("Wait For Event") anziché WFI. Gli esempi di Gekko sembrano usare WFI, ma penso che anche WFE potrebbe funzionare. qualche idea?
supercat

10

Inseriscilo in una sezione critica. Gli ISR ​​non verranno eseguiti, quindi non si corre il rischio che dont_sleep cambi prima di WFI, ma continueranno a riattivare il processore e gli ISR ​​verranno eseguiti non appena termina la sezione critica.

uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
  WFI();
ExitCriticalSection(interruptStatus);

Il tuo ambiente di sviluppo ha probabilmente funzioni di sezione critica, ma è più o meno così:

EnterCriticalSection è:

MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */

ExitCriticalSection è:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */

2
Curiosamente, alcune librerie ARM usano un'implementazione di una sezione critica che utilizza un contatore globale invece di preservare lo stato localmente. Trovo che la mente sia sbalorditiva poiché l'approccio del contatore è più complicato e funzionerà solo se tutto il codice a livello di sistema utilizza lo stesso contatore.
supercat

1
Gli interrupt non saranno disabilitati fino all'uscita dalla sezione critica? In tal caso, WFI non farà attendere indefinitamente la CPU?
Corneliu Zuzu,

1
La risposta di @Kenzi Shrimp punta a un thread di discussione Linux che risponde alla mia domanda precedente. Ho modificato la sua risposta e la tua per chiarirla.
Corneliu Zuzu,

@CorneliuZuzu Modificare la risposta di qualcun altro per interporre la tua discussione non è una grande idea. L'aggiunta del preventivo per migliorare una risposta "solo collegamento" è una questione diversa. Se hai una vera domanda sulla tua vittoria, magari ponila come domanda e collegala a questa.
Sean Houlihane,

1
@SeanHoulihane Non ho invalidato né rimosso nulla dalla sua risposta. Un breve chiarimento sul perché questo funziona non è una discussione separata. Onestamente non credo che questa risposta meriti un voto positivo senza il chiarimento del WFI, ma se lo merita di più.
Corneliu Zuzu,

7

La tua idea va bene, questo è esattamente ciò che implementa Linux. Vedi qui .

Citazione utile dal thread di discussione sopra menzionato per chiarire perché WFI funziona anche con gli interrupt disabilitati:

Se hai intenzione di restare inattivo fino al prossimo interrupt, devi fare un po 'di preparazione. Durante quella preparazione, un interrupt può diventare attivo. Tale interruzione potrebbe essere un evento di sveglia che stai cercando.

Non importa quanto sia buono il tuo codice, se non disabiliti gli interrupt, avrai sempre una corsa tra prepararti ad andare a dormire e effettivamente andare a dormire, il che si traduce in eventi di sveglia persi.

Questo è il motivo per cui tutte le CPU ARM di cui sono a conoscenza si riattivano anche se sono mascherate nella CPU principale (bit CPSR I.)

Nient'altro e dovresti dimenticare di utilizzare la modalità di inattività.


1
Ti riferisci alla disabilitazione degli interrupt al momento dell'istruzione WFI o WFE? Vedi qualche distinzione significativa tra l'uso di WFI o WFE allo scopo?
supercat

1
@supercat: utilizzerei sicuramente WFI. WFE IMO è principalmente per i suggerimenti di sincronizzazione tra i core nel sistema multicore (ad esempio facendo WFE su spinlock che fallisce ed emette SEV dopo essere uscito da spinlock). Inoltre, WFE tiene conto del flag di mascheramento degli interrupt, quindi qui non è utile come WFI. Questo modello funziona davvero bene in Linux.
Gamberetti,

2

Supponendo che:

  1. Il thread principale esegue attività in background
  2. Gli interrupt eseguono solo attività ad alta priorità e nessuna attività in background
  3. Il thread principale può essere interrotto in qualsiasi momento (normalmente non maschera gli interrupt)

Quindi la soluzione è utilizzare PRIMASK per bloccare gli interrupt tra la validazione del flag e WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();

0

Che dire della modalità Sleep on Exit? Questo si spegne automaticamente ogni volta che esce un gestore IRQ, quindi non esiste alcuna "modalità normale" in esecuzione dopo che è stata configurata. Si verifica un IRQ, si sveglia, esegue il gestore e torna a dormire. Nessun WFI necessario.


2
Come si dovrebbe affrontare meglio il fatto che il tipo di sospensione in cui dovrebbe cadere il processore potrebbe variare in base a qualcosa che accade durante un'interruzione? Ad esempio, un evento di cambio pin potrebbe indicare che potrebbero essere disponibili dati seriali e che il processore dovrebbe quindi mantenere in funzione l'oscillatore di clock principale in attesa dei dati. Se il ciclo principale cancella il flag di evento, esamina cosa sta succedendo e pone il processore in una modalità di sospensione appropriata con un WFI, quindi qualsiasi interruzione che potrebbe aver influito su quale modalità sarebbe appropriata imposterà il flag di evento ...
supercat

... e interrompere il sonno. Avere un gestore di loop principale che controlla la modalità di sospensione sembra più pulito che doversi preoccupare ad ogni interruzione. Dover "girare" quella parte del ciclo principale su ogni interruzione potrebbe non essere in modo ottimale efficiente, ma non dovrebbe essere troppo male, specialmente se tutti gli interrupt che potrebbero influenzare il comportamento del sonno colpiscono un po 'di bandiera.
supercat
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.