频繁中断的ISR,执行时间需要优化
花儿开,鸟儿叫,早起上班的路迢迢。风儿吹,阳光照,为国工作的豪情万丈高。
本文引用地址:http://www.amcfsurvey.com/article/201907/402884.htm那日,洒家虎虎生风地走在上班的路上,正好碰上一位父亲领着女儿去上幼儿园。看得出来,小家伙儿的起床气还没有完全消散,耷拉着脑袋,慢腾腾地踢踏着。爸爸一边急火火地拽着女儿的小胳膊往前赶,一边跟她说着什么。
洒家正待从这对父女俩身边走过时,正听得爸爸弯着腰对女儿说:“不上幼儿园怎么上小学呀?”小丫头片子奶声奶气地说不想上小学。爸爸不以为然地继续说教着:“不上小学怎么上大学呀?”
‘恩?小学之后不该是初中吗,还有高中呐!’我正在心里犯着嘀咕,一声嘹亮的哭腔便划破空气,直冲耳膜而来。回头一看,小家伙正一边甩着胳膊打爸爸的屁股,一边咧着大大的嘴巴哭着说:“我什么学都不想上!!”
我初觉好笑,继而感到有些悲哀。看着她那梨花带雨的样子,一股惆怅在我心底潮起:你以为我愿意上班的吗?
1
顺利出差回来后第一天的上班生活悠闲而自在,可以东转转西逛逛,和这个同事打个闲茬,和那个同仁热切但放松地讨论技术。
但是如果出差不顺利,回来第一天的上班生活就没有那么轻松美好了。在邂逅不愿上幼儿园的女娃娃那天,我正是这样的状态。
这次出差的工作任务是到汽车厂进行总线测试,笔者出发时怀着胀得鼓鼓的信心,带着游山玩水的闲情逸致,回来时揣着灰溜溜的挫败感,和不知如何是好的沮丧。
几个月前去测试时,明明测得好好的,这一次,居然测试失败了!
测试报告中那几个夹在绿色OK项目中间的NOK项,发着悠悠的红光,带着冷冷的笑意,让我的寒意从头顶直接凉到了大地。
点开这几个NOK项的测试数据,一行行标记着报文收发时间、收发方向、ID、数据场的数据眼花缭乱地扑面而来。根据错误提示,笔者产品发送报文的周期性没有满足要求。我拖着鼠标从左拖到西,又从上拖到下,终于发现,有一条以50ms为周期固定发送的报文,有那么一次,没有及时地发出来。
各种思路向我的脑海涌来,洒家稳了稳心神,首先把怀疑的目光放到了CAN通信代码的一致性上面。如果两个版本的CAN通信代码不一致,把代码改回去就还有测试通过的希望!!
许是这几个月来不小心改了CAN通信代码中的哪个模块或哪个函数呢?
我迫不及待地调出了几个月前来做第一次测试时的代码,用Beyond Compare对比工具和这次的测试代码进行了比对。
除了这几个月中添加的许多其它模块的代码,CAN通信代码竟然是一致的。刚刚升起的一丝希望被无情地浇灭了。通信代码一致意味着,是这几个月来添加的不知道哪段代码改变了这次的测试结果。
麻烦大了!这几个月来添了那么多代码,鬼才知道到底是哪块影响了倒霉的CAN通信。
在汽车厂搞了一天,仍然不得要领,测试MM也开始置我的美色于不顾,露出不耐烦的神色来,于是我只好灰溜溜地回了公司。
2
据说篮球和足球比赛有主场、客场之分,在自己的主场上,在一众球迷的呐喊助威下,在家乡父老的气氛烘托下,球队容易比出好的成绩来。
回到公司的我,虽然听不到同事们的鼓掌声,但是,回到自己熟悉的地盘,战斗力也直线上升。
我戴上耳机,耳旁响起舒缓的轻音乐,在仿佛与世隔绝的静谧中,我小心地观察着那几个测试NOK项的测试数据。
很快,这些数据的特征浮现在洒家的面前。
在这几项测试中,测试软件发送了大量优先级或高或低的无关报文,相比之下,之前的测试中也发送了大量报文,但是都是产品用得到的报文。
不同之处找到了,问题的原因自然也就很容易找到了。为了帮助读者理解,笔者先简单介绍一下CAN报文接收的处理程序:
①总线上接收到报文时,MCU被触发报文接收中断。
②进入ISR程序后,MCU会拿接收到的报文ID和产品规范所定义的需要解析的ID依次进行比较。
③经过若干次比较后,如果接收报文ID和需要解析的ID一致,把报文存入接收缓冲区,发送“接收到新报文”信号。如果不一致,表明接收到的是无关报文,MCU直接丢弃报文。
在洒家这个产品中,需要解析大约20条报文。为了简单,笔者按照ID从小到大的顺序进行比较。显然,无关报文需要进行20次比较,最终被MCU丢弃掉,相关报文进行比较的平均次数则为10次。实际上,测试软件给出的那几个OK项测试中,最多的比较次数也没有超过10次。
写到这里,问题的原因呼之欲出了!在大量高频次报文的冲击下,MCU一直忙于进行ID的比较匹配。无关报文的冲击力量尤甚,因为MCU进入报文接收ISR后,每次都要进行多达20次比较!!
深谙摩尔定律的看官也许笑了,现在MCU性能这么高,20次比较算逑?!
3
笔者喜欢拿数据说话,这回咱就用初中数学知识掰扯掰扯。
CAN报文的数据帧由7个不同的位场组成:帧起始、仲裁场、控制场、数据场、CRC场、应答场、帧结尾。
其中,帧起始标志数据帧和远程帧的起始,由一个单独的“显性”位组成。仲裁场包括识别符和远程发送请求位(RTR)。识别符的长度为11位。控制场由6个位组成,包括数据长度代码和两个将来作为扩展用的保留位。数据场由数据帧中的发送数据组成。它可以为0~8 个字节。CRC场包括CRC序列(CRC SEQUENCE),其后是CRC界定符(CRC DELIMITER)。CRC序列为15位,CRC界定符包含一个单独的“隐性”位 。应答场长度为2个位,包含应答间隙(ACK SLOT)和应答界定符(ACK DELIMITER)。帧结尾由一标志序列界定。这个标志序列由7 个“隐性”位组成。
所以一个8字节的数据帧的位数为1(帧起始)+ 12(仲裁场)+ 6(控制场)+ 64(数据场)+ 16(CRC场)+ 2(应答场)+ 7(帧结尾)= 108位。
报文之间存在帧间空间INTERFRAME SPACE。帧间包括间歇场、总线空闲的位场。间歇场包括3 个“隐性”的位。
所以,一个8字节的数据帧至少需要(108+3+1)* bitrate的时长,对于125kbps,需要0.896ms。对于500kbps,需要0.224ms。
不巧的是,笔者的产品需要面临的就是500kbps通讯速率的总线通信。
那么问题来了,0.224ms来一次中断,每次中断执行20次数据比较,你说MCU累不累?
累,MCU累得都快冒烟了!
剑客的最高境界是人剑合一,人就是剑,剑就是人。洒家远没有到码农的最高境界,但也高山仰止,知道要善待MCU,才能最终达到人机合一。
看着累得偶尔愣了神忘了发送报文的MCU,笔者在心疼又无奈的泪眼朦胧中苦苦思索着,怎么给MCU减减负呢?
4
这里要做的工作一言以蔽之,就是针对频繁中断的ISR,优化它的执行时间,卸掉MCU的负担。
看着那20个需要进行比较的报文ID,洒家眉头一皱,计上心来。
CAN报文ID是11位,头三位的取值为0-7,相当于将CAN报文ID的取值区间划分成了八段,分别是0-0xff、0x100-0x1ff、0x200-0x2ff。。。0x700-0x7ff。
如果将CAN报文ID右移八位,得到头三位取值,就可以知道这个报文ID处于这八段取值区间的哪一段,然后再到这个段内进行比较,比较次数不就下降很多了吗?
比如说,产品需要解析的0x700-0x7ff段的ID有0x701、0x7df两个报文,接收到一个0x745的无关报文时,之前的比较次数是20次,现在是执行一次8位移位,然后进行2次比较。
只需要执行一次移位运算,比较次数从20次陡然下降到了2次!!
我被这种效率的提升幅度惊呆了。只是一个非常简单的方法,就得到了这么好的效果!
带着修改后的代码,怀着一丝忐忑九分坦荡,洒家又直奔汽车厂测试去了。
测试通过后,测试MM向我投来赞许的目光,我回之以笑意,心中实则感慨万千。
时间是世界上最为倔强的东西,它一往无前,绝不回头。但是鲁迅先生说:时间就像海绵里的水,挤一挤总会有的。
MCU的能力也是如此,只要你怀着一颗精益求精的心,好好地设计代码,比如对于频繁中断的ISR,执行时间仔细优化,就能很好地驾驭它,发挥它的潜力。
套用鲁迅先生的话,就是:
MCU的性能就是海绵里的水,挤一挤总会有的!
评论