37. CAN——双机通信实验

本章可参考资料:瑞萨的 《CAN入门书 Rev.1.00 2006.02 》 ,这本手册对传统 CAN 进行了详细介绍。

37.1. CAN协议简介

CAN是控制器局域网络(Controller Area Network)的简称,它是由研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO11519),是国际上应用最广泛的现场总线之一。

CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。近年来, 它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。

37.1.1. CAN物理层

与I2C、SPI等具有时钟信号的同步通讯方式不同,CAN通讯并不是以时钟信号来进行同步的,它是一种异步通讯,只具有CAN_High和CAN_Low两条信号线, 共同构成一组差分信号线,以差分信号的形式进行通讯。

37.1.1.1. 闭环总线网络

CAN物理层的形式主要有两种,图 CAN闭环总线通讯网络 中的CAN通讯网络是一种遵循ISO11898标准的高速、 短距离“闭环网络”,它的总线最大长度为40m,通信速度最高为1Mbps,总线的两端各要求有一个“120欧”的电阻。

CAN闭环总线通讯网络

图1 CAN闭环总线通讯网络

37.1.1.2. 开环总线网络

CAN开环总线通讯网络 中的是遵循ISO11519-2标准的低速、远距离“开环网络”,它的最大传输距离为1km, 最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。

CAN开环总线通讯网络

图2 CAN开环总线通讯网络

37.1.1.3. 通讯节点

从CAN通讯网络图可了解到,CAN总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。由于CAN通讯协议不对节点进行地址编码, 而是对数据内容进行编码的,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。

CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连。 其中CAN_Tx及CAN_Rx使用普通的类似TTL逻辑信号,而CAN_High及CAN_Low是一对差分信号线,使用比较特别的差分信号,下一小节再详细说明。

当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器,然后由收发器把这个普通的逻辑电平信号转化成差分信号, 通过差分线CAN_High和CAN_Low线输出到CAN总线网络。而通过收发器接收总线上的数据到控制器时,则是相反的过程, 收发器把总线上收到的CAN_High及CAN_Low信号转化成普通的逻辑电平信号,通过CAN_Rx输出到控制器中。

例如,启明RA系列开发板的CAN片上外设就是通讯节点中的控制器,为了构成完整的节点,还要给它外接一个收发器,在我们实验板中使用型号为TJA1050的芯片作为CAN收发器。 CAN控制器与CAN收发器的关系如同TTL串口与MAX3232电平转换芯片的关系,MAX3232芯片把TTL电平的串口信号转换成RS-232电平的串口信号, CAN收发器的作用则是把CAN控制器的TTL电平信号转换成差分信号(或者相反)。

37.1.1.4. 差分信号

差分信号又称差模信号,与传统使用单根信号线电压表示逻辑的方式有区别,使用差分信号传输时,需要两根信号线,这两个信号线的振幅相等, 相位相反,通过两根信号线的电压差值来表示逻辑0和逻辑1。见图 差分信号 ,它使用了V+与V-信号的差值表达出了图下方的信号。

差分信号

图3 差分信号

相对于单信号线传输的方式,使用差分信号传输具有如下优点:

  • 抗干扰能力强,当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。

  • 能有效抑制它对外部的电磁干扰,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。

  • 时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小, 能降低时序上的误差,同时也更适合于低幅度信号的电路。

由于差分信号线具有这些优点,所以在USB协议、485协议、以太网协议及CAN协议的物理层中,都使用了差分信号传输。

37.1.1.5. CAN协议中的差分信号

CAN协议中对它使用的CAN_High及CAN_Low表示的差分信号做了规定,见表 CAN协议标准表示的信号逻辑 及图 CAN的差分信号高速。 以高速CAN协议为例,当表示逻辑1时(隐性电平),CAN_High和CAN_Low线上的电压均为2.5v, 即它们的电压差VH-VL=0V;而表示逻辑0时(显性电平), CAN_High的电平为3.5V,CAN_Low线的电平为1.5V, 即它们的电压差为VH-VL=2V。例如,当CAN收发器从CAN_Tx线接收到来自CAN控制器的低电平信号时(逻辑0), 它会使CAN_High输出3.5V,同时CAN_Low输出1.5V,从而输出显性电平表示逻辑0。

CAN协议标准表示的信号逻辑

表1 CAN协议标准表示的信号逻辑

CAN的差分信号高速

图4 CAN的差分信号高速

在CAN总线中,必须使它处于隐性电平(逻辑1)或显性电平(逻辑0)中的其中一个状态。假如有两个CAN通讯节点,在同一时间,一个输出隐性电平, 另一个输出显性电平,类似I2C总线的“线与”特性将使它处于显性电平状态,显性电平的名字就是这样来的,即可以认为显性具有优先的意味。

由于CAN总线协议的物理层只有1对差分线,在一个时刻只能表示一个信号,所以对通讯节点来说,CAN通讯是半双工的,收发数据需要分时进行。 在CAN的通讯网络中,因为共用总线,在整个网络中同一时刻只能有一个通讯节点发送信号,其余的节点在该时刻都只能接收。

37.1.2. 协议层

以上是CAN的物理层标准,约定了电气特性,以下介绍的协议层则规定了通讯逻辑。

37.1.2.1. CAN的波特率及位同步

由于CAN属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的波特率进行通讯, 特别地,CAN还会使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。

37.1.2.1.1. 位时序分解

为了实现位同步,CAN协议把每一个数据位的时序分解成如图 CAN位时序分解图 所示的SS段、 PTS段、PBS1段、PBS2段,这四段的长度加起来即为一个CAN数据位的长度。分解后最小的时间单位是Tq,而一个完整的位由8~25个Tq组成。 为方便表示,图 CAN位时序分解图 中的高低电平直接代表信号逻辑0或逻辑1(不是差分信号)。

CAN位时序分解图

图5 CAN位时序分解图

该图中表示的CAN通讯信号每一个数据位的长度为19Tq,其中SS段占1Tq,PTS段占6Tq,PBS1段占5Tq,PBS2段占7Tq。 信号的采样点位于PBS1段与PBS2段之间,通过控制各段的长度,可以对采样点的位置进行偏移,以便准确地采样。

各段的作用如介绍下:

  • SS段(SYNC SEG)

SS译为同步段,若通讯节点检测到总线上信号的跳变沿被包含在SS段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。SS段的大小固定为1Tq。

  • PTS段(PROP SEG)

PTS译为传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的两倍。PTS段的大小可以为1~8Tq。

  • PBS1段(PHASE SEG1),

PBS1译为相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。PBS1段的初始大小可以为1~8Tq。

  • PBS2段(PHASE SEG2)

PBS2这是另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2段的初始大小可以为2~8Tq。

37.1.2.1.2. 通讯的波特率

总线上的各个通讯节点只要约定好1个Tq的时间长度以及每一个数据位占据多少个Tq,就可以确定CAN通讯的波特率。

例如,假设上图中的1Tq=1us,而每个数据位由19个Tq组成, 则传输一位数据需要时间T1bit =19us,从而每秒可以传输的数据位个数为:

1x106­/19 = 52631.6 (bps)

这个每秒可传输的数据位的个数即为通讯中的波特率。

37.1.2.1.3. 同步过程分析

波特率只是约定了每个数据位的长度,数据同步还涉及到相位的细节,这个时候就需要用到数据位内的SS、PTS、PBS1及PBS2段了。

根据对段的应用方式差异,CAN的数据同步分为硬同步和重新同步。其中硬同步只是当存在“帧起始信号”时起作用,无法确保后续一连串的位时序都是同步的, 而重新同步方式可解决该问题,这两种方式具体介绍如下:

(1) 硬同步

若某个CAN节点通过总线发送数据时,它会发送一个表示通讯起始的信号(即下一小节介绍的帧起始信号),该信号是一个由高变低的下降沿。 而挂载到CAN总线上的通讯节点在不发送数据时,会时刻检测总线上的信号。

见图 硬同步过程图 ,可以看到当总线出现帧起始信号时, 某节点检测到总线的帧起始信号不在节点内部时序的SS段范围,所以判断它自己的内部时序与总线不同步,因而这个状态的采样点采集得的数据是不正确的。 所以节点以硬同步的方式调整,把自己的位时序中的SS段平移至总线出现下降沿的部分,获得同步,同步后采样点就可以采集得正确数据了。

硬同步过程图

图6 硬同步过程图

(2) 重新同步

前面的硬同步只是当存在帧起始信号时才起作用,如果在一帧很长的数据内,节点信号与总线信号相位有偏移时,这种同步方式就无能为力了。 因而需要引入重新同步方式,它利用普通数据位的高至低电平的跳变沿来同步(帧起始信号是特殊的跳变沿)。重新同步与硬同步方式相似的地方是它们都使用SS段来进行检测,同步的目的都是使 节点内的SS段把跳变沿包含起来。

重新同步的方式分为超前和滞后两种情况,以总线跳变沿与SS段的相对位置进行区分。第一种相位超前的情况如图 相位超前时的重新同步 , 节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前2Tq,这时控制器在下一个位时序中的PBS1段增加2Tq的时间长度,使得节点与总线时序重新同步。

相位超前时的重新同步

图7 相位超前时的重新同步

第二种相位滞后的情况如图 相位滞后时的重新同步 ,节点从总线的边沿跳变中, 检测到它的时序比总线的时序相对滞后2Tq,这时控制器在前一个位时序中的PBS2段减少2Tq的时间长度,获得同步。

相位滞后时的重新同步

图8 相位滞后时的重新同步

在重新同步的时候, PBS1和PBS2中增加或减少的这段时间长度被定义为“重新同步补偿宽度SJW (reSynchronization Jump Width)”。 一般来说CAN控制器会限定SJW的最大值,如限定了最大SJW=3Tq时,单次同步调整的时候不能增加或减少超过3Tq的时间长度,若有需要, 控制器会通过多次小幅度调整来实现同步。当控制器设置的SJW极限值较大时,可以吸收的误差加大,但通讯的速度会下降。

37.1.2.1.4. CAN的报文种类及结构

在SPI通讯中,片选、时钟信号、数据输入及数据输出这4个信号都有单独的信号线,I2C协议包含有时钟信号及数据信号2条信号线,异步串口包含接收与发送2条信号线, 这些协议包含的信号都比CAN协议要丰富,它们能轻易进行数据同步或区分数据传输方向。而CAN使用的是两条差分信号线,只能表达一个信号, 简洁的物理层决定了CAN必然要配上一套更复杂的协议,如何用一个信号通道实现同样、甚至更强大的功能呢?CAN协议给出的解决方案是对数据、 操作命令(如读/写)以及同步信号进行打包,打包后的这些内容称为报文。

37.1.2.1.5. 报文的种类

在原始数据段的前面加上传输起始标签、片选(识别)标签和控制标签,在数据的尾段加上CRC校验标签、应答标签和传输结束标签,把这些内容按特定的格式打包好, 就可以用一个通道表达各种信号了,各种各样的标签就如同SPI中各种通道上的信号,起到了协同传输的作用。当整个数据包被传输到其它设备时, 只要这些设备按格式去解读,就能还原出原始数据,这样的报文就被称为CAN的“数据帧”。

为了更有效地控制通讯,CAN一共规定了5种类型的帧, 它们的类型及用途说明如表 帧的种类及其用途

帧的种类及其用途

表2 帧的种类及其用途

37.1.2.2. 数据帧

数据帧是在CAN通讯中最主要、最复杂的报文,我们来了解它的结构,见图 数据帧的结构

数据帧的结构

图9 数据帧的结构

数据帧以一个显性位(逻辑0)开始,以7个连续的隐性位(逻辑1)结束,在它们之间,分别有仲裁段、控制段、数据段、CRC段和ACK段。

  • 帧起始

SOF段(Start Of Frame),译为帧起始,帧起始信号只有一个数据位,是一个显性电平, 它用于通知各个节点将有数据传输,其它节点通过帧起始信号的电平跳变沿来进行硬同步。 见下图:

数据帧的帧起始

图10 数据帧的帧起始

  • 仲裁段

当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输,这也是它名称的由来。 见下图:

数据帧的仲裁段

图11 数据帧的仲裁段

仲裁段的内容主要为本数据帧的ID信息(标识符),数据帧具有标准格式和扩展格式两种,区别就在于ID信息的长度,标准格式的ID为11位,扩展格式的ID为29位, 它在标准ID的基础上多出18位。在CAN协议中,ID起着重要的作用,它决定着数据帧发送的优先级,也决定着其它节点是否会接收这个数据帧。 CAN协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由信息的重要性决定的,即对于重要的信息,我们会给它打包上一个优先级高的ID, 使它能够及时地发送出去。也正因为它这样的优先级分配原则,使得CAN的扩展性大大加强,在总线上增加或减少节点并不影响其它设备。

报文的优先级,是通过对ID的仲裁来确定的。根据前面对物理层的分析我们知道如果总线上同时出现显性电平和隐性电平,总线的状态会被置为显性电平,CAN正是利用这个特性进行仲裁。

若两个节点同时竞争CAN总线的占有权,当它们发送报文时,若首先出现隐性电平,则会失去对总线的占有权,进入接收状态。 见图 仲裁过程 ,在开始阶段,两个设备发送的电平一样, 所以它们一直继续发送数据。到了图中箭头所指的时序处,节点单元1发送的为隐性电平,而此时节点单元2发送的为显性电平, 由于总线的“线与”特性使它表达出显示电平,因此单元2竞争总线成功,这个报文得以被继续发送出去。

仲裁过程

图12 仲裁过程

仲裁段ID的优先级也影响着接收设备对报文的反应。因为在CAN总线上数据是以广播的形式发送的,所有连接在CAN总线的节点都会收到所有其它节点发出的有效数据, 因而我们的CAN控制器大多具有根据ID过滤报文的功能,它可以控制自己只接收某些ID的报文。

回看图 数据帧的结构 中的仲裁段,可看到仲裁段除了报文ID外,还有RTR、IDE和SRR位。

(1) RTR位(Remote Transmission Request Bit),译作远程传输请求位, 它是用于区分数据帧和遥控帧的,当它为显性电平时表示数据帧,隐性电平时表示遥控帧。

(2) IDE位(Identifier Extension Bit),译作标识符扩展位, 它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式。

(3) SRR位(Substitute Remote Request Bit),只存在于扩展格式,它用于替代标准格式中的RTR位。 由于扩展帧中的SRR位为隐性位,RTR在数据帧为显性位,所以在两个ID相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。

  • 控制段

在控制段中的r1和r0为保留位,默认设置为显性位。它最主要的是DLC段(Data Length Code),译为数据长度码, 它由4个数据位组成,用于表示本报文中的数据段含有多少个字节,DLC段表示的数字为0~8。 见下图:

控制段

图 13 控制段

数据长度码和字节数的关系:

数据字节数

DLC3

DLC2

DLC1

DLC0

0

D

D

D

D

1

D

D

D

R

2

D

D

R

D

3

D

D

R

R

4

D

R

D

D

5

D

R

D

R

6

D

R

R

D

7

D

R

R

R

8

R

D

D

D

  • 数据段

数据段为数据帧的核心内容,它是节点要发送的原始信息,由0~8个字节组成,MSB先行。 见下图:

数据段

图 14 数据段

  • CRC段

为了保证报文的正确传输,CAN的报文包含了一段15位的CRC校验码,一旦接收节点算出的CRC码跟接收到的CRC码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC部分的计算一般由CAN控制器硬件完成,出错时的处理则由软件控制最大重发数。

在CRC校验码之后,有一个CRC界定符,它为隐性位,主要作用是把CRC校验码与后面的ACK段间隔起来。 见下图:

CRC段

图15 CRC段

  • ACK段

ACK段包括一个ACK槽位,和ACK界定符位。类似I2C总线,在ACK槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在ACK槽和帧结束之间由ACK界定符间隔开。 见下图:

ACK段

图16 ACK段

  • 帧结束段

EOF段(End Of Frame),译为帧结束,帧结束段由发送节点发送的7个隐性位表示结束。 见下图:

帧结束段

图17 帧结束段

37.1.2.3. 遥控帧

接收单元向发送单元请求发送数据所用的帧。遥控帧由 6 个段组成。遥控帧没有数据帧的数据段。

  1. 帧起始(SOF):表示帧开始的段。

  2. 仲裁段:表示该帧优先级的段。可请求具有相同 ID 的数据帧。

  3. 控制段:表示数据的字节数及保留位的段。

  4. CRC 段:检查帧的传输错误的段。

  5. ACK 段:表示确认正常接收的段。

  6. 帧结束:表示遥控帧结束的段。

图

图18 遥控帧

遥控帧和数据帧区别?

  • 遥控帧的 RTR 位为隐性位,没有数据段。

  • 没有数据段的数据帧和遥控帧可通过 RTR 位区别开来。

遥控帧没有数据段,数据长度码该如何表示?

  • 遥控帧的数据长度码以所请求数据帧的数据长度码表示。

遥控帧没有数据段,有何用途?

  • 例如,可用于各单元的定期连接确认/应答、或仲裁段本身带有实质性信息的情况。

37.1.2.4. 错误帧

用于在接收和发送消息时检测出错误通知错误的帧。错误帧由错误标志和错误界定符构成。

  1. 错误标志:错误标志包括主动错误标志和被动错误标志两种。

    • 主动错误标志:处于主动错误状态的单元检测出错误时输出的错误标志。6 个位的显性位。

    • 被动错误标志:处于被动错误状态的单元检测出错误时输出的错误标志。6 个位的隐性位。

  2. 错误界定符:错误界定符由 8 个位的隐性位构成。

图

图19 错误帧

37.1.2.5. 过载帧

过载帧是用于接收单元通知其尚未完成接收准备的帧。过载帧由过载标志和过载界定符构成。

  1. 过载标志:6 个位的显性位。

    过载标志的构成与主动错误标志的构成相同。

  2. 过载界定符:8 个位的隐性位。

    过载界定符的构成与错误界定符的构成相同。

图

图20 过载帧

37.1.2.6. 过载帧

帧间隔是用于分隔数据帧和遥控帧的帧。 数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧)分开。

过载帧和错误帧前不能插入帧间隔。

  1. 间隔:3 个位的隐性位。

  2. 总线空闲:隐性电平,无长度限制(0 既可)。

    本状态下,可视为总线空闲,要发送的单元可开始访问总线。

  3. 延迟传送(发送暂时停止):8 个位的隐性位。

    只在处于被动错误状态的单元刚发送一个消息后的帧间隔中包含的段。

图

图21 过载帧

37.2. CAN收发器:TJA1042

TJA1042是一款高速CAN收发器,可在控制器局域网(CAN)协议控制器和物理双线式CAN总线之间提供接口。该收发器专为汽车行业的高速CAN应用而打造,可以为(微控制器中的)CAN协议控制器提供发送和接收差分信号的功能。

TJA1042属于恩智浦半导体的第三代高速CAN收发器,相比第一代和第二代器件(如TJA1040),有明显的改进。它提升了电磁兼容性(EMC),并提高了静电放电(ESD)性能,同时还提供以下特性:

  • 断开电源时CAN总线具有理想的无源性能

  • 电流消耗极低的待机模式,具有总线唤醒能力

  • TJA1042T/3和TJA1042TK/3可直接连接供电电压为3V至5V的微控制器

TJA1042实现了当前ISO11898标准(ISO11898-2:2003, ISO11898-5:2007)以及ISO 11898-2:2016即将发布的更新版本中定义的CAN物理层。包括CAN FD和SAE J2284-4/5的ISO11898-2:2016更新版本即将发布,规定了用于定义循环延迟对称性的新增时序参数。在CAN FD快速相位下,即使数据速率高达5Mbit/s,此实施也能实现可靠的通信。

TJA1042提供这些强大的功能,是所有类型HS-CAN网络的极佳选择,适用于要求具备低功耗模式并能通过CAN总线唤醒的节点。

通用特性
  • 完全符合ISO 11898-2:2003和ISO 11898-5:2007标准

  • 在CAN FD快速相位中,时序保证数据速率高达5Mbit/s

  • 适用于12V和24V系统

  • 低电磁辐射(EME)和高抗电磁干扰(EMI)

  • TJA1042T/3和TJA1042TK/3上的VIO输入允许直接连接到3 V至5 V微控制器

  • TJA1042T上的SPLIT电压输出用于稳定隐性总线电平

  • 提供SO8封装和无引脚HVSON8封装(3.0mm×3.0mm),提升了自动光学检测(AOI)能力

  • “深绿色”产品(无卤且符合有害物质限制(RoHS)指令)

  • 获得AEC-Q100认证

可预测和故障安全行为
  • 极低电流待机模式,并具有主机和总线唤醒功能

  • 在所有电源条件下,功能行为均可预测

  • 收发器会在断电(零负载)时脱离总线

  • 发送数据(TXD)主导的超时功能

  • 待机模式下总线主导的超时功能

  • 引脚VCC和VIO提供欠压检测

保护
  • 总线引脚具有较高的ESD处理能力(±8kV)

  • CAN引脚的高压稳健性(±58V)

  • 在汽车应用环境下总线引脚具有瞬态保护

  • 过热保护功能

37.3. CAN 模块框图分析

如下图所示,为 RA4M2 芯片 CAN 外设模块的结构框图:

图

图22 CAN 外设模块的结构框图

37.3.1. CAN通道和CAN时钟

见图中标注 ① 处。

瑞萨 RA 芯片外部可以通过引脚 CTX 和 RTX 连接到外围 CAN 收发器。

PCLKB 和 CANMCLK 为 CAN-FD 模块的输入时钟,通过CCLKs选择其一输入, 经过波特率预分频器(Baud rate prescaler)进行分频后输入到 CAN 协议控制器(Protocol controller)。 该输入时钟对于计算 CAN 波特率非常重要。

协议控制器:处理CAN协议处理,例如总线仲裁、发送和接收期间的位时序、填充和错误处理

37.3.2. CAN相关寄存器

见图中标注 ② 处。

这部分属于 CAN 模块的寄存器,用于配置该模块丰富且强大的功能。 由于过于复杂,且对于用户来说一般没有必要深入了解,因此这里不对其进行过多地讲解了。

37.3.3. 接收过滤器和邮箱

见图中标注 ③ 处。

接收过滤器(Acceptance filter)和ID优先传输控制器:用于实现 CAN 的接收过滤功能。 而对于使用 CAN 来说,接收过滤功能至关重要,使用 CAN 就绕不过 CAN 外设模块中的接收过滤器。

Mailboxes:包含32个邮箱,可配置为发送或接收。每个邮箱都有一个单独的ID、数据长度代码(DLC)、数据字段(8个 字节) 和一个时间戳。

Timer:用于时间戳功能。将消息存储在邮箱中时的计时器值作为时间戳值写入。

37.3.4. 中断信号

见图中标注 ④ 处。

中断生成器(Interrupt generator)用于生成 CAN 相关的中断信号,包含如下信号:

  • CANi接受完成中断

  • CANi传输完成中断

  • CANi接收FIFO中断

  • CANi发送FIFO中断

  • CANi错误中断

37.4. CAN测试模式

CAN模块可以配置为测试模式以允许测试某些功能。

启明RA4M2/RA2L1的CAN外设有3种测试模式。如下: - 监听模式 - 自测模式0(外部环回) - 自测模式1(内部环回)

37.4.1. 监听模式

ISO11898-1 推荐一种可选的总线监控模式。 在此模式下,CAN 通道能够接收有效的数据帧和有效的远程帧。 但是,它只在CAN总线上发送隐性位,不允许传输数据。

如果 CAN 引擎需要发送显性位(ACK位、过载标志、活动错误标志),则该位在内部路由, 以便 CAN 引擎将其监控为显性。外部TX引脚保持隐性状态。

该模式可用于波特率检测。在此模式下,如果发生总线错误并启用中断,则会产生错误中断。

在此模式下,不允许从该通道的任何正常 TX 消息缓冲区或 TX/GW FIFO 请求传输。

注意:

如果消息存储在 GWFIFO 或路由TXQ中,请确保发送通道不处于监听模式, 以便不会从 GW FIFO 或路由TXQ请求此通道的传输。

图

图23 监听模式

37.4.2. 自检模式0(外部环回模式)

在自检模式0中,CAN 引擎将自己发送的消息视为通过 CAN 收发器接收到的消息,并将它们存储到其接收消息缓冲区中。

为了独立于外部激励,引擎会生成自己的确认位。 此测试可用于 CAN 收发器测试,并且 RX/TX 引脚应连接到收发器。

图

图24 自检模式0

37.4.3. 自检模式1(内部环回模式)

在自检模式1中,CAN 引擎将自己发送的消息视为接收的消息,并将它们存储到接收缓冲区中。此模式用于自检功能。

为了独立于外部刺激,CAN 引擎生成自己的确认位。 在这种模式下,CAN 引擎执行从 TX 内部到 RX 内部的内部反馈。CAN 引擎忽略外部 RX 输入的实际值。

外部 TX 引脚仅输出隐性位。RX/TX 引脚不需要连接到 CAN 总线或任何外部设备。

图

图25 自检模式1

37.5. 实验:CAN 双机通信

本实验需要使用两个 CAN 硬件互相通信。

37.5.1. 硬件设计

野火启明 6M5 开发板的 CAN 硬件原理图如图所示:

图

图26 启明 6M5 CAN 硬件原理图

野火启明 4M2 开发板的 CAN 硬件原理图如图所示:

图

图27 启明 4M2 CAN 硬件原理图

野火启明 2L1 开发板的 CAN 硬件原理图如图所示:

图

图28 启明 2L1 CAN 硬件原理图

对于拥有两个板载 CAN 的启明6M5开发板,我们使用 CAN0 与 CAN1 互相连接; 而对于仅有一个板载 CAN 的板子(启明4M2和启明2L1开发板),我们使用需要使用两块板子并将两个 CAN 互相连接。 其中,CAN_H连接CAN_H,CAN_L连接CAN_L。

37.5.2. 软件设计

37.5.2.1. 新建工程

由于本实验需要用到串口打印提示信息, 因此我们在前面串口通信章节的“实验1:UART收发回显”例程的基础上修改程序。

对于 e2 studio 开发环境:

拷贝一份我们之前的 e2s 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “CAN_Receive_Send”,最后再将它导入到我们的 e2 studio 工作空间中。

对于 Keil 开发环境:

拷贝一份我们之前的 Keil 工程 “19_UART_Receive_Send”, 然后将工程文件夹重命名为 “CAN_Receive_Send”,并进入该文件夹里面双击 Keil 工程文件,打开该工程。

工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “can” 文件夹, 再进入 “can” 文件夹里面新建源文件和头文件:“bsp_can.c” 和 “bsp_can.h”。 然后在拷贝一份 12_GPIO_Key 工程目录下的 src/key 文件夹,粘贴到我们新建工程下的 “src” 文件夹下。 工程文件结构如下。

文件结构
CAN_Receive_Send
├─ ......
└─ src
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ can
   │  ├─ bsp_can.c
   │  └─ bsp_can.h
   ├─ key
   │  ├─ bsp_key.c
   │  └─ bsp_key.h
   └─ hal_entry.c

37.5.2.2. FSP配置

下面让我们一起来看看启明4M2/2L1开发板的FPS的配置方法(启明6M5开发板上板载的是CAN-FD,我们在下一章再进行讲解)

打开 FSP 配置页面:

配置 CAN 引脚

然后切换到 FSP 的引脚(“Pins”)配置页面,根据开发原理图上的对应引脚,配置 CAN 的引脚与按键引脚。

Key:

图

图29 Key的FSP配置

CAN:

图

图30 CAN的FSP配置

配置 CAN 模块

最后切换到 FSP 的模块堆叠(“Stacks”)配置页面配置 CAN 模块。

按照如下步骤添加 CAN 模块实例:

图

下面配置 CAN 模块的属性配置,注意更改中断优先级,防止与串口的中断优先级冲突,其他参数保持默认即可:

图

图31 CAN的FSP属性配置

CAN 模块配置属性描述:

CAN属性描述

属性

描述

Parameter Checking

参数检查(选择是否生成包含参数检查的代码)

FIFO Support

使能或禁用 FIFO

Name

模块名字

Channel

指定要使用的 CAN 通道

Clock Source

CAN 时钟源选择

Overwrite/Overrrun Mode

选择重写模式或重载模式

Global ID Mode

ID格式模式选择

  • 标准ID模式

  • 扩展ID模式

  • 混合ID模式

Number of MAilboxes

选择使用邮箱数目

Sample-Point(%)

自动设置:指定所需的采样点。

CAN Baud Rate (Hz)

自动设置:指定数据传输频率。

Override Baud Rate

选择是否覆盖自动波特率生成,使用此处手动指定的值。

Baud Rate Prescaler

手动设置预分频数据参数

Time Segment 1

设置帧时间段1

Time Segment 2

设置帧时间段2

Sync Jump Width

选择“同步跳转宽度”值(1-4)

Callback

用户的中断回调函数

如果提供了此回调函数,则每次发生任何中断时,

都会从中断服务函数(ISR)调用该函数。

Channel Interrupt Priority Level

通道错误/传输中断优先级

Transmit FIFO Interrupt Mode

选择接收FIFO是否应该对每个接收到的消息抛出中断,或者当它变为空时。

到这里就完成了需要在 FSP 界面进行的配置,点击配置界面右上角的按钮生成相应的代码。

37.5.2.3. CAN波特率计算

在上面的 FSP 配置中,我们使用的是自动波特率设置, 只需要指定标称比特率、数据比特率以及采样点这3个参数即可让软件自动计算和设置 CAN 的时序参数值。 如果需要手动配置 CAN 波特率,则需要根据模块输入时钟来计算所需的波特率及其时序参数。

波特率 = can_clock_hz / ((time_segment_1 + time_segment_2 + 1) * prescalar)。

例如,当TM1=3Ts,TM2=2Ts,prescalar 为4时,波特率为:

波特率 = 24MHz / ((3 + 2 + 1) * 4) = 1MHz

37.5.2.4. CAN初始化函数

CAN 初始化函数
/* CAN 初始化函数 */
void CAN_Init(void)
{
   fsp_err_t err = R_CAN_Open(&g_can0_ctrl, &g_can0_cfg);
   assert(FSP_SUCCESS == err);
}

37.5.2.5. CAN中断回调函数

CAN 中断回调函数
/* 要在回调函数中设置的标志 */
volatile bool can_tx_complete_flag = false;
volatile bool can_rx_complete_flag = false;
volatile bool can_err_status_flag = false;
volatile can_error_t can_err_status = (can_error_t) 0;


/* CAN 中断回调函数 */
void can_callback(can_callback_args_t * p_args)
{
   switch (p_args->event)
   {
      case CAN_EVENT_RX_COMPLETE: //接收完成中断
      {
            can_rx_complete_flag = true;    //can接收到数据

            /* 读取接收帧 */
            memcpy(&can_rx_frame, &(p_args->frame), sizeof(can_frame_t));

            break;
      }
      case CAN_EVENT_TX_COMPLETE: //传输完成中断
      {
            can_tx_complete_flag = true;    //can数据发送完成
            break;
      }
      case CAN_EVENT_ERR_BUS_OFF:          /* Bus error event. (bus off) */
      case CAN_EVENT_ERR_PASSIVE:          /* Bus error event. (error passive) */
      case CAN_EVENT_ERR_WARNING:          /* Bus error event. (error warning) */
      case CAN_EVENT_BUS_RECOVERY:         /* Bus error event. (bus recovery) */
      case CAN_EVENT_MAILBOX_MESSAGE_LOST: /* Overwrite/overrun error */
      {
            can_err_status_flag = true;     //设置标志位

            /* 获取错误状态 */
            can_err_status = (can_error_t) p_args->error;

            break;
      }
      default:
      {
            break;
      }
   }
}

37.5.2.6. CAN 发送信息函数

CAN_Send函数
void CAN_Send(unsigned char* Send_data,unsigned char ID)
{
   fsp_err_t err = FSP_SUCCESS;
   uint32_t time_out = WAIT_TIME;

      /* Clear the data part of receive frame */
      memset(can_rx_frame.data, 0, CAN_FRAME_TRANSMIT_DATA_BYTES);
      /* 更新传输帧的参数 */
      /* CAN Destination Device ID, in this case it is the same device with another mailbox */
      can_tx_frame.id               = ID;
      can_tx_frame.type             = CAN_FRAME_TYPE_DATA;
      can_tx_frame.data_length_code = CAN_FRAME_TRANSMIT_DATA_BYTES;

      /* 填充将要传输的帧数据 */
      /* copy the tx data frame with TXD_MESG */
      memcpy((uint8_t*)&can_tx_frame.data[0], Send_data, CAN_FRAME_TRANSMIT_DATA_BYTES);


      CAN_MSG_PRINTF("CAN 正在传输数据%s",Send_data);
      CAN_MSG_PRINTF("Transmitting the data");
      /* transmit the data from mail box #0 with tx_frame */
      err = R_CAN_Write(&g_can0_ctrl, CAN_MAILBOX_NUMBER_0, &can_tx_frame);
      /* Error trap */
      if (FSP_SUCCESS != err)
      {
            CAN_MSG_PRINTF("CAN Write API FAILED");
            while(1);
      }

      /* 等待传输完成 */
      while ((true != can_tx_complete_flag) && (--time_out));
      can_tx_complete_flag = false;
      if (0 == time_out)
      {
            CAN_MSG_PRINTF("传输超时!!传输失败!!");
            return;
      }
      CAN_MSG_PRINTF("传输完成\r\n");
      LED1_ON;
}

37.5.2.7. hal_entry入口函数

hal_entry入口函数
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "can/bsp_can.h"
#include "key/bsp_key.h"
/* 外部变量和函数声明 */
extern volatile bool can_tx_complete_flag;
extern volatile bool can_rx_complete_flag;
extern can_frame_t can_tx_frame;
extern can_frame_t can_rx_frame;
extern uint8_t can_rx_msg[CAN_FRAME_TRANSMIT_DATA_BYTES];

void hal_entry(void)
{
   /* TODO: add your own code here */

   LED_Init();         // LED 初始化
             Key_Init();                       // Key 初始化
   Debug_UART0_Init(); // SCI0 UART 调试串口初始化
             CAN_Init();                       //CAN 初始化
   printf("\r\n 欢迎使用野火启明开发板。\r\n");
   printf("\r\n 野火CAN通讯实验例程\r\n");

   printf("\r\n 实验步骤:\r\n");

   printf("\r\n 1.使用导线连接好两个CAN讯设备\r\n");
   printf("\r\n 2.按下开发板的KEY1键,会使用CAN向外发送消息“Tx_Data”,ID为8,发送成功后点亮LED1\r\n");
   printf("\r\n 3.按下开发板的KEY2键,会使用CAN向外发送消息“LED_OFF”,ID为10,发送成功后点亮LED1\r\n");
   printf("\r\n 3.若开发板的CAN接收到数据包,会把数据包与ID信息打印到串口\r\n");
   printf("\r\n 4.若开发板的CAN接收到ID为10的数据包,会熄灭LED1\r\n");
   while(1)
   {
      if(Key_Scan(KEY1_SW2_PIN)==KEY_ON)//按下按键Key1
      {
         CAN_Send((unsigned char*)"Tx_Data",8);
      }
      if(Key_Scan(KEY2_SW3_PIN)==KEY_ON)//按下按键Key2
      {
         CAN_Send((unsigned char*)"LED_OFF",10);
      }
      if (true == can_rx_complete_flag)//接收到数据
      {
            can_rx_complete_flag = false; //清零标志位
            printf("接收到来自ID:%d的消息:“%s”\r\n",can_rx_frame.id,can_rx_frame.data);
            if(can_rx_frame.id == 10)
            {
               LED1_OFF;
            }
      }
   }
   #if BSP_TZ_SECURE_BUILD
      /* Enter non-secure code */
      R_BSP_NonSecureEnter();
   #endif
}

37.5.3. 下载验证

首先使用导线连接两个 CAN 硬件。

下载程序后,打开串口调试助手,复位开发板,然后根据串口打印的提示进行操作。

根据提示按下两个按键测试两个 CAN 数据传输是否正常,程序的正常运行结果如下图所示:

图

图32 串口助手调试信息