STM32 LibOpenCM3:WWDG 窗口看門狗計時器

系列:簡單入門 LibOpenCM3 STM32 嵌入式系統開發 Posted on 2022-10-01

前言

上一篇中已經介紹了 WWDG 的基本概念。這一篇要接著介紹 WWDG 窗口看門狗的程式。

正文

首先一樣以 Nucleo-F446RE 做示範。

首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/ 資料夾中新增並開啓 main.c 檔案。

完整程式

  1/**
  2 * @file   main.c
  3 * @brief  WWDG (Window watchdog) example for STM32 Nucleo-F446RE.
  4 */
  5
  6#include <libopencm3/stm32/rcc.h>
  7#include <libopencm3/stm32/gpio.h>
  8#include <libopencm3/stm32/wwdg.h>
  9#include <libopencm3/cm3/systick.h>
 10#include <libopencm3/cm3/nvic.h>
 11
 12#define WWDG_COUNTER (0x7F) /* WWDG_CR  -> T[6:0], 0x7F ~ 0x40. */
 13#define WWDG_WINDOWS (0x5F) /* WWDG_CFR -> W[6:0], T[6:0] ~ 0x40. */
 14
 15#define WWDG_MS(v) (1.0 / (rcc_apb1_frequency / 1000) * 4096 * 8 * (v))
 16
 17#define RCC_LED_GPIO (RCC_GPIOA)
 18#define GPIO_LED_PORT (GPIOA)
 19#define GPIO_LED_PIN (GPIO5) /* D13. */
 20
 21static volatile uint32_t systick_delay = 0;
 22
 23static void delay_ms(uint32_t value)
 24{
 25  systick_delay = value;
 26  while (systick_delay != 0)
 27  {
 28    /* Wait. */
 29  }
 30}
 31
 32static void rcc_setup(void)
 33{
 34  rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
 35  rcc_periph_clock_enable(RCC_LED_GPIO);
 36  rcc_periph_clock_enable(RCC_WWDG);
 37}
 38
 39static void systick_setup(void)
 40{
 41  systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8);
 42  systick_set_reload(rcc_ahb_frequency / 8 / 1000 - 1);
 43  systick_interrupt_enable();
 44  systick_counter_enable();
 45}
 46
 47static void wwdg_refresh(void)
 48{
 49  WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB;
 50}
 51
 52static void wwdg_setup(void)
 53{
 54  WWDG_CFR |= WWDG_CFR_WDGTB_CK_DIV8 << WWDG_CFR_WDGTB_LSB; /* Set WDG prescaler to div8. */
 55
 56  WWDG_CR &= ~(0x7F << WWDG_CR_T_LSB);      /* Clear T[6:0]. */
 57  WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB; /* Setup T[6:0]. */
 58
 59  WWDG_CFR &= ~(0x7F << WWDG_CFG_W_LSB);      /* Clear W[6:0]. */
 60  WWDG_CFR |= WWDG_WINDOWS << WWDG_CFG_W_LSB; /* Setup W[6:0]. */
 61
 62  WWDG_CR |= WWDG_CR_WDGA; /* Enable WWDG. */
 63}
 64
 65static void led_setup(void)
 66{
 67  gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN);
 68  gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);
 69}
 70
 71int main(void)
 72{
 73  rcc_setup();
 74  systick_setup();
 75  led_setup();
 76
 77  gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN);
 78  delay_ms(10);
 79  gpio_set(GPIO_LED_PORT, GPIO_LED_PIN);
 80  delay_ms(1000);
 81
 82  wwdg_setup(); /* Setup and start WWDG. */
 83
 84  delay_ms(WWDG_MS(WWDG_COUNTER - WWDG_WINDOWS + 1));
 85  wwdg_refresh();
 86
 87  while (1)
 88  {
 89    gpio_toggle(GPIO_LED_PORT, GPIO_LED_PIN); /* LED on/off. */
 90    delay_ms(WWDG_MS((WWDG_COUNTER & 0x3F) + 1)); /* 0x3F is the mask for bit[5:0]. */
 91    wwdg_refresh();
 92  }
 93
 94  return 0;
 95}
 96
 97/**
 98 * @brief  SysTick handler.
 99 */
100void sys_tick_handler(void)
101{
102  if (systick_delay != 0)
103  {
104    systick_delay--;
105  }
106}

分段說明

Include

1#include <libopencm3/stm32/rcc.h>
2#include <libopencm3/stm32/gpio.h>
3#include <libopencm3/stm32/wwdg.h>
4#include <libopencm3/cm3/systick.h>
5#include <libopencm3/cm3/nvic.h>

和 IWDG 時一樣,爲了要更方便驗證 WWDG 的運作,我使用 SysTick 實現較精確的 ms 級 delay(),因此需要 systick.hnvic.h。當然也需要今天的主角——wwdg.h

RCC

1static void rcc_setup(void)
2{
3  rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
4  rcc_periph_clock_enable(RCC_LED_GPIO);
5  rcc_periph_clock_enable(RCC_WWDG);
6}

要注意這裡與 IWDG 不同,WWDG 在 APB1 底下,所以要記得爲它致能 Clock。

WWDG Timeout 計算

1#define WWDG_COUNTER (0x7F) /* WWDG_CR  -> T[6:0], 0x7F ~ 0x40. */
2#define WWDG_WINDOWS (0x5F) /* WWDG_CFR -> W[6:0], T[6:0] ~ 0x40. */
3
4#define WWDG_MS(v) (1.0 / (rcc_apb1_frequency / 1000) * 4096 * 8 * (v))

複習一下上一篇提到的基本概念。在啓用 WWDG 時有兩種情況會造成它觸發 System Reset:

  1. 當 WWDG 下數計數器的值 T[6:0] 變得小於 0x40,即 T6 位元變成 0
  2. 在時間窗口(Window)外(即 T[6:0] > W[6:0])時下數計數器被重新裝載(Reload)。

▲ WWDG 的 Window 示意圖。取自 RM0390 Rev 6 P.648。

▲ WWDG 的 Window 示意圖。取自 RM0390 Rev 6 P.648。

▲ WWDG 的 Timeout 計算公式。取自 RM0390 Rev 6 P.648。

▲ WWDG 的 Timeout 計算公式。取自 RM0390 Rev 6 P.648。

只要參考上面的公式就可以計算 WWDG 的 Timeout 長度。

我這裡以 WWDG_COUNTER 爲名定義 T[6:0] 爲 0x7F,以 WWDG_WINDOWS 爲名定義 W[6:0] 爲 0x5F

再使用一個 Macro WWDG_MS() 來定義 Timeout 計算公式爲 1.0 / (rcc_apb1_frequency / 1000) * 4096 * 8 * (v)

依此設定,必須要在 T[6:0] = 0x5F~0x40 的這段時間內才可以 Refresh。T[6:0] = 0x7F~0x60 是 Window 外,T[6:0] ≦ 0x3F時代表 Timeout。

WWDG 設定

 1static void wwdg_setup(void)
 2{
 3  WWDG_CFR |= WWDG_CFR_WDGTB_CK_DIV8 << WWDG_CFR_WDGTB_LSB; /* Set WDG prescaler to div8. */
 4
 5  WWDG_CR &= ~(0x7F << WWDG_CR_T_LSB);      /* Clear T[6:0]. */
 6  WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB; /* Setup T[6:0]. */
 7
 8  WWDG_CFR &= ~(0x7F << WWDG_CFG_W_LSB);      /* Clear W[6:0]. */
 9  WWDG_CFR |= WWDG_WINDOWS << WWDG_CFG_W_LSB; /* Setup W[6:0]. */
10
11  WWDG_CR |= WWDG_CR_WDGA; /* Enable WWDG. */
12}

有沒有感受到這一段程式的風格突變?

因爲截止寫文章當下,LibOpenCM3 還沒有實作任何 WWDG 的相關函式,所以只好回歸最原始的暫存器操作。還好 WWDG 是個很簡單的功能,要操作的暫存器甚至比使用 GPIO 還少。

要設定的值只有四個,分別爲 WDG 預除頻器的除頻值 WDGTW、T[6:0]、W[6:0],最後再將 WDGA 設爲 1 以致能 WWDG。

注意,寫入 WWDG_CR 暫存器的值必須要在 0xFF0xC0 之間。由於第 7 位 WDGA 只能在 Reset 後由硬體清爲 0,所以寫入 WWDG_CR 的第 7 位元一定是 1。而如果第 6 位 T6 被設定爲 0 的話會立刻觸發 Reset。

WWDG Refresh

1static void wwdg_refresh(void)
2{
3  WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB;
4}

Refresh 也非常單純,就是寫入 T[6:0] 讓計數器 Reload。

主程式

 1int main(void)
 2{
 3  rcc_setup();
 4  systick_setup();
 5  led_setup();
 6
 7  gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN);
 8  delay_ms(10);
 9  gpio_set(GPIO_LED_PORT, GPIO_LED_PIN);
10  delay_ms(1000);
11
12  wwdg_setup(); /* Setup and start WWDG. */
13
14  delay_ms(WWDG_MS(WWDG_COUNTER - WWDG_WINDOWS + 1));
15  wwdg_refresh();
16
17  while (1)
18  {
19    gpio_toggle(GPIO_LED_PORT, GPIO_LED_PIN); /* LED on/off. */
20    delay_ms(WWDG_MS((WWDG_COUNTER & 0x3F) + 1)); /* 0x3F is the mask for bit[5:0]. */
21    wwdg_refresh();
22  }
23
24  return 0;
25}

主程式的部分和 IWDG 時差不多。在 WWDG 設定並啓動(wwdg_setup())前先讓 LED off 10ms 後 on 1s,以方便觀察是否發生 Reset。

在 WWDG 啓動後等待數毫秒再進行一次 Refresh,這邊是要驗證 Window(條件 2),如果更早進行 Refresh 的話就會觸發 Reset。

主迴圈就是讓 LED 閃爍,並在一定時間後進行 Refresh,這裡是要驗證 WWDG 的 Timeout(條件 1),若更晚進行 Refresh 的話就會觸發 Reseet。

多環境程式(F446RE + F103RB)

由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。

以下列出主要的差異部分。完整的程式請看 GitHub repo

 1static void rcc_setup(void)
 2{
 3#if defined(STM32F1)
 4  rcc_clock_setup_in_hse_8mhz_out_72mhz();
 5#elif defined(STM32F4)
 6  rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
 7#endif
 8
 9  rcc_periph_clock_enable(RCC_LED_GPIO);
10  rcc_periph_clock_enable(RCC_WWDG);
11}
 1static void led_setup(void)
 2{
 3  /* Set LED pin to output push-pull. */
 4#if defined(STM32F1)
 5  gpio_set_mode(GPIO_LED_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_LED_PIN);
 6#else
 7  gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN);
 8  gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);
 9#endif
10}

成果

這次使用 PlatformIO 的 Debug 功能來測試 WWDG 的運作。

可以看到 Refresh 前,T[6:0] 的值數到 0x5F,已經不大於 W[6:0] 了(條件 2),所以這時 Refresh 不會觸發 Reset。

在 Refresh 前,T[6:0] 的值爲 0x40,還沒到下限 0x3F(條件 1),所以這時還來得及 Refresh 而不會觸發 Reset。

這裡的 delay 的最小單位是 1 ms,但實際計算 WWDG 的各項參數是會算到小數點後,這一點在實際應用上應該被考慮,例如使用 ns 級的 delay 函式。

小結

這次接續 IWDG 的內容,繼續介紹 WWDG 的用法。由於 LibOpenCM3 目前沒有實作 WWDG 的相關操作函式,所以這次是使用操作暫存器的方式來示範,但因爲我幾乎沒有在直接操作暫存器,因此不確定上述的寫法是不是最好的,畢竟這種東西應該有不少細節是需要注意的,若有任何建議都歡迎提出。

另外,這次也使用了 PIO 的 Debug 功能來做程式的驗證。Debug 是非常好用的功能,尤其 Nucleo 開發板上都有 ST-Link,可以直接進行 Debug,即時查看程式的運作與 STM32 中的暫存器數值。如果還沒用過的話請一定要學習並嘗試看看。

參考資料

本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽



留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)

comments powered by Disqus