poniedziałek, 15 stycznia 2018

[7] STM32F4 - Rejestry - Watchdog

Tym razem chciałbym opisać sposób konfiguracji Watchdoga IWDG oraz WWDG za pomocą rejestrów na mikrokontrolerze STM32F4.

[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]

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:

  1. RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD; /* Clock for Diodes */
  2. __DSB();
  3. /* Set diode pins as output */
  4. for(uint8_t loop = 12; loop<16; loop++)
  5. {
  6.     setPinAsOutput(GPIOD, loop);
  7. }

Dalej uruchamiany jest SysTick. W przerwaniu od niego będzie następowało czyszczenie danych w rejestrze:

  1. SysTick_Config(10000);

Teraz sprawdzam czy IWDG wywołało ostatni reset:

  1. if(RCC->CSR & RCC_CSR_WDGRSTF)
  2. {
  3.     RCC->CSR = RCC_CSR_RMVF;
  4.     blinkDiode(DIODE_GREEN);
  5.     blinkDiode(DIODE_GREEN);
  6. }

W kolejnym kroku zanim zostanie uruchomiony LSI. Jest on niezbędny do działania IWDG. Ponieważ z tego źródła jest on taktowany:

  1. RCC->CSR |= RCC_CSR_LSION;
  2.    
  3. while((RCC->CSR & RCC_CSR_LSIRDY) == 0)  { ; }

Ustawienie parametrów dla watchdoga:

  1. IWDG->KR = 0x5555;      /* Enable access to IWDG_PR */
  2. IWDG->PR = 2;           /* Set prescaler Value*/
  3. IWDG->RLR = 1000;       /* Set counter value */
  4. IWDG->KR = 0xaaaa;      /* Clear data, set counter to max (In out case 1000) value */
  5. IWDG->KR = 0xcccc;      /* Enable IWDG */
  6. __DSB();                /* Wait */
  7.    
  8. startFlag = 1;          /* Start count process */  
  9.    
  10. EXTI->IMR = EXTI_IMR_MR0;   /* Unmask interrupt, need for systick interrupts */

Obsługa przerwania od licznika:

  1. void SysTick_Handler(void)
  2. {
  3.     if(startFlag)
  4.     {
  5.         reload++;
  6.     }
  7.     if(reload == 1600)
  8.     {
  9.         IWDG->KR = 0xaaaa;
  10.         reload = 0;
  11.         togglePin(GPIOD, DIODE_ORANGE);
  12.     }
  13.    
  14.     if(delay)
  15.     {
  16.         delay--;
  17.     }
  18. }

Cały kod:

  1. #include "stm32f4xx.h"
  2. #include "stm32f4xx_rcc.h"
  3. #include "stm32f4xx_gpio.h"
  4. #define DIODE_GREEN             GPIO_Pin_12
  5. #define DIODE_ORANGE            GPIO_Pin_13
  6. #define DIODE_RED               GPIO_Pin_14
  7. #define DIODE_BLUE              GPIO_Pin_15
  8. #define BTN_PIN                 GPIO_Pin_0
  9. #define setPinHigh(GPIOx, PIN)  ((GPIOx)->BSRRL = (PIN))
  10. #define setPinLow(GPIOx, PIN)   ((GPIOx)->BSRRH = (PIN))
  11. #define togglePin(GPIOx, PIN)   ((GPIOx)->ODR ^= (PIN))
  12. static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
  13. static void setPinAsInput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
  14. static void configExtiInterrupt();
  15. void blinkDiode(uint16_t GPIO_PIN);
  16. static void delayTime(uint32_t cnt);
  17. volatile uint32_t reload;
  18. volatile uint32_t delay;
  19. volatile uint32_t startFlag;
  20. int main()
  21. {
  22.     RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;   /* Clock for Diodes */
  23.     __DSB();
  24.    
  25.     /* Set diode pins as output */
  26.     for(uint8_t loop = 12; loop<16; loop++)
  27.     {
  28.         setPinAsOutput(GPIOD, loop);
  29.     }
  30.    
  31.     SysTick_Config(10000);
  32.    
  33.     if(RCC->CSR & RCC_CSR_WDGRSTF)
  34.     {
  35.         RCC->CSR = RCC_CSR_RMVF;
  36.         blinkDiode(DIODE_GREEN);
  37.         blinkDiode(DIODE_GREEN);
  38.     }
  39.    
  40.     RCC->CSR |= RCC_CSR_LSION;
  41.     while((RCC->CSR & RCC_CSR_LSIRDY) == 0) { ; }
  42.    
  43.     setPinHigh(GPIOD, DIODE_BLUE);
  44.    
  45.     IWDG->KR = 0x5555;      /* Enable access to IWDG_PR */
  46.     IWDG->PR = 2;           /* Set prescaler Value*/
  47.     IWDG->RLR = 1000;       /* Set counter value */
  48.     IWDG->KR = 0xaaaa;      /* Clear data, set counter to max (In out case 1000) value */
  49.     IWDG->KR = 0xcccc;      /* Enable IWDG */
  50.     __DSB();                /* Wait */
  51.    
  52.     startFlag = 1;          /* Start count process */  
  53.    
  54.     EXTI->IMR = EXTI_IMR_MR0;   /* Unmask interrupt, need for systick interrupts */
  55.    
  56.     while (1) { }
  57. }
  58. void SysTick_Handler(void)
  59. {
  60.     if(startFlag)
  61.     {
  62.         reload++;
  63.     }
  64.     if(reload == 1600)
  65.     {
  66.         IWDG->KR = 0xaaaa;
  67.         reload = 0;
  68.         togglePin(GPIOD, DIODE_ORANGE);
  69.     }
  70.    
  71.     if(delay)
  72.     {
  73.         delay--;
  74.     }
  75. }
  76. static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber)
  77. {
  78.     GPIOx->MODER |= GPIO_Mode_OUT << (pinNumber * 2);
  79.     GPIOx->OSPEEDR |= GPIO_Speed_25MHz << (pinNumber * 2);
  80.     GPIOx->OTYPER |= GPIO_OType_PP << pinNumber;
  81.     GPIOx->PUPDR |= GPIO_PuPd_NOPULL << (pinNumber * 2);
  82. }
  83. void blinkDiode(uint16_t GPIO_PIN)
  84. {
  85.     togglePin(GPIOD, GPIO_PIN);
  86.     delayTime(20);
  87.     togglePin(GPIOD, GPIO_PIN);
  88.     delayTime(20);
  89. }
  90. static void delayTime(uint32_t cnt)
  91. {
  92.     delay = cnt;
  93.     while(delay){ ; }
  94. }

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:

  1. #include "stm32f4xx.h"
  2. #include "stm32f4xx_rcc.h"
  3. #include "stm32f4xx_gpio.h"
  4. #define DIODE_GREEN             GPIO_Pin_12
  5. #define DIODE_ORANGE            GPIO_Pin_13
  6. #define DIODE_RED               GPIO_Pin_14
  7. #define DIODE_BLUE              GPIO_Pin_15
  8. #define BTN_PIN                 GPIO_Pin_0
  9. #define setPinHigh(GPIOx, PIN)  ((GPIOx)->BSRRL = (PIN))
  10. #define setPinLow(GPIOx, PIN)   ((GPIOx)->BSRRH = (PIN))
  11. #define togglePin(GPIOx, PIN)   ((GPIOx)->ODR ^= (PIN))

Następnie definicja funkcji statycznych:

  1. static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
  2. static void setPinAsInput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
  3. static void configExtiInterrupt();
  4. static void delayTime10_ms(uint32_t count);
  5. static void blinkDiode(void);

Teraz przejdę do opisu pętli głównej programu:

  1. RCC->CFGR = (uint32_t)RCC_CFGR_PPRE1_DIV2;              /* Config PCLK1 = HCLK */
  2. RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;                   /* Clock for Diodes */
  3. RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA;                   /* Clock for Btn */
  4. RCC->APB2ENR = RCC_APB2ENR_SYSCFGEN;                    /* Config SYSCFG Clock */
  5. RCC->APB1ENR = RCC_APB1ENR_WWDGEN;                      /* Clock for WWDG */
  6. __DSB();

Kolejnym elementem jest uruchomienie Systicka. W jego przerwaniu będzie umieszczona instrukcja ładująca dane do WWDG.

  1. SysTick_Config(10000);

Dalej w kolejce jest uruchomienie diod świecących:

  1. for(uint8_t loop = 12; loop<16; loop++)
  2. {
  3.     setPinAsOutput(GPIOD, loop);
  4. }

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.

  1. if(RCC->CSR & RCC_CSR_WWDGRSTF) /* Check if reset made by watchdog */
  2. {
  3.     blinkDiode(DIODE_GREEN);
  4.     blinkDiode(DIODE_GREEN);
  5. }
  6. else
  7. {
  8.     blinkDiode(DIODE_BLUE);
  9.     blinkDiode(DIODE_BLUE);
  10. }

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.

  1. 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.

  1. WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;
  2. WWDG->CFR = WWDG_CFR_EWI | 2<<7 | WWDG_CFR_W6 | 0x3F;  
  3. WWDG->SR &= ~WWDG_SR_EWIF;

Uruchomienie przerwania od WWDG:

  1. EXTI->IMR = EXTI_IMR_MR0;
  2.    
  3. NVIC_ClearPendingIRQ(WWDG_IRQn);
  4. NVIC_EnableIRQ(WWDG_IRQn);

Funkcja obsługi przerwania od licznika. Co określoną ilość cyklów dane od WWDG są przeładowywane.

  1. void SysTick_Handler(void)
  2. {
  3.     if(startFlag)
  4.     {
  5.         reload++;
  6.     }
  7.     if(reload == 200)
  8.     {
  9.         WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;
  10.         reload = 0;
  11.         startFlag = 1;
  12.         togglePin(GPIOD, DIODE_ORANGE);
  13.     }
  14.    
  15.     if(delay)
  16.     {
  17.         delay--;
  18.     }
  19. }

Cały program wygląda następująco:

  1. #include "stm32f4xx.h"
  2. #include "stm32f4xx_rcc.h"
  3. #include "stm32f4xx_gpio.h"
  4. #define DIODE_GREEN             GPIO_Pin_12
  5. #define DIODE_ORANGE            GPIO_Pin_13
  6. #define DIODE_RED               GPIO_Pin_14
  7. #define DIODE_BLUE              GPIO_Pin_15
  8. #define BTN_PIN                 GPIO_Pin_0
  9. #define setPinHigh(GPIOx, PIN)      ((GPIOx)->BSRRL = (PIN))
  10. #define setPinLow(GPIOx, PIN)       ((GPIOx)->BSRRH = (PIN))
  11. #define togglePin(GPIOx, PIN)       ((GPIOx)->ODR ^= (PIN))
  12. static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
  13. static void setPinAsInput(GPIO_TypeDef* GPIOx, uint8_t pinNumber);
  14. static void configExtiInterrupt();
  15. void blinkDiode(uint16_t GPIO_PIN);
  16. static void delayTime(uint32_t cnt);
  17. volatile uint32_t reload;
  18. volatile uint32_t delay;
  19. volatile uint32_t startFlag;
  20. int main()
  21. {
  22.     RCC->CFGR = (uint32_t)RCC_CFGR_PPRE1_DIV2;                  /* Config PCLK1 = HCLK */
  23.     RCC->AHB1ENR |= RCC_AHB1Periph_GPIOD;                       /* Clock for Diodes */
  24.     RCC->AHB1ENR |= RCC_AHB1Periph_GPIOA;                       /* Clock for Btn */
  25.     RCC->APB2ENR = RCC_APB2ENR_SYSCFGEN;                        /* Config SYSCFG Clock */
  26.     RCC->APB1ENR = RCC_APB1ENR_WWDGEN;                          /* Clock for WWDG */
  27.     __DSB();
  28.    
  29.     SysTick_Config(10000);
  30.    
  31.     /* Set diode pins as output */
  32.     for(uint8_t loop = 12; loop<16; loop++)
  33.     {
  34.         setPinAsOutput(GPIOD, loop);
  35.     }
  36.    
  37.     if(RCC->CSR & RCC_CSR_WWDGRSTF) /* Check if reset made by watchdog */
  38.     {
  39.         blinkDiode(DIODE_GREEN);
  40.         blinkDiode(DIODE_GREEN);
  41.     }
  42.     else
  43.     {
  44.         blinkDiode(DIODE_BLUE);
  45.         blinkDiode(DIODE_BLUE);
  46.     }
  47.    
  48.     RCC->CSR = RCC_CSR_RMVF;                /* Set RMVF bit to clear the reset flags */
  49.    
  50.     startFlag = 1;
  51.     setPinHigh(GPIOD, DIODE_BLUE);
  52.    
  53.     WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;        /* Enable WWDG, set bit T6 to 1, write coutner value */
  54.     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 */
  55.     WWDG->SR &= ~WWDG_SR_EWIF;                          /* Clear flag */
  56.    
  57.     EXTI->IMR = EXTI_IMR_MR0;
  58.    
  59.     NVIC_ClearPendingIRQ(WWDG_IRQn);
  60.     NVIC_EnableIRQ(WWDG_IRQn);
  61.     while (1) { }
  62. }
  63. void SysTick_Handler(void)
  64. {
  65.     if(startFlag)
  66.     {
  67.         reload++;
  68.     }
  69.     if(reload == 200)
  70.     {
  71.         WWDG->CR = WWDG_CR_WDGA | WWDG_CR_T6 | 0x7F;
  72.         reload = 0;
  73.         startFlag = 1;
  74.         togglePin(GPIOD, DIODE_ORANGE);
  75.     }
  76.    
  77.     if(delay)
  78.     {
  79.         delay--;
  80.     }
  81. }
  82. static void setPinAsOutput(GPIO_TypeDef* GPIOx, uint8_t pinNumber)
  83. {
  84.     GPIOx->MODER |= GPIO_Mode_OUT << (pinNumber * 2);
  85.     GPIOx->OSPEEDR |= GPIO_Speed_25MHz << (pinNumber * 2);
  86.     GPIOx->OTYPER |= GPIO_OType_PP << pinNumber;
  87.     GPIOx->PUPDR |= GPIO_PuPd_NOPULL << (pinNumber * 2);
  88. }
  89. void blinkDiode(uint16_t GPIO_PIN)
  90. {
  91.     togglePin(GPIOD, GPIO_PIN);
  92.     delayTime(20);
  93.     togglePin(GPIOD, GPIO_PIN);
  94.     delayTime(20);
  95. }
  96. static void delayTime(uint32_t cnt)
  97. {
  98.     delay = cnt;
  99.     while(delay){ ; }
  100. }
  101. void WWDG_IRQHandler(void)
  102. {
  103.     WWDG->SR &= ~WWDG_SR_EWIF;  /* Clear interrupt flag */
  104.     while(1) {}                 /* kill device, restart it */
  105. }