piątek, 10 września 2021

LPC1769 - Watchdog

W tym poście chciałbym opisać licznik watchdog.

Image of LPCXpresso1769/CD

Rejestry watchdoga:


Można wyróżnić następujące rejestry:
  • WDMOD - tryb pracy oraz status watchdoga
  • WDTC - wartość do jakiej licznik musi odliczać.
  • WDFEED - Przeładowanie licznika do wartości początkowej.
  • WDTV - Aktualna wartość licznika;
  • WDCLKSEL - Źródło zegara;

Dokładne informacje o każdym z rejestrów można znaleźć tutaj.

Gdy korzystamy z gotowych bibliotek lista dostępnych funkcji jest następująca:

  1. void WDT_Init (uint32_t ClkSrc, uint32_t WDTMode);
  2. void WDT_Start(uint32_t TimeOut);
  3. void WDT_Feed (void);
  4. FlagStatus WDT_ReadTimeOutFlag (void);
  5. void WDT_ClrTimeOutFlag (void);
  6. void WDT_UpdateTimeOut ( uint32_t TimeOut);
  7. uint32_t WDT_GetCurrentCount(void);

Wykaz flag:

  1. /************************** WDT Control **************************/
  2. #define WDT_WDMOD_WDEN              ((uint32_t)(1<<0))  /** WDT interrupt enable bit */
  3. #define WDT_WDMOD_WDRESET           ((uint32_t)(1<<1))  /** WDT interrupt enable bit */
  4. #define WDT_WDMOD_WDTOF             ((uint32_t)(1<<2))  /** WDT time out flag bit */
  5. #define WDT_WDMOD_WDINT             ((uint32_t)(1<<3))  /** WDT Time Out flag bit */
  6. #define WDT_WDMOD(n)                ((uint32_t)(1<<n))  /** WDT Mode */
  7.  
  8. /**************************** PRIVATE TYPES ***************************/
  9. #define WDT_US_INDEX    ((uint32_t)(1000000))       /** Define divider index for microsecond ( us ) */
  10. #define WDT_TIMEOUT_MIN ((uint32_t)(0xFF))          /** WDT Time out minimum value */
  11. #define WDT_TIMEOUT_MAX ((uint32_t)(0xFFFFFFFF))    /** WDT Time out maximum value */
  12.  
  13. /**************************** GLOBAL/PUBLIC TYPES ***************************/
  14. #define WDT_WDMOD_MASK          (uint8_t)(0x02)         /** Watchdog mode register mask */
  15. #define WDT_WDTC_MASK           (uint8_t)(0xFFFFFFFF)   /** Watchdog timer constant register mask */
  16. #define WDT_WDFEED_MASK         (uint8_t)(0x000000FF)   /** Watchdog feed sequence register mask */
  17. #define WDT_WDCLKSEL_MASK       (uint8_t)(0x03)         /** Watchdog timer value register mask */
  18. #define WDT_WDCLKSEL_RC         (uint8_t)(0x00)         /** Clock selected from internal RC */
  19. #define WDT_WDCLKSEL_PCLK       (uint8_t)(0x01)         /** Clock selected from PCLK */
  20. #define WDT_WDCLKSEL_RTC        (uint8_t)(0x02)         /** Clock selected from external RTC */

Dostępne opcje wyboru trybów:

  1. /** @brief Clock source option for WDT */
  2. typedef enum {
  3.     WDT_CLKSRC_IRC = 0, /*!< Clock source from Internal RC oscillator */
  4.     WDT_CLKSRC_PCLK = 1, /*!< Selects the APB peripheral clock (PCLK) */
  5.     WDT_CLKSRC_RTC = 2 /*!< Selects the RTC oscillator */
  6. } WDT_CLK_OPT;
  7. #define PARAM_WDT_CLK_OPT(OPTION)  ((OPTION ==WDT_CLKSRC_IRC)||(OPTION ==WDT_CLKSRC_IRC)||(OPTION ==WDT_CLKSRC_IRC))
  8. /** @brief WDT operation mode */
  9. typedef enum {
  10.     WDT_MODE_INT_ONLY = 0, /*!< Use WDT to generate interrupt only */
  11.     WDT_MODE_RESET = 1    /*!< Use WDT to generate interrupt and reset MCU */
  12. } WDT_MODE_OPT;
  13. #define PARAM_WDT_MODE_OPT(OPTION)  ((OPTION ==WDT_MODE_INT_ONLY)||(OPTION ==WDT_MODE_RESET))

Watchdog może pobierać zegar z trzech źródeł, oscylator RC, RTC albo z APB (ang. Advanced Peripheral Bus. Może on także działać w dwóch trybach jeden z nich wywołuje przerwanie, drugi dodatkowo resetuje układ.

Poniżej przykładowe funkcje:


Inicjalizacja watchdoga:

  1. //WDT_Init_Start(WDT_CLKSRC_IRC, WDT_MODE_RESET, 6000000);
  2.  
  3. //Z stabdardowymi bibliotekami
  4. void WDT_Init_Start(WDT_CLK_OPT wdt_clk_src, WDT_MODE_OPT wdt_mode, uint32_t wdt_time)
  5. {
  6.     WDT_Init(wdt_clk_src, wdt_mode);
  7.     WDT_Start(wdt_time);
  8. }
  9.  
  10. //Bez standardowych bibliotek
  11. void WDT_Init_Start(WDT_CLK_OPT wdt_clk_src, WDT_MODE_OPT wdt_mode, uint32_t wdt_time)
  12. {
  13.     CLKPWR_SetPCLKDiv (CLKPWR_PCLKSEL_WDT, CLKPWR_PCLKSEL_CCLK_DIV_4);
  14.  
  15.     LPC_WDT->WDCLKSEL &= ~WDT_WDCLKSEL_MASK;
  16.     LPC_WDT->WDCLKSEL |= ClkSrc;
  17.     if (WDTMode == WDT_MODE_RESET){ LPC_WDT->WDMOD |= WDT_WDMOD(WDTMode); }
  18.  
  19.     uint32_t ClkSrc;
  20.     ClkSrc = LPC_WDT->WDCLKSEL;
  21.     ClkSrc &=WDT_WDCLKSEL_MASK;
  22.     WDT_SetTimeOut(ClkSrc,TimeOut);
  23.     LPC_WDT->WDMOD |= WDT_WDMOD_WDEN;
  24.    
  25.     __disable_irq();
  26.     LPC_WDT->WDFEED = 0xAA;
  27.     LPC_WDT->WDFEED = 0x55;
  28.     __enable_irq();
  29. }

Aktualizacja licznika:

  1. void WDT_FeedTimer(void)
  2. {
  3.     __disable_irq();
  4.     LPC_WDT->WDFEED = 0xAA;
  5.     LPC_WDT->WDFEED = 0x55;
  6.     __enable_irq();
  7. }

Do rejestru WDFEED należy wpisać wartość 0xAA a następnie 0x55. W celu przeładowania wartości.
Ta operacja powoduje także uruchomienie watchdoga.

Dodatkowo w przypadku wygenerowania resetu układu warto zweryfikować co wyzwoliło przerwanie. 

  1. FlagStatus WDT_ReadTimeOutFlag (void)
  2. {
  3.     return ((LPC_WDT->WDMOD & WDT_WDMOD_WDTOF)>>2);
  4. }
  5.  
  6. void WDT_ClrTimeOutFlag (void)
  7. {
  8.     LPC_WDT->WDMOD &=~WDT_WDMOD_WDTOF;
  9. }
  10. /////////////////////////////////////////////////////////
  11. if(WDT_ReadTimeOutFlag() != 0) //Reset wywołany z WDT
  12. {
  13.     WDT_ClrTimeOutFlag();
  14.     //Wyświetl informację, mrugnij diodą itp itd.
  15. }

W tym celu można skorzystać z funkcji, która odczytuje informację z rejestru WDMOD. Jeśli reset był wywołany z watchdoga to bit w tym rejestrze jest ustawiony. Następnie należy go wyczyścić.

Za pomocą WDT można także wygenerować restart. W tym celu wystarczy stworzyć funkcję która przerwie normalne wykonywanie programu i wejdzie w pętlę nieskończoną:

  1. void WDT_Resart(unsigned int _time)
  2. {
  3.     WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_RESET);
  4.     WDT_Start(_time);
  5.     while(1);
  6. }
  7.  
  8. void WDT_Restart_Quick(void)
  9. {
  10.     WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_RESET);
  11.     WDT_Start(1);
  12.     while(1);
  13. }

Jeśli zachodzi konieczność debugowania programu, to należy wyłączyć watchdoga na ten czas. Pozwoli to na bardziej efektywne testowanie urządzenia. Bez niepotrzebnych resetów. Można to wykonać np. przez stosowanie rejestrów preprocesora:

  1. #define ENABLE_WATCHDOG
  2.  
  3. #ifndef ENABLE_WATCHDOG
  4. //Kod do incjalizacji WDT
  5. #endif

Można też włączyć WDT tak aby generował tylko przerwanie bez resetu. Pozwoli to na ustalenie kiedy nastąpiło przepełnienie timera bez konieczności resetu LPC.

W trybach uśpienia wygenerowanie przerwania od WDT wybudzi urządzenie z tego trybu.

  1. void WDT_IRQHandler(void)
  2. {
  3.     WDT_ClrTimeOutFlag();
  4. }