sobota, 13 lutego 2016

[14] STM32 M3 - Nucleo - F103RB - Watchdog

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.

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);
}