Sezioni critiche su Cortex-M3


10

Mi chiedo qualcosa sull'implementazione di sezioni di codice critico su una Cortex-M3 in cui non sono consentite eccezioni a causa di vincoli di temporizzazione o problemi di concorrenza.

Nel mio caso, sto utilizzando un LPC1758 e ho un ricetrasmettitore TI CC2500 a bordo. Il CC2500 ha pin che possono essere usati come linee di interruzione per i dati nel buffer RX e spazio libero nel buffer TX.

Ad esempio, voglio avere un buffer TX nella SRAM della mia MCU e quando c'è spazio libero nel buffer TX del ricetrasmettitore, voglio scrivere questi dati lì. Ma la routine che inserisce i dati nel buffer SRAM non può ovviamente essere interrotta dall'interruzione di spazio libero in TX. Quindi quello che voglio fare è disabilitare temporaneamente gli interrupt mentre si esegue questa procedura di riempimento di questo buffer ma fare in modo che eventuali interruzioni che si verificano durante questa procedura vengano eseguite al termine.

Come viene fatto meglio su Cortex-M3?

Risposte:


11

Cortex M3 supporta una coppia utile di operazioni (comuni anche in molte altre macchine) denominate "Load-Exclusive" (LDREX) e "Store-Exclusive" (STREX). Concettualmente, l'operazione LDREX esegue un caricamento, inoltre imposta un hardware speciale per osservare se la posizione che è stata caricata potrebbe essere scritta da qualcos'altro. L'esecuzione di uno STREX all'indirizzo utilizzato dall'ultimo LDREX causerà la scrittura di tale indirizzo solo se nient'altro lo ha scritto per primo . L'istruzione STREX caricherà un registro con 0 se il negozio ha avuto luogo o 1 se è stato interrotto.

Si noti che STREX è spesso pessimista. Esistono diverse situazioni in cui potrebbe decidere di non eseguire il negozio anche se la posizione in questione non è stata effettivamente toccata. Ad esempio, un interruzione tra un LDREX e STREX provocherà che lo STREX presuma che la posizione sotto osservazione potrebbe essere stata colpita. Per questo motivo, in genere è una buona idea ridurre al minimo la quantità di codice tra LDREX e STREX. Ad esempio, considera qualcosa di simile al seguente:

inline void safe_increment (uint32_t * addr)
{
  uint32_t new_value;
  fare
  {
    new_value = __ldrex (addr) + 1;
  } while (__ strex (new_value, addr));
}

che si compila in qualcosa del tipo:

; Supponiamo che R0 contenga l'indirizzo in questione; r1 cestinato
lp:
  ldrex r1, [r0]
  aggiungi r1, r1, # 1
  strex r1, r1, [r0]
  cmp r1, # 0; Test se diverso da zero
  bne lp
  .. il codice continua

La maggior parte delle volte che il codice viene eseguito, tra LDREX e STREX non accadrà nulla per "disturbarli", quindi STREX avrà successo senza ulteriori indugi. Se, tuttavia, si verifica un interruzione immediatamente dopo l'istruzione LDREX o ADD, lo STREX non eseguirà il negozio, ma il codice tornerà a leggere il valore (possibilmente aggiornato) di [r0] e calcolerà un nuovo valore incrementato basato su quello.

L'uso di LDREX / STREX per formare operazioni come safe_increment rende possibile non solo la gestione di sezioni critiche, ma anche in molti casi per evitarne la necessità.


Quindi non c'è modo di "bloccare" gli interrupt in modo che possano essere nuovamente serviti una volta sbloccati? Mi rendo conto che questa è probabilmente una soluzione non elegante anche se possibile, ma voglio solo saperne di più sulla gestione degli interrupt ARM.
Emil Eriksson,

3
È possibile disabilitare gli interrupt e su Cortex-M0 spesso non c'è alternativa pratica a farlo. Ritengo che l'approccio LDREX / STREX sia più pulito rispetto alla disabilitazione degli interrupt, anche se, in molti casi, non importa davvero (penso che abilitare e disabilitare finiscano per essere un ciclo ciascuno, e disabilitare gli interrupt per cinque cicli probabilmente non è un grosso problema) . Si noti che un approccio ldrex / strex funzionerà se il codice viene migrato su una CPU multi-core, mentre un approccio che disabilita gli interrupt non lo farà. Inoltre, alcuni RTOS eseguono il codice con autorizzazioni ridotte che non possono disabilitare gli interrupt.
supercat

Probabilmente finirò comunque con FreeRTOS, quindi non lo farò da solo, ma mi piacerebbe comunque imparare. Quale metodo di disabilitazione degli interrupt dovrei usare per bloccare gli interrupt come descritto anziché scartare eventuali interruzioni che si verificano durante la procedura? Come lo farei se volessi scartarli?
Emil Eriksson,

L'unica risposta di cui sopra non può essere considerata attendibile perché nel codice associato manca una parentesi: while(STREXW(new_value, addr); come possiamo credere a ciò che dici sia corretto se il tuo codice non verrà nemmeno compilato?

@Tim: mi dispiace che la mia digitazione non sia perfetta; Non ho il codice reale che ho scritto utile per il confronto, quindi non ricordo se il sistema che stavo usando usava STREXW o __STREXW, ma un riferimento al compilatore elenca __strex come intrinseco (a differenza di STREXW che è limitato a STREX a 32 bit, gli usi intrinseci di __strex genera uno STREXB, STREXH o STREX a seconda della dimensione del puntatore fornita)
supercat

4

Sembra che tu abbia bisogno di alcuni buffer circolari o FIFO nel tuo software MCU. Tracciando due indici o puntatori nell'array per la lettura e la scrittura, è possibile avere sia in primo piano che in background l'accesso allo stesso buffer senza interferenze.

Il codice di primo piano è libero di scrivere nel buffer circolare in qualsiasi momento. Inserisce i dati sul puntatore di scrittura, quindi incrementa il puntatore di scrittura.

Il codice in background (gestione degli interrupt) consuma i dati dal puntatore di lettura e incrementa il puntatore di lettura.

Quando i puntatori di lettura e scrittura sono uguali, il buffer è vuoto e il processo in background non invia dati. Quando il buffer è pieno, il processo in primo piano si rifiuta di scrivere più (o può sovrascrivere i vecchi dati, a seconda delle esigenze).

L'uso di buffer circolari per disaccoppiare lettori e scrittori dovrebbe eliminare la necessità di disabilitare gli interrupt.


Sì, ovviamente userò i buffer circolari ma l'incremento e il decremento non sono operazioni atomiche.
Emil Eriksson,

3
@Emil: non devono esserlo. Per un classico buffer circolare, con due puntatori e uno slot "inutilizzabile", tutto ciò che è necessario è che le scritture di memoria siano atomiche e applicate nell'ordine. Il lettore possiede un puntatore, lo scrittore possiede l'altro e, sebbene entrambi possano leggere entrambi i puntatori, solo il proprietario del puntatore scrive il proprio puntatore. A quel punto, tutto ciò di cui hai bisogno sono scritture atomiche in ordine.
John R. Strohm,

2

Non riesco a ricordare la posizione esatta, ma nelle librerie che provengono da ARM (non TI, ARM, dovrebbe essere in CMSIS o qualcosa del genere, uso ST ma ricordo di aver letto da qualche parte che questo file proviene da ARM, quindi dovresti averlo anche tu ) esiste un'opzione di disabilitazione dell'interrupt globale. È una chiamata di funzione. (Non sono al lavoro ma domani cercherò la funzione esatta). Vorrei concludere con un bel nome nel tuo sistema e disabilitare gli interrupt, fare le tue cose e abilitarlo di nuovo. Detto questo, l'opzione migliore sarebbe l'implementazione di un semaforo o di una struttura di coda invece della disabilitazione dell'interrupt globale.


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.