Używaj AVR Watchdog jak zwykłego ISR

17

Próbuję owinąć głowę wokół timera nadzorującego w serii ATTinyX5. Więc rzeczy, które przeczytałem, sprawiły, że wydawało się, że możesz go użyć do zrobienia czegoś konkretnego zawsze N ​​sekund, ale nigdy tak naprawdę nie pokazał, jak to zrobić. Inni sprawiali wrażenie, jakby resetował tylko układ, chyba że coś w kodzie resetowania jest w międzyczasie liczone (co wydaje się być „normalnym” użyciem).

Czy jest jakiś sposób korzystania z WDT, tak jak TIMER1_COMPA_vect lub podobny. Zauważyłem, że ma on tryb 1-sekundowego limitu czasu i naprawdę chciałbym móc go użyć, aby coś działo się co 1 sekundę w moim kodzie (i najlepiej spać pomiędzy nimi).

Myśli?

* Aktualizacja: * Odkąd zostało zadane pytanie, mam na myśli sekcję 8.4 karty danych ATTinyX5 . Nie do końca to rozumiem, co jest moim problemem ...

Adam Haile
źródło
1
+1 za myślenie nieszablonowe. Dodatkowe kciuki w górę, jeśli dodasz link do arkusza danych dla AVR.
jippie

Odpowiedzi:

23

Z pewnością możesz. Zgodnie z arkuszem danych, zegar nadzoru może być ustawiony tak, aby resetował MCU lub powodował przerwanie po uruchomieniu. Wygląda na to, że bardziej interesuje Cię możliwość przerwania.

WDT jest w rzeczywistości łatwiejszy do skonfigurowania niż zwykły Timer z tego samego powodu, dla którego jest mniej przydatny: mniej opcji. Działa na wewnętrznie skalibrowanym zegarze 128 kHz, co oznacza, że ​​na jego taktowanie nie ma wpływu szybkość głównego zegara MCU. Może także działać w najgłębszych trybach uśpienia, aby zapewnić źródło budzenia.

Przejdę przez kilka przykładów arkusza danych, a także kod, którego użyłem (w C).

Dołączone pliki i definicje

Aby rozpocząć, prawdopodobnie będziesz chciał dołączyć następujące dwa pliki nagłówkowe, aby wszystko działało:

#include <avr/wdt.h>        // Supplied Watch Dog Timer Macros 
#include <avr/sleep.h>      // Supplied AVR Sleep Macros

Ponadto używam makra <_BV (BIT)>, które jest zdefiniowane w jednym ze standardowych nagłówków AVR jako następujące (które mogą być dla ciebie bardziej rodzinne):

#define _BV(BIT)   (1<<BIT)

Początek kodu

Kiedy MCU jest uruchamiane po raz pierwszy, zwykle inicjujesz I / O, ustawiasz timery itp. Gdzieś tutaj jest dobry moment, aby upewnić się, że WDT nie spowodował resetu, ponieważ mógł to zrobić ponownie, utrzymując program w niestabilna pętla.

if(MCUSR & _BV(WDRF)){            // If a reset was caused by the Watchdog Timer...
    MCUSR &= ~_BV(WDRF);                 // Clear the WDT reset flag
    WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
    WDTCSR = 0x00;                      // Disable the WDT
}

Konfiguracja WDT

Następnie, po skonfigurowaniu reszty układu, wykonaj ponownie WDT. Konfiguracja WDT wymaga „sekwencji czasowej”, ale jest to naprawdę łatwe do zrobienia ...

// Set up Watch Dog Timer for Inactivity
WDTCSR |= (_BV(WDCE) | _BV(WDE));   // Enable the WD Change Bit
WDTCSR =   _BV(WDIE) |              // Enable WDT Interrupt
           _BV(WDP2) | _BV(WDP1);   // Set Timeout to ~1 seconds

Oczywiście, twoje przerwania powinny być wyłączone podczas tego kodu. Pamiętaj, aby je później ponownie włączyć!

cli();    // Disable the Interrupts
sei();    // Enable the Interrupts

Procedura przerwania usługi WDT Następną rzeczą, o którą należy się martwić, jest obsługa WDT ISR. Odbywa się to w ten sposób:

ISR(WDT_vect)
{
  sleep_disable();          // Disable Sleep on Wakeup
  // Your code goes here...
  // Whatever needs to happen every 1 second
  sleep_enable();           // Enable Sleep Mode
}

MCU Sleep

Zamiast uśpienia MCU w WDR ISR, zalecam po prostu włączenie trybu uśpienia na końcu ISR, a następnie program MAIN uśpienie MCU. W ten sposób program faktycznie opuszcza ISR, zanim przejdzie w tryb uśpienia, a następnie obudzi się i przejdzie bezpośrednio do WDT ISR.

// Enable Sleep Mode for Power Down
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // Set Sleep Mode: Power Down
sleep_enable();                     // Enable Sleep Mode  
sei();                              // Enable Interrupts 

/****************************
 *  Enter Main Program Loop  *
 ****************************/
 for(;;)
 {
   if (MCUCR & _BV(SE)){    // If Sleep is Enabled...
     cli();                 // Disable Interrupts
     sleep_bod_disable();   // Disable BOD
     sei();                 // Enable Interrupts
     sleep_cpu();           // Go to Sleep

 /****************************
  *   Sleep Until WDT Times Out  
  *   -> Go to WDT ISR   
  ****************************/

   }
 }
Kurt E. Clothier
źródło
WOW ... bardzo szczegółowe. Dzięki! Jestem trochę zdezorientowany sekcją uśpienia, w której wyświetlasz główną pętlę (jeśli (MCUCR i _BV (SE)) {// Jeśli uśpienie jest włączone ... itd.) Jestem zdezorientowany, dlaczego w głównym wyglądzie ciągle wyłączaj i włączaj przerwania. I część u góry tej sekcji (set_sleep_mode (SLEEP_MODE_PWR_DOWN);) Gdzie to ma biec?
Adam Haile
OK, część „set_sleep_mode (MODE)” powinna znajdować się w głównej części PRZED główną pętlą, najlepiej w innym kodzie inicjującym, w którym konfigurujesz porty I / O, timery itp. Tak naprawdę nie musisz włączać enable_sleep (); w tym momencie, ponieważ nastąpi to po pierwszym wyzwalaczu WDT. WEWNĄTRZ głównej pętli, ten kod uśpienia będzie wykonywany tylko wtedy, gdy uśpienie jest włączone i nie jest całkowicie konieczne wyłączanie / włączanie tam ponownych przerwań, tylko jeśli wykonujesz sleep_bod_disable (); Cała instrukcja JEŻELI może znajdować się na dole (ale nadal wewnątrz) pętli MAIN po każdym innym kodzie, który tam wykonujesz.
Kurt E. Clothier
Ok ... to ma teraz większy sens. Tylko ostatnia rzecz, jestem zamazany na to, co to „czasowy sekwencji” jest ...
Adam Haile
Dygresja: wiem, dlaczego chcesz iść spać, ale zakładam, że nie mają w czasie korzystania z WDT?
Adam Haile
Spójrz na tę małą część arkusza danych: 8.4.1. Zasadniczo, aby zmienić rejestr WDT, musisz ustawić bit zmiany, a następnie ustawić odpowiednie bity WDTCR w tak wielu cyklach zegara. Kod podany w sekcji Konfiguracja WDT robi to, najpierw włączając bit zmiany WD. I nie, nie musisz wcale korzystać z funkcji uśpienia z WDT. Może to być standardowe źródło przerwania czasowego z dowolnego powodu.
Kurt E. Clothier
1

Według arkusza danych jest to możliwe. Możesz nawet włączyć zarówno przerwanie, jak i zresetowanie. Jeśli oba są włączone, pierwszy limit czasu watchdoga wyzwoli przerwanie, co spowoduje, że bit Aktywacji Przerwania zostanie rozbrojony (przerwanie wyłączone). Następny limit czasu spowoduje zresetowanie procesora. Jeśli włączysz Przerwanie bezpośrednio po jego wykonaniu, następny limit czasu (ponownie) wyzwoli tylko przerwanie.

Możesz także włączyć przerwanie i w ogóle nie włączyć resetowania. Będziesz musiał ustawić bit WDIE za każdym razem, gdy nastąpi przerwanie.

Tom L.
źródło
Hmmm .... Myślę, że to ma sens. Dam temu szansę. Zawsze lubię używać rzeczy do tego, do czego nie były przeznaczone :)
Adam Haile
Właściwie myślę, że to sprytny projekt. Oszczędza czas, zachowując jednocześnie funkcję watchdoga.
Tom L.
2
Ten początkowy limit czasu WDT i kolejne przerwanie może być również wykorzystany w niektórych aplikacjach, które umożliwiają WDT faktyczne przywracanie połączenia. Można spojrzeć na skumulowany adres zwrotny w WDT ISR, aby wywnioskować, co kod próbował zrobić, gdy nastąpiło „nieoczekiwane przekroczenie limitu czasu”.
Michael Karas
1

Jest to o wiele łatwiejsze niż sugerowano powyżej i gdzie indziej.

Dopóki WDTONbezpiecznik nie jest zaprogramowany (nie jest zaprogramowany domyślnie), wystarczy tylko ...

  1. Ustaw bit aktywacji przerwania watchdoga i limit czasu w rejestrze kontrolnym watchdoga.
  2. Włącz przerwania.

Oto przykład kodu, który będzie wykonywał ISR raz na 16ms ...

ISR(WDT_vect) {
   // Any code here will get called each time the watchdog expires
}

void main(void) {
   WDTCR =  _BV(WDIE);    // Enable WDT interrupt, leave existing timeout (default 16ms) 
   sei();                                           // Turn on global interrupts
   // Put any code you want after here.
   // You can also go into deep sleep here and as long as 
   // global interrupts are eneabled, you will get woken 
   // up when the watchdog timer expires
   while (1);
}

To jest naprawdę to. Ponieważ nigdy nie włączamy resetowania watchdoga, nigdy nie musimy bawić się sekwencjami czasowymi, aby je wyłączyć. Flaga przerwania watchdoga jest automatycznie usuwana po wywołaniu ISR.

Jeśli chcesz okres inny niż co 1 sekundę, możesz użyć tych wartości tutaj, aby ustawić odpowiednie bity w WDTCR...

wprowadź opis zdjęcia tutaj

Pamiętaj, że musisz wykonać sekwencję czasową, aby zmienić limit czasu. Oto kod, który ustawia limit czasu na 1 sekundę ...

   WDTCR = _BV(WDCE) | _BV(WDE);                   // Enable changes
   WDTCR = _BV(WDIE) | _BV( WDP2) | _BV( WDP1);    // Enable WDT interrupt, change timeout to 1 second
bigjosh
źródło
Nie wykonanie sekwencji czasowej podczas instalacji zapisuje jeden wiersz kodu - operację odczytu-modyfikacji-zapisu. Początkowa sekwencja jest zalecana w arkuszu danych „Jeśli Watchdog został przypadkowo włączony, na przykład przez niekontrolowany wskaźnik lub stan wygaszenia”, a reszta mojego kodu jest specyficzna dla używania WDT w połączeniu z trybem uśpienia, zgodnie z żądaniem OP. Twoje rozwiązanie nie jest prostsze, po prostu zlekceważyłeś zalecany / wymagany kod płyty kotła.
Kurt E. Clothier
@ KurtE.Clothier Przepraszamy, próbuję podać najprostszy działający przykład!
bigjosh,