Czy przypisania wskaźników funkcji są atomowe w Arduino?

11

Poniższe fragmenty kodu źródłowego biblioteki 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();                                                                                            
}

Pytanie: jeśli minutnik już działa, a główny program wywołuje attachInterrupt(), czy przerwanie czasomierza może nastąpić podczas przypisywania wskaźnika funkcji isrCallback = isr;? Czy przy szczęśliwym czasie Timer1.isrCallback();wskaźnik funkcji składałby się częściowo ze starego, a częściowo z nowego adresu, powodując skok ISR do fałszywej lokalizacji?

Myślę, że może tak być, ponieważ wskaźniki funkcji są z pewnością szersze niż 1 bajt, a dostęp do danych> 1 bajtowych nie jest atomowy. Możliwe obejścia to:

  • Zawsze zadzwoń, detachInterrupt()aby upewnić się, że stoper nie jest uruchomiony, zanim zadzwonisz attachInterrupt(), tj. Wyjaśnij dokumenty Timera 1.
  • Lub zmodyfikuj Timer1, wyłączając tymczasowo przepełnienie timera isrCallback = isr;

Czy to ma sens, czy jest coś Timer1, co brakowało w przypisaniach do źródeł lub wskaźników funkcji?

Joonas Pulakka
źródło

Odpowiedzi:

7

Spójrz na kod attachInterrupt () i detachInterrupt () w /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(cóż, i tak tam są na komputerze Mac. Struktura plików Arduino w innych systemach operacyjnych prawdopodobnie wygląda podobnie na niższych poziomach ścieżki).

Wygląda na to, że attachInterrupt () zakłada, że ​​dane przerwanie nie jest jeszcze włączone, ponieważ zapisuje wskaźnik funkcji bez żadnych środków ostrożności. Zauważ, że detachInterrupts () wyłącza przerwanie celu przed zapisaniem wskaźnika NULL do jego wektora. Więc przynajmniej użyłbym detachInterrupt()/ attachInterrupt()pary

Sam chciałbym uruchomić taki kod w sekcji krytycznej. Wygląda na to, że twój pierwszy sposób (odłącz, a następnie dołącz) zadziałałby, chociaż nie mogę być pewien, że nie umknie niestety przerwanie czasowe. Arkusz danych twojego MCU może mieć więcej do powiedzenia na ten temat. Ale nie jestem też pewien, czy globalny cli()/ też sei()nie umknie. Arkusz danych ATMega2560, sekcja 6.8, mówi: „Podczas korzystania z instrukcji SEI w celu włączenia przerwań, instrukcja po SEI zostanie wykonana przed wszelkimi oczekującymi przerwaniami, jak pokazano w tym przykładzie”, co wydaje się sugerować, że może buforować przerwanie podczas przerwania Są wyłączone.

JRobert
źródło
Rzeczywiście warto nurkować w źródłach :) Mechanizm przerwania dołączania / odłączania TimerOne wydaje się być wykonywany podobnie jak standardowy (WInterrupt), a zatem ma te same „funkcje”.
Joonas Pulakka
0

Wygląda na to, że masz rację. Logiczną rzeczą byłoby wyłączenie przerwań w taki sposób, abyś nie włączał ich ponownie, gdyby były one w pierwszej kolejności wyłączone. Na przykład:

  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

„W przypadku użycia instrukcji SEI do włączenia przerwań instrukcja po SEI zostanie wykonana przed wszelkimi oczekującymi przerwaniami, jak pokazano w tym przykładzie”

Ma to na celu umożliwienie pisania takiego kodu:

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

Bez tego przepisu możesz mieć przerwę między tymi dwiema liniami, a zatem spać w nieskończoność (ponieważ przerwanie, które miało cię obudzić, nastąpiło przed zaśnięciem ). Zapewnienie w procesorze, że następna instrukcja po przerwie staje się włączona, jeśli wcześniej nie były włączone, jest zawsze wykonywana, chroni przed tym.

Nick Gammon
źródło
Czy nie byłoby bardziej wydajne TIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);? Oszczędza jeden rejestr procesora i nie przyczynia się do opóźnienia.
Edgar Bonet
Co z pozostałymi bitami, takimi jak ICIE1, OCIE1B, OCIE1A? Rozumiem opóźnienie, ale przerwania powinny być w stanie poradzić sobie z kilkoma cyklami zegara, które są niedostępne. Mogą występować warunki wyścigowe. Przerwanie może być już uruchomione (tj. Ustawiona jest flaga procesora), a wyłączenie TIMSK1 może nie zatrzymać obsługi. Może być również konieczne zresetowanie TOV1 (przez napisanie 1 w TIFR1), aby upewnić się, że tak się nie stanie. Niepewność powoduje, że myślę, że wyłączenie przerwań na całym świecie jest bezpieczniejsze.
Nick Gammon