示波器破解CAN错误帧/BusOff的经验分享

摘要:can的busoff源于错误帧的积累,而错误帧这个东西,是一个接收节点 认为数据有误 故意打断通信,好让发送节点感知到 并重发报文的设计。注意这里边有个“我觉得你有病”的认知陷阱,让can的诊断变得近似玄学。本文分享一种用can波形的幅度和脉宽信息来精确定位错误帧来源的方法,来自知乎的大灯。
我们先从基础的讲起。can节点的电路一般如下图所示,mcu内置了can控制器用来将mcu的数据封装为can帧格式,同时它也负责can帧的校验和错误帧的处理。控制器封装好的逻辑报文经tx rx送到can收发器,将逻辑信号转变为真正的总线差分波形。
一、can物理层
也就是can收发器干了啥?
一个典型的双节点can网络的物理层等效电路如上图,两颗120ω终端电阻并联呈现总线电阻60ω。黑框里是a、b两个节点的can收发器(transceiver),它只负责电平转换。当总线静默时,收发器内部的2.5v电源经15kω电阻把can-h和can-l都拉到2.5v,总线这个状态称之为隐性。当节点a想要驱动总线的时候(tx=0),它同时把内部的上下两个mos管导通,整个网络的电流流向:节点a的5v电源经二极管、24ω、两颗终端电阻并联、24ω、二极管回到节点a的地,总线这个状态称之为显性。can总线上的电压实际上就是终端电阻的分压。从节点b来看,can-h就变成3.5v,can-l变成1.5v,拉出了总线 h - l = 2v 的差分电压,大于0.7v的判断阈值,节点b就认为收到了一个显性(rx=0)。大家可以算一下分压值以增强记忆,后边会用到。
反直觉知识点①:总线无人驱动时,也就是各个节点都隐性时,can标准定义这时的tx/rx逻辑电平为1;总线有节点驱动显性,也就是主动拉开差分电压的时候,对应tx/rx端逻辑电平0,这个1/0的反逻辑类似i2c等oc门的驱动逻辑,努力适应一下。这么做我猜有两个原因:一是对地逻辑的抗扰能力强一些,npn载流能力强&回流路径短;二是为了数学上的严谨性:1x1x1x1...1x0 = 0,任意节点驱动显性0,那总线就是显性0;所有节点隐性1,总线才是隐性1。但这样的反逻辑带来一个问题是,电路设计时需要尤其注意上下电时序,上电/休眠/唤醒过程中千万不要出现mcu已下电(tx拉低)但can收发器还供5v电的情况。如果实在难以避免,可以试试单3.3v的can收发器max3051,它不需要5v电源,逻辑电和驱动电共用同一路3.3v,肯定不会出现电源时序问题。
反直觉知识点②:理论上can_l短地,或 can_h短路12v,因60ω终端电阻的存在,隐性时can-h与can-l之间基本还是重合的,显性时也能正常拉开压差,能维持正常通信,只不过丢包率可能会大一点。大家可以算算总线电压,示波器很容易诊断这个问题。另外,如果你看到can的通信电压不是以2.5v为中心对称的,也有可能是多个can线交叉错接,比如can1_l错接到了can2_l上。
反直觉知识点③:一个can网络里,120ω终端电阻1~4颗都能工作,少了的话 离终端电阻远的节点 抗扰度会差,多了的话 显性差分电压可能无法触发阈值。
反直觉知识点④:除了线路的最远端,任何稍长的can分支都可以加1k~4.7k的支线电阻,跑点电流来改善抗扰度。只要分支别太长,大致1mbps以内的任何总线其终端电阻都是跑电流增强抗扰的,不涉及真正的阻抗或者反射抑制,双绞的要求也不是特别严格。
二、can链路层
也就是can控制器干了啥?
回顾完物理层,咱来看链路层,can帧的标准格式。当发送节的mcu将tx由1变0的时候,can收发器将can-h拉高&can-l拉低,接收节点收到了h-l>0.7v的压差后,接收节点的can收发器rx输出由1变0。下图是一个节点接收到can波形后解码出的rx逻辑。
一帧报文里边有比较关键的几段:仲裁段、控制段 、 数据段、crc段、ack段。
仲裁段中的大部分是can报文的id,起名为“仲裁”其实是因为这一段有优先级仲裁的功能:假设a、b两个节点在同一时刻抢发报文,节点a要发二进制id为001的报文,b要发010。当a、b节点都在发第一位的显性0的时候,总线会同时被两个节点驱动显性,a、b节点回读总线也都是显性,相互之间还意识不到对方的存在。当节点a发到第二位的0,节点b发到第二位的1的时候,总线只有节点a驱动显性0,节点b不驱动 却发现总线被别人驱动了,此时节点b会认为can线上有比自己这帧010优先级更高的数据,节点b就会主动停发,让节点a独占总线发完。之后节点b怀揣着这帧数据再次参与总线优先级的仲裁。
反直觉知识点①:can作为一个对等网络,没有主从关系,报文全部广播,节点本身也没有优先级概念,只有报文id的优先级。可以这么理解:can节点是“由事件驱动的”,比如刹车制动器,它能发高优先级的“刹车被踩下”的报文,也会发低优先级的“刹车油位正常”的报文,这些报文根据id的大小在总线上自由竞争优先级,而不是刹车制动器这个节点的话语权一定高。这个特性就要求设计人员提前规划好所有报文优先级和周期(即“通信矩阵”)才能保证整个can网络如期运转。如果你的can网络有大量雷同节点,节点又只有一帧报文,那么id数大(优先级低)的节点一定会在总线繁忙或干扰重发的时候插不上话,可以试试把时间戳融合到id里边,确保各节点的新数据优先级最高,旧数据自然会被仲裁掉。
反直觉知识点②:在a、b节点同时驱动第一个显性0的时候,总线被两个节点同时驱动,电压会显著高于2v。示波器上会看到在仲裁段的头部有明显的电平凸台,后续节点a抢占总线之后电压会回归正常的2v。
反直觉知识点③:各个节点的时钟同步是把每个bit做16~20份的数字切片来实现的,这个切的份数不建议太多或太少。详细机制请参阅 zlg致远电子的这篇:can同步机制,你真的了解吗?
控制段中有几个控制位,这里拿几个常用的举例。ide位为扩展id的指示。如果ide位为隐性1,就会在后边再续上18位的id,共11+18=29位长度。比如0x9e就是个11位长度的id,0x0151就是个29位的id。r0位是can里边的预留位,在can-fd里被用作fd帧格式的标志位fdf,这一位为隐性1就会按fd的帧格式解码后续报文。dlc指示了后边的数据段的长度,例如1000表示后续会有8个byte长度的数据。can-fd协议只在数据段会切换成高速率,比如2mbps/8mbps,前后其他段的速率保持500kbps不变。
反直觉知识点①:can与can-fd除了数据段波特率的不同,帧格式也有区别,can-fd多了一些控制位。比如fdf(也叫edl)位用来指示是否按fd帧格式解码,brs位用来指示是否需要切换高波特率,也就是说,一个fd帧可以全程500kbps不切速率的。
反直觉知识点②:can控制器的标准iso11898-1里要求接收方不解读r0位的显隐性,所以can的控制器无法过滤fd帧。标准can网络里边一旦出现fd帧会因为多了brs、esi等控制位被认为是格式错误。同样的,因为can 2.0时代r0/fdf帧无意义,也有一些设备把发送出去的can帧的r0位错误地置了隐性1,这样的设备在can网络里一切正常,但若进入can-fd网络就会被解读成fd帧,进而因为缺少brs、esi等控制位被认为是格式错误。所以,can-fd并不是真的向下兼容can,因为旧时代的can设备并没有判别r0/fdf位的能力,一旦它进入fd网络就会疯狂地打断通信。
反直觉知识点③:dlc的长度,在can标准里dlc可以是0000~1000之间的二进制值,可以用8421的算法直接计算出数据长度。而在can-fd中,1001~1111之间的值则被解读为离散的12,16,20,24,32,48,64byte。
crc段对于从帧头到data结束之间的数据,can协议使用了crc15这个比较特别的多项式计算校验,有兴趣的可以手算crc试试。can-fd根据数据长度的不同使用了crc17和crc21,这里暂不做展开。
ack段是由收到该帧的can节点回复的确认(acknowledge)。注意 发送节点在ack位一定发的是隐性1,由接收节点回应显性0,双方无缝衔接才在总线上呈现出一个完整的can报文。
反直觉知识点①:总线上任何节点 只要认为这个帧的结构正确,都会在ack位回显性0,不管需不需要这一帧的id和数据。为什么不需要的节点也会回ack?因为等mcu算完会造成这一位的延迟,搅乱总线时序,不如只保障链路层本身的格式正确,纯芯片数字逻辑实现无延迟。嗯,90年代的总线要求不要太高。
反直觉知识点②:发送节点若发现自己这一帧没有ack回应,它也会认为总线出错,重发16次后进入passive error状态,有兴趣的自行研究一下,这里不做展开。
三、真实can波形
来看一个两节点案例:若节点a发送0x9e报文到总线,从节点b收到的总线波形和逻辑侧波形如下:
黄线为can-h,绿线为can-l,蓝线为节点b的逻辑侧rx,紫线为节点b的逻辑侧tx。可以看到,作为接收方的节点b,总线拉差分电压拉出显性的时候,收发器将rx拉0给到mcu。在节点b想要回应ack的时候,mcu将tx拉0,can收发器在总线上拉出了一个歪斜的显性(歪斜是因为测量点的寄生电感影响)。rx在ack位置的0,是收发器tx=0驱动总线显性之后 回读到的0。
再看一个比较真实的车上波形,can网络上大于4个节点:
黄色是can_h,高电平表示显性0,绿色是我们挂示波器这个节点的逻辑侧tx,低电平表示显性0。箭头a~d是一帧完整的can报文,箭头a ~ b这个过程中,我们挂示波器的这个节点和另一个节点正在进行优先级仲裁,根据我们之前讲到的物理层的分压原理,两个节点同时驱动电压会高一截。在箭头c这个bit 该节点想发隐性1但发现总线是显性0,那就说明有另外的节点在发送更高优先级的报文,我们这个节点会主动退出发送,成为接收节点,并在箭头d点校验成功后回应ack,等待报文结束后这个节点再次参与总线仲裁,成功抢占总线如e点所示。
注意波形高度,在箭头a~b之间,差分电压略高于2v,这是正常现象,说明有两个节点同时驱动总线显性,但从逻辑看,因为h-l>0.7v所以都为显性0,纯数字逻辑的can控制器在箭头a~b之间还感知不到对方的存在,箭头c点之后才感知得到;而在箭头d点,因为除了发送节点之外的所有节点都在同时驱动ack,所以总线电压比箭头a~b之间的双节点驱动 电压更高。
四、错误帧
终于到了错误帧,注意,错误帧不是由哪个节点发出的,而是由某个接收节点认为总线错误,才故意驱动总线打断发送方,在总线上呈现为一个错误帧。也就是说错误帧 一定是由 一个发送节点和至少一个 认为发送方有错的节点 共同形成的。
五、位填充
位填充规则是can协议的灵魂,简单来讲就几个字:逢五补一。当发送节点想要发连续5个bit的显性0的数据,会故意插入一个无意义的隐性1;当出现连续5个bit的隐性1,会故意插入一个无意义的显性0,如下图的紫色bit。如果发送节点漏填了这个0/1,或者这个0/1被干扰成了1/0,接收节点就会判定为“填充错误”,向总线上输出“主动错误标志”——连续六个显性0,故意破坏这一帧报文,发送节点感知到总线错误之后停止发送这一报文的后续部分。你说巧妙不巧妙?连续6个显性0本身就是破坏“逢五补一”规则的,被拿来当错误标志回给发送节点。
假如原始数据是0x00,二进制0000 0000,发送节点发到0000 0的时候发送节点会先插一个1,再发后续的000,成为0000 01000,共9bit长度,接收节点也会在第5bit的0之后预期一个无效的1,解码时抠掉。
假如原始数据是0000 0100,第六位自带1,发送节点发到00000的时候也会先插一个1,再发后续的100,成为0000 01100,共9bit长度。
六、回读确认
发送节点发送了0或1的时候,会回读确认总线是否和自己的发送相符,比如在仲裁段抢优先级失败就会等下一帧再发;如果发到了数据段,按理说此时总线应该只有自己,发着发着突然发现回读的0/1与自己发的不同,比如受到了干扰,发送节点就会输出“主动错误标志”——连续6bit显性0,来主动抛弃后续报文,同时让接收节点知道我这一帧有误。

在这时,接收节点收到第6bit显性0的时候,因违背逢五补一的位填充规则,也会往总线上输出“主动错误标志”,所以会在总线上看到连续12bit的显性0,前6个来自发送节点,后6个来自接收节点。

正常情况来说,总线上的显性不应该>5bits=10us。那么用示波器设置>11us的脉宽触发模式就很容易定位错误帧的位置,不一定要用解码示波器。
七、升维打击
can网络的幅度和电流可以为我们提供更多维度的信息,此所谓升维打击。
我们先来看一个正常帧,我们叫它节点a吧,它内部有终端电阻,蓝线为h-l的差分电压,紫线是我们节点a的can-h引脚电流,输出为正,输入为负。
先看蓝色的总线电压波形,从0x83到end之间是一帧正常波形,注意看帧头有多级台阶,帧尾ack位置也特别高,这是正常的,可以理解 当多个节点同时驱动总线就会导致60ω终端电阻上的分压高于2v。从这些台阶来看,可以判断出网络上至少有5个节点。为啥?先看报文中部的幅度,这肯定是只有一个节点抢占总线之后的波形,往前有两级台阶,可以认为a、b、c三个节点同时抢占总线出现了第一个高台,然后节点c优先级仲裁失败退出总线,a、b节点继续抢占出现了第二个台阶,之后节点a成功抢占到了总线优先级,发送中间的数据。最后的ack位比3节点驱动的第一个bit更高,说明至少有4个节点在驱动ack,再加上节点a,网络上至少有5个节点。
再看紫色的电流波形,已知节点a自己有终端电阻,外边有另一颗终端电阻。波形中部的数据区肯定是节点a在驱动总线,差分电压流经外边的终端电阻形成回路,所以我们在节点a的引脚上观察到了输出的正向电流;往前一个电压台阶的位置,电流为0,是a、b两个带终端电阻的节点在驱动总线,所以总线电压拉开了但电流仍是无进无出的;再往前一个台阶,a、b、c三个节点驱动,节点c的电流流入a、b的终端电阻,所以在节点a的引脚上测到了输入的负向电流;然后帧尾的ack位置,至少有4个节点同时驱动,流入终端电阻a和b的负向电流更大了。
八、错误帧实战
这是一个两节点网络,一个节点发,另一个节点收,两方都有终端电阻,发送节点用的是tja1042,接收节点用的是单3.3v收发器max3051。在帧头就发生了错误,这种错误帧一般源于时钟偏差或采样点过小。
我们将示波器的差分探头和电流探头挂在接收端,下图黄色为h-l的差分电压,蓝色为接收节点的输出电流,rx为收发器将h-l差分电压转换出的逻辑波形,mcu内部的can控制器会根据rx的0/1来解读总线。tx为接收节点的发送逻辑,mcu将tx拉低的时候收发器会往总线上驱动显性。
我们已知500kbps的每个bit宽2us,注意上图紫线tx在2 ~ 4箭头之间出现了连续2us * 6=12us的显性0,说明我们挂示波器的这个接收节点在此刻往外输出了一个“主动错误标志”,那一定是接收节点在此之前认为总线出现了错误。我们来往前看,箭头1~2之间总线差分电压和rx逻辑侧都只有10us/2us=5bit的显性0,帧前边都是长隐性1,这能有什么错?一个可能是我们碰到了传说中的过载帧,这个东西本应该很少见了;另一个可能是接收节点把对方来的正确报文认成了错的,这10us被接收节点认成了6bit,错误的采样点+硬同步(帧头对齐)做得稀烂的国产mcu更容易出现这样的帧头报错。
不管哪种,我们推演一下看看是否符合我们的理论,在箭头2~3之间发送节点应该是想发送一个隐性,但这时接收节点已经觉得不对开始发“主动错误标志”,将总线拉成了显性。然后发送节点读到这一bit自己想发送隐性但总线是显性,所以。。。仲裁区抢优先级失败退出总线,,,怎么可能,之前有6个连续显性呢,所以发送节点因违反“逢五补一”在箭头3~5也输出“主动错误标志”。所以就成了黄色总线波形的7个bit的“凸”型,中间的凸台的位置总线被两个节点驱动,电压高起一个台阶。
再注意一个细节,凸台的左肩膀和右肩膀高度不一样,左肩膀是接收节点max3051驱动的电平,它比 右肩膀tja1042的驱动能力弱一些,总线电平低一点。这个特性可以用来区分总线上的不同设备。
蓝色线,是接收节点的输出电流。箭头1~2之间的负向电流为发送节点驱动总线,差分电压流经接收节点内部的终端电阻带来的负电流;箭头2~3之间的正电流是接收节点驱动的主动错误的第一个bit;后边3~4的凸台两个节点都在驱动显性 但对应的电流也是负的,这是因为发送节点的驱动能力强过接收节点,整个网络电流还是由发送节点灌入接收节点;再往后4~5的负电流是发送端驱动接收节点的终端电阻的电流。
下图我标出了两个节点的输出bit流,红框是“主动错误标志”。
仍然是这个两节点网络,仍然是这个稀烂的国产mcu,我们来看这个错误帧是怎么个情况:
这一帧的dlc=0x01,也就是只有1byte数据,数据区之后就是crc区,我们的“主动错误标志”就发生在这个区,观察又没有填充错误,那就是我们挂示波器这个接收节点认为发送节点出现了crc错误。但我们看到黄线在“主动错误标志”中间出现了凹坑,意味着发送节点还是想继续发隐性,并不认为自己有错,直到发现这一位被“主动错误标志”覆盖为显性才感知到位错误后抛弃后续报文。
原因最后定位到:过小的采样点+过大的再同步补偿宽度sjw让时钟误差逐步积累,这颗国产mcu的重同步又做得稀烂,把正常报文错读了一位导致算crc错误。最后通过调整采样点和sjw宽度减少了这种错误的出现频次,得到正常波形如下:
我们再试着从差分波形来分析一个错误帧:
错误发生在crc区,我们放大一下,看看各节点都发生了啥:
从每一个台阶往前画12us的方框,得到每个节点输出的“主动错误标志”,分析可知:这是一帧节点b发送的报文,节点a认为它的crc算错了,节点c凑了个热闹,三者一起形成了这个12bits长的“主动错误标志”。那,节点a为什么会认为crc有错呢?大概率是因为之前的数据读错了一位。这么好的波形也能读错?是的,我们无法判断节点a所在的位置波形有多差,可能分支上没有终端电阻振铃很大呢?我们只能相信节点a不会乱搞。另外,采样点偏差会导致节点对噪声额外地敏感。
九、can-fd错误排查
来看一个a b c三节点can-fd错误帧的案例,节点c发,节点a、b收:黄色是h-l的差分电压,绿色是节点b的逻辑tx。0x0677和0x0176是两个错误帧。fd区波特率设置为2mbps。
放大0x0176帧的细节:
标尺a b之间时间长度约0.8us,由一个2mbps fd bit的0.5us + 一个can-fd的tdc(300ns)组成。
之后出现了6个fd bit(0.5us*6=3000ns)的连续显性位,电平高度与之前相同,之后有连续2usx6=12us的显性。
在标尺b线后12us位置出现了一个电压跌落的小小的下降台阶,见下下图。
综上三条,认为节点c所在位置干扰过大/分支线路过长,节点c自己回读↓下图↓框出的bit位失败,自己往总线上输出“主动错误标志”(连续6bit=2usx6=12us的显性),其他设备在接收到第6个can-fd的bit=0.5usx6=3us的时候就读到了错误(违反fd速率的“逢五补一”规则),也往总线上叠加2us*6=12us的主动错误标志。然后,12us时节点c的主动错误标志先结束,其他节点的主动错误在2usx6+0.5usx6=15us后结束。至此,错误帧形态完成。

“逢五补一”这条规则是跟随波特率变化的,6个连续的高波特率0或1都会触发填充错误。但填充错误之后输出的“主动错误标志”是500kbps波特率的6bit,固定长度12us。

再来一个案例:can-fd采样点设置出错导致节点b把节点a发送的can-fd报文当can来解析出错。
黄色can_h,绿色can_l,蓝色l-h反向差分电压,紫线为节点b逻辑rx,青线为节点b逻辑tx。
注意看0x00前后的数据段,这一段是can-fd的2mbps速率,节点b因为采样点设置错误读错了brs这一波特率转换标志,仍按照标准的500kbps去解析节点a的2mbps速率的数据,对rx信号2us一个采样我用黄色箭头标出来了,可以看到这恰好是6个连续显性0,违反“逢五补一”的规则,故而接收节点b在箭头2~4之间发“主动错误标志”,打断总线通信,告知发送节点你发错了。箭头2~3之间,发送节点a恰好也要发显性,所以节点a此时还没感觉到不对。箭头3之后,节点a想要拉隐性,电压出现一个坑,却发现总线还是显性,此时节点a判断出现了“位错误”,开始输出“主动错误标志”,想告知接收方放弃我这一帧报文。箭头4的位置节点b释放“主动错误标志”,箭头5的位置节点a释放“主动错误标志”。
如果数据比较巧,恰好能满足逢五补一的规则,那这种错误形态会在发送很多数据之后才会出现,但最晚也会被crc拦截:
补充知识:can-fd网络各个节点的采样点必须完全相同,高速率导致对时序敏感很多,这一点与can网络容许一个范围显著不同。上边这一帧的brs位怎么读错的呢?再一次违反直觉:can-fd的采样点影响发送节点的驱动波形!用示波器可以轻松量出fd的采样点位置。
看下图,can-fd报文的控制段中的brs位(bit rate switch)明显是短于前边的fdf、r0位的,采样点不匹配的话很容易读错。因为-fd的速率翻转是在这一bit的采样点位置发生的。比如采样点80%的2mbps can-fd网络,brs这一位的宽度为2us80%+0.5us20%=1.7us,而不是2us。接收节点的采样点如果设置大于85%就会错过整个brs位(2us*85%=1.7us),从而导致如上的brs位读错的问题。
以上,就是示波器升维破解can错误帧/busoff的经验分享,总结一下:
结合已知id是哪个节点发的先验信息,逐个拔掉非终端节点,示波器观察“主动错误标志”,就能模糊定位错误源头;
如果能引出敏感设备的tx,哪个节点认为哪个节点出了什么错就会非常清晰明了;
其次,测量can的输出电流也能清楚地定位谁在驱动“错误标志”,进而找到故障点;
如果上述难以实现,以12us间隔拆分“错误标志”的电压台阶,也能定位大部分错误原因;


OPPOR11什么时候上市最新消息:OPPO手机凭什么卖的那么火,原来竟是有这项黑科技
莱迪思携手MediaTek实现智能手机的Type-C4K视频输出
通过实时传感提高检测率和可见性
华为海思进入后麒麟时代
苹果成功修复iPhone等设备的圣诞BUG
示波器破解CAN错误帧/BusOff的经验分享
【虹科案例 】激光雷达高效准确测量料余量体积
化工人员定位系统精确到“位”
新松行走辅助机器人实现商用,未来康复机器人市场需求不断上升
dfrobot350度高温传感器简介
agc电路可以应用在哪些地方(雷达、接收机)
TJA1043收发器信息梳理
传感器超详细分类大汇总
旷视科技产品总经理对手机端的几点思考
OPPO不止定制IMX766,IMX789正在测试中
oracle创建表空间的sql语句
公网访问虹科工业树莓派的快捷工具--Teamviewer
基于frida-inject脚本持久化开发实战分享
苹果或先推出支持6GHz以下频段的5G iPhone
PathWave制造业分析软体开启工业4.0数字转型之旅