Domanda

Questa domanda è sulla programmazione piccoli microcontrollori, senza un sistema operativo. In particolare, mi interessa PIC al momento, ma la domanda è generale.

Ho visto più volte il seguente schema per il tempo mantenendo:

Codice Timer interrupt (ad esempio i fuochi timer ogni secondo):

...
if (sec_counter > 0)
  sec_counter--;
...

Codice Mainline (non interrupt):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

Il codice linea principale può ripetere, impostare il contatore a diversi valori (non solo secondi) e così via.

Mi sembra ci sia una condizione di competizione qui quando il compito di sec_counter nel codice mainline non è atomica. Ad esempio, in PIC18 l'assegnazione viene tradotto a 4 dichiarazioni ASM (loading ogni byte al tempo e selezionando il byte destro da banco di memoria prima). Se il codice di interruzione viene nel mezzo di questo, il valore finale potrebbe essere danneggiato.

Curiosamente, se il valore assegnato è inferiore a 256, l'assegnazione è atomico, quindi non c'è nessun problema.

Ho ragione su questo problema? Quali modelli si usa per implementare correttamente tale comportamento? Vedo diverse opzioni:

  • Disattiva interrompe prima di ogni incarico di sec_counter e attivare dopo - questo non è abbastanza
  • Non utilizzare un interrupt, ma un timer separato che viene avviato e poi interrogato. Questo è pulito, ma utilizza un intero timer (nel caso precedente il timer di cottura 1 secondo può essere utilizzato per altri scopi).

Tutte le altre idee?

È stato utile?

Soluzione

L'architettura PIC è atomica, come si arriva. Assicura che tutte le operazioni di lettura-modifica-scrittura a un file di memoria sono 'atomica'. Anche se ci vuole 4-clock per eseguire l'intera lettura-modifica-scrittura, tutti e 4 i-orologi sono consumati in una singola istruzione e la successiva istruzione usa il successivo ciclo di 4-clock. E 'il modo in cui la pipeline funziona. In 8-orologi, due istruzioni sono in cantiere.

Se il valore è maggiore di 8 bit, diventa un problema come il PIC è una macchina a 8 bit e operandi più grandi sono gestite in più istruzioni. Che introdurrà le questioni atomiche.

Altri suggerimenti

Scrivi il valore quindi controllare che sia il valore richiesto sembrerebbe essere l'alternativa più semplice.

do {
 sec_counter = value;
} while (sec_counter != value);

A proposito si dovrebbe fare la variabile volatile, se si utilizza C.

Se avete bisogno di leggere il valore allora si può leggere due volte.

do {
    value = sec_counter;
} while (value != sec_counter);

È assolutamente necessario disattivare l'allarme prima di impostare il contatore. Brutto come può essere, è necessario. È buona norma disabilitare SEMPRE l'interrupt prima di configurare registri hardware o software variabili che influenzano il metodo ISR. Se si scrive in C, si dovrebbe prendere in considerazione tutte le operazioni di cui alle non-atomica. Se si scopre che si deve guardare le generati assemblaggio troppe volte, allora può essere meglio abbandonare C e programmare in assembly. Nella mia esperienza, questo è raramente il caso.

Per quanto riguarda il problema discusso, questo è quello che suggerisco:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

e l'impostazione del contatore:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

Hai bisogno di una variabile in più ed è meglio per avvolgere tutto in una funzione:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

In questo modo è astratto il metodo di avvio (e nascondere la bruttezza, se necessario). Per esempio è possibile modificare facilmente per avviare un timer hardware senza compromettere i chiamanti del metodo.

A causa accessi alla variabile sec_counter non sono atomiche, non c'è davvero nessun modo per evitare la disabilitazione interrupt prima di accedere questa variabile nel codice mainline e il ripristino dello stato di interruzione dopo l'accesso, se si desidera un comportamento deterministico. Questo sarebbe probabilmente una scelta migliore di dedicare un timer HW per questo compito (a meno che non si dispone di un surplus di temporizzatori, nel qual caso si potrebbe anche usare uno).

Se si scarica gratis TCP Microchip / IP Stack ci sono le routine in là che utilizzano un overflow timer per tenere traccia del tempo trascorso. In particolare "tick.c" e "tick.h". Basta copiare i file sopra al vostro progetto.

All'interno quei file si può vedere come lo fanno.

Non è così curioso di sapere i meno di 256 mosse essere atomica - lo spostamento di un valore di 8 bit è uno codice operativo in modo che sia come atomica come si ottiene.

La soluzione migliore a tale microcontrollore come il PIC è quello di disabilitare gli interrupt prima di modificare il valore del timer. È anche possibile controllare il valore del flag di interrupt quando si modifica la variabile nel ciclo principale e gestire se si desidera. Ne fanno una funzione che cambia il valore della variabile e si potrebbe anche chiamare dalla ISR pure.

Bene, che cosa fa il codice assembly aspetto di confronto come?

prese per conto che conta verso il basso, e che è solo uno zero confronto, dovrebbe essere sicuro se esso controlla prima l'MSB, quindi la LSB. Ci potrebbe essere la corruzione, ma in realtà non importa se si tratta in mezzo tra 0x100 e 0xFF e il valore corrotto confrontare è 0x1ff.

Il modo in cui si utilizza il timer ora, non conterà interi secondi in ogni caso, perché si potrebbe cambiare nel bel mezzo di un ciclo. Quindi, se non si cura su di esso. Il modo migliore, a mio parere, sarebbe quello di leggere il valore, e quindi confrontare solo la differenza. Ci vogliono un paio di PO di più, ma non ha problemi multi-threading. (Dal momento che il timer ha la priorità)

Se siete più rigoroso circa il valore del tempo, vorrei disattivare automaticamente il timer una volta che il conto alla rovescia a 0, e cancellare il contatore interno del timer e attivare una volta che ne avete bisogno.

Spostare la porzione di codice che sarebbe sul principale () per un corretto funzionamento, e farlo condizionatamente chiamato dal ISR.

Inoltre, per evitare qualsiasi tipo di ritardare o zecche mancante, scegliere questa ISR timer per essere un alto-prio interrupt (PIC18 ha due livelli).

Un approccio è quello di avere un interrupt mantenere una variabile di byte, e avere qualcos'altro che viene convocata almeno una volta ogni 256 volte il contatore viene colpito; fare qualcosa di simile:

// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;

void poll_counter(void)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

Una leggera variazione, se non ti dispiace costringendo bancone del interrupt di rimanere in sincronia con il LSB del vostro grande contatore:

ul big_ctr;
void poll_counter(void)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}

Nessuno ha affrontato il tema del lettura registri hardware più byte (ad esempio un timer. Il timer potrebbe rotolare e incrementare il suo secondo byte, mentre si sta leggendo.

Say è 0x0001ffff e lo si legge. Si potrebbe ottenere 0x0010ffff o 0x00010000.

Il registro periferica a 16 bit è volatili al codice.

Per volatili "variabili", io uso la tecnica della doppia lettura.

do {
       t = timer;
 } while (t != timer);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top