Tym razem chciałbym opisać sposób konfiguracji Watchdoga IWDG oraz WWDG za pomocą rejestrów na mikrokontrolerze STM32F4.
Wcześniej umieściłem na blogu podobny post dotyczący układu Watchdog, jednak dotyczył on tylko układu IWDG i nie polegał w pełni na rejestrach.
Jest to licznik 12 bitowy zliczający w dół od zadanej wartości. Taktowany jest z oscylatora LSI. W przypadku dojścia do zera mikrokontroler zostaje zresetowany.
IWDG_KR - rejestr odpowiedzialny za sterowanie pracą IWDG. Zdefiniowane są trzy wartości stałe jakie można do niego wprowadzić.
- 0xCCCC - uruchomienie watchdoga
- 0xAAAA - wartość czyszcząca licznik.
- 0x5555 - uruchamia dostęp do IWDG_PR oraz IWDG_RLR. Po wprowadzeniu kolejnej wartości do IWDG_KR blokada zostaje ponownie aktywowana.
IWDG_PR - ustawienie dzielnika dla watchdoga.
IWDG_RLR - wartość od jakiej licznik rozpocznie zliczanie.
IWDG_SR - przechowuje on dwie wartości. Pierwsza RVU odpowiada za informacje o trwającej aktualizacji dla wartości licznika. Druga PVU o aktualizacji dzielnika.
Najpierw należy uruchomić zegar oraz diody sygnalizacyjne:
Dalej uruchamiany jest SysTick. W przerwaniu od niego będzie następowało czyszczenie danych w rejestrze:
Teraz sprawdzam czy IWDG wywołało ostatni reset:
W kolejnym kroku zanim zostanie uruchomiony LSI. Jest on niezbędny do działania IWDG. Ponieważ z tego źródła jest on taktowany:
Ustawienie parametrów dla watchdoga:
Obsługa przerwania od licznika:
Cały kod:
Działą podobnie zlicza w dół i generuje reset. Licznik IWDG może zostać załadowany nowymi wartościami w każdej chwili (byle przed doliczeniem do zera), natomiast WWDG potrzebuje aby odbyło się to w określonym oknie czasowym (ang. Window). W praktyce oznacza to że jeśli skasuje się licznik za wcześnie bądź za późno to nastąpi reset.
Minusem tego układu jest duża zależność od programu i od samego sprzętu. Nie działa on osobno ja IWDG, nie jest też taktowany z osobnej linii (WWDG taktowany z APB1). Wobec sposobu taktowania, układ nie będzie działał w trybach uśpienia. Natomiast dzięki temu dokładność odmierzanego przez układ czasu jest dosyć wysoka.
WWDG_CR - rejestr kontrolny, zawiera on bit włączający WDGA oraz zawartość licznika T.
Należy pamiętać, że wygenerowanie sygnału reset nastąpi po odliczeniu z 0x40 na 0x3F. Czyli gdy bit T[6] zostanie wyzerowany.
WWDG_CFR - rejestr konfiguracyjny zawiera bit wybudzający (EWI), dzielnik (WDGTB) oraz wartość okna (W) która będzie porównywana do zawartości licznika.
WWDG_SR - rejestr statusowy, przechowuje informację z flagą która zostaje aktywowana gdy rejestr dobije do 0x40. Program może wprowadzać tam tylko 0 w celu wyczyszczenia. Wprowadzenie 1 nie będzie miało żadnego efektu.
Należy pamiętać, że układ WWDG w przeciwieństwie do IWDG musi zostać włączony.
Program poniżej prezentuje przykładowe użycie. WWDG jest odświeżane w przerwaniu od Systicka. Teraz przejdę po fragmentach programu. Na samym końcu umieszczę cały kod zgrupowany razem.
Na początek dołączenie bibliotek oraz definicji z numerami pinów oraz makrami dla świecenia diodami. Posłuży to do informowania użytkownika o statusie operacji:
Następnie definicja funkcji statycznych:
Teraz przejdę do opisu pętli głównej programu:
Kolejnym elementem jest uruchomienie Systicka. W jego przerwaniu będzie umieszczona instrukcja ładująca dane do WWDG.
Dalej w kolejce jest uruchomienie diod świecących:
Tutaj funkcja wywołuje się cztery razy przechodząc przez piny od 12 do 15.
Następnym krokiem jest sprawdzanie co wywołało przerwanie.
W celu sprawdzenia czy watchdog wywołał reset korzysta się z rejestru CSR w RCC.
Dalej należy wyczyścić flagę resetu, tak aby dane były czyste.
Teraz pora na uruchomienie WWDG. Najpierw potrzebne wzory:
Aby wyliczyć opóźnienie pomiędzy przeładowaniem a wyjście z okna czasowego:
Drugi przydatny wzór dotyczy obliczenia opóźnienia jakie jest potrzebne aby skasować licznik w odpowiednim momencie:
Teraz proces uruchamiania watchdog. Najpierw należy go włączyć po czym ustawić czas do resetu oraz wprowadzić wartość do bitu na pozycji szóstej w polu T. Te operacja są wykonywane na rejestrze CR.
Kolejna instrukcja wykonuje operacje na rejestrze CFR. Tam ustawiany jest dzielnik WWDG oraz konfigurowane jest okno czasowe. Tutaj podobnie jak w przypadku zawartości licznika (T) okno czasowe musi mieć ustawiony bit szósty.
Ostatnia operacja na rejestrze SR tam należy skasować flagę wywołania przerwania od WWDG.
Uruchomienie przerwania od WWDG:
Funkcja obsługi przerwania od licznika. Co określoną ilość cyklów dane od WWDG są przeładowywane.
Cały program wygląda następująco:
Wcześniej umieściłem na blogu podobny post dotyczący układu Watchdog, jednak dotyczył on tylko układu IWDG i nie polegał w pełni na rejestrach.
IWDG:
Jest to licznik 12 bitowy zliczający w dół od zadanej wartości. Taktowany jest z oscylatora LSI. W przypadku dojścia do zera mikrokontroler zostaje zresetowany.
IWDG_KR - rejestr odpowiedzialny za sterowanie pracą IWDG. Zdefiniowane są trzy wartości stałe jakie można do niego wprowadzić.
- 0xCCCC - uruchomienie watchdoga
- 0xAAAA - wartość czyszcząca licznik.
- 0x5555 - uruchamia dostęp do IWDG_PR oraz IWDG_RLR. Po wprowadzeniu kolejnej wartości do IWDG_KR blokada zostaje ponownie aktywowana.
IWDG_PR - ustawienie dzielnika dla watchdoga.
IWDG_RLR - wartość od jakiej licznik rozpocznie zliczanie.
IWDG_SR - przechowuje on dwie wartości. Pierwsza RVU odpowiada za informacje o trwającej aktualizacji dla wartości licznika. Druga PVU o aktualizacji dzielnika.
Najpierw należy uruchomić zegar oraz diody sygnalizacyjne:
- RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD; /* Clock for Diodes */
- __DSB();
- /* Set diode pins as output */
- for(uint8_t loop = 12; loop<16; loop++)
- {
- setPinAsOutput(GPIOD, loop);
- }
Dalej uruchamiany jest SysTick. W przerwaniu od niego będzie następowało czyszczenie danych w rejestrze:
- SysTick_Config(10000);
Teraz sprawdzam czy IWDG wywołało ostatni reset:
- if(RCC->CSR & RCC_CSR_WDGRSTF)
- {
- RCC->CSR = RCC_CSR_RMVF;
- blinkDiode(DIODE_GREEN);
- blinkDiode(DIODE_GREEN);
- }
W kolejnym kroku zanim zostanie uruchomiony LSI. Jest on niezbędny do działania IWDG. Ponieważ z tego źródła jest on taktowany:
- RCC->CSR |= RCC_CSR_LSION;
- while((RCC->CSR & RCC_CSR_LSIRDY) == 0) { ; }
Ustawienie parametrów dla watchdoga:
- IWDG->KR = 0x5555; /* Enable access to IWDG_PR */
- IWDG->PR = 2; /* Set prescaler Value*/
- IWDG->RLR = 1000; /* Set counter value */
- IWDG->KR = 0xaaaa; /* Clear data, set counter to max (In out case 1000) value */
- IWDG->KR = 0xcccc; /* Enable IWDG */
- __DSB(); /* Wait */
- startFlag = 1; /* Start count process */
- EXTI->IMR = EXTI_IMR_MR0; /* Unmask interrupt, need for systick interrupts */
Obsługa przerwania od licznika:
- void SysTick_Handler(void)
- {
- if(startFlag)
- {
- reload++;
- }
- if(reload == 1600)
- {
- IWDG->KR = 0xaaaa;
- reload = 0;
- togglePin(GPIOD, DIODE_ORANGE);
- }
- if(delay)
- {
- delay--;
- }
- }
Cały kod:
- #include "stm32f4xx.h"
- #include "stm32f4xx_rcc.h"
- #include "stm32f4xx_gpio.h"
- #define DIODE_GREEN GPIO_Pin_12
- #define DIODE_ORANGE GPIO_Pin_13
- #define DIODE_RED GPIO_Pin_14
- #define DIODE_BLUE GPIO_Pin_15
- #define BTN_PIN GPIO_Pin_0
- #define setPinHigh(GPIOx, PIN) ((GPIOx)->BSRRL = (PIN))
- #define setPinLow(GPIOx, PIN) ((GPIOx)->BSRRH = (PIN))
- #define togglePin(GPIOx, PIN) ((GPIOx)->ODR ^= (PIN))
- static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
- static void setPinAsInput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
- static void configExtiInterrupt();
- void blinkDiode(uint16_t GPIO_PIN);
- static void delayTime(uint32_t cnt);
- volatile uint32_t reload;
- volatile uint32_t delay;
- volatile uint32_t startFlag;
- int main()
- {
- RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD; /* Clock for Diodes */
- __DSB();
- /* Set diode pins as output */
- for(uint8_t loop = 12; loop<16; loop++)
- {
- setPinAsOutput(GPIOD, loop);
- }
- SysTick_Config(10000);
- if(RCC->CSR & RCC_CSR_WDGRSTF)
- {
- RCC->CSR = RCC_CSR_RMVF;
- blinkDiode(DIODE_GREEN);
- blinkDiode(DIODE_GREEN);
- }
- RCC->CSR |= RCC_CSR_LSION;
- while((RCC->CSR & RCC_CSR_LSIRDY) == 0) { ; }
- setPinHigh(GPIOD, DIODE_BLUE);
- IWDG->KR = 0x5555; /* Enable access to IWDG_PR */
- IWDG->PR = 2; /* Set prescaler Value*/
- IWDG->RLR = 1000; /* Set counter value */
- IWDG->KR = 0xaaaa; /* Clear data, set counter to max (In out case 1000) value */
- IWDG->KR = 0xcccc; /* Enable IWDG */
- __DSB(); /* Wait */
- startFlag = 1; /* Start count process */
- EXTI->IMR = EXTI_IMR_MR0; /* Unmask interrupt, need for systick interrupts */
- while (1) { }
- }
- void SysTick_Handler(void)
- {
- if(startFlag)
- {
- reload++;
- }
- if(reload == 1600)
- {
- IWDG->KR = 0xaaaa;
- reload = 0;
- togglePin(GPIOD, DIODE_ORANGE);
- }
- if(delay)
- {
- delay--;
- }
- }
- static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber)
- {
- GPIOx->MODER |= GPIO_Mode_OUT << (pinNumber * 2);
- GPIOx->OSPEEDR |= GPIO_Speed_25MHz << (pinNumber * 2);
- GPIOx->OTYPER |= GPIO_OType_PP << pinNumber;
- GPIOx->PUPDR |= GPIO_PuPd_NOPULL << (pinNumber * 2);
- }
- void blinkDiode(uint16_t GPIO_PIN)
- {
- togglePin(GPIOD, GPIO_PIN);
- delayTime(20);
- togglePin(GPIOD, GPIO_PIN);
- delayTime(20);
- }
- static void delayTime(uint32_t cnt)
- {
- delay = cnt;
- while(delay){ ; }
- }
WWDG:
Działą podobnie zlicza w dół i generuje reset. Licznik IWDG może zostać załadowany nowymi wartościami w każdej chwili (byle przed doliczeniem do zera), natomiast WWDG potrzebuje aby odbyło się to w określonym oknie czasowym (ang. Window). W praktyce oznacza to że jeśli skasuje się licznik za wcześnie bądź za późno to nastąpi reset.
Minusem tego układu jest duża zależność od programu i od samego sprzętu. Nie działa on osobno ja IWDG, nie jest też taktowany z osobnej linii (WWDG taktowany z APB1). Wobec sposobu taktowania, układ nie będzie działał w trybach uśpienia. Natomiast dzięki temu dokładność odmierzanego przez układ czasu jest dosyć wysoka.
WWDG_CR - rejestr kontrolny, zawiera on bit włączający WDGA oraz zawartość licznika T.
Należy pamiętać, że wygenerowanie sygnału reset nastąpi po odliczeniu z 0x40 na 0x3F. Czyli gdy bit T[6] zostanie wyzerowany.
WWDG_CFR - rejestr konfiguracyjny zawiera bit wybudzający (EWI), dzielnik (WDGTB) oraz wartość okna (W) która będzie porównywana do zawartości licznika.
WWDG_SR - rejestr statusowy, przechowuje informację z flagą która zostaje aktywowana gdy rejestr dobije do 0x40. Program może wprowadzać tam tylko 0 w celu wyczyszczenia. Wprowadzenie 1 nie będzie miało żadnego efektu.
Należy pamiętać, że układ WWDG w przeciwieństwie do IWDG musi zostać włączony.
Program poniżej prezentuje przykładowe użycie. WWDG jest odświeżane w przerwaniu od Systicka. Teraz przejdę po fragmentach programu. Na samym końcu umieszczę cały kod zgrupowany razem.
Na początek dołączenie bibliotek oraz definicji z numerami pinów oraz makrami dla świecenia diodami. Posłuży to do informowania użytkownika o statusie operacji:
- #include "stm32f4xx.h"
- #include "stm32f4xx_rcc.h"
- #include "stm32f4xx_gpio.h"
- #define DIODE_GREEN GPIO_Pin_12
- #define DIODE_ORANGE GPIO_Pin_13
- #define DIODE_RED GPIO_Pin_14
- #define DIODE_BLUE GPIO_Pin_15
- #define BTN_PIN GPIO_Pin_0
- #define setPinHigh(GPIOx, PIN) ((GPIOx)->BSRRL = (PIN))
- #define setPinLow(GPIOx, PIN) ((GPIOx)->BSRRH = (PIN))
- #define togglePin(GPIOx, PIN) ((GPIOx)->ODR ^= (PIN))
Następnie definicja funkcji statycznych:
- static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
- static void setPinAsInput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
- static void configExtiInterrupt();
- static void delayTime10_ms(uint32_t count);
- static void blinkDiode(void);
Teraz przejdę do opisu pętli głównej programu:
- RCC->CFGR = (uint32_t)RCC_CFGR_PPRE1_DIV2; /* Config PCLK1 = HCLK */
- RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD; /* Clock for Diodes */
- RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA; /* Clock for Btn */
- RCC->APB2ENR = RCC_APB2ENR_SYSCFGEN; /* Config SYSCFG Clock */
- RCC->APB1ENR = RCC_APB1ENR_WWDGEN; /* Clock for WWDG */
- __DSB();
Kolejnym elementem jest uruchomienie Systicka. W jego przerwaniu będzie umieszczona instrukcja ładująca dane do WWDG.
- SysTick_Config(10000);
Dalej w kolejce jest uruchomienie diod świecących:
- for(uint8_t loop = 12; loop<16; loop++)
- {
- setPinAsOutput(GPIOD, loop);
- }
Tutaj funkcja wywołuje się cztery razy przechodząc przez piny od 12 do 15.
Następnym krokiem jest sprawdzanie co wywołało przerwanie.
- if(RCC->CSR & RCC_CSR_WWDGRSTF) /* Check if reset made by watchdog */
- {
- blinkDiode(DIODE_GREEN);
- blinkDiode(DIODE_GREEN);
- }
- else
- {
- blinkDiode(DIODE_BLUE);
- blinkDiode(DIODE_BLUE);
- }
W celu sprawdzenia czy watchdog wywołał reset korzysta się z rejestru CSR w RCC.
Dalej należy wyczyścić flagę resetu, tak aby dane były czyste.
- RCC->CSR = RCC_CSR_RMVF; /* Set RMVF bit to clear the reset flags */
Teraz pora na uruchomienie WWDG. Najpierw potrzebne wzory:
Aby wyliczyć opóźnienie pomiędzy przeładowaniem a wyjście z okna czasowego:
Drugi przydatny wzór dotyczy obliczenia opóźnienia jakie jest potrzebne aby skasować licznik w odpowiednim momencie:
Teraz proces uruchamiania watchdog. Najpierw należy go włączyć po czym ustawić czas do resetu oraz wprowadzić wartość do bitu na pozycji szóstej w polu T. Te operacja są wykonywane na rejestrze CR.
Kolejna instrukcja wykonuje operacje na rejestrze CFR. Tam ustawiany jest dzielnik WWDG oraz konfigurowane jest okno czasowe. Tutaj podobnie jak w przypadku zawartości licznika (T) okno czasowe musi mieć ustawiony bit szósty.
Ostatnia operacja na rejestrze SR tam należy skasować flagę wywołania przerwania od WWDG.
- WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;
- WWDG->CFR = WWDG_CFR_EWI | 2<<7 | WWDG_CFR_W6 | 0x3F;
- WWDG->SR &= ~WWDG_SR_EWIF;
Uruchomienie przerwania od WWDG:
- EXTI->IMR = EXTI_IMR_MR0;
- NVIC_ClearPendingIRQ(WWDG_IRQn);
- NVIC_EnableIRQ(WWDG_IRQn);
Funkcja obsługi przerwania od licznika. Co określoną ilość cyklów dane od WWDG są przeładowywane.
- void SysTick_Handler(void)
- {
- if(startFlag)
- {
- reload++;
- }
- if(reload == 200)
- {
- WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;
- reload = 0;
- startFlag = 1;
- togglePin(GPIOD, DIODE_ORANGE);
- }
- if(delay)
- {
- delay--;
- }
- }
Cały program wygląda następująco:
- #include "stm32f4xx.h"
- #include "stm32f4xx_rcc.h"
- #include "stm32f4xx_gpio.h"
- #define DIODE_GREEN GPIO_Pin_12
- #define DIODE_ORANGE GPIO_Pin_13
- #define DIODE_RED GPIO_Pin_14
- #define DIODE_BLUE GPIO_Pin_15
- #define BTN_PIN GPIO_Pin_0
- #define setPinHigh(GPIOx, PIN) ((GPIOx)->BSRRL = (PIN))
- #define setPinLow(GPIOx, PIN) ((GPIOx)->BSRRH = (PIN))
- #define togglePin(GPIOx, PIN) ((GPIOx)->ODR ^= (PIN))
- static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
- static void setPinAsInput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
- static void configExtiInterrupt();
- void blinkDiode(uint16_t GPIO_PIN);
- static void delayTime(uint32_t cnt);
- volatile uint32_t reload;
- volatile uint32_t delay;
- volatile uint32_t startFlag;
- int main()
- {
- RCC->CFGR = (uint32_t)RCC_CFGR_PPRE1_DIV2; /* Config PCLK1 = HCLK */
- RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD; /* Clock for Diodes */
- RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA; /* Clock for Btn */
- RCC->APB2ENR = RCC_APB2ENR_SYSCFGEN; /* Config SYSCFG Clock */
- RCC->APB1ENR = RCC_APB1ENR_WWDGEN; /* Clock for WWDG */
- __DSB();
- SysTick_Config(10000);
- /* Set diode pins as output */
- for(uint8_t loop = 12; loop<16; loop++)
- {
- setPinAsOutput(GPIOD, loop);
- }
- if(RCC->CSR & RCC_CSR_WWDGRSTF) /* Check if reset made by watchdog */
- {
- blinkDiode(DIODE_GREEN);
- blinkDiode(DIODE_GREEN);
- }
- else
- {
- blinkDiode(DIODE_BLUE);
- blinkDiode(DIODE_BLUE);
- }
- RCC->CSR = RCC_CSR_RMVF; /* Set RMVF bit to clear the reset flags */
- startFlag = 1;
- setPinHigh(GPIOD, DIODE_BLUE);
- WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F; /* Enable WWDG, set bit T6 to 1, write coutner value */
- WWDG->CFR = WWDG_CFR_EWI | 2<<7 | WWDG_CFR_W6 | 0x3F; /* Set Early wakeup interrupt, set Timer base, set W6 bit to 1, write window value */
- WWDG->SR &= ~WWDG_SR_EWIF; /* Clear flag */
- EXTI->IMR = EXTI_IMR_MR0;
- NVIC_ClearPendingIRQ(WWDG_IRQn);
- NVIC_EnableIRQ(WWDG_IRQn);
- while (1) { }
- }
- void SysTick_Handler(void)
- {
- if(startFlag)
- {
- reload++;
- }
- if(reload == 200)
- {
- WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;
- reload = 0;
- startFlag = 1;
- togglePin(GPIOD, DIODE_ORANGE);
- }
- if(delay)
- {
- delay--;
- }
- }
- static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber)
- {
- GPIOx->MODER |= GPIO_Mode_OUT << (pinNumber * 2);
- GPIOx->OSPEEDR |= GPIO_Speed_25MHz << (pinNumber * 2);
- GPIOx->OTYPER |= GPIO_OType_PP << pinNumber;
- GPIOx->PUPDR |= GPIO_PuPd_NOPULL << (pinNumber * 2);
- }
- void blinkDiode(uint16_t GPIO_PIN)
- {
- togglePin(GPIOD, GPIO_PIN);
- delayTime(20);
- togglePin(GPIOD, GPIO_PIN);
- delayTime(20);
- }
- static void delayTime(uint32_t cnt)
- {
- delay = cnt;
- while(delay){ ; }
- }
- void WWDG_IRQHandler(void)
- {
- WWDG->SR &= ~WWDG_SR_EWIF; /* Clear interrupt flag */
- while(1) {} /* kill device, restart it */
- }