Le assegnazioni di puntatori di funzioni sono atomiche in Arduino?


11

I seguenti frammenti provengono dal codice sorgente della libreria TimerOne :

// TimerOne.h:
void (*isrCallback)();

// TimerOne.cpp:
ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  Timer1.isrCallback();
}

// TimerOne.cpp:
void TimerOne::attachInterrupt(void (*isr)(), long microseconds)
{
  if(microseconds > 0) setPeriod(microseconds);
  isrCallback = isr; // register the user's callback with the real ISR
  TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
  resume();                                                                                            
}

La domanda: se il timer è già in esecuzione e il programma principale chiama attachInterrupt(), potrebbe verificarsi l'interruzione del timer durante l'assegnazione del puntatore a funzione isrCallback = isr;? Quindi, con un tempismo fortunato, il Timer1.isrCallback();puntatore a funzione consisterebbe in parte nel vecchio e in parte nel nuovo indirizzo, causando il salto ISR in una posizione fasulla?

Suppongo che potrebbe essere così, dal momento che i puntatori a funzione sono certamente più ampi di 1 byte e l'accesso a dati> 1 byte non è atomico. Le possibili soluzioni alternative potrebbero essere:

  • Chiamare sempre detachInterrupt()per assicurarsi che il timer non sia in esecuzione, prima di chiamare attachInterrupt(), ad esempio chiarire i documenti Timer1.
  • In alternativa, modificare Timer1, disabilitando temporaneamente le interruzioni di overflow del timer poco prima isrCallback = isr;

Ha senso o c'è qualcosa nelle Timer1fonti o nelle assegnazioni di puntatori a funzioni che mi sono perso?

Risposte:


7

Dai un'occhiata al codice per attachInterrupt () e detachInterrupt () in /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(beh, è ​​lì che si trovano su un Mac, comunque. La struttura dei file Arduino su altri sistemi operativi probabilmente sembra simile nei livelli inferiori del percorso).

Sembra che attachInterrupt () presuma che l'interruzione in questione non sia ancora abilitata perché scrive il puntatore alla funzione senza prendere precauzioni. Nota che detachInterrupts () disabilita l'interrupt di destinazione prima di scrivere un puntatore NULL sul suo vettore. Quindi userei almeno un detachInterrupt()/ attachInterrupt()paio

Vorrei eseguire questo codice in una sezione critica, me stesso. Sembra che il tuo primo modo (stacca, quindi attacca) funzionerebbe, anche se non posso essere sicuro che non possa perdere un interruzione purtroppo temporizzato. Il foglio dati per il tuo MCU potrebbe avere altro da dire al riguardo. Ma non sono nemmeno sicuro a questo punto, che un globale cli()/ sei()non mi mancherebbe neanche. Il foglio dati ATMega2560, sezione 6.8, dice "Quando si utilizza l'istruzione SEI per abilitare gli interrupt, l'istruzione che segue SEI verrà eseguita prima di qualsiasi interruzione in sospeso, come mostrato in questo esempio", sembrando implicare che può bufferizzare un interrupt mentre gli interrupt sono spenti.


È davvero utile immergersi alle fonti :) Il meccanismo di interruzione attacco / distacco di TimerOne sembra essere fatto in modo simile allo standard (WInterrupt) e quindi ha le stesse "caratteristiche".
Joonas Pulakka,

0

Sembra che tu abbia un punto. La cosa logica da fare sarebbe disabilitare gli interrupt in modo tale da non riattivarli se in primo luogo erano disabilitati. Per esempio:

  uint8_t oldSREG = SREG;  // remember if interrupts are on
  cli();                   // make the next line interruptible
  isrCallback = isr;       // register the user's callback with the real ISR
  SREG = oldSREG;          // turn interrupts back on, if they were on before

"Quando si utilizza l'istruzione SEI per abilitare gli interrupt, l'istruzione che segue SEI verrà eseguita prima di qualsiasi interruzione in sospeso, come mostrato in questo esempio"

L'intenzione è di farti scrivere codice in questo modo:

  sei ();         // enable interrupts
  sleep_cpu ();   // sleep

Senza quella disposizione potresti ottenere un interrupt tra quelle due linee e quindi dormire indefinitamente (perché l'interrupt che ti avrebbe svegliato si è verificato prima di dormire). La disposizione nel processore secondo cui l'istruzione successiva, dopo gli interrupt viene abilitata se non sono stati abilitati in precedenza, viene sempre eseguita, protegge da ciò.


Non sarebbe più efficiente TIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);? Risparmia un registro della CPU e non contribuisce alla latenza di interrupt.
Edgar Bonet,

E gli altri bit, come ICIE1, OCIE1B, OCIE1A? Comprendo la latenza, ma gli interrupt dovrebbero essere in grado di far fronte a un paio di cicli di clock di non essere disponibili. Potrebbe esserci una condizione di gara. L'interruzione potrebbe essere già innescata (ovvero è impostato il flag nella CPU) e la disattivazione di TIMSK1 potrebbe non impedirne la gestione. Potrebbe anche essere necessario ripristinare TOV1 (scrivendo 1 su di esso in TIFR1) per assicurarsi che ciò non accada. L'incertezza lì mi porta a pensare che disattivare le interruzioni a livello globale sia il corso più sicuro.
Nick Gammon
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.