W tym poście opiszę sposób w jaki obsłużyć układ Watchdoga w mikrokontrolerze F103RB.
Watchdog
Watchdog inaczej układ czuwający jest odpowiedzialny za automatyczne zresetowanie mikrokontrolera gdy zostanie wykryte zawieszenie programu. Cały proces odbywa się bez udziału użytkownika. Stosowany jest on jako środek zaradczy w przypadku wystąpienia nieprzewidzianego problemu.
Jego działanie jest dosyć proste, opiera się na zasadzie działania licznika, który jest wywoływany w programie co określony interwał czasowy. Jeśli licznik z jakiegoś powodu nie zliczy do zera, będzie to oznaczało zawieszenie mikrokontrolera, co spowoduje jego zresetowanie przez układ watchdoga.
Watchdog w STM32F103RB
Te miktokontrolery zostały wyposażone w dwa układy czuwające niezależny (Independent watchdog) oraz okienkowy (Window watchdog).
Niezależny watchdog jest 12 bitowym licznikiem zliczającym w dól z 8 bitowym dzielnikiem. Dostępne są jego przedziały odpowiadające potędze 2 od 4 następnie 8, 16, 32, 64, 128 oraz 256. Taktowany jest z wewnętrznego źródła częstotliwością LSI 40 kHz. Minusem takiego sposobu taktowania jest duży margines błędu sygnału głównego (30 kHz do 60 kHz zamiast założonych 40 kHz), plusem natomiast jest uniezależnienie watchdoga od pozostałych elementów. Układ niezależny może pracować w dwóch trybach stop oraz czuwania. Dopuszcza się też wykorzystywanie go jako zwykłego timera.
Jego programowanie wygląda następująco. W pierwszej kolejności należy wykorzystać funkcję IWDG_WriteAccessCMD(), która zezwoli na zapis jego rejestrów. Kolejnym krokiem jest ustawienie dzielnika częstotliwości IWDG_SetPrescaler() oraz wartości początkowej IWDG_SetReload(). Wybraną konfigurację należy przeładować, wykorzystując do tego komendę IWDG_ReloadCounter(). Ostanim elementem jest włączenie całego układu, do czego wykorzystuje się funkcję IWDG_Enable().
Całość programu należy wyposażyć dodatkowo w komendę pozwalającą na ponowne przeładowanie licznika.
Watchdog okienkowy jest to 7 bitowy licznik zliczający w dół. Taktowany jest z głównego zegara systemowego HSI bądź HSE. Sygnał PCLK1, którym jest taktowany watchdog, zostaje podzielony przez 4096. Następnie sygnał ten idzie na dzielnik, który może przyjąć wartości 1, 2, 4 bądź 8.
Stosuje się go w przypadkach gdy istnieje ryzyko niewykonania części programu poprzez jego pominięcie. Przez co całość programu zostanie wykonana zbyt szybko i może zakłócić dalszą pracę urządzeń podlegających.
W tym liczniku ważne jest 6 niższych bitów. 7 bit musi być ustawiony na 1, jeśli będzie 0, mikrokontroler zostanie zrestartowany. Minusem tego układu jest jego źródło taktujące, które nie pozwoli mu być odpornym na zakłócenia pracy mikrokontrolera. Dodatkowo zostanie on wyłączony gdy źródło taktujące w jednym z trybów uśpienia również zostanie wyłączone.
Układ zostanie zresetowany gdy zostanie osiągnięta wartość poniżej wartości początkowej. Ponieważ taką wartością jest 0x40 (64), musi wystąpić 0x3F (63). Drugi przypadek wygenerowania resetu wystąpi gdy zostanie wywołany sygnał przeładowania zanim zostanie osiągnięta wartość minimalna.
Programowanie tego układu wygląda następująco:
W pierwszej kolejności należy ustalić dzielnik, wykonuje się to poprzez komendę WWDG_SetPrescaler(). Kolejnym krokiem jest wybór dolnej wartości okna WWDG_SetWindowValue(). Włączenie układu rozpoczyna się po podaniu funkcji WWDG_Enable(). Aby przeładować licznik bezpośrednio w programie należy wykorzystać funkcję WWDG_SetCounter().
W celu odpowiedniego sprawdzenia przyczyny jaka spowodowała restart mikrokontrolera można posłużyć się znacznikami z rejestru Constrol Status Register (CSR) dla układu Reset and Clock Control (RCC). Aby je odczytać należy zastosować funkcję RCC_GetFlagStatus(). Dostępne są następujące znaczniki tego sygnału:
Wszystkie bity tego rejestru gdy nie otrzymają sygnału mają domyślnie wartość 0. Jeśli natomiast otrzymają sygnał reset wtedy na odpowiednim bicie nastąpi zmiana stanu na 1. W związku z tym po odczytaniu znacznika i rozpoznaniu przyczyny resetu należy pamiętać o jego wyzerowaniu poprzez funkcję RCC_ClearFlag().
Są to tzw. Backup Domain. Pozwalają one na przechowywanie pewnej ilości danych, które mogłyby zostać utracone w innym przypadku. Dostępne jest 42 16 bitowych rejestrów, które służą do przechowywania 84 bajtów danych. Aby skasować te dane należy zmienić stan w rejestrze TEMPER bądź poprzez wykorzystanie funkcji RCC_BackupResetCmd() bądź poprzez całkowitą utratę zasilania.
Aby skorzystać z rejestrów chronionych należy postępować w następujący sposób:
W kolejnym kroku należy włączyć przycisk oraz diody LED.
Ostatnim elementem jest pętla główna programu. Wszystkie potrzebne opisy zawarłem w komentarzach.
Gdy wbudowany przycisk zostanie wciśnięty i przytrzymany obwód z zapali diodę wbudowaną po czym wejdzie do pętli nieskończonej, z której dzięki układowi czuwającemu powróci do normalnej pracy.
Stosuje się go w przypadkach gdy istnieje ryzyko niewykonania części programu poprzez jego pominięcie. Przez co całość programu zostanie wykonana zbyt szybko i może zakłócić dalszą pracę urządzeń podlegających.
W tym liczniku ważne jest 6 niższych bitów. 7 bit musi być ustawiony na 1, jeśli będzie 0, mikrokontroler zostanie zrestartowany. Minusem tego układu jest jego źródło taktujące, które nie pozwoli mu być odpornym na zakłócenia pracy mikrokontrolera. Dodatkowo zostanie on wyłączony gdy źródło taktujące w jednym z trybów uśpienia również zostanie wyłączone.
Układ zostanie zresetowany gdy zostanie osiągnięta wartość poniżej wartości początkowej. Ponieważ taką wartością jest 0x40 (64), musi wystąpić 0x3F (63). Drugi przypadek wygenerowania resetu wystąpi gdy zostanie wywołany sygnał przeładowania zanim zostanie osiągnięta wartość minimalna.
Rys. 1. Schemat blokowy watchdoga okienkowego
Programowanie tego układu wygląda następująco:
W pierwszej kolejności należy ustalić dzielnik, wykonuje się to poprzez komendę WWDG_SetPrescaler(). Kolejnym krokiem jest wybór dolnej wartości okna WWDG_SetWindowValue(). Włączenie układu rozpoczyna się po podaniu funkcji WWDG_Enable(). Aby przeładować licznik bezpośrednio w programie należy wykorzystać funkcję WWDG_SetCounter().
Sprawdzenie przyczyny zerowania
W celu odpowiedniego sprawdzenia przyczyny jaka spowodowała restart mikrokontrolera można posłużyć się znacznikami z rejestru Constrol Status Register (CSR) dla układu Reset and Clock Control (RCC). Aby je odczytać należy zastosować funkcję RCC_GetFlagStatus(). Dostępne są następujące znaczniki tego sygnału:
Rys. 2. Opis bitów rejestru CSR
- RCC_FLAG_LPWRRSTF - reset z powodu zaniku zasilania układu
- RCC_FLAG_WWDGRSTF - poprzez układ watchdoga okienkowego
- RCC_FLAG_IWDGRSTF - poprzez układ watchdoga niezależnego
- RCC_FLAG_SFTRSTF - reset wywołany w programie
- RCC_FLAG_PORRSTF - poprzez włączenie bądź odłączenie zasilania
- RCC_FLAG_PINRSTF - wywołanie sprzętowe
Wszystkie bity tego rejestru gdy nie otrzymają sygnału mają domyślnie wartość 0. Jeśli natomiast otrzymają sygnał reset wtedy na odpowiednim bicie nastąpi zmiana stanu na 1. W związku z tym po odczytaniu znacznika i rozpoznaniu przyczyny resetu należy pamiętać o jego wyzerowaniu poprzez funkcję RCC_ClearFlag().
Rejestry chronione
Są to tzw. Backup Domain. Pozwalają one na przechowywanie pewnej ilości danych, które mogłyby zostać utracone w innym przypadku. Dostępne jest 42 16 bitowych rejestrów, które służą do przechowywania 84 bajtów danych. Aby skasować te dane należy zmienić stan w rejestrze TEMPER bądź poprzez wykorzystanie funkcji RCC_BackupResetCmd() bądź poprzez całkowitą utratę zasilania.
Aby skorzystać z rejestrów chronionych należy postępować w następujący sposób:
- Podanie sygnału taktującego na układ PWR, który jest odpowiedzialny za kontrolę zasilania
- Podanie sygnału taktującego na układ Backup domain
- Włączyć dostęp do rejestrów chronionych PWR_BackupAccessCmd()
- Zapis danych do rejestru za pomocą funkcji BKP_WriteBackupRegister()
- Do odczytu wykorzystuje się funkcję BKP_ReadBackupRegister.
Program
Poniżej przedstawię program którego zadaniem będzie wykorzystanie układu czuwającego, który zabezpieczy cały układ przez zablokowaniem.
Podstawowym elementem w tym przypadku jest poprawna definicja watchdoga. Wygląda ona następująco:
//Konfiguracja niezależnego układu czuwającego void IWDGKonfiguracja(void) { //Zezwolenie na zapis do rejestrów IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //Ustawienie taktowania, 40kHz/4 = 10kHz IWDG_SetPrescaler(IWDG_Prescaler_4); //Ustawienie wartości początkowej IWDG_SetReload(0xFFF); //Przeładowanie wybranej konfiguraci IWDG_ReloadCounter(); //Włączenie watchdoga oraz sygnału taktującego LSI IWDG_Enable(); }
W kolejnym kroku należy włączyć przycisk oraz diody LED.
void GPIOKonfiguracja(void) { GPIO_InitTypeDef GpioInit; //Definicja portu z podłączoną wbudowaną diodą LED //Włączenie zegara RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_StructInit(&GpioInit); //Definicja portu z podłączoną wbudowaną diodą LED //Definicja dla pinu zewnętrznego z podłączoną diodą LED //Wybranie konfigurowanego pinu GpioInit.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //Ustawienie pinu jak wyjście PushPull GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; //Częstotliwośc GpioInit.GPIO_Speed = GPIO_Speed_50MHz; //Inicjalizacja linii z podanymi ustawieniami GPIO_Init(GPIOA, &GpioInit); //Definicja portu z podłączonym wbydowanym przyciskiem GpioInit.GPIO_Pin = GPIO_Pin_13; //Tryb jako wejscie z rezystorem podciągającym PullUp GpioInit.GPIO_Mode = GPIO_Mode_IPU; //Częstotliwośc GpioInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GpioInit); }
Ostatnim elementem jest pętla główna programu. Wszystkie potrzebne opisy zawarłem w komentarzach.
Gdy wbudowany przycisk zostanie wciśnięty i przytrzymany obwód z zapali diodę wbudowaną po czym wejdzie do pętli nieskończonej, z której dzięki układowi czuwającemu powróci do normalnej pracy.
#include <stdio.h> #include "stm32f10x.h" void IWDGKonfiguracja(void); void GPIOKonfiguracja(void); int main(void) { volatile unsigned long int i; //Konfiguracja GPIO GPIOKonfiguracja(); //Warunek sprawdzający czy system zaczał pracę po wykorzystaniu resetu //przez układ czuwający, Jeżeli tak to wejście w pętle if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) { //Włączenie diody wbudowanej GPIO_SetBits(GPIOA, GPIO_Pin_7); //Wyczyszczenie flagi resetu RCC_ClearFlag(); //Pętla opóźniająca for (i=0;i<10000000;i++); } //Konfiguracja układu czuwającego zewnętrznego IWDGKonfiguracja(); GPIO_ResetBits(GPIOA, GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7); while (1) { //Przeładowanie Watchdoga IWDG_ReloadCounter(); //W przypadku wciśnięcia przycisku warunek zostaje zpełniony if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0) { //Zapalenie diody GPIO_SetBits(GPIOA, GPIO_Pin_5); //Wejście w nieskończoną pętle for(;;) {} } GPIO_WriteBit(GPIOA, GPIO_Pin_6, (BitAction)(1-GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_6))); for (i=0;i<2000000ul;i++); }; return 0; } //Konfiguracja niezależnego układu czuwającego void IWDGKonfiguracja(void) { //Zezwolenie na zapis do rejestrów IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //Ustawienie taktowania, 40kHz/4 = 10kHz IWDG_SetPrescaler(IWDG_Prescaler_4); //Ustawienie wartości początkowej IWDG_SetReload(0xFFF); //Przeładowanie wybranej konfiguraci IWDG_ReloadCounter(); //Włączenie watchdoga oraz sygnału taktującego LSI IWDG_Enable(); } void GPIOKonfiguracja(void) { GPIO_InitTypeDef GpioInit; //Definicja portu z podłączoną wbudowaną diodą LED //Włączenie zegara RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_StructInit(&GpioInit); //Definicja portu z podłączoną wbudowaną diodą LED //Definicja dla pinu zewnętrznego z podłączoną diodą LED //Wybranie konfigurowanego pinu GpioInit.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //Ustawienie pinu jak wyjście PushPull GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; //Częstotliwośc GpioInit.GPIO_Speed = GPIO_Speed_50MHz; //Inicjalizacja linii z podanymi ustawieniami GPIO_Init(GPIOA, &GpioInit); //Definicja portu z podłączonym wbydowanym przyciskiem GpioInit.GPIO_Pin = GPIO_Pin_13; //Tryb jako wejscie z rezystorem podciągającym PullUp GpioInit.GPIO_Mode = GPIO_Mode_IPU; //Częstotliwośc GpioInit.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GpioInit); }