最近因為專案產品硬體設計有問題,導致設計的一款產品把硬體電源開關以及硬體系統復位功能去掉了。更嚴重的是,這產品已經開始生產了,硬體已經無法修改,所以軟體必須上看門狗,否則裝置宕機或是異常後就只能拆裝置拔電池復位了。
我們使用的MCU是普冉的PY32F030,這顆晶片在低功耗應用場景下,使用看門狗會有很多的問題和缺陷,需要非常注意,稍有不慎,就會出問題。
關於看門狗在低功耗場景下的應用,幾個問題點可以提前思考一下:
看門狗的分類,根據實現方式的不同,可以分為軟體看門狗和硬體看門狗:
根據使用方式的不同,又可以區分為獨立看門狗和視窗看門狗
獨立看門狗: 獨立看門狗通常用於監控整個系統的執行狀態,而不特定於某個任務或程序,當系統故障,死鎖,無響應的時候,應用程式無法進行正常喂狗,看門狗超時從而產生復位。
視窗看門狗: 視窗看門狗更專注於監控特定任務或程序的執行狀態,並在特定的時間視窗內完成。比如在某個任務中,它的執行時間要求非常高,可以使用視窗看門狗,它有一個時間視窗,如果太早喂狗和太晚喂狗,都會產生異常,正因為它喂狗時間有個時間視窗,所以才叫視窗看門狗。
我使用的普冉PY32F030系列MCU,它是32位元Cortex-M0+的核心,裡面帶有一個獨立看門狗IWDG和一個視窗看門狗WWDG。
其中,獨立看門狗和視窗看門狗,還有軟體和硬體的區別,主要差異是在看門狗的啟動方式上不同。下面我們的介紹,主要針對獨立看門狗。
看門狗的啟動有多種方式:
這裡可以直接參考官方sample進行初始化:
IWDG_HandleTypeDef IwdgHandle;
HAL_Init();
/*##-3- Configure & Start the IWDG peripheral #########################################*/
IwdgHandle.Instance = IWDG;
IwdgHandle.Init.Prescaler = IWDG_PRESCALER_32;//T=1MS
IwdgHandle.Init.Reload = (1000); //1ms*1000=1s
IwdgHandle.Init.Window = IWDG_WINDOW_DISABLE;
if(HAL_IWDG_Init(&IwdgHandle) != HAL_OK)
{
/* Initialization Error */
Error_Handler();
}
這裡需要特別注意,因為IWDG是依賴於LSI時鐘的,也就是在HAL_IWDG_Init 函數呼叫之前,必須先開啟LSI時鐘。
官方給的sample中,是在HAL_Init()中把LSI時鐘開啟了。當你把上面這段程式碼移植到你自己工程上,如果你LSI沒有開啟,或者是在HAL_IWDG_Init後面才開LSI時鐘,你呼叫HAL_IWDG_Init就會一直失敗,系統一直ERROR,整個MCU會啟動不了。
直接往 IWDG_SR,IWDG_RLR,IWDG_KR三個暫存器地址寫入對應的引數,使能IWDG
void init_wtd(void)
{
volatileu int32_t *IWDG_KR_ADDR = (volatileuint32_t *)0x40003000UL;
volatileu int32_t *IWDG_PR_ADDR = (volatileuint32_t *)0x40003004UL;
volatileu int32_t *IWDG_RLR_ADDR = (volatileuint32_t *)0x40003008UL;
*IWDG_KR_ADDR = 0x5555;
*IWDG_PR_ADDR = 0x03;
*IWDG_RLR_ADDR = 0xF40;
}
實際IWDG是有四個暫存器,還有一個IWDG_PR,它與前面一樣,如果不初始化時鐘,看門狗會啟動不了,就算是設定了,看門狗也是不會啟動。
如果要使能時鐘,可以新增時鐘設定語句:
SET_BIT(RCC->CSR, RCC_CSR_LSION);
直接設定暫存器有一個好處,就是在boot中, 因為對程式碼量要求比較高,可以比較精簡的實現功能
MCU上內部有一個小的flash,裡面有個FLASH user option,在這裡面可以設定MCU的一些設定引數
這個引數是可以通過燒錄器在燒錄的時候就把引數設定進去,對於已經燒錄的裝置,可以通過寫選項位元組的方式把IWDG_SW置位或是清零。
void Option_config_NRST_to_gpio_hwwdg(void)
{
FLASH_OBProgramInitTypeDef OBInitCfg;
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
/* 初始化flash擦寫時間引數 */
HAL_FLASH_Init(FLASH_PROGRAM_ERASE_CLOCK_8MHZ);
/* 獲取option bytes資料 */
HAL_FLASHEx_OBGetConfig(&OBInitCfg);
//設定Nreset為GPIO
if(((OBInitCfg.USERConfig & OB_RESET_MODE_GPIO) != OB_RESET_MODE_GPIO)||((OBInitCfg.USERConfig & OB_IWDG_SW) == OB_IWDG_SW))
{
/* 修改 USER(RESET , WWDG, IWDG) 設定值 , 注意一定要3個一起設定*/
OBInitCfg.OptionType = OPTIONBYTE_USER;
MODIFY_REG(OBInitCfg.USERConfig, (OB_RESET_MODE_GPIO|OB_WWDG_SW|OB_IWDG_SW), (OB_RESET_MODE_GPIO | OB_WWDG_SW | OB_IWDG_HW));
/* 啟動option byte程式設計 */
HAL_FLASHEx_OBProgram(&OBInitCfg);
/* 產生一個復位,option byte裝載 */
HAL_FLASH_OB_Launch();
}
}
通過選項位元組設定了硬體看門狗之後,晶片會自動開啟LSI時鐘,這個時候,軟體要關閉LSI時鐘是關閉不了的。
軟體獨立看門狗與硬體獨立看門狗的區別:
在低功耗裝置中,MCU更加多的時候是在深度睡眠的模式,以達到省功耗的目的。在深度休眠模式下,看門狗還是在正常執行的。
也就是說,在深度休眠模式下,還是需要定時喚醒裝置進行喂狗,喂完狗之後,裝置再重新進入休眠。
官方補充檔案上有介紹,在PY32F030、PY32F003、PY32F002A系列上,在休眠前,需要進行下面幾個操作:
實際在使用的時候,我們比較常用的方式是,使用RTC的秒中斷,在休眠的時候,每秒喚醒一下裝置,然後進行喂狗操作,最後再休眠下去。
實際測試的時候發現,在普冉030使用RTC喚醒喂狗的方式,隨著時間的推移,裝置會出現異常導致看門狗復位。
我們升級五百臺裝置,24小時內,會有幾臺裝置偶爾出現該問題,36小時後,大部分的裝置基本上都會出現這個異常。
普冉官方的解釋是,它們RTC作為喚醒源確實是會存在這個問題,沒有好的解決方案,只能是改用LPTIM來做喚醒源。出現這類問題的根本原因是如果休眠的stop指令與喚醒源中斷同一時間觸發,那麼他們晶片就會掛死。
實際使用的時候,使用LPTIM的方式,還是會存在上面的內容,只是出現的概率會比較低而已。
上面的異常情況,是裝置在產線上才發現的,那要怎麼解?客戶肯定也是接受不了這種頻繁重啟的情況,特別是在低功耗裝置上。
最後的方式是將RAM進行分割區,分出一個IRAM2區,將一些狀態位儲存在IRAM2區,該區啟動的時候不進行初始化,看門狗復位的時候,該區的資料也不會被清除掉。
如果是檢測到看門狗異常導致的復位,可以通過儲存在狀態位資訊恢復到復位前的狀態。
使用IRAM2區不初始化的方式需要注意一點:如果程式分為boot和app兩個部分,需要在boot和app上同時設定該區域,否則可能在boot執行階段,IRAM2區的資料就被清除掉了。
針對普冉PY32F030 MCU,如果要使用獨立看門狗,需要注意幾點:
有些坑,沒踩之前並不知道這是一個坑,對於做嵌入式應用軟體的工程師而言,他並不知道晶片設計上會存在什麼樣的缺陷。
如果一顆晶片,價格比別人便宜很多倍,那麼在使用的時候就需要特別注意了,為啥它可以做到這麼便宜?是不是哪裡有坑我們不清楚?就算時間再緊急,最好也要小批次試產之後才能批次使用。