中断和主循环不登对 系统一直休眠久睡
你永远也唤不醒一个装睡的人,但是你可以一巴掌呼醒他。
可如果一个嵌入式系统休眠之后,就犹抱琵琶半遮面,千呼万唤醒不来了呢?笔者担纲开发的中控锁模块就醒不过来了。
1
这个问题已经折磨我整整两天了,搞得我心力憔悴。合作伙伴一天几个电话,恨不得从手机里面直接跳过来当面质问我:“为什么唤不醒,为什么死睡?”
平时对我爱答不理的领导也一改常态,转而死死地盯着我。老实厚道的我呢,自然也十分紧张且惶恐,因为问题十有八九确实出在我写的代码上。
关键是这个问题出现在马上大批量供货的前夜,太不是时候了!我们给某车厂供货的中控锁模块已经走到了小批量供货阶段,前期供货20套倒是无惊无险,没发现什么问题,按照流程,接下来要供货100套。
如果阿弥陀佛的话,就再供货200套,再没有什么问题,就进入大批量供货阶段了。到时,合作伙伴开始稳定盈利,领导政绩、奖金到手,不会争功只会揽过的我呢,挥一挥手退到幕后,深藏功与名,一切就万事大吉,只待岁月静好了。可是,恰恰在供货100套期间,忘了求菩萨保佑,结果就出事了。
一天,车厂的生产线上下线了装配了我们的中控锁模块的50辆新车,本来一切顺利,在生产线上操作中控锁没什么问题,匹配学习了遥控钥匙,也能用遥控正常操作。但是到了下线后车厂人员要把新车开回仓库时,突然发现有几台车用遥控钥匙、机械钥匙死活也唤醒不了了!!
事关重大,车厂立马通知了中控锁的供货商—我们的合作伙伴,合作伙伴立马找到了我们(中控锁的开发商)领导,领导立马找到了我。我也立即给自己上紧了发条,进入战斗状态。
2
眼看到手的政绩和奖金要飞,领导心急火燎,早被晾到一边的我却颇不以为然。
到了现在这个阶段,软件肯定没问题,有问题早就测出来了嘛,还能等到现在?肯定是中控锁的线束出了问题。
当我把轻描淡写的分析说与领导时,领导一改往日的温和,一下子急了:“人家线束供了这么多年了,按你的说法,有问题早就测出来了,还能等到现在?”
岁月的风霜早已经消去了我性情中所有的刚硬和火热,只剩下如水的柔和。看到领导急了,我心中起着嘀咕,脸上泛起笑容,小心翼翼地对领导言道:“要不我再看一下代码,没准真是哪里出了岔子呢?”
得到我的表态后,领导转身踱了开去,一边嘴里念叨着得跟车厂确认一下线束有没有问题,一边转过头来再次叮咛我一番:抓紧啊!看着领导那殷切的眼神和黑黑的眼圈,我用力地点了点头,一个猛子扎进代码的汪洋大海,迅速游至中控锁的休眠和唤醒之地。
代码的设计思路总是简单而且正确的。中控锁进入休眠之前,设置了两个唤醒条件:①机械解锁信号的上升沿中断唤醒MCU;②定期检查遥控钥匙信号的定时器周期唤醒MCU。当出现有效的机械锁信号时,或者检测到有效的遥控信号时,中控锁禁止这两个唤醒条件并返回正常状态。
在具体的代码实现上,把中控锁的休眠模式处理分成了两部分:中断服务程序和循环体。中断由机械锁解锁开关信号触发,执行完ISR后先返回循环体,再退出休眠模式。循环体中反复休眠和临时唤醒,在临时唤醒期间通过三级滤波机制检查是否存在有效的遥控钥匙信号。存在遥控信号时,这个循环体层层地通过三级滤波后,退出休眠模式。
逻辑上清晰,代码也很简单,我反反复复检查了几遍,没看出个子丑演卯来,就准时准点地下班回家了。
第二天上班后,我还没在工位上坐定,还没来得及平复一下自己的燥热劲头,领导就带着于我少有的笑意猫过来了。不等他开口,老实巴交的我就主动汇报了检查代码后没有发现什么问题的情况,no news is good news,但是此时代码没有问题就是大问题啊!
话音甫落,领导脸上还没来得及展开的笑容就凝固不动了,他张着口,默默地看着我。
我们俩就这么站着,不说话,就十分地不美好!
城府甚深的领导终于没有说话,他不动声色地安排了一位同事搭建了中控锁的测试系统,让他反复测试休眠、唤醒的情况。
3
测试的方法很简单,把电流表串进中控锁的供电线上,看着电流下降到休眠电流范围后,便操作一下遥控钥匙,或者给一个机械解锁信号,看能否执行解闭锁操作。
我在一边冷眼观察着测试人员的操作,心情竟然无比地矛盾,既希望他快点测出来不能正常唤醒的故障,又盼着最好测试不出来问题。
大半天下来,中控锁反反复复地正常休眠、被正常唤醒,我的心也反反复复地七上八下,百般煎熬。后来,测试人员的电话响了,我默默转身离开,同时发现自己竟然更加惶恐了!
刚刚坐到电脑前,测试人员就大呼小叫着跑了过来:“寂寞君,问题再现了!”我就像屁股上安了个弹簧一样,一下子被凳子弹了起来,待我三步并作两步跨到测试台前时,领导也已经闻声迅速赶来。
我抓着遥控钥匙一边操作,一边注视着电流表的读数,电流一直稳定在2毫安左右。
问题确认了,确实死活也唤不醒了。
我又一屁股栽倒在凳子上,抬起头来,正赶上领导意味深长的目光。我抿着嘴笑了笑,刚想说点什么,测试台上测试人员的电话又嗡嗡响了起来。
听着熟悉的手机铃声在耳边萦绕,看着手机在桌子上震动个不停,我回想起测试人员刚才打电话时的情景,暗钝的思维再度转动起来:之前只是考虑了机械解锁单独触发唤醒、遥控钥匙单独触发唤醒的情况,没有考虑过遥控信号通过了前两级滤波而此时机械解锁信号突然触发执行了ISR这种罕见情况,莫非...?
测试人员刚才打电话时,由于手机的辐射,RF信号线上出现了若干有效的射频位,造成RF信号通过了前两级滤波,此时要在循环体的第三级滤波程序要等待200ms,判断是否是遥控按键信号。
对于嵌入式系统而言,200ms是一个不容忽视的时间段,倘若测试人员在这期间试图机械解锁,触发中断执行ISR后会发生什么呢?
我继续扒拉开代码看进去,一丝寒意向我心头袭来。
原来我在ISR中执行了退出休眠模式的函数-ExitSleepMode(这个函数里面会禁能唤醒条件),当ISR执行结束后回到循环体中时,它会在200ms超时后进入循环体第三级滤波,当然它会发现不是有效的遥控按键信号,于是再度进入休眠。
但是这个时候已经禁能了唤醒条件,这就意味着它再也唤不醒了呀!!
4
人的思维真的很奇怪,本来bug明明就在眼前却视而不见,可是一旦猜到了bug的可能原因,就立马火眼金睛起来。
为了帮助读者的理解,笔者给出了下面的伪代码,相信聪慧的读者也能看出问题所在。
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 ;
}
}
再让大家加深一下理解。
假设机械解锁信号触发ISR时,循环体正运行到RF信号的第三级滤波程序ConfirmRkeWakeUp之前,MCU会先执行ISR,在ISR中禁能唤醒条件,然后返回循环体中执行ConfirmRkeWakeUp。
ConfirmRkeWakeUp函数延时200ms后,判断这200ms之间有没有有效的遥控钥匙信号,显然这里是没有的,于是Return to sleep。
但是,这个时候唤醒条件已经被禁能了(在ISR中被禁能了),系统就永远不会被唤醒了。
眼尖的读者可能会纳闷,这种故障之前为什么没有测试出来?
因为按照之前的测试条件,这种情况发生的几率非常小:循环体内执行ConfirmRkeWakeUp函数之前需要经过双重RF滤波,这要求中控锁的RF信号线上必须在较短的时间内存在较多的符合宽度要求的RF信号位,否则就不会执行ConfirmRkeWakeUp这个函数;平时用机械钥匙解锁时,虽然ISR中禁能了唤醒条件,但是由于不会执行ConfirmRkeWakeUp,ISR回到循环体之后,系统依然会退出休眠。
平时用遥控钥匙解锁时,无论期间有没有发生机械解锁ISR,最终都会执行ConfirmRkeWakeUp并退出休眠。
所以,只有无效的遥控按键信号触发执行ConfirmRkeWakeUp函数时机械解锁信号同时有效,才会触发这种故障。
汽车生产线上为什么测试出来这种故障了呢?
那是因为在生产线上装配了中控锁的车太多了,产线后端的中控锁模块进了休眠之后,用机械钥匙唤醒时很容易被其它车的遥控钥匙操作误触发,导致循环体执行到ConfirmRkeWakeUp上来。
但是其它车的遥控钥匙对于本车来说是无效的,所以,根据上面的原因,故障就出现了。
结语
定位了故障的原因之后,我不禁又洋洋得意起来,‘这么高级的bug都被我逮出来了!’。好似全然忘记了这个故障就是我自己埋下的坑。
事后,领导让我写总结原因时,我意识到了这种故障背后的深层次原因在于中断和循环体的不登对:中断服务程序结束后必然回到主循环体中被中断的位置;嵌入式系统中,中断可能发生在主循环体的任何位置;ISR和循环体程序之间不存在执行时间的先后顺序关系,先后次序不同可能导致不同的运行结果;由于ISR发生时循环体可能会执行到任何代码位置上,如果代码设计地不严谨就有可能会造成问题!
煮熟的鸭子最终没有飞走,领导又笑意盈盈地踱了过来,跟我讨教故障的原因。看着他那被即将到手的奖金鼓舞地有些肿胀的胸膛,我一边默默地念叨着‘狡兔死走狗烹,飞鸟尽良弓藏’,一边悠悠地说了一句总结陈词:中断和主循环不登对,系统会一直休眠久睡!
加入微信
获取电子行业最新资讯
搜索微信公众号:EEPW
或用微信扫描左侧二维码
相关文章
-
-
-
2024-07-16
-
-
-
-
-