W tym poście chciałbym opisać licznik watchdog.
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:
- void WDT_Init (uint32_t ClkSrc, uint32_t WDTMode);
- void WDT_Start(uint32_t TimeOut);
- void WDT_Feed (void);
- FlagStatus WDT_ReadTimeOutFlag (void);
- void WDT_ClrTimeOutFlag (void);
- void WDT_UpdateTimeOut ( uint32_t TimeOut);
- uint32_t WDT_GetCurrentCount(void);
Wykaz flag:
- /************************** WDT Control **************************/
- #define WDT_WDMOD_WDEN ((uint32_t)(1<<0)) /** WDT interrupt enable bit */
- #define WDT_WDMOD_WDRESET ((uint32_t)(1<<1)) /** WDT interrupt enable bit */
- #define WDT_WDMOD_WDTOF ((uint32_t)(1<<2)) /** WDT time out flag bit */
- #define WDT_WDMOD_WDINT ((uint32_t)(1<<3)) /** WDT Time Out flag bit */
- #define WDT_WDMOD(n) ((uint32_t)(1<<n)) /** WDT Mode */
- /**************************** PRIVATE TYPES ***************************/
- #define WDT_US_INDEX ((uint32_t)(1000000)) /** Define divider index for microsecond ( us ) */
- #define WDT_TIMEOUT_MIN ((uint32_t)(0xFF)) /** WDT Time out minimum value */
- #define WDT_TIMEOUT_MAX ((uint32_t)(0xFFFFFFFF)) /** WDT Time out maximum value */
- /**************************** GLOBAL/PUBLIC TYPES ***************************/
- #define WDT_WDMOD_MASK (uint8_t)(0x02) /** Watchdog mode register mask */
- #define WDT_WDTC_MASK (uint8_t)(0xFFFFFFFF) /** Watchdog timer constant register mask */
- #define WDT_WDFEED_MASK (uint8_t)(0x000000FF) /** Watchdog feed sequence register mask */
- #define WDT_WDCLKSEL_MASK (uint8_t)(0x03) /** Watchdog timer value register mask */
- #define WDT_WDCLKSEL_RC (uint8_t)(0x00) /** Clock selected from internal RC */
- #define WDT_WDCLKSEL_PCLK (uint8_t)(0x01) /** Clock selected from PCLK */
- #define WDT_WDCLKSEL_RTC (uint8_t)(0x02) /** Clock selected from external RTC */
Dostępne opcje wyboru trybów:
- /** @brief Clock source option for WDT */
- typedef enum {
- WDT_CLKSRC_IRC = 0, /*!< Clock source from Internal RC oscillator */
- WDT_CLKSRC_PCLK = 1, /*!< Selects the APB peripheral clock (PCLK) */
- WDT_CLKSRC_RTC = 2 /*!< Selects the RTC oscillator */
- } WDT_CLK_OPT;
- #define PARAM_WDT_CLK_OPT(OPTION) ((OPTION ==WDT_CLKSRC_IRC)||(OPTION ==WDT_CLKSRC_IRC)||(OPTION ==WDT_CLKSRC_IRC))
- /** @brief WDT operation mode */
- typedef enum {
- WDT_MODE_INT_ONLY = 0, /*!< Use WDT to generate interrupt only */
- WDT_MODE_RESET = 1 /*!< Use WDT to generate interrupt and reset MCU */
- } WDT_MODE_OPT;
- #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:
- //WDT_Init_Start(WDT_CLKSRC_IRC, WDT_MODE_RESET, 6000000);
- //Z stabdardowymi bibliotekami
- void WDT_Init_Start(WDT_CLK_OPT wdt_clk_src, WDT_MODE_OPT wdt_mode, uint32_t wdt_time)
- {
- WDT_Init(wdt_clk_src, wdt_mode);
- WDT_Start(wdt_time);
- }
- //Bez standardowych bibliotek
- void WDT_Init_Start(WDT_CLK_OPT wdt_clk_src, WDT_MODE_OPT wdt_mode, uint32_t wdt_time)
- {
- CLKPWR_SetPCLKDiv (CLKPWR_PCLKSEL_WDT, CLKPWR_PCLKSEL_CCLK_DIV_4);
- LPC_WDT->WDCLKSEL &= ~WDT_WDCLKSEL_MASK;
- LPC_WDT->WDCLKSEL |= ClkSrc;
- if (WDTMode == WDT_MODE_RESET){ LPC_WDT->WDMOD |= WDT_WDMOD(WDTMode); }
- uint32_t ClkSrc;
- ClkSrc = LPC_WDT->WDCLKSEL;
- ClkSrc &=WDT_WDCLKSEL_MASK;
- WDT_SetTimeOut(ClkSrc,TimeOut);
- LPC_WDT->WDMOD |= WDT_WDMOD_WDEN;
- __disable_irq();
- LPC_WDT->WDFEED = 0xAA;
- LPC_WDT->WDFEED = 0x55;
- __enable_irq();
- }
Aktualizacja licznika:
- void WDT_FeedTimer(void)
- {
- __disable_irq();
- LPC_WDT->WDFEED = 0xAA;
- LPC_WDT->WDFEED = 0x55;
- __enable_irq();
- }
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.
- FlagStatus WDT_ReadTimeOutFlag (void)
- {
- return ((LPC_WDT->WDMOD & WDT_WDMOD_WDTOF)>>2);
- }
- void WDT_ClrTimeOutFlag (void)
- {
- LPC_WDT->WDMOD &=~WDT_WDMOD_WDTOF;
- }
- /////////////////////////////////////////////////////////
- if(WDT_ReadTimeOutFlag() != 0) //Reset wywołany z WDT
- {
- WDT_ClrTimeOutFlag();
- //Wyświetl informację, mrugnij diodą itp itd.
- }
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ą:
- void WDT_Resart(unsigned int _time)
- {
- WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_RESET);
- WDT_Start(_time);
- while(1);
- }
- void WDT_Restart_Quick(void)
- {
- WDT_Init(WDT_CLKSRC_IRC, WDT_MODE_RESET);
- WDT_Start(1);
- while(1);
- }
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:
- #define ENABLE_WATCHDOG
- #ifndef ENABLE_WATCHDOG
- //Kod do incjalizacji WDT
- #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.
- void WDT_IRQHandler(void)
- {
- WDT_ClrTimeOutFlag();
- }