Pułapki watchdoga AVR

Wiele czasu ostatnio spędziłem na walce z watchdogiem. Ten niepozorny i dość prosty mechanizm może przysporzyć sporo problemów, zwłaszcza gdy nie bierze się pod uwagę paru dość ważnych następstw jego działania. Jako, że źródła internetowe piszą o problematyce różnie i przeważnie tylko o jej fragmencie, postanowiłem zebrać w całość wszystkie znane mi pułapki i je szerzej omówić.

Watchdog po zadziałaniu się sam nie wyłączy

Watchdog jest różnie implementowany na AVRach. Te mniejsze, o ile internet nie kłamie, mają WD, który po swoim zadziałaniu wraca do domyślnego stanu, czyli wyłączenia, o ile fuse’y nie stanowią inaczej. Na tę informację dość często można natrafić. Z kolei bardziej rozbudowane scalaki, w tym ATmega328p mają tak zwanego Enhanced Watchdoga, który po swoim zadziałaniu dalej jest aktywny pomimo restartu programu. Na domiar złego po restarcie będzie on działał z najmniejszym prescalerem, dając restarty po 15ms! Z tego też powodu, jeśli przy starcie aplikacji ma on nie działać, to należy go w pierwszych liniach kodu wyłączyć, mimo że czysto teoretycznie nie został on wcześniej aktywowany.

Przykładowo, na mikrokontrolerze ATmega328p z domyślnie zaprogramowanymi fuse’ami, poniższy kod powinien objawiać się pierwszym dłuższym zaświeceniem diody, a następnie jej krótkimi mignięciami:

#define F_CPU 1000000L
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/wdt.h>

void main(void) {
	DDRC |= _BV(PC5);
	PORTC |= _BV(PC5);
	_delay_ms(100);
	wdt_enable(WDTO_15MS);
	while(1);
}

Wyłączenie Watchdoga musi nastąpić dość szybko

Datasheet mikrokontrolerów AVR definiuje procedurę zmiany konfiguracji WD jako:

  • Wyzerowanie bitu restartu WD w MCUSR,
  • Wstawienie w jednej operacji jedynki do pól WDE oraz WDCE,
  • W przeciągu kolejnych czterech cyklów procesora przestawienie bitów WDE lub WDP z wyzerowanym bitem WDCE.

Aby ułatwić programistom życie, istnieją funkcje wdt_enable() , wdt_disable()  oraz wdt_reset() . Problem w tym, że o ile funkcja wdt_reset()  jest aliasem jednej instrukcji asemblerowej, to pozostałe dwie są już bardziej złożone i lubią nie działać w zależności od użytych flag optymalizacyjnych, a także od wersji nagłówków AVR – niektóre bowiem są zbugowane. Problem ten można łatwo odwołując się do rejestrów na piechotę:

MCUSR = 0;
WDTCSR|=_BV(WDCE) | _BV(WDE);
WDTCSR=0;

Dla pewności na czas wyłączenia WD powinny zostać wyłączone przerwania, o ile są używane. Z funkcją wdt_enable()  póki co problemu nie miałem, choć pewnie to tylko kwestia czasu. Można by zastąpić czymś ją analogicznym – różnica będzie tylko w drugiej operacji na WDTCSR. W przypadku użycia wdt_disable()  trzeba pamiętać o resecie MCUSR – z nieznanych mi przyczyn gotowiec tego nie robi.

This entry was posted in AVR and tagged , , , . Bookmark the permalink.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.