前言 在上一篇 中已經介紹了 WWDG 的基本概念。這一篇要接著介紹 WWDG 窗口看門狗的程式。
正文 首先一樣以 Nucleo-F446RE 做示範。
首先建立一個 PIO 的專案 ,選擇 Framework 為「libopencm3」,並在 src/
資料夾中新增並開啓 main.c
檔案。
完整程式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/gpio.h> #include <libopencm3/stm32/wwdg.h> #include <libopencm3/cm3/systick.h> #include <libopencm3/cm3/nvic.h> #define WWDG_COUNTER (0x7F) #define WWDG_WINDOWS (0x5F) #define WWDG_MS(v) (1.0 / (rcc_apb1_frequency / 1000) * 4096 * 8 * (v)) #define RCC_LED_GPIO (RCC_GPIOA) #define GPIO_LED_PORT (GPIOA) #define GPIO_LED_PIN (GPIO5) static volatile uint32_t systick_delay = 0 ;static void delay_ms (uint32_t value) { systick_delay = value; while (systick_delay != 0 ) { } }static void rcc_setup (void ) { rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]); rcc_periph_clock_enable(RCC_LED_GPIO); rcc_periph_clock_enable(RCC_WWDG); }static void systick_setup (void ) { systick_set_clocksource(STK_CSR_CLKSOURCE_AHB_DIV8); systick_set_reload(rcc_ahb_frequency / 8 / 1000 - 1 ); systick_interrupt_enable(); systick_counter_enable(); }static void wwdg_refresh (void ) { WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB; }static void wwdg_setup (void ) { WWDG_CFR |= WWDG_CFR_WDGTB_CK_DIV8 << WWDG_CFR_WDGTB_LSB; WWDG_CR &= ~(0x7F << WWDG_CR_T_LSB); WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB; WWDG_CFR &= ~(0x7F << WWDG_CFG_W_LSB); WWDG_CFR |= WWDG_WINDOWS << WWDG_CFG_W_LSB; WWDG_CR |= WWDG_CR_WDGA; }static void led_setup (void ) { gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN); gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN); }int main (void ) { rcc_setup(); systick_setup(); led_setup(); gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN); delay_ms(10 ); gpio_set(GPIO_LED_PORT, GPIO_LED_PIN); delay_ms(1000 ); wwdg_setup(); delay_ms(WWDG_MS(WWDG_COUNTER - WWDG_WINDOWS + 1 )); wwdg_refresh(); while (1 ) { gpio_toggle(GPIO_LED_PORT, GPIO_LED_PIN); delay_ms(WWDG_MS((WWDG_COUNTER & 0x3F ) + 1 )); wwdg_refresh(); } return 0 ; }void sys_tick_handler (void ) { if (systick_delay != 0 ) { systick_delay--; } }
分段說明 Include 1 2 3 4 5 #include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/gpio.h> #include <libopencm3/stm32/wwdg.h> #include <libopencm3/cm3/systick.h> #include <libopencm3/cm3/nvic.h>
和 IWDG 時一樣,為了要更方便驗證 WWDG 的運作,我使用 SysTick 實現較精確的 ms 級 delay()
,因此需要 systick.h
與 nvic.h
。當然也需要今天的主角——wwdg.h
。
RCC 1 2 3 4 5 6 static void rcc_setup (void ) { rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]); rcc_periph_clock_enable(RCC_LED_GPIO); rcc_periph_clock_enable(RCC_WWDG); }
要注意這裡與 IWDG 不同,WWDG 在 APB1 底下,所以要記得為它致能 Clock。
WWDG Timeout 計算 1 2 3 4 #define WWDG_COUNTER (0x7F) #define WWDG_WINDOWS (0x5F) #define WWDG_MS(v) (1.0 / (rcc_apb1_frequency / 1000) * 4096 * 8 * (v))
複習一下上一篇提到的基本概念。在啓用 WWDG 時有兩種情況會造成它觸發 System Reset:
當 WWDG 下數計數器的值 T[6:0] 變得小於 0x40
,即 T6 位元變成 0
。
在時間窗口(Window)外(即 T[6:0] > W[6:0])時下數計數器被重新裝載(Reload)。
只要參考上面的公式就可以計算 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 設定 1 2 3 4 5 6 7 8 9 10 11 12 static void wwdg_setup (void ) { WWDG_CFR |= WWDG_CFR_WDGTB_CK_DIV8 << WWDG_CFR_WDGTB_LSB; WWDG_CR &= ~(0x7F << WWDG_CR_T_LSB); WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB; WWDG_CFR &= ~(0x7F << WWDG_CFG_W_LSB); WWDG_CFR |= WWDG_WINDOWS << WWDG_CFG_W_LSB; WWDG_CR |= WWDG_CR_WDGA; }
有沒有感受到這一段程式的風格突變?
因為截止寫文章當下,LibOpenCM3 還沒有實作任何 WWDG 的相關函式,所以只好回歸最原始的暫存器操作。還好 WWDG 是個很簡單的功能,要操作的暫存器甚至比使用 GPIO 還少。
要設定的值只有四個,分別為 WDG 預除頻器的除頻值 WDGTW、T[6:0]、W[6:0],最後再將 WDGA 設為 1
以致能 WWDG。
注意,寫入 WWDG_CR 暫存器的值必須要在 0xFF
與 0xC0
之間。由於第 7 位 WDGA 只能在 Reset 後由硬體清為 0
,所以寫入 WWDG_CR 的第 7 位元一定是 1
。而如果第 6 位 T6 被設定為 0
的話會立刻觸發 Reset。
WWDG Refresh 1 2 3 4 static void wwdg_refresh (void ) { WWDG_CR |= WWDG_COUNTER << WWDG_CR_T_LSB; }
Refresh 也非常單純,就是寫入 T[6:0] 讓計數器 Reload。
主程式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int main (void ) { rcc_setup(); systick_setup(); led_setup(); gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN); delay_ms(10 ); gpio_set(GPIO_LED_PORT, GPIO_LED_PIN); delay_ms(1000 ); wwdg_setup(); delay_ms(WWDG_MS(WWDG_COUNTER - WWDG_WINDOWS + 1 )); wwdg_refresh(); while (1 ) { gpio_toggle(GPIO_LED_PORT, GPIO_LED_PIN); delay_ms(WWDG_MS((WWDG_COUNTER & 0x3F ) + 1 )); wwdg_refresh(); } return 0 ; }
主程式的部分和 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 。
1 2 3 4 5 6 7 8 9 10 11 static void rcc_setup (void ) {#if defined(STM32F1) rcc_clock_setup_in_hse_8mhz_out_72mhz();#elif defined(STM32F4) rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);#endif rcc_periph_clock_enable(RCC_LED_GPIO); rcc_periph_clock_enable(RCC_WWDG); }
1 2 3 4 5 6 7 8 9 10 static void led_setup (void ) { #if defined(STM32F1) gpio_set_mode(GPIO_LED_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_LED_PIN);#else gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN); gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);#endif }
成果 這次使用 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 鐵人賽 。