看門(mén)狗原理詳解:從程序跑飛到獨(dú)立與窗口看門(mén)狗配置)
做嵌入式開(kāi)發(fā)的人十有八九都被“程序跑飛”坑過(guò)。代碼明明寫(xiě)得好好的一上電就不按套路走串口亂打、LED亂閃、按鍵失靈最后排查半天發(fā)現(xiàn)是某個(gè)野指針把棧給踩了。這種問(wèn)題靠人盯著根本來(lái)不及所以芯片設(shè)計(jì)者給單片機(jī)配了最后一道防線——WDT看門(mén)狗??撮T(mén)狗這東西說(shuō)透了就是一個(gè)計(jì)數(shù)器它不問(wèn)你的程序現(xiàn)在跑到哪一步、邏輯對(duì)不對(duì)只關(guān)心你有沒(méi)有在規(guī)定時(shí)間內(nèi)“喂它”。沒(méi)喂到它就強(qiáng)制把系統(tǒng)復(fù)位回正常狀態(tài)。本文會(huì)從原理講到選型再結(jié)合具體的配置過(guò)程把獨(dú)立看門(mén)狗、窗口看門(mén)狗這兩種常見(jiàn)形態(tài)一次講明白。想徹底搞懂看門(mén)狗并用好它的話這篇應(yīng)該能幫你省下不少自己翻手冊(cè)折騰的時(shí)間。1. 看門(mén)狗到底解決什么問(wèn)題1.1 從一次“跑飛”事故講起我之前負(fù)責(zé)過(guò)一個(gè)數(shù)據(jù)采集設(shè)備客戶(hù)那邊反饋說(shuō)設(shè)備運(yùn)行幾天后會(huì)偶發(fā)“假死”。癥狀很典型屏幕卡住、網(wǎng)口不通外部通訊輪詢(xún)也沒(méi)響應(yīng)。工程師趕到現(xiàn)場(chǎng)只能斷電重啟重啟后又完全正常。這種問(wèn)題最折磨人因?yàn)楦怕实?、?fù)現(xiàn)難拿萬(wàn)用表量了半天電壓也看不出異常。后來(lái)把故障設(shè)備接上調(diào)試器看寄存器才發(fā)現(xiàn)程序計(jì)數(shù)器已經(jīng)跑到了一片完全不屬于代碼段的空間。再深挖是某個(gè)外設(shè)的中斷服務(wù)函數(shù)里用了未經(jīng)初始化的指針做寫(xiě)操作把棧里保存的返回地址給改了。CPU在執(zhí)行完中斷返回時(shí)直接跳進(jìn)“野區(qū)”卡在那里一動(dòng)不動(dòng)。這種故障靠提高代碼質(zhì)量能降低概率但永遠(yuǎn)無(wú)法降到零。電磁干擾、電源毛刺、棧溢出任何一個(gè)都可能讓飛車(chē)事故重演。看門(mén)狗就是針對(duì)這種“程序邏輯失效但硬件還在運(yùn)行”的狀態(tài)設(shè)計(jì)的。那看門(mén)狗能修復(fù)被改壞的變量嗎不能。它的邏輯簡(jiǎn)單粗暴不喂狗就復(fù)位把系統(tǒng)拉回一個(gè)已知的、可預(yù)期的起點(diǎn)。程序跑飛了往往就不會(huì)再執(zhí)行喂狗操作計(jì)數(shù)器一旦溢出芯片自動(dòng)復(fù)位設(shè)備重新進(jìn)入正常的初始化流程。1.2 看門(mén)狗的核心邏輯不喂就咬把看門(mén)狗想象成一條拴在院子里的狗你的程序就是養(yǎng)狗的人。正常情況下你得隔一段時(shí)間扔塊肉過(guò)去喂狗狗就一直安靜待著。如果你某天忘了喂狗餓壞了就會(huì)掙脫鏈子把院子鬧個(gè)底朝天——對(duì)應(yīng)到單片機(jī)里就是這個(gè)“鬧”的動(dòng)作被設(shè)計(jì)成了復(fù)位或者觸發(fā)中斷。具體到硬件實(shí)現(xiàn)看門(mén)狗模塊核心是一個(gè)遞減計(jì)數(shù)器。計(jì)數(shù)器在每個(gè)時(shí)鐘周期減一減到零就觸發(fā)復(fù)位信號(hào)。程序正常運(yùn)行時(shí)必須在計(jì)數(shù)器減到零之前執(zhí)行一次“喂狗”操作就是把計(jì)數(shù)器重新賦一個(gè)比較大的初始值。只要喂狗間隔小于計(jì)數(shù)器從初始值減到零所需的時(shí)間系統(tǒng)就不會(huì)復(fù)位。一旦代碼跑飛、死循環(huán)、任務(wù)卡死喂狗操作不再被執(zhí)行計(jì)數(shù)器一路減到零復(fù)位信號(hào)就來(lái)了。這里有個(gè)很多人容易誤解的點(diǎn)喂狗本身不保證程序是對(duì)的它只保證程序在“至少能跑到喂狗這一步”。所以喂狗的位置選得好不好直接決定看門(mén)狗是幫你兜底還是給你添亂。2. 看門(mén)狗的分類(lèi)與選型思路2.1 硬件看門(mén)狗與軟件看門(mén)狗按實(shí)現(xiàn)方式看門(mén)狗可以分成硬件看門(mén)狗和軟件看門(mén)狗兩大類(lèi)。硬件看門(mén)狗通常指芯片內(nèi)部集成的獨(dú)立看門(mén)狗外設(shè)模塊或者外部單獨(dú)的看門(mén)狗芯片。它們的共同點(diǎn)是擁有獨(dú)立的時(shí)鐘源和計(jì)數(shù)器不需要CPU干預(yù)也能自己工作。即便主程序的死循環(huán)把中斷全部關(guān)掉只要沒(méi)有喂狗動(dòng)作計(jì)數(shù)器照樣往下減復(fù)位照樣發(fā)生。軟件看門(mén)狗則是用一個(gè)定時(shí)器中斷來(lái)模擬。程序在主循環(huán)里定時(shí)給一個(gè)“軟件計(jì)數(shù)器”清零定時(shí)器中斷里對(duì)這個(gè)變量做檢查如果發(fā)現(xiàn)超時(shí)沒(méi)清零就認(rèn)為主循環(huán)卡死然后執(zhí)行復(fù)位操作。這種做法的優(yōu)點(diǎn)是不占額外的硬件資源幾個(gè)變量加一個(gè)定時(shí)器臨界中斷就能實(shí)現(xiàn)。缺點(diǎn)是它依賴(lài)中斷的正常工作如果代碼把中斷全局關(guān)閉了軟件看門(mén)狗自己也被“餓死”了根本起不到保護(hù)作用。還有外置看門(mén)狗芯片比如經(jīng)典的MAX706、TPS3823這類(lèi)。它們一般有一個(gè)喂狗引腳和復(fù)位輸出引腳程序得在指定時(shí)間內(nèi)翻轉(zhuǎn)喂狗引腳的電平否則芯片輸出復(fù)位信號(hào)。外置看門(mén)狗芯片在可靠性要求高的工業(yè)控制板里仍然很常見(jiàn)因?yàn)榧词箚纹瑱C(jī)內(nèi)部的時(shí)鐘徹底o(hù)ut of spec外部芯片依然能獨(dú)立工作。從選型角度說(shuō)MCU內(nèi)置的獨(dú)立看門(mén)狗已經(jīng)能滿足絕大多數(shù)場(chǎng)景。外置芯片更適合對(duì)可靠性有變態(tài)要求的場(chǎng)景比如車(chē)載控制器、服務(wù)器電源管理或者是MCU本身沒(méi)有內(nèi)置看門(mén)狗的老舊方案。2.2 獨(dú)立看門(mén)狗與窗口看門(mén)狗內(nèi)置硬件看門(mén)狗又分兩種獨(dú)立看門(mén)狗IWDGIndependent Watchdog和窗口看門(mén)狗WWDGWindow Watchdog。這個(gè)“窗口”是理解兩者差異的關(guān)鍵。獨(dú)立看門(mén)狗只有一個(gè)下限喂狗操作必須在計(jì)數(shù)器溢出之前完成早喂晚喂都行只要?jiǎng)e太晚。就像你養(yǎng)狗規(guī)定一天喂一次你早上喂也行晚上喂也行只要在狗餓急眼之前喂就行。獨(dú)立看門(mén)狗實(shí)現(xiàn)簡(jiǎn)單適合作為“最后崩潰保護(hù)”也是用得最多的一種。窗口看門(mén)狗則多了一個(gè)上限它要求喂狗操作必須在一個(gè)時(shí)間窗口內(nèi)完成。喂早了不行喂晚了也不行。還是用養(yǎng)狗類(lèi)比窗口看門(mén)狗規(guī)定你每天只能在下午5點(diǎn)到6點(diǎn)之間喂狗其他時(shí)間喂狗它會(huì)咬你。為什么需要這種限制因?yàn)樗軝z測(cè)到“程序運(yùn)行節(jié)奏異?!?。比如主循環(huán)里某段代碼執(zhí)行太快跳過(guò)了關(guān)鍵路徑提前執(zhí)行到喂狗語(yǔ)句獨(dú)立看門(mén)狗看不出來(lái)窗口看門(mén)狗就能抓出來(lái)。窗口看門(mén)狗常用于對(duì)軟件執(zhí)行時(shí)序有嚴(yán)格要求的場(chǎng)景比如電機(jī)控制、電源管理中的關(guān)鍵巡檢流程。這兩種看門(mén)狗內(nèi)部結(jié)構(gòu)也有差異。獨(dú)立看門(mén)狗通常掛在低速內(nèi)部時(shí)鐘上比如LSI不依賴(lài)主時(shí)鐘主時(shí)鐘掛了它依然在跑。窗口看門(mén)狗一般掛在系統(tǒng)時(shí)鐘上主時(shí)鐘出問(wèn)題時(shí)它也可能跟著失效。2.3 幾種常見(jiàn)實(shí)現(xiàn)方式對(duì)比方案類(lèi)型獨(dú)立性對(duì)時(shí)鐘依賴(lài)適用場(chǎng)景典型缺點(diǎn)MCU內(nèi)置獨(dú)立看門(mén)狗高低速獨(dú)立時(shí)鐘主時(shí)鐘掛了仍有效絕大多數(shù)嵌入式應(yīng)用喂狗窗口不可調(diào)檢測(cè)不到節(jié)奏異常MCU內(nèi)置窗口看門(mén)狗中依賴(lài)系統(tǒng)時(shí)鐘對(duì)執(zhí)行時(shí)序要求高的控制類(lèi)應(yīng)用喂狗時(shí)序配置繁瑣易誤復(fù)位外部看門(mén)狗芯片最高完全獨(dú)立自帶振蕩源車(chē)載、工業(yè)、電源等嚴(yán)苛環(huán)境成本高、占PCB面積、多一個(gè)器件軟件看門(mén)狗低依賴(lài)定時(shí)器和中斷資源受限的項(xiàng)目、無(wú)硬件看門(mén)狗關(guān)中斷即失效只能兜底用選型的時(shí)候別貪心。獨(dú)立看門(mén)狗夠用就別強(qiáng)行上窗口看門(mén)狗后者配置不當(dāng)會(huì)比不裝看門(mén)狗還讓人頭疼。產(chǎn)品應(yīng)用環(huán)境比較惡劣、比如高低溫、強(qiáng)振動(dòng)、強(qiáng)電磁干擾再考慮外置看門(mén)狗芯片也不遲。3. 關(guān)鍵參數(shù)與設(shè)計(jì)要點(diǎn)3.1 超時(shí)時(shí)間怎么計(jì)算獨(dú)立看門(mén)狗的超時(shí)時(shí)間由三個(gè)參數(shù)決定獨(dú)立時(shí)鐘頻率、預(yù)分頻系數(shù)、重載值。公式大概是這樣的T 預(yù)分頻系數(shù) × 重載值 / 獨(dú)立時(shí)鐘頻率以常見(jiàn)的低速內(nèi)部時(shí)鐘32kHz為例。如果我把預(yù)分頻系數(shù)設(shè)置為128重載值設(shè)為4095那么一次完整的倒計(jì)時(shí)時(shí)間就是T 128 × (4095 1) / 32000 16.384秒這里有個(gè)細(xì)節(jié)很多人會(huì)忽略重載值寫(xiě)入后實(shí)際計(jì)數(shù)器往往是從重載值加1開(kāi)始遞減的所以計(jì)算時(shí)要記得把重載值加1。每一款MCU的說(shuō)明可能略有不同以數(shù)據(jù)手冊(cè)為準(zhǔn)但原理一致。超時(shí)時(shí)間該選多大我一般按這個(gè)思路來(lái)先找出軟件里最長(zhǎng)的一次“合法不喂狗時(shí)間”也就是從一次喂狗到下一次喂狗之間最極端的情況比如某次耗時(shí)的加密運(yùn)算、Flash擦寫(xiě)、外部阻塞等待把這個(gè)時(shí)間量出來(lái)或算出來(lái)然后超時(shí)時(shí)間取這個(gè)值的2到4倍。太小了偶發(fā)執(zhí)行慢一點(diǎn)就誤復(fù)位生產(chǎn)線上退貨率上升太大了系統(tǒng)死機(jī)后要等十幾秒才恢復(fù)用戶(hù)早就暴躁了。工業(yè)級(jí)設(shè)備有個(gè)常用的經(jīng)驗(yàn)值主循環(huán)周期在幾毫秒到幾十毫秒的應(yīng)用超時(shí)時(shí)間取1秒左右比較合適。3.2 喂狗位置怎么選喂狗位置是整個(gè)看門(mén)狗設(shè)計(jì)里最需要琢磨的地方。最懶的做法是在主循環(huán)開(kāi)頭喂狗代碼簡(jiǎn)單但只要這個(gè)主循環(huán)本身有邏輯漏洞比如某個(gè)設(shè)備狀態(tài)判斷錯(cuò)誤導(dǎo)致進(jìn)入死循環(huán)喂狗照樣執(zhí)行看門(mén)狗形同虛設(shè)。我覺(jué)得拿“喂狗一定要喂在關(guān)鍵執(zhí)行路徑的尾部”這個(gè)原則來(lái)指導(dǎo)更合理。比如一個(gè)數(shù)據(jù)采集設(shè)備主循環(huán)要做的事情是讀取傳感器數(shù)據(jù)、處理數(shù)據(jù)、寫(xiě)入Flash、上報(bào)上位機(jī)。那么喂狗動(dòng)作應(yīng)該放在“上報(bào)上位機(jī)”完成之后。這樣只要傳感器讀取卡死、Flash寫(xiě)入卡死、通訊流程卡死無(wú)論卡在哪一步CPU都無(wú)法走到最后的喂狗語(yǔ)句看門(mén)狗就會(huì)觸發(fā)復(fù)位。如果把喂狗放在最前面后面哪一步卡住了系統(tǒng)都不會(huì)復(fù)位問(wèn)題就大了。還有一種玩法叫“喂狗任務(wù)管理”。先定義一個(gè)全局變量組記錄每個(gè)關(guān)鍵任務(wù)的運(yùn)行狀態(tài)喂狗前檢查這些狀態(tài)是否都被置位。只有所有關(guān)鍵任務(wù)都按時(shí)跑過(guò)才真正喂狗否則就算主循環(huán)還在轉(zhuǎn)也不喂。這種方案在RTOS環(huán)境的項(xiàng)目里很流行相當(dāng)于給看門(mén)狗加了一層“業(yè)務(wù)判斷”。3.3 中斷處理與復(fù)位方式某些看門(mén)狗模塊會(huì)提供一個(gè)“提前喚醒中斷”功能也就是計(jì)數(shù)器遞減到某個(gè)閾值時(shí)先觸發(fā)一次中斷如果這個(gè)中斷沒(méi)被正確處理再繼續(xù)遞減到零才復(fù)位。這個(gè)中斷特別適合用來(lái)做善后工作保存關(guān)鍵運(yùn)行數(shù)據(jù)到非易失存儲(chǔ)、記錄復(fù)位現(xiàn)場(chǎng)、把輸出設(shè)備切到安全狀態(tài)。注意這個(gè)中斷里千萬(wàn)別做耗時(shí)操作Flash擦寫(xiě)這種少說(shuō)幾十毫秒的活放到這里干基本來(lái)不及還容易造成二次異常。復(fù)位方式也要留意??撮T(mén)狗超時(shí)后的系統(tǒng)復(fù)位可能分為“內(nèi)核復(fù)位”和“系統(tǒng)復(fù)位”。內(nèi)核復(fù)位只復(fù)位CPU內(nèi)核外設(shè)狀態(tài)基本保留系統(tǒng)復(fù)位是整顆芯片回到類(lèi)似剛上電的狀態(tài)。不同MCU的看門(mén)狗復(fù)位行為不一樣如果你的產(chǎn)品需要區(qū)分“看門(mén)狗復(fù)位”和“上電復(fù)位”記得在初始化時(shí)第一時(shí)間讀取復(fù)位狀態(tài)標(biāo)志寄存器并記錄下來(lái)這個(gè)標(biāo)志是排查問(wèn)題的關(guān)鍵線索。4. 實(shí)操以NSUC1612E為例完成看門(mén)狗配置4.1 NSUC1612E看門(mén)狗模塊概況最近我在一個(gè)低功耗物聯(lián)終端項(xiàng)目里用了NSUC1612E這顆國(guó)產(chǎn)MCU它的看門(mén)狗模塊做得比較典型很適合拿來(lái)講配置流程。這顆芯片內(nèi)部集成了獨(dú)立看門(mén)狗和窗口看門(mén)狗獨(dú)立看門(mén)狗掛在獨(dú)立的低速時(shí)鐘上不受主時(shí)鐘影響。配置寄存器有寫(xiě)保護(hù)要解除寫(xiě)保護(hù)才能修改配置。這個(gè)寫(xiě)保護(hù)設(shè)計(jì)我是支持的防止程序跑飛時(shí)順手把看門(mén)狗關(guān)掉那就等于把保安給辭退了。NSUC1612E的獨(dú)立看門(mén)狗涉及幾個(gè)核心寄存器控制寄存器控制使能和工作模式、預(yù)分頻寄存器、重載寄存器、狀態(tài)寄存器。不同廠家的寄存器命名風(fēng)格不一樣有的叫WDT_CR有的叫IWDG_KR但功能結(jié)構(gòu)大同小異??磾?shù)據(jù)手冊(cè)時(shí)先定位這幾個(gè)角色時(shí)鐘來(lái)源是什么、預(yù)分頻在哪配置、重載值往哪個(gè)寄存器寫(xiě)、狀態(tài)標(biāo)志位有哪些。把這幾個(gè)關(guān)鍵點(diǎn)梳理清楚換任何一顆芯片都能快速上手。4.2 初始化流程與代碼骨架我用標(biāo)準(zhǔn)庫(kù)風(fēng)格的代碼來(lái)演示配置流程。具體到NSUC1612E配置步驟和代碼骨架如下void WDT_Init(void) { // 1. 解除寫(xiě)保護(hù)允許修改預(yù)分頻和重載寄存器 WDT_Unlock(); // 2. 設(shè)置預(yù)分頻系數(shù)為128 WDT_SetPrescaler(WDT_PRESCALER_128); // 3. 設(shè)置重載值這里用4095對(duì)應(yīng)超時(shí)時(shí)間約16秒 WDT_SetReload(4095); // 4. 喂一次狗讓計(jì)數(shù)從頭開(kāi)始 WDT_ReloadCounter(); // 5. 啟動(dòng)看門(mén)狗 WDT_Enable(); // 6. 重新使能寫(xiě)保護(hù)防止后續(xù)權(quán)限混亂 WDT_Lock(); } void WDT_Feed(void) { // 每次喂狗其實(shí)就是執(zhí)行重載操作 WDT_ReloadCounter(); }步驟1為什么必須先“解鎖”因?yàn)榭撮T(mén)狗一旦使能正常狀態(tài)下不允許隨意修改配置否則你的程序就是地球人無(wú)法控制自己的飛船。寫(xiě)保護(hù)機(jī)制可以防止程序跑飛后修改看門(mén)狗配置把自己“放生”。這在產(chǎn)品量產(chǎn)調(diào)試中能擋掉相當(dāng)一部分低級(jí)失誤。步驟2和3屬于參數(shù)設(shè)定。預(yù)分頻128和重載值4095對(duì)應(yīng)超時(shí)16秒多一點(diǎn)這個(gè)值對(duì)我的項(xiàng)目來(lái)說(shuō)是合理的設(shè)備主循環(huán)正常周期50毫秒左右即使某個(gè)通訊流程出現(xiàn)超時(shí)重試也能在2到3秒內(nèi)跑完整個(gè)路徑16秒足夠?qū)捲S植粫?huì)讓客戶(hù)等太久。步驟5和6的順序別寫(xiě)反。先使能再上鎖順序反了會(huì)出現(xiàn)寫(xiě)保護(hù)狀態(tài)下仍然能操作寄存器的情況等于白設(shè)了一道鎖。4.3 喂狗策略與任務(wù)集成初始化只是第一步真正決定看門(mén)狗效果的是喂狗策略。我這個(gè)物聯(lián)終端跑的是一個(gè)簡(jiǎn)單的裸機(jī)狀態(tài)機(jī)狀態(tài)切換包括傳感器采集、數(shù)據(jù)加密、NB-IoT模塊發(fā)送、進(jìn)入低功耗休眠。在低功耗模式下MCU會(huì)停掉大部分時(shí)鐘這時(shí)候如果看門(mén)狗還在跑就必須在休眠前做特殊處理否則休眠時(shí)間超過(guò)看門(mén)狗超時(shí)時(shí)間設(shè)備會(huì)被狗咬醒。我的處理方式是進(jìn)入休眠前計(jì)算休眠時(shí)長(zhǎng)如果休眠會(huì)超過(guò)看門(mén)狗的一半超時(shí)時(shí)間就把喂狗任務(wù)交給一個(gè)超低功耗定時(shí)器讓它提前一點(diǎn)醒來(lái)喂狗再睡回去。同時(shí)看門(mén)狗配置成在休眠模式下繼續(xù)計(jì)數(shù)。別忘了考慮這一點(diǎn)很多低功耗項(xiàng)目栽在“休眠期間被看門(mén)狗復(fù)位”這個(gè)坑上。如果項(xiàng)目用了RTOS喂狗策略要更講究。最簡(jiǎn)單的辦法是創(chuàng)建一個(gè)專(zhuān)門(mén)的喂狗任務(wù)優(yōu)先級(jí)設(shè)最低每次被調(diào)度就喂狗。這樣只要低優(yōu)先級(jí)任務(wù)還能得到調(diào)度機(jī)會(huì)說(shuō)明系統(tǒng)整體沒(méi)有崩潰。但缺點(diǎn)也很明顯高優(yōu)先級(jí)任務(wù)如果陷入死循環(huán)低優(yōu)先級(jí)任務(wù)永遠(yuǎn)得不到調(diào)度看門(mén)狗卻不會(huì)觸發(fā)復(fù)位因?yàn)槟銖膩?lái)沒(méi)走到喂狗那一步。嚴(yán)格來(lái)說(shuō)這算設(shè)計(jì)缺陷不能只看低優(yōu)先級(jí)任務(wù)喘氣就完事。我習(xí)慣在喂狗任務(wù)里順便檢查幾個(gè)業(yè)務(wù)心跳變量比如“數(shù)據(jù)采集任務(wù)上次執(zhí)行時(shí)間戳”“通訊任務(wù)上次執(zhí)行時(shí)間戳”這些時(shí)間戳超過(guò)閾值就主動(dòng)觸發(fā)軟件復(fù)位相當(dāng)于給RTOS看門(mén)狗做了一層定制化體檢。5. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄5.1 復(fù)位原因不明很多時(shí)候設(shè)備被看門(mén)狗復(fù)位了但我們不知道。特別是裸機(jī)程序沒(méi)有日志系統(tǒng)掉了電再看現(xiàn)場(chǎng)一片空白。這種情況我強(qiáng)烈建議上電初始化第一件事讀取復(fù)位狀態(tài)寄存器把復(fù)位原因打印出來(lái)或存到Flash里。NSUC1612E這一類(lèi)芯片基本都有復(fù)位標(biāo)志寄存器可以區(qū)分上電復(fù)位、外部引腳復(fù)位、看門(mén)狗復(fù)位、低功耗復(fù)位。日志信息加上“上次復(fù)位原因”字段很多難纏的問(wèn)題一下就定位了。比如我遇到過(guò)設(shè)備運(yùn)行幾個(gè)小時(shí)后重啟加了復(fù)位原因記錄后發(fā)現(xiàn)是看門(mén)狗復(fù)位再追查喂狗時(shí)間戳發(fā)現(xiàn)是某個(gè)外設(shè)驅(qū)動(dòng)在特定數(shù)據(jù)模式下面陷入阻塞。沒(méi)有復(fù)位標(biāo)志這個(gè)問(wèn)題可能在現(xiàn)場(chǎng)反復(fù)折騰一個(gè)月都找不到方向。5.2 系統(tǒng)頻繁復(fù)位系統(tǒng)頻繁復(fù)位是最常見(jiàn)的看門(mén)狗故障表現(xiàn)。通常有幾個(gè)方向要查。第一喂狗間隔是否真的小于超時(shí)時(shí)間。有人把超時(shí)時(shí)間算了10秒但主循環(huán)里有個(gè)阻塞式模塊耗時(shí)20秒那狗咬你屬于正常操作。把各任務(wù)最大耗時(shí)列出來(lái)加一加就知道瓶頸在哪。第二中斷和臨界區(qū)是否關(guān)閉太長(zhǎng)時(shí)間。NSUC1612E的喂狗操作本身可能是一個(gè)簡(jiǎn)單的寄存器寫(xiě)入但如果你的喂狗代碼在臨界區(qū)里而臨界區(qū)長(zhǎng)時(shí)間關(guān)中斷喂狗指令根本執(zhí)行不到。這種問(wèn)題在調(diào)試器上單步跑看不出全速跑才暴露。第三低功耗模式下喂狗是否還正常。休眠時(shí)間超過(guò)超時(shí)時(shí)間又不處理復(fù)位是必然結(jié)果。要么把看門(mén)狗在進(jìn)入休眠前臨時(shí)禁用部分芯片支持要么用定時(shí)器喚醒喂狗二選一。第四某些外設(shè)故障會(huì)導(dǎo)致死鎖。比如I2C從設(shè)備地址不對(duì)讀操作一直拉低時(shí)鐘線程序卡死在等總線釋放的循環(huán)里。這種看門(mén)狗能兜住但更建議在外設(shè)驅(qū)動(dòng)里加超時(shí)退出機(jī)制讓狗只做最后防線。5.3 調(diào)試時(shí)總被復(fù)位用調(diào)試器在線調(diào)試看門(mén)狗程序時(shí)會(huì)遇到一個(gè)非常煩人的情況自己在某個(gè)斷點(diǎn)停了幾分鐘看變量值結(jié)果看門(mén)狗計(jì)數(shù)器溢出整個(gè)芯片復(fù)位了斷點(diǎn)狀態(tài)全丟。這不是芯片有問(wèn)題而是調(diào)試暫停期間看門(mén)狗依然在運(yùn)行你停得越久狗越餓。解決辦法有三個(gè)。第一個(gè)調(diào)試前把看門(mén)狗初始化代碼注釋掉調(diào)試完再恢復(fù)。簡(jiǎn)單粗暴但容易忘記恢復(fù)導(dǎo)致燒進(jìn)量產(chǎn)板的程序沒(méi)有開(kāi)看門(mén)狗風(fēng)險(xiǎn)很大不推薦。第二個(gè)使用仿真器提供的“調(diào)試時(shí)禁止看門(mén)狗”選項(xiàng)很多IDE里都有這個(gè)配置原理是調(diào)試器連上芯片后主動(dòng)把看門(mén)狗計(jì)數(shù)器凍結(jié)。第三個(gè)在代碼里加編譯開(kāi)關(guān)#ifdef DEBUG時(shí)不使能看門(mén)狗#else時(shí)正常使能發(fā)布固件用Release編譯這樣就不容易漏掉。調(diào)試中還有一個(gè)怪脾氣在斷點(diǎn)處你會(huì)查看外設(shè)寄存器部分芯片在CPU掛起時(shí)外設(shè)時(shí)鐘也停計(jì)數(shù)器就停了完全沒(méi)問(wèn)題但有些芯片的看門(mén)狗模塊掛在獨(dú)立時(shí)鐘上CPU停了它照樣跑這時(shí)斷點(diǎn)一停幾分鐘必被復(fù)位。搞清楚自己芯片的看門(mén)狗時(shí)鐘源能少踩很多坑。5.4 常見(jiàn)問(wèn)題速查表現(xiàn)象常見(jiàn)原因排查思路上電能跑運(yùn)行一段時(shí)間后靜默重啟喂狗路徑上有阻塞某個(gè)條件分支異常導(dǎo)致喂狗語(yǔ)句無(wú)法執(zhí)行打印各任務(wù)執(zhí)行時(shí)間戳找最長(zhǎng)喂狗間隔調(diào)試器全速跑正常單步就復(fù)位調(diào)試暫停時(shí)看門(mén)狗繼續(xù)計(jì)數(shù)超時(shí)觸發(fā)復(fù)位配置IDE凍結(jié)看門(mén)狗或DEBUG版本不使能狗低功耗模式頻繁喚醒但狀態(tài)不對(duì)休眠時(shí)長(zhǎng)超過(guò)看門(mén)狗超時(shí)被狗咬醒后執(zhí)行了復(fù)位流程休眠前重新計(jì)算喂狗方式或臨時(shí)禁用看門(mén)狗用窗口看門(mén)狗后頻繁誤復(fù)位喂狗窗口配置過(guò)窄代碼執(zhí)行時(shí)序波動(dòng)就出窗口放寬窗口或改用獨(dú)立看門(mén)狗配置之后看門(mén)狗沒(méi)生效寫(xiě)保護(hù)未解除使能后又被其他代碼誤關(guān)閉檢查配置順序搜索代碼里所有寫(xiě)看門(mén)狗控制寄存器的位置復(fù)位后關(guān)鍵數(shù)據(jù)丟失未使用提前喚醒中斷做數(shù)據(jù)保存復(fù)位過(guò)于暴力利用看門(mén)狗提前中斷服務(wù)函數(shù)保存關(guān)鍵參數(shù)6. 我的幾點(diǎn)實(shí)操心得6.1 經(jīng)驗(yàn)性建議與教訓(xùn)先說(shuō)一個(gè)我早年踩過(guò)的坑。當(dāng)時(shí)做一款消費(fèi)電子趕進(jìn)度在主循環(huán)開(kāi)頭加了一句喂狗就交差了。量產(chǎn)之后市場(chǎng)反饋設(shè)備偶爾死機(jī)但死機(jī)之后過(guò)幾十秒能自己恢復(fù)用戶(hù)還以為是什么“自動(dòng)恢復(fù)”功能。其實(shí)那就是看門(mén)狗在起作用但起作用的范圍很小因?yàn)橹餮h(huán)開(kāi)頭喂完狗后面卡在哪一步狗都不會(huì)管。后來(lái)把喂狗挪到整個(gè)主循環(huán)業(yè)務(wù)鏈路的最后同樣的故障概率下設(shè)備恢復(fù)速度明顯變快了因?yàn)槿魏我徊娇ㄗ∠到y(tǒng)都能在超時(shí)時(shí)間內(nèi)重啟。這個(gè)教訓(xùn)我記到現(xiàn)在每次都當(dāng)成必講案例。另一個(gè)經(jīng)驗(yàn)是喂狗代碼一定要足夠簡(jiǎn)單不要在里面做復(fù)雜的邏輯判斷。有人喜歡在喂狗函數(shù)里檢查所有傳感器狀態(tài)十幾個(gè)條件都滿足才喂狗結(jié)果這個(gè)檢查過(guò)程本身出問(wèn)題導(dǎo)致系統(tǒng)“健康狀態(tài)良好但被狗咬”。喂狗函數(shù)的職責(zé)就是喂狗業(yè)務(wù)健康度檢查可以另寫(xiě)一個(gè)模塊不要混在一起。最后是量產(chǎn)前一定要做可靠性測(cè)試??撮T(mén)狗最容易在高溫、長(zhǎng)時(shí)間運(yùn)行、掉電復(fù)現(xiàn)等場(chǎng)景下暴露問(wèn)題特別是Flash寫(xiě)入時(shí)喂狗超時(shí)這種邊界情況你不把擦寫(xiě)Flash的時(shí)間拉出來(lái)實(shí)測(cè)就永遠(yuǎn)不知道自己的超時(shí)設(shè)置是否合理。6.2 這個(gè)內(nèi)容后續(xù)還可以這樣擴(kuò)展看門(mén)狗不是單片機(jī)上的孤島它跟低功耗管理、任務(wù)調(diào)度、異常處理都有關(guān)聯(lián)。如果你已經(jīng)有獨(dú)立看門(mén)狗能正常保護(hù)系統(tǒng)了下一步可以試著把窗口看門(mén)狗用起來(lái)為關(guān)鍵任務(wù)流程增加時(shí)序約束。再進(jìn)階一點(diǎn)可以設(shè)計(jì)一個(gè)“看門(mén)狗外部復(fù)位IC”的雙保險(xiǎn)方案用于產(chǎn)品生命周期更長(zhǎng)、現(xiàn)場(chǎng)維護(hù)成本更高的工業(yè)設(shè)備。也可以考慮在系統(tǒng)里記錄歷次復(fù)位原因和時(shí)間戳通過(guò)遠(yuǎn)程或者本地日志回傳形成一個(gè)簡(jiǎn)單的設(shè)備健康檔案。這樣設(shè)備返修回來(lái)你能在幾秒內(nèi)判斷是軟件問(wèn)題還是硬件問(wèn)題而不是從零開(kāi)始接示波器抓現(xiàn)場(chǎng)。我現(xiàn)在做的新項(xiàng)目都會(huì)在啟動(dòng)代碼里留一段復(fù)位歷史記錄區(qū)這個(gè)習(xí)慣幫我省了不少售后排查的力氣。