第一篇文档主要说明一些后边用到的概念,第二篇是怎么做的具体道路。我个人是这么做的,先了解概念,才去做的。如果这跟您的做事方式不太一致,你可以先调至(二),了解怎么做,再了解概念,或者交叉进行。
我们从例子,引发出具体的概念。
理发店共有3名理发师(处理器同时处理事务数)处理到店理发的用户,理发师给每个用户理发的时间为1小时(处理时间),店里有三个等待的理发的椅子(请求缓存),客人在理发店呆的时间超过3个小时就会无法忍受离开(超时时间)。
在这个场景里,随着用户增大(负载),1.资源利用率逐渐增加,到3名客户后维持不变。2.系统吞吐量逐渐增加,到达3个客户后增加的速度变小,到了9个客户左右开始下降。3.响应时间在0~3个客户之间保持稳定,超过3个客户之后逐渐增加,增长速度也逐渐增加,超过9个客户之后接近无穷大。
资源利用率和相应时间随着负载的变化曲线比较好理解,吞吐量在这里表示系统成功执行的请求数,吞吐量在超过9个客户下降的主要原因是因为系统超负荷运算,多余的上线文切换,低速IO的访问,或如STM32的CAN超负荷报文下,多余的中断响应,ARM模式的切换,客观上将处理时间(1小时)拉长了,所以会导致吞吐量下降。因为用“并发用户数”这个词容易引起误会,尤其是在现在多核的处理器已经普及。我将并发改为单位时间的请求数。
对一个确定的被测系统(无论是从整体,业务软件+硬件来说,还是硬件本身)来说,在一个特定的环境中,他的"最佳单位时间请求数"以及“最大单位时间请求数”都是客观存在的,我们做的系统,应该确保系统的“最大单位时间请求数”大于系统需要承受的峰值负载,并且在系统的日常使用过程中,负载处在“最佳单位时间请求数”左右。
CAN总线上任意两个节点之间的最大传输距离,决定因素有两个方面,一个是互连总线节点的回路延时(由CAN总线控制器和驱动器等引入)+总线线路延时。另一个是由于总线电缆串联等效电阻和总线节点的输入电阻而导致的信号幅度下降。
《ISO 11898-1》把一个位时间分为四段。
Sync_Seg(同步段)、Phase_Seg1(相位缓冲段1)、Phase_Seg2(相位缓冲段2):CAN控制器在每一个CAN帧的帧起始做硬同步,将总线的跳变沿定位到这个段内,在传输过程中,因为CAN帧使用位填充的方式进行编码,总线上不会连续出现6个相同的位电平,CAN控制通过预设的SJW调整位宽长度进行软同步,如果节点出现正相位差,则增加Phase_Seg1段长度,当节点产生负的相位差,则减少Phase_Seg2段长度,确保总线上的跳变沿处在同步段。
Prop_Seg(传输段):一般的CAN控制器都将传输段和相位缓冲段1合并为一个时间段供用户配置(诸如STM32的bxCAN)。传输段用于抵消数据由CAN控制器发出,到总线上另一个CAN控制器的时间延时。
Sample Point(采样点):CAN控制器在这个时间点判断总线当前位是显性的还是隐性的。
CAN系统之所以对信号传输延时非常敏感,主要来源于节点之间竞争访问网络时的非破坏性仲裁以及帧内应答。也就是说,一个节点A控制总线由显性转换为隐性时(ACK应答间隙),另一个节点B在采样到总线为隐性的时候,需要控制总线为显性,而节点A能够正常采样到总线为显性。设tprop(a,b)为由A节点的CAN控制到B节点控制器的通信延时。
以收发器选择TJA1044(循环延时为210ns),波特率为500K,采样点为位时间的80%的为例,我们先将电信号在线缆上的传输速度暂定为0.23m/ns。可以计算出总线的长度为(((1 * 109/5 * 105)* 80% - 2 * 210)*0.23)/2= 135.7m。
计算的原理是(硬同步到采样点所持续的时间-2倍的循环延时时间)/2倍的信号在总线上传输的速度。这里电信号在线缆的传输的速度,是跟实际选择的线缆有关,当然,线缆以及节点数的接入,产生的容抗和感抗都会在不同的侧面影响这个“速度”,从而对长度产生影响
我看网上还有专门测总线传输延时和节点的容抗和阻抗的。其实把握了基本原理底线,再测试一些自己开发设备在几种典型应用的性能,就能得针对现场的实际情况得出一个性价比较高的方案。
CAN总线降低了通信频率之后,限制总线长度的就不是线上的延时了,而是输入差动电压。因为我做的应用对速率有硬性要求,这部分这篇文章就点到计算公式上。
公式一:Vdiff.in.req=Vth+Ksm*(Vdiff.out-Vth)
其中Vdiff.in.req为收发器显性电平采样需要的压差,Vth为接收差分阈值如下图>0.9v,KSM为决定安全电压的差分系数,Vdiff.out为收发器输出的差动电压。
公式二:Vdiff.in=Vdiff.out/(2RW(1/RT+(n-1)/Rdiff)+1)
其中Vdiff.in为差分输入电压,Vdiff.out为差分输出电压,RW为总线电缆电阻,RT为终端电阻,Rdiff为CAN节点差分电阻。n为总线终端数。
公式三:RWmax=Pmax*Lmax
其中RWmax为最大线阻。Pmax最大电阻率,Lmax总线最大长度。
将公式三和公式二代入公式一,即可得到总线最大长度。
可以看出,在差分电压在总线上压降为决定因素的情况下(速率低,通信距离长),总线最大长度与节点数和终点电阻以及导线的电阻率成反比。
单纯谈CAN总线的节点数量是由收发器的可驱动最小负载阻抗决定。
最大节点数量节点数量计算公式(假设总线电阻RW为0,此时为最坏情况):n < 1+Riff_min*(1/Rl_min-2/Rt_min)
下面举一个例子来计算一下最大的带节点数量:
收发器选型TJA1044:
Riff_min(差动输入阻抗最小值):19K欧姆
Rl_min(可驱动最小负载阻抗):45欧姆
终端匹配电阻阻值:
Rt_min(特征阻抗):120欧姆
最终得值:107个终端。
107个终端的计算值可能比在实际环境下的带机量要小一些,如果注意can节点之间的距离,线缆的质量,支线长度等因素,可以做到120个左右的稳定使用。
因为这块是CAN通信出现问题的多发原因,我尽量的多说一些,但是我会避开地弹、眼图这些概念。我们都知道CAN通信是差分信号,但是我先从单端的信号讲起,什么是振铃(过冲)。我们从一个测试电路入手,A点为电压输出端口,B点是为了接入电阻切的口,C点为同轴电压测试点。
在B点处用导线相连,C点引同轴线到示波器(内阻1M),A点接入电源,能够看到示波器上升沿有过冲现象。
如果信号在传输过程中感受到阻抗的变化,就会发生信号的反射。根据反射系数公式,当信号感受到阻抗变小之后,就会发生负反射,反射的负电压就会使信号产生下冲,反之像刚才的例子,信号遇到了开路,阻抗突然变成了无穷大,就会发生过冲。
而信号在驱动端和远端负载之间多次反射,其结果就是信号振铃。信号振铃的过程可以用反弹图来直观解释。假设驱动端的输出阻抗是10欧姆,PCB的特性阻抗是50欧姆(可以通过改变PCB走线走线宽度,走线方式,以及内层参考平面间介质厚度来调整),远端开路(远端阻抗无穷大),驱动端产生传输信号3.3v,我们忽略寄生电容电感的影响,只考虑阻性负载,我们跟着信号在这条传输线上跑一次,看看到底发生了什么。
第一反射:信号从驱动端芯片出发,经过10欧姆的输出阻抗和PCB走线50欧姆的特性阻抗分压,实际加到PCB走线源端A点的电压为2.75V。信号传输到远端B点,由于遇到开路,阻抗无穷大,反射系数为1,信号全部反射,反射信号也为2.75v,则此时B点的电压是2.75+2.75=5.5v。
第二次反射:传输线上抬高的2.75v反射电压传输回A点,阻抗由50欧姆变为10欧姆,发生负反射,根据阻抗变化的关系,A点反射的电压为-1.83v,该电压再次到达B点,再次发生反射,反射电压为-1.83v。则此时B点测量的电压值为5.5v-1.83-1.83=1.84V。
第三次反射…第四次反射。反射电压在A点与B电来回回弹,直至消失。如下图,线上的电压标值为信号(压差)值。
观察B点测量的电压,我们期望的是一个稳定的3.3输出,但是得到的却是5.5v----1.84v----4.28v。在示波器上显示的信号传输波形就如振铃一样。
解决这种单端信号过冲的一般方法叫做匹配,或者叫做端接(termination).端接可以总结为两种形式:第一种是源端串联电阻,与传输线做阻抗匹配,消除第二次反射。
第二种是终端并联匹配消除第一次反射。
在实际的电路中,我们要根据电路不同的特点来选择合适的端接方式,具体可以可以从驱动DDR的电路入手,参考高速PCB理论。
单端的信号就介绍这么多,说差分信号。
差分信号的干扰从两个方面理解:1.共模干扰(Common-mode):两导线上振幅相等,而方向相同称为共模干扰,它串入的回路是信号线和地线之间。2.差模干扰(Differential-mode):两导线上干扰电流,振幅相等,方向相反。他串入的回路是两个差分信号线之间。
差模信号采用跨接电阻来分别匹配,消除信号的反射影响,CAN线如果不接终端电阻,振铃的现象会非常明显的用示波器量出,CAN总线的通信质量下降。具体的信号反弹图如上边的单端信号。如果加了终端电阻,信号的CANH和CANL的信号反弹会相互抵消,差模信号的振铃效应会消失。实际的电压如下图。
但是,这种端接方式对共模信号产生问题。CANH与CANL的地同时向上抬1v会产生振铃效应直至衰减完毕,不过信号共模信号的干扰产生的是同相同幅的的干扰,对我们差分信号的数据识别影响不大,而且CAN线上走的也不是有周期的时钟信号。但是共模信号带来的振铃效应电压幅值过大,还有有几率带来can位数据丢失同步,导致CAN错误计数累加的问题。
有些文章介绍过can信号下降沿因为总线间电容斜率变大,终端电阻有帮助放电,提高斜率的作用,我做的测试没有测出来,就不赘述了。
这里强调一下不管是跨接终端电阻做匹配,还是分别并联匹配。只要是CAN总线,无论总线长短,带机数量,都要做这个匹配,CAN总线对接收器的定义与其余的现场总线不同。
讲了这么多跟软件没有关系的概念,这个一级标题的内容我要告诉你怎么简单处理CAN总线上的拆包问题。
CAN协议的帧格式规定了一帧CAN协议中包含8字节64位数据。如果你的系统应用层的协议的数据量小于一个CAN帧中的数据量,那么恭喜你,可以不用看这部分了。最近CIA的网站介绍了一种CAN FD的实现,波特率最高支持到5Mbit/s,一帧包含的有效数据能达到64字节512位的数据。
粘包和半包的产生原因,是因为链路层(MAC芯片,CAN控制器)将应用层的下发的业务数据做分片处理(CAN分割成每8字节一个数据帧),那么势必会引起数据传输到对端的基本数据单位是分片的长度,那么就会出现收到的数据不是一整包的数据(半包),这个比较好理解。粘包是因为数据传输延时等原因,两整包的数据一起收到。
发送部分的逻辑:将发送的整包数据的源地址和目标地址以及这个数据在目标设备的缓存起始地址(这个地址表示的宽度是8字节数据)写入要发送帧的标识符(使用扩展帧)。将分片的数据写入帧的数据区,然后发送。因为这里我们先将拆包,所以这部分逻辑我先点到这个局部,下一个文章我再细讲发送部分如何将并行转成串行,以及总线时间片的测量及实现。
接收部分逻辑:先说存储数据的核心的数据结构,因为数据的缓存是随机存储,且单位数据长度是固定的,这里就选择最单纯的char类型的二维数组S,列数指定为8字节,行数根据实际的平台的限制,选择合适的帧缓存数据最大值(256)。数据结构A不仅包含S,还包括两个char类型指针,一个指向待处理数据头,一个指向待处理数据尾。然后根据根据实际的需要以及处理器的RAM剩余情况,选择合适的支持节点数(64个终端),数据结构B是我们拆包使用的数据结构,由支持节点数个数的数据结构A,和一个表示数据结构A有待处理项的Int值。
CAN接收中断函数先判断从标识符中取出目标地址,判断合法之后将CAN数据放到源地址对应的数据结构B.A[源地址].S[行地址]中,,并移动待处理数据为尾指针。这块处理需要注意两点。
1.放数据的时候根据帧中实际数据长度,结合尾指针位置以及标识符中指定行地址综合判断,决定放到内存8字节单位空间的那个字节位置,保证针对每个终端的内存地址空间从字节流的角度上看是连续的。
2.遇到不连续的写入(尾指针在行地址2,写入数据的行地址却是10),要根据包长度来判断合法性。
至此,我们有了总线上不同源地址对这个目标地址的发送数据的缓存,下面就是拆包了。
拆包因协议的特征而定拆包的逻辑,Netty 中就有针对不同特点的拆包器,比如固定长度的拆包器 FixedLengthFrameDecoder,分隔符拆包器 DelimiterBasedFrameDecoder以及基于数据包长度的拆包器 LengthFieldBasedFrameDecoder。
因为我使用的协议是有版本号和包长度的,跟 LengthFieldBasedFrameDecoder有点像,但是CAN通信要面对的是百分之百的分包,数据帧的乱序,以及一定概率的误码,且处理器性能以及资源有限。所以就复杂一些。拿两个点来具体说明一下:
1.如果有数据就直接进行拆包的处理验证,会引起冲突/MCU空转的问题,甚至有一定概率引起RAM混乱的问题,这时就需要测量一下总线一个完整协议传输的时间,我这边最长的协议时间为3ms,所以这边就在自接收到第一帧数据之后3ms,再进行分包处理。
2.因为是百分之百的分包,所以数据帧的乱序概率我测试下来比实际想象的要高,所以就需要一个兜底的PlanB去将杂讯中的有效数据进行拆包,将会在这样不会因为总线在出现误码/乱序的情况下,影响后续拆包的操作。(当然,这部分可以采用定期清缓存来保证,但是,我们要的是即刻的“恢复“,即PlanB能保证不丢有效帧,定期清缓存只是一种维护手段)。
具体的逻辑我如下图流程图