文章目录
背景介绍
问题描述
分析排查
解决方案
总结归纳
背景介绍
在一个嵌入式软件项目中,有一段使用C语言写的嵌入式代码,功能是把CAN总线上的几帧报文接收进来,并解析出数据。示例如下:
乍一看感觉挺简单,想着直接用一个while循环,周期提取CAN buffer中缓存的数据就行了。但是就这么一个小应用让我栽了跟头,在整个工程中排查了几个小时才找到问题,下面就来分享一下这一小段个人经历。
一开始代码调试的时候很顺利,一共接收5帧报文,总共40个信号,相关代码大概是下面这个样子:
void main(void)
{
//省略若干行if(Timer10ms == 1){do{ret = GetCANFrame(1, &Ext, &ID, &Length, &DataBuf);if(ret){if(ID == 0x100){memcpy(&SignalArray[0], DataBuf, 8)}if(ID == 0x101){memcpy(&SignalArray[8], DataBuf, 8)}if(ID == 0x102){memcpy(&SignalArray[16], DataBuf, 8)}if(ID == 0x103){memcpy(&SignalArray[24], DataBuf, 8)}if(ID == 0x104){memcpy(&SignalArray[32], DataBuf, 8)}}}while(ret && (i <= 100))}//省略若干行
}
问题描述
按照上述示例开发的软件,烧录到ECU中在实验室桌面上调试,使用Vector的CAN接口卡模拟残余总线发送0x100、0x101、0x102、0x103、0x104这五帧报文,ECU都能正常接收。但是把ECU集成到实车之后,却收到反馈ECU的CAN接收有丢帧现象。
分析排查
把实车上的CAN报文Log打开之后,看到总线上除了0x100、0x101、0x102、0x103、0x104这五帧报文之外,还有几十个其他ID的报文,所以推测是这些残余总线报文干扰了ECU的CAN接收。所以在实验室桌面上把这些残余总线模拟出来,复现了实车上的CAN接收丢帧现象,这就锁定了是残余总线的问题。
为了更具体的分析Bug机理,在代码中加入了一些调试信息,每个ID接收到之后都把相应的计数加1,示例如下:
void main(void)
{
//省略若干行if(Timer10ms == 1){do{ret = GetCANFrame(1, &Ext, &ID, &Length, &DataBuf);if(ret){if(ID == 0x100){memcpy(&SignalArray[0], DataBuf, 8)FrameCount[0]++;}if(ID == 0x101){memcpy(&SignalArray[8], DataBuf, 8)FrameCount[1]++;}if(ID == 0x102){memcpy(&SignalArray[16], DataBuf, 8)FrameCount[2]++;}if(ID == 0x103){memcpy(&SignalArray[24], DataBuf, 8)FrameCount[3]++;}if(ID == 0x104){memcpy(&SignalArray[32], DataBuf, 8)FrameCount[4]++;}}}while(ret && (i <= 100))}//省略若干行
}
烧录上述软件后可以看到,发生CAN接收丢帧时都伴随着残余总线报文的集中突增,总线瞬时100%负载率会持续十多ms,所以推测可能是残余总线报文挤占了CAN接收buffer。翻了一下CAN接收buffer的配置,是40帧的size。根据CAN总线500k的波特率计算100%负载率持续十多ms总线上会有50帧左右的报文。而我的代码是10ms从CAN接收buffer提取一次数据,这样必然会导致CAN接收buffer塞满后无法继续接收,也就是丢帧的现象。
解决方案
针对上述CAN接收buffer溢出的问题,解决方案可以从如下公式的每一个因子入手:
Buffer占用空间 = 单位时间最多的报文数量 * 提取Buffer的周期 公式(1)
方案一:扩大buffer size配置。其他条件不变,这里只要把原本40帧的size扩大到45帧左右即可。因为500k波特率的CAN总线,10ms时间内最多产生的报文不会超过45帧。这个方案需要MCU硬件资源的支持。
方案二:避免报文突增。其他条件不变,这里只要在CAN协议中约定好,各个ECU节点都按既定节拍发送报文,避免瞬时负载率拉升到100%,或者控制100%负载率持续时间要小于10ms。这样任何一个10ms周期内,总线上的报文最多也不会超过40帧,现有的40帧的Buffer size就够用了。这个方案需要总线上各个节点ECU的配合。
方案三:降低波特率。其他条件不变,这里只要把原本500k的波特率降下来,也就降低了单位时间最多的报文数量,实现的原理跟方案二一样。这个方案也需要总线上各个节点ECU配合变更波特率。
方案四:CAN接收ID过滤。其他条件不变,这里只要在底层CAN接收模块中做一个ID过滤,只允许0x100、0x101、0x102、0x103、0x104这五帧报文进入CAN接收buffer,实现的原理也跟方案二一样。这个方案只需要自己的ECU修改底层软件即可,不需要其他方面的支持和配合。
方案五:缩短提取Buffer的周期。其他条件不变,这里只要把原本10ms的提取周期改成5ms或者其他小于10ms的数值即可。因为因为500k波特率的CAN总线,5ms时间内最多产生的报文在20帧左右,远远低于40帧的buffer空间。这个方案只需要自己的ECU修改应用软件即可,不需要其他方面的支持和配合。
上述五种解决方案各有优劣,适用于不同的项目开发情景。方案一适用于ECU开发的早期,在MCU选项时,或者在软件架构定型时,就根据CAN总线的应用需求为buffer size留出足够的资源空间。方案二和方案三适用于系统集成阶段,在制定整车的网络协议时,约定好各个ECU节点需要遵循的公共基础和公用策略。方案四和方案五适用于ECU的应用开发阶段,在开发具体功能时,更具其他已经设定的条件,决定如何设计ID过滤和报文提取周期。
总结归纳
那么这个代码调试过程,发现的问题可以积累下来这么几条小经验以供自己将来使用,也给广大网友参考:
1、CAN网络相关的模块设计与开发时,不光要考虑自身ECU节点的发送和接收,还要考虑其他节点不相关报文的实际存在。对于不能确定确定参数时,就要按照最严酷的情况来估算,比如残余总线瞬时负载率达到100%,而且还持续挺长一段时间。
2、方案设计用的是等式(解决方案中的哪个等式),但是软件开发中用的是不等式,因为实际情况中要面对的有各种误差,所有在留下余量时,等式中的“=”就要变成“>”或者“<”。比如理论计算的buffer size是40帧,这里就应该使用>40帧。
以上就是本人在解决CAN接收Buffer溢出Bug时,一些个人理解和分析的总结,首先介绍了基本的项目背景,然后描述了问题的想象,最后分析排查了Bug原因,并给出了问题解决方案。
后续还会分享其他的,使用C/C++研发时遇到的Bug,欢迎评论区留言、点赞、收藏和关注,这些鼓励和支持都将成文本人持续分享的动力。
述例程使用的Demo工程,可以到笔者的主页查找和下载。
版权声明:原创文章,转载请注明出处与链接,违者必究!