中斷和主循環(huán)不登對 系統一直休眠久睡
你永遠也喚不醒一個(gè)裝睡的人,但是你可以一巴掌呼醒他。
本文引用地址:http://dyxdggzs.com/article/201905/400938.htm可如果一個(gè)嵌入式系統休眠之后,就猶抱琵琶半遮面,千呼萬(wàn)喚醒不來(lái)了呢?筆者擔綱開(kāi)發(fā)的中控鎖模塊就醒不過(guò)來(lái)了。
1
這個(gè)問(wèn)題已經(jīng)折磨我整整兩天了,搞得我心力憔悴。合作伙伴一天幾個(gè)電話(huà),恨不得從手機里面直接跳過(guò)來(lái)當面質(zhì)問(wèn)我:“為什么喚不醒,為什么死睡?”
平時(shí)對我愛(ài)答不理的領(lǐng)導也一改常態(tài),轉而死死地盯著(zhù)我。老實(shí)厚道的我呢,自然也十分緊張且惶恐,因為問(wèn)題十有八九確實(shí)出在我寫(xiě)的代碼上。
關(guān)鍵是這個(gè)問(wèn)題出現在馬上大批量供貨的前夜,太不是時(shí)候了!我們給某車(chē)廠(chǎng)供貨的中控鎖模塊已經(jīng)走到了小批量供貨階段,前期供貨20套倒是無(wú)驚無(wú)險,沒(méi)發(fā)現什么問(wèn)題,按照流程,接下來(lái)要供貨100套。
如果阿彌陀佛的話(huà),就再供貨200套,再沒(méi)有什么問(wèn)題,就進(jìn)入大批量供貨階段了。到時(shí),合作伙伴開(kāi)始穩定盈利,領(lǐng)導政績(jì)、獎金到手,不會(huì )爭功只會(huì )攬過(guò)的我呢,揮一揮手退到幕后,深藏功與名,一切就萬(wàn)事大吉,只待歲月靜好了??墒?,恰恰在供貨100套期間,忘了求菩薩保佑,結果就出事了。
一天,車(chē)廠(chǎng)的生產(chǎn)線(xiàn)上下線(xiàn)了裝配了我們的中控鎖模塊的50輛新車(chē),本來(lái)一切順利,在生產(chǎn)線(xiàn)上操作中控鎖沒(méi)什么問(wèn)題,匹配學(xué)習了遙控鑰匙,也能用遙控正常操作。但是到了下線(xiàn)后車(chē)廠(chǎng)人員要把新車(chē)開(kāi)回倉庫時(shí),突然發(fā)現有幾臺車(chē)用遙控鑰匙、機械鑰匙死活也喚醒不了了??!
事關(guān)重大,車(chē)廠(chǎng)立馬通知了中控鎖的供貨商—我們的合作伙伴,合作伙伴立馬找到了我們(中控鎖的開(kāi)發(fā)商)領(lǐng)導,領(lǐng)導立馬找到了我。我也立即給自己上緊了發(fā)條,進(jìn)入戰斗狀態(tài)。
2
眼看到手的政績(jì)和獎金要飛,領(lǐng)導心急火燎,早被晾到一邊的我卻頗不以為然。
到了現在這個(gè)階段,軟件肯定沒(méi)問(wèn)題,有問(wèn)題早就測出來(lái)了嘛,還能等到現在?肯定是中控鎖的線(xiàn)束出了問(wèn)題。
當我把輕描淡寫(xiě)的分析說(shuō)與領(lǐng)導時(shí),領(lǐng)導一改往日的溫和,一下子急了:“人家線(xiàn)束供了這么多年了,按你的說(shuō)法,有問(wèn)題早就測出來(lái)了,還能等到現在?”
歲月的風(fēng)霜早已經(jīng)消去了我性情中所有的剛硬和火熱,只剩下如水的柔和??吹筋I(lǐng)導急了,我心中起著(zhù)嘀咕,臉上泛起笑容,小心翼翼地對領(lǐng)導言道:“要不我再看一下代碼,沒(méi)準真是哪里出了岔子呢?”
得到我的表態(tài)后,領(lǐng)導轉身踱了開(kāi)去,一邊嘴里念叨著(zhù)得跟車(chē)廠(chǎng)確認一下線(xiàn)束有沒(méi)有問(wèn)題,一邊轉過(guò)頭來(lái)再次叮嚀我一番:抓緊??!看著(zhù)領(lǐng)導那殷切的眼神和黑黑的眼圈,我用力地點(diǎn)了點(diǎn)頭,一個(gè)猛子扎進(jìn)代碼的汪洋大海,迅速游至中控鎖的休眠和喚醒之地。
代碼的設計思路總是簡(jiǎn)單而且正確的。中控鎖進(jìn)入休眠之前,設置了兩個(gè)喚醒條件:①機械解鎖信號的上升沿中斷喚醒MCU;②定期檢查遙控鑰匙信號的定時(shí)器周期喚醒MCU。當出現有效的機械鎖信號時(shí),或者檢測到有效的遙控信號時(shí),中控鎖禁止這兩個(gè)喚醒條件并返回正常狀態(tài)。
在具體的代碼實(shí)現上,把中控鎖的休眠模式處理分成了兩部分:中斷服務(wù)程序和循環(huán)體。中斷由機械鎖解鎖開(kāi)關(guān)信號觸發(fā),執行完ISR后先返回循環(huán)體,再退出休眠模式。循環(huán)體中反復休眠和臨時(shí)喚醒,在臨時(shí)喚醒期間通過(guò)三級濾波機制檢查是否存在有效的遙控鑰匙信號。存在遙控信號時(shí),這個(gè)循環(huán)體層層地通過(guò)三級濾波后,退出休眠模式。
邏輯上清晰,代碼也很簡(jiǎn)單,我反反復復檢查了幾遍,沒(méi)看出個(gè)子丑演卯來(lái),就準時(shí)準點(diǎn)地下班回家了。
第二天上班后,我還沒(méi)在工位上坐定,還沒(méi)來(lái)得及平復一下自己的燥熱勁頭,領(lǐng)導就帶著(zhù)于我少有的笑意貓過(guò)來(lái)了。不等他開(kāi)口,老實(shí)巴交的我就主動(dòng)匯報了檢查代碼后沒(méi)有發(fā)現什么問(wèn)題的情況,no news is good news,但是此時(shí)代碼沒(méi)有問(wèn)題就是大問(wèn)題??!
話(huà)音甫落,領(lǐng)導臉上還沒(méi)來(lái)得及展開(kāi)的笑容就凝固不動(dòng)了,他張著(zhù)口,默默地看著(zhù)我。
我們倆就這么站著(zhù),不說(shuō)話(huà),就十分地不美好!
城府甚深的領(lǐng)導終于沒(méi)有說(shuō)話(huà),他不動(dòng)聲色地安排了一位同事搭建了中控鎖的測試系統,讓他反復測試休眠、喚醒的情況。
3
測試的方法很簡(jiǎn)單,把電流表串進(jìn)中控鎖的供電線(xiàn)上,看著(zhù)電流下降到休眠電流范圍后,便操作一下遙控鑰匙,或者給一個(gè)機械解鎖信號,看能否執行解閉鎖操作。
我在一邊冷眼觀(guān)察著(zhù)測試人員的操作,心情竟然無(wú)比地矛盾,既希望他快點(diǎn)測出來(lái)不能正常喚醒的故障,又盼著(zhù)最好測試不出來(lái)問(wèn)題。
大半天下來(lái),中控鎖反反復復地正常休眠、被正常喚醒,我的心也反反復復地七上八下,百般煎熬。后來(lái),測試人員的電話(huà)響了,我默默轉身離開(kāi),同時(shí)發(fā)現自己竟然更加惶恐了!
剛剛坐到電腦前,測試人員就大呼小叫著(zhù)跑了過(guò)來(lái):“寂寞君,問(wèn)題再現了!”我就像屁股上安了個(gè)彈簧一樣,一下子被凳子彈了起來(lái),待我三步并作兩步跨到測試臺前時(shí),領(lǐng)導也已經(jīng)聞聲迅速趕來(lái)。
我抓著(zhù)遙控鑰匙一邊操作,一邊注視著(zhù)電流表的讀數,電流一直穩定在2毫安左右。
問(wèn)題確認了,確實(shí)死活也喚不醒了。
我又一屁股栽倒在凳子上,抬起頭來(lái),正趕上領(lǐng)導意味深長(cháng)的目光。我抿著(zhù)嘴笑了笑,剛想說(shuō)點(diǎn)什么,測試臺上測試人員的電話(huà)又嗡嗡響了起來(lái)。
聽(tīng)著(zhù)熟悉的手機鈴聲在耳邊縈繞,看著(zhù)手機在桌子上震動(dòng)個(gè)不停,我回想起測試人員剛才打電話(huà)時(shí)的情景,暗鈍的思維再度轉動(dòng)起來(lái):之前只是考慮了機械解鎖單獨觸發(fā)喚醒、遙控鑰匙單獨觸發(fā)喚醒的情況,沒(méi)有考慮過(guò)遙控信號通過(guò)了前兩級濾波而此時(shí)機械解鎖信號突然觸發(fā)執行了ISR這種罕見(jiàn)情況,莫非...?
測試人員剛才打電話(huà)時(shí),由于手機的輻射,RF信號線(xiàn)上出現了若干有效的射頻位,造成RF信號通過(guò)了前兩級濾波,此時(shí)要在循環(huán)體的第三級濾波程序要等待200ms,判斷是否是遙控按鍵信號。
對于嵌入式系統而言,200ms是一個(gè)不容忽視的時(shí)間段,倘若測試人員在這期間試圖機械解鎖,觸發(fā)中斷執行ISR后會(huì )發(fā)生什么呢?
我繼續扒拉開(kāi)代碼看進(jìn)去,一絲寒意向我心頭襲來(lái)。
原來(lái)我在ISR中執行了退出休眠模式的函數-ExitSleepMode(這個(gè)函數里面會(huì )禁能喚醒條件),當ISR執行結束后回到循環(huán)體中時(shí),它會(huì )在200ms超時(shí)后進(jìn)入循環(huán)體第三級濾波,當然它會(huì )發(fā)現不是有效的遙控按鍵信號,于是再度進(jìn)入休眠。
但是這個(gè)時(shí)候已經(jīng)禁能了喚醒條件,這就意味著(zhù)它再也喚不醒了呀??!
4
人的思維真的很奇怪,本來(lái)bug明明就在眼前卻視而不見(jiàn),可是一旦猜到了bug的可能原因,就立馬火眼金睛起來(lái)。
為了幫助讀者的理解,筆者給出了下面的偽代碼,相信聰慧的讀者也能看出問(wèn)題所在。
void interrupt Wake_ISR(void)
{
...
ExitSleepMode();//disable wakeup event
...
}
static void ConfirmRkeWakeUp(void)
{
__delay_ms(200);
if(Rke is valid){
...
ExitSleepMode();
...
}
else{
return to sleep ;
}
}
再讓大家加深一下理解。
假設機械解鎖信號觸發(fā)ISR時(shí),循環(huán)體正運行到RF信號的第三級濾波程序ConfirmRkeWakeUp之前,MCU會(huì )先執行ISR,在ISR中禁能喚醒條件,然后返回循環(huán)體中執行ConfirmRkeWakeUp。
ConfirmRkeWakeUp函數延時(shí)200ms后,判斷這200ms之間有沒(méi)有有效的遙控鑰匙信號,顯然這里是沒(méi)有的,于是Return to sleep。
但是,這個(gè)時(shí)候喚醒條件已經(jīng)被禁能了(在ISR中被禁能了),系統就永遠不會(huì )被喚醒了。
眼尖的讀者可能會(huì )納悶,這種故障之前為什么沒(méi)有測試出來(lái)?
因為按照之前的測試條件,這種情況發(fā)生的幾率非常?。?span style="text-indent: 2em;">循環(huán)體內執行ConfirmRkeWakeUp函數之前需要經(jīng)過(guò)雙重RF濾波,這要求中控鎖的RF信號線(xiàn)上必須在較短的時(shí)間內存在較多的符合寬度要求的RF信號位,否則就不會(huì )執行ConfirmRkeWakeUp這個(gè)函數;平時(shí)用機械鑰匙解鎖時(shí),雖然ISR中禁能了喚醒條件,但是由于不會(huì )執行ConfirmRkeWakeUp,ISR回到循環(huán)體之后,系統依然會(huì )退出休眠。
平時(shí)用遙控鑰匙解鎖時(shí),無(wú)論期間有沒(méi)有發(fā)生機械解鎖ISR,最終都會(huì )執行ConfirmRkeWakeUp并退出休眠。
所以,只有無(wú)效的遙控按鍵信號觸發(fā)執行ConfirmRkeWakeUp函數時(shí)機械解鎖信號同時(shí)有效,才會(huì )觸發(fā)這種故障。
汽車(chē)生產(chǎn)線(xiàn)上為什么測試出來(lái)這種故障了呢?
那是因為在生產(chǎn)線(xiàn)上裝配了中控鎖的車(chē)太多了,產(chǎn)線(xiàn)后端的中控鎖模塊進(jìn)了休眠之后,用機械鑰匙喚醒時(shí)很容易被其它車(chē)的遙控鑰匙操作誤觸發(fā),導致循環(huán)體執行到ConfirmRkeWakeUp上來(lái)。
但是其它車(chē)的遙控鑰匙對于本車(chē)來(lái)說(shuō)是無(wú)效的,所以,根據上面的原因,故障就出現了。
結語(yǔ)
定位了故障的原因之后,我不禁又洋洋得意起來(lái),‘這么高級的bug都被我逮出來(lái)了!’。好似全然忘記了這個(gè)故障就是我自己埋下的坑。
事后,領(lǐng)導讓我寫(xiě)總結原因時(shí),我意識到了這種故障背后的深層次原因在于中斷和循環(huán)體的不登對:中斷服務(wù)程序結束后必然回到主循環(huán)體中被中斷的位置;嵌入式系統中,中斷可能發(fā)生在主循環(huán)體的任何位置;ISR和循環(huán)體程序之間不存在執行時(shí)間的先后順序關(guān)系,先后次序不同可能導致不同的運行結果;由于ISR發(fā)生時(shí)循環(huán)體可能會(huì )執行到任何代碼位置上,如果代碼設計地不嚴謹就有可能會(huì )造成問(wèn)題!
煮熟的鴨子最終沒(méi)有飛走,領(lǐng)導又笑意盈盈地踱了過(guò)來(lái),跟我討教故障的原因??粗?zhù)他那被即將到手的獎金鼓舞地有些腫脹的胸膛,我一邊默默地念叨著(zhù)‘狡兔死走狗烹,飛鳥(niǎo)盡良弓藏’,一邊悠悠地說(shuō)了一句總結陳詞:中斷和主循環(huán)不登對,系統會(huì )一直休眠久睡!
評論