38. CAN——FD通信实验

38.1. CAN FD简介

38.1.1. 什么是CAN FD

CAN协议自1986年问世以来就很流行:几乎任何移动的机器如今都使用CAN,无论是汽车、卡车、轮船、飞机还是机器人。 但是随着现代科技的兴起,对“传统”的CAN协议(ISO 11898-1:2015中使用的官方术语)的要求越来越高:

  • 汽车功能的高速发展正在推动数据的大爆炸;

  • 网络越来越受到1Mbit/s带宽的限制;

  • 为了应对这些情况,OEM创造的复杂且又昂贵的解决方案。

具体而言,传统CAN的开销很大(> 50%),因为每个CAN数据帧只能包含8个数据字节。 此外,网络速度限制为1 Mbit/s,从而限制了数据生成功能的实现。CAN FD解决了这些问题,使其具有前瞻性。

CAN FD (CAN with Flexible Data-Rate),字面意思上看,相较于 CAN 多了 FD (Flexible Data-Rate),也就是“灵活数据速率”。

38.1.2. CAN FD的优势

CAN FD协议是由Bosch以及行业专家预研开发的,并于2012年发布。 通过标准化对其进行了改进,现已纳入ISO 11898-1:2015。 原始的Bosch CAN FD版本(非ISO CAN FD)与ISO CAN FD是不兼容。CAN FD具有四个主要优点:

  1. 增加了数据的长度: CAN FD每个数据帧最多支持64个数据字节,而传统CAN最多支持8个数据字节。这减少了协议开销,并提高了协议效率。

  2. 增加传输的速度: CAN FD支持双比特率;与传统CAN一样,标称(仲裁)比特率限制为1 Mbit/s,而数据比特率则取决于网络拓扑/收发器。实际上,可以实现高达5 Mbit/s的数据比特率(实际应用中可以达到8 Mbit/s,但没有标准)。

  3. 更好的可靠性: CAN FD使用改进的循环冗余校验(CRC)和“受保护的填充位计数器”,从而降低了未被检测到的错误的风险。这在汽车和工业自动化等安全攸关的应用中至关重要。

  4. 平滑过渡: 在一些特定的情况下CAN FD能用在仅使用传统CAN的ECU上,这样就可以逐步引入CAN FD节点,从而为OEM简化程序和降低成本。

图

与传统CAN相比,CAN FD可以将网络带宽提高3到8倍,效率可从50%提升到90%,从而为数据的增长提供了一种简单的解决方案。

38.2. CAN FD协议

38.2.1. 物理层

在上一章中,我们已经介绍过了传统CAN协议的物理层,而CAN FD协议的物理层与传统CAN协议是一样的,这里就不过多赘述。

38.2.2. 协议层

以下介绍CAN-FD的协议层则规定了通讯逻辑。

38.2.2.1. 帧的种类

因为CAN-FD取消了对远程帧的支持,所以CAN-FD通信是通过以下 4 种类型的帧进行的。

  • 数据帧

  • 错误帧

  • 过载帧

  • 帧间隔

38.2.2.2. 数据帧

数据帧由 7 个段构成。

  1. 帧起始: 表示数据帧开始的段。

  2. 仲裁段: 表示该帧优先级的段。

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

  4. 数据段: 数据的内容,可发送 0~8 个字节的数据。

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

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

  7. 帧结束: 表示数据帧结束的段。

图

帧起始

表示帧开始的段。1 个位的显性位。

图

注解

显性电平和隐性电平:总线上的电平有显性电平和隐性电平两种。总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。“显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平。并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强。)

仲裁段

表示数据的优先级的段。 标准格式和扩展格式在此的构成有所不同。

图

标准格式的 ID 有 11 个位。从 ID28 到 ID18 被依次发送。禁止高 7 位都为隐性。 (禁止设定:ID=1111111XXXX)

扩展格式的 ID 有 29 个位。基本 ID 从 ID28 到 ID18,扩展 ID 由 ID17 到 ID0 表示。基本 ID 和 标准格式的 ID 相同。禁止高 7 位都为隐性。(禁止设定:基本 ID=1111111XXXX)

  • SRR:原为传统CAN协议中,当仲裁段为扩展格式时,代替远程帧请求位,且CAN FD不支持远程帧。

  • IDE:标志扩展位, 它是用于区分标准格式与扩展格式,当它为显性电平(0)时表示标准格式,隐性电平(1)时表示扩展格式。

  • r1:保留位供位未来使用,保持显性。

控制段

控制段主要用于确定数据段的长度。

图
  • 扩展数据长度 Extended Data Length (EDL):隐性表示帧为CAN-FD,否则该位为显性(称为R0)在CAN 2.0帧中。在传统CAN格式的帧中,所对应传输的是位R0而不是EDL。

  • r0:保留位供位未来使用。

  • 比特率切换 Bit Rate Switch (BRS):指示是否启用两个比特率。如果是隐性,则比特率从仲裁阶段的标准比特率切换到数据阶段的预配置交替比特率。如果是显性,则不切换比特率。

  • 错误状态指示器 Error State Indicator (ESI):表示发送节点状态,指示节点处于错误活动模式还是错误被动模式。

  • DLC:像在传统CAN中一样,CAN FD DLC是4位,表示帧中数据字节的数量。下表显示了这两种协议如何始终使用多达8个数据字节的DLC。为了维持4位DLC,CAN FD使用从9到15的其余7个值来表示所使用的数据字节数(12、16、20、24、32、48、64)。

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

数据字节数

DLC3

DLC2

DLC1

DLC0

0

0

0

0

0

1

0

0

0

1

2

0

0

1

0

3

0

0

1

1

4

0

1

0

0

5

0

1

0

1

6

0

1

1

0

7

0

1

1

1

8

1

0

0

0

12

1

0

0

1

16

1

0

1

0

20

1

0

1

1

24

1

1

0

0

32

1

1

0

1

48

1

1

1

0

64

1

1

1

1

数据段

数据段可包含 0~64 个字节的数据。从 MSB(最高位)开始输出。

图

CRC 段

传统CAN中的循环冗余校验(CRC)为15位,而在CAN FD中为17位(最多16个数据字节)或21位(20-64个数据字节)。 在传统CAN中,CRC中可以包含0到3个填充位,而在CAN FD中,总是有四个固定填充位以提高通信可靠性。

图
  • CRC:CRC 顺序是根据多项式生成的 CRC 值,CRC 的计算范围包括帧起始、仲裁段、控制段、数据段。接收方以同样的算法计算 CRC 值并进行比较,不一致时会通报错误。

  • DEL:CRC界定符,CAN-FD数据段以CRC界定符采样点为结束点,由于段转换的存在,CAN-FD控制器为了使接收位位数达到两位,则会接收带有CRC界定符的帧。

ACK 段

ACK 段用来确认是否正常接收。由 ACK 槽(ACK Slot)和 ACK 界定符(DEL) 2 个位构成。

发送单元的 ACK 段: 发送单元在 ACK 段发送 2 个位的隐性位。

接收单元的 ACK 段: 接收到正确消息的单元在 ACK 槽(ACK Slot)发送显性位,通知发送单元正常接收结束。 这称作“发送 ACK”或者“返回 ACK”。

图

帧结束

帧结束是表示该该帧的结束的段。由 7 个位的隐性位构成。

图

注解

CAN FD最大的比特率是多少?: CAN FD的一个令人困惑的方面是有效负载阶段的最大比特率(最大数据比特率),因为不同的文章里面的最大比特率的不一定相同。 有人指出,实际应用中可以达到8 Mbit/s,理论上可以达到15 Mbit/s。其他则规定最高为12 Mbit/s。 可达到的数据相位比特率取决于许多因素。比如工作环境温度,和所选的拓扑, 对比长分支甚至星形的混合拓扑,短分支的总线拓扑可以显著提高比特率。 从ISO 11898-2(收发器芯片标准)来看,它指定了两个对称参数集。 推荐使用那些具有改进的对称参数,通常宣传为5Mbit/s的收发器。

38.2.2.3. 错误帧

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

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

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

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

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

图

38.2.2.4. 过载帧

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

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

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

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

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

图

38.2.2.5. 位时序

由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为 4 段。

  • 同步段(SS)

  • 传播时间段(PTS)

  • 相位缓冲段 1(PBS1)

  • 相位缓冲段 2(PBS2)

这些段又由可称为 Time Quantum(以下称为 Tq)的最小时间单位构成。

1 位分为 4 个段,每个段又由若干个 Tq 构成,这称为位时序。

1 位由多少个 Tq 构成、每个段又由多少个 Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可 同时采样,也可任意设定采样点。

段及其作用

段名称

段的作用

Tq 数

同步段 (SS: Synchronization Segment)

多个连接在总线上的单元通过此段实现时序调整,同步进行接收和发送的工作。由隐性电平到显性电平的边沿或由显性电平到隐性电平边沿最好出现在此段中。

1Tq

传播时间段(PTS: Propagation Time Segment)

用于吸收网络上的物理延迟的段。所谓的网络的物理延迟指发送单元的输出延迟、总线上信号的传播延迟、接收单元的输入延迟。

这个段的时间为以上各延迟时间的和的两倍。

1~8Tq

相位缓冲段 1 (PBS1: Phase Buffer Segment 1)

当信号边沿不能被包含于 SS 段中时,可在此段进行补偿。由于各单元以各自独立的时钟工作,细微的时钟误差会累积起来,PBS 段可用于吸收此误差。

通过对相位缓冲段加减 SJW 吸收误差。SJW 加大后允许误差加大,但通信速度下降。

1~8Tq

相位缓冲段 2 (PBS2: Phase Buffer Segment 2)

当信号边沿不能被包含于 SS 段中时,可在此段进行补偿。由于各单元以各自独立的时钟工作,细微的时钟误差会累积起来,PBS 段可用于吸收此误差。

通过对相位缓冲段加减 SJW 吸收误差。SJW 加大后允许误差加大,但通信速度下降。

2~8Tq

再同步补偿宽度(SJW: reSynchronization Jump Width)

因时钟频率偏差、传送延迟等,各单元有同步误差。SJW 为补偿此误差的最大值。

1~4Tq

下面是 1 个位的构成:

图

38.3. CAN-FD模块简介

瑞萨单片机的CAN-FD模块具有灵活的消息缓冲区和FIFO结构,可满足各种应用的要求。并具有以下特征:

  • 兼容性
    • 在同一通道上发送和接收CAN 2.0和CAN-FD帧

    • 比特率高达 1 Mbps,FD 数据相位速度高达 5-8 Mbps

    • 支持两个 ISO 11898-1

  • 缓冲区
    • 32 个全局接收消息缓冲区 (RX MB)

    • 2-8 个全球接收 FIFO (RX FIFO)

    • 每通道 4-16 个传输消息缓冲区 (TX MB)

  • 滤波
    • 可以单独将每个筛选规则配置为根据以下条件接受邮件
      • 编号

      • 标准或扩展 ID(IDE 位)

      • 数据或远程帧(RTR 位)

      • ID/IDE/RTR 掩码

      • 最小 DLC(数据长度)值

  • 中断
    • 可配置的全局 RX FIFO 中断
      • 可单独配置每个 FIFO

      • 在接收到特定深度或每收到一条消息触发中断

    • 信道 TX 中断

    • 全局错误
      • 数据链路校验

      • 邮件丢失

      • FD 有效负载溢出

    • 通道错误
      • 总线错误

      • 警告错误

      • 被动错误

      • 总线断开

      • 总线断开恢复

      • 超载

      • 总线锁

      • 仲裁损失

      • 传输中止

RA6M5 的 CANFD 模块具有:

  • CAN通道: 2

  • 最大标称(仲裁)比特率:1Mbps

  • 最大数据比特率:8Mbps

  • 工作时钟:50MHz(PCLKB)、RAM 时钟100MHz(PCLKA)

  • 过滤规则:128

  • 发送消息缓冲区:16/ch

  • 接收消息缓冲区:32

  • 接收:FIFO 8

  • 接收缓冲区:RAM 4864 bytes

  • CAN标准:CAN-FD ISO 11898-1 (2015)

全局模式 这些模式适用于整个CAN-FD模块,因此称为全局模式。 全局模式包括:

  • 全局睡眠

  • 全局重置

  • 全局停滞

  • 全局操作

38.4. CAN-FD模块框图分析

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

图

38.4.1. CAN通道和CAN时钟

见图中标注 ① 处。

瑞萨RA MCU内部的 CAN-FD 外设模块包含两个 CAN 通道,也就是说, 有两个 CAN 控制器可以用,芯片外部可以通过引脚 CTX 和 RTX 连接到两个 CAN 收发器。

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

38.4.2. CAN相关寄存器

见图中标注 ② 处。

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

38.4.3. 接收过滤器和RAM

见图中标注 ③ 处。

接收过滤器(Acceptance filter)和接收过滤器列表(AFL,Acceptance filter list) 用于实现 CAN 的接收过滤功能。 而对于使用 CAN 来说,接收过滤功能至关重要,使用 CAN 就绕不过 CAN 外设模块中的接收过滤器, 后面在代码中需要手动配置接收过滤器列表(AFL,Acceptance filter list), 因此对于这部分我们需要详细了解。

38.4.4. 中断信号

见图中标注 ④ 处。

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

  • 成功接收并存到 RX FIFO 中断

  • 全局错误中断

  • 通道相关的传输中断

  • 通道错误中断

  • 从 COM FIFO 成功接收中断

另外,两个 CAN 通道的 CRX 引脚可用于产生通道唤醒中断信号:

  • 通道唤醒中断(CRX0、CRX1)

38.5. CAN-FD测试模式

CAN-FD 模块可以配置为测试模式以允许测试某些功能。 不同于正常的 CAN 通信模式,测试模式的功能仅用于特殊目的,在测试模式下配置 CAN-FD 模块时必须小心。

测试模式可以大致分为两组:

  • 通道特定的测试模式

    • 基本测试模式(Basic test mode)

    • 监听模式(Listen-only mode)

    • 自检模式0(外部环回模式)(Self-test mode 0 (External loop back mode))

    • 自检模式1(内部环回模式)(Self-test mode 1 (Internal loop back mode))

    • 受限操作模式(Restricted operation mode)

  • 全局测试模式

    • RAM 测试模式

    • 内部 CAN 总线通讯方式

    • CRC 错误测试

下面我们仅介绍通道特定的测试模式当中的几种测试模式, 所介绍的这几种测试模式会在后面的实验例程当中用到。

38.5.1. 监听模式

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

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

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

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

注意:

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

图

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

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

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

图

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

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

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

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

图

38.6. 实验:CAN FD通信

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

38.6.1. 硬件设计

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

图

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

38.6.2. 软件设计

38.6.2.1. 新建工程

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

对于 e2 studio 开发环境:

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

对于 Keil 开发环境:

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

工程新建好之后,在工程根目录的 “src” 文件夹下面新建 “canfd” 文件夹, 再进入 “canfd” 文件夹里面新建源文件和头文件:“bsp_canfd0.c” 和 “bsp_canfd0.h”、“bsp_canfd1.c” 和 “bsp_canfd1.h”。 工程文件结构如下。

文件结构
CAN_FD
├─ ......
└─ src
   ├─ led
   │  ├─ bsp_led.c
   │  └─ bsp_led.h
   ├─ debug_uart
   │  ├─ bsp_debug_uart.c
   │  └─ bsp_debug_uart.h
   ├─ canfd
   │  ├─ bsp_canfd0.c
   │  ├─ bsp_canfd0.h
   │  ├─ bsp_canfd1.c
   │  └─ bsp_canfd1.h
   └─ hal_entry.c

38.6.2.2. FSP配置

打开 FSP 配置页面。

配置 CAN 时钟

首先切换到 FSP 的时钟(“Clocks”)配置页面进行 CAN 的时钟配置。 按照如下图所示来配置即可。

图

这里配置为选用 PLL2 作为输入时钟,然后经过 /6 分频得到 CANFD 的时钟。

配置 CAN 引脚

然后切换到 FSP 的引脚(“Pins”)配置页面配置 CAN 的引脚。

CANFD0:

图

CANFD1:

图

配置 CAN 模块

最后切换到 FSP 的模块堆叠(“Stacks”)配置页面配置 CAN 模块。 CAN FD 的配置项比较多,我们先配置一遍,后面会一一讲解每一项属性的作用和功能。

按照如下步骤添加两个 CANFD 模块实例:

图

添加完成后如下图所示,显示有报错,不过没关系,等配置完了之后就会变正常了。

图

最后的最后,参考下面两张图,依次配置这两个 CANFD 模块即可。

CANFD0 和 CANFD1 共同使用的全局属性配置(这部分是同步的,仅需配置一次即可):

图

仅用于 CANFD0 的属性配置:

图

仅用于 CANFD1 的属性配置:

图

CAN-FD 模块配置属性描述:

CAN-FD属性描述:“Common”部分

CAN-FD属性描述:“Common”部分

属性

描述

Global Error Interrupt > Sources

选择全局错误中断源。

可选择以下这些错误源来触发该中断:

  • DLC Check:DLC 数据长度检查

  • Message Lost:数据丢失

  • FD Payload Overflow:FD有效负载溢出

Global Error Interrupt > Priority

全局错误中断的优先级。

Global Error Interrupt > Callback Channel

选择哪个通道回调函数来处理该全局错误中断。

Flexible Data (FD) > FD Frame Format

选择 FD 帧格式标准。

可选协议 ISO 11898-1 或 Bosch CAN FD Specification V1.0

Flexible Data (FD) > Protocol Exceptions

选择当一个在 ISO11898-1 定义的 RES 位是隐性采样时,是否进入协议异常处理状态。

Flexible Data (FD) > Payload Overflow

缓冲区(有效负载)溢出。

配置接收到的大于目标缓冲区的消息是否应该被截断或拒绝。

Reception > Message Buffers

配置 RX 消息缓冲区。

可配置用于接收的消息缓冲区数量以及所有RX消息缓冲区的有效负载大小。

  • Number of Buffers:缓冲区个数

  • Payload Size:有效负载大小

Reception > FIFOs > FIFO x > Enable

使能或禁用 RX FIFO x。

Reception > FIFOs > FIFO x > Interrupt Mode

设置 RX FIFO x 的中断模式。

阈值模式下在每次传入消息超过下面设置的阈值时,仅会触发一次中断。

Reception > FIFOs > FIFO x > Interrupt Threshold

设置 RX FIFO x 的中断阈值。

Reception > FIFOs > FIFO x > Payload Size

选择 RX FIFO x 的消息有效负载的大小

Reception > FIFOs > FIFO x > Depth

深度。即选择 RX FIFO x 的阶数(stages)

Reception > Acceptance Filtering 接收过滤

专用于通道 x 的验收筛选器列表(AFL)规则数。

  • Channel 0 Rule Count:通道0规则计数

  • Channel 1 Rule Count:通道1规则计数

Parameter Checking

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

Transmission Priority

传输优先级。

DLC Check

数据长度检查(DLC)。

当启用接收的消息时,

如果它们的 DLC 字段小于关联的 AFL 规则中配置的值,则将拒绝它们。

如果选择“ Enabedw/truncate”并且消息通过 DLC 检查,

则 DLC 字段被设置为相关 AFL 规则中的值,并且丢弃任何多余的数据。

CAN FD属性描述:“Module CAN FD (r_canfd)”部分

CAN FD属性描述:“Module CAN FD (r_canfd)”部分

属性

描述

General > Name

模块名字

General > Channel

指定要使用的 CAN 通道

Bitrate > Automatic > Nominal Rate (bps)

自动设置:指定标称比特率(位/秒)。

Bitrate > Automatic > FD Data Rate (bps)

自动设置:指定数据比特率(位/秒)。

Bitrate > Automatic > Sample Point (%)

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

Bitrate > Manual > Nominal 标称比特率

手动设置:标称参数:预分频、TS1、TS2、SJW

  • Nominal->Prescaler (divisor):预分频(1 ~ 1024)

  • Nominal->Time Segment 1 (Tq):2 ~ 256 Tq

  • Nominal->Time Segment 2 (Tq):2 ~ 128 Tq

  • Nominal->Sync Jump Width (Tq):1 ~ Time Segment 2

Bitrate > Manual > Data 数据比特率

手动设置:数据参数:预分频、TS1、TS2、SJW

  • Data->Prescaler (divisor):预分频(1 ~ 256)

  • Data->Time Segment 1 (Tq):2 ~ 32 Tq

  • Data->Time Segment 2 (Tq):2 ~ 16 Tq

  • Data->Sync Jump Width (Tq):1 ~ Time Segment 2

Bitrate > Manual > Use manual settings

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

Bitrate > Delay Compensation

延迟补偿

当启用 CANFD 模块时,将自动补偿发送和接收位之间的任何收发器或总线延迟。

当手动提供具有延迟补偿功能的位定时值时,确保数据预分频器为2或更小,以便正确操作。

启用时,CANFD模块将自动补偿发送和接收位之间的任何收发器或总线延迟。

在启用延迟补偿的情况下手动提供位定时值时,确保数据预分频器为2或更小,以便正确操作

Interrupts > Callback

用户的中断回调函数

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

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

Interrupts > Channel Interrupt Priority Level

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

Transmit Interrupts

选择在传输完成时会触发中断的 TX 消息缓冲区

(TXMB 0 ~ TXMB 7 和 TXMB 32 ~ TXMB 39)。

Channel Error Interrupts

使能通道错误中断源。

  • Error Warning:错误警告

  • Error Passive:被动错误

  • Bus-Off Entry:错误总线关闭

  • Bus-Off Recovery:总线恢复错误

  • Overload:超载

Filter List Array

接收筛选器列表(Acceptance Filter List (AFL))规则数组变量名。

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

38.6.2.3. CAN波特率计算

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

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

  • 其中,canfd_clock_hz 是 CANFD 模块的时钟, 是之前在 FSP 的时钟配置页面上已配置好的 CANFDCLK,为 40 MHz。

对于CANFD,每个元素的可能值如下:

CANFD 波特率

成员

最小值

最大值(标称波特率)

最大值(数据波特率)

比特率

-

1 Mbps

5-8 Mbps

Time Segment 1

2 Tq

256 Tq

32 Tq

Time Segment 2

2 Tq

128 Tq

16 Tq

Sync Jump Width

(SJW:再同步补偿宽度)

1 Tq

Time Segment 2

Time Segment 2

Prescalar (预分配系数)

1

1024

256

比如可以配置为:

图

38.6.2.4. 接收过滤器列表(AFL)

除了在 FSP 配置界面中对 CANFD 的属性进行配置外, 其实还需要对 CANFD 的接收过滤器列表(Acceptance Filter List,AFL)进行配置, 因为接收过滤器对于 CAN 而言是必须的, 而接收过滤器列表是不能在 FSP 配置界面中进行配置的,只能通过代码进行配置。

使用 e2 studio 内置的开发人员帮助功能,可以轻松地将 AFL 配置模板添加到项目中。 将 CANFD 模块添加到项目后,拖放下面圈出的模块即可构建过滤器列表:

图

AFL 数组会在 hal_data.c 文件中使用到,因此会进行外部变量的声明:

extern const canfd_afl_entry_t p_canfd0_afl[CANFD_CFG_AFL_CH0_RULE_NUM];

我们需要在我们的源文件中对 AFL 数组进行定义,如果不进行定义的话是会报错的。

举一个 AFL 的例子,下面是一个带有两个规则的接收过滤器列表声明的例子:

const canfd_afl_entry_t p_canfd0_afl[CANFD_CFG_AFL_CH1_RULE_NUM] =
{
   /* 将来自标准ID 0x40-0x4F的至少4个字节的所有数据帧存储在RX FIFO 0和RX FIFO 1中 */
   {
      .id =
      {
            .id         = 0x40,
            .frame_type = CAN_FRAME_TYPE_DATA,
            .id_mode    = CAN_ID_MODE_STANDARD
      },
      .mask =
      {
            .mask_id         = 0x7F0,  //对应二进制 0111 1111 0000
            .mask_frame_type = 1,
            .mask_id_mode    = 1
      },
      .destination =
      {
            .minimum_dlc       = CANFD_MINIMUM_DLC_4, //最小接收长度为4
            .rx_buffer         = CANFD_RX_MB_NONE,    //无接收缓冲区
            .fifo_select_flags = (canfd_rx_fifo_t) (CANFD_RX_FIFO_0 | CANFD_RX_FIFO_1) //接收到 RX FIFO 0 和 RX FIFO 1
      }
   },

   /* 将扩展ID 0x1100的所有帧存储在RX FIFO 2和RX MB 0中 */
   {
      .id =
      {
            .id         = 0x1100,
            .frame_type = CAN_FRAME_TYPE_DATA,  // 掩码会忽略此设置
            .id_mode    = CAN_ID_MODE_EXTENDED
      },
      .mask =
      {
            .mask_id         = 0x1FFFFFFF,   //对应二进制 0001 1111 1111 1111 1111 1111 1111 1111
            .mask_frame_type = 0,
            .mask_id_mode    = 1
      },
      .destination =
      {
            .minimum_dlc       = CANFD_MINIMUM_DLC_0, //不启用
            .rx_buffer         = CANFD_RX_MB_0,       //RX MB 0 作为缓冲区
            .fifo_select_flags = CANFD_RX_FIFO_2      //接收到 RX FIFO 2
      }
   }
};

下面来对上面 FAL 的例子做一些简单的解释和说明:

id

想要接收的id

frame_type

想要接收到的帧类型

id_mode

想要接收到的id模式(标准id或拓展id)

mask_id

需要屏蔽检查id的哪几位

mask_frame_type

决定 frame_type 是否其作用

mask_id_mode

决定 id_mode 是否其作用

minimum_dlc

最小的接收数据长度(0为不检查)

rx_buffer

接收此规则接受的消息的RX消息缓冲区

fifo_select_flags

接收此规则接受的消息的RX FIFO

第一个过滤器:

由于 mask_frame_type 和 mask_id_mode 都设置为1, 所以 frame_type 和 id_mode 项配置都会起作用,也就是说只会接收标准id的数据帧。

id设置为 0x40 ,mask_id设置为 0x7F0 ,如下图所示。

图

黄色的部分表明过滤器会进行检查,也就是说接收到的标准id,id值的黄色标注部分必须一致才能被接收; 而蓝色部分则不会进行检查,过滤器会忽视这部分,这部分可以为任意值。

因此,第一个过滤器的作用就是, 将来自标准ID 0x40-0x4F 的至少4个字节数据长度的所有数据帧存储在 RX FIFO 0 和 RX FIFO 1 中。

第二个过滤器:

第二个过滤器也是同理, mask_frame_type 设置为0,表明 frame_type 项设置无效; mask_id_mode 设置为1,表明 id_mode 项的配置会起作用,也就是说会接收拓展id,所以帧类型都会接收。

这里 mask_id 设置为 0x1FFFFFFF 对应二进制 0001_1111_1111_1111_1111_1111_1111_1111b , 所以 id 项配置的所以位都会起到作用,如下图黄色区域,也就是说必须要和设置的id 0x1100一致才会被接收。

图

所以第二个过滤器的作用就是,将扩展ID 0x1100 的任意数据长度的所有帧存储在 RX FIFO 2 和 RX MB 0 中。

在本例程中 CANFD0 和 CANFD1 实际所使用的 AFL 如下所示。

代码 36‑1 CANFD0 的 AFL 数组
/* CANFD Channel 0 Acceptance Filter List (AFL) rule array */
const canfd_afl_entry_t p_canfd0_afl[CANFD_CFG_AFL_CH0_RULE_NUM] =
{
   {
      .id =
      {
            /* 指定要接受的ID、ID类型和帧类型。 */
            .id         = CANFD_FILTER_ID,
            .frame_type = CAN_FRAME_TYPE_DATA,
            .id_mode    = CAN_ID_MODE_EXTENDED
      },

      .mask =
      {
            /* 这些值屏蔽了过滤邮件时要比较的ID/模式位。 */
            .mask_id         = MASK_ID,
            .mask_frame_type = 0,
            .mask_id_mode    = MASK_ID_MODE
      },

      .destination =
      {
            /* 如果启用了DLC检查,则任何短于以下设置的消息都将被拒绝。 */
            .minimum_dlc = CANFD_MINIMUM_DLC_0,

            /* 也可以指定接收消息缓冲区(RX MB)来存储接受的帧。
            * RX MB没有中断或重写保护,必须使用R_CANFD_INFO Get和R_CANFD_READ进行检查。 */
            .rx_buffer   = CANFD_RX_MB_0,

            /* 指定要将筛选的消息发送到的FIFO。多个FIFO可以一起进行或运算。 */
            .fifo_select_flags = CANFD_RX_FIFO_0
      }
   },
};
代码 36‑2 CANFD1 的 AFL 数组
/* CANFD Channel 1 Acceptance Filter List (AFL) rule array */
const canfd_afl_entry_t p_canfd1_afl[CANFD_CFG_AFL_CH1_RULE_NUM] =
{
   {
      .id =
      {
            /* 指定要接受的ID、ID类型和帧类型。 */
            .id         = CANFD_FILTER_ID,
            .frame_type = CAN_FRAME_TYPE_DATA,
            .id_mode    = CAN_ID_MODE_EXTENDED
      },

      .mask =
      {
            /* 这些值屏蔽了过滤邮件时要比较的ID/模式位。 */
            .mask_id         = MASK_ID,
            .mask_frame_type = 0,
            .mask_id_mode    = MASK_ID_MODE
      },

      .destination =
      {
            /* 如果启用了DLC检查,则任何短于以下设置的消息都将被拒绝。 */
            .minimum_dlc = CANFD_MINIMUM_DLC_0,

            /* 也可以指定接收消息缓冲区(RX MB)来存储接受的帧。
            * RX MB没有中断或重写保护,必须使用R_CANFD_INFO Get和R_CANFD_READ进行检查。 */
            .rx_buffer   = CANFD_RX_MB_0,

            /* 指定要将筛选的消息发送到的FIFO。多个FIFO可以一起进行或运算。 */
            .fifo_select_flags = CANFD_RX_FIFO_0
      }
   },
};

38.6.2.5. CANFD初始化函数

代码 36‑3 CANFD0 初始化函数
/* CAN 初始化函数 */
void CANFD0_Init(void)
{
   fsp_err_t err = R_CANFD_Open(&g_canfd0_ctrl, &g_canfd0_cfg);
   assert(FSP_SUCCESS == err);
}
代码 36‑4 CANFD1 初始化函数
/* CAN 初始化函数 */
void CANFD1_Init(void)
{
   fsp_err_t err = R_CANFD_Open(&g_canfd1_ctrl, &g_canfd1_cfg);
   assert(FSP_SUCCESS == err);
}

38.6.2.6. CAN中断回调函数

代码 36‑5 CANFD0 中断回调函数
/* 要在回调函数中设置的标志 */
volatile bool canfd0_tx_complete_flag = false;
volatile bool canfd0_rx_complete_flag = false;
volatile bool canfd0_err_status_flag = false;
volatile canfd_error_t canfd0_err_status = (canfd_error_t) 0;


/* CANFD0 中断回调函数 */
void canfd0_callback(can_callback_args_t * p_args)
{
   switch (p_args->event)
   {
      case CAN_EVENT_RX_COMPLETE:     //接收完成中断
      {
            canfd0_rx_complete_flag = true; //canfd0接收到数据

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

            break;
      }
      case CAN_EVENT_TX_COMPLETE:     //传输完成中断
      {
            canfd0_tx_complete_flag = true; //canfd0数据发送完成
            break;
      }
      case CAN_EVENT_ERR_WARNING:             //error warning event
      case CAN_EVENT_ERR_PASSIVE:             //error passive event
      case CAN_EVENT_ERR_BUS_OFF:             //error Bus Off event
      case CAN_EVENT_BUS_RECOVERY:            //Bus recovery error event
      case CAN_EVENT_MAILBOX_MESSAGE_LOST:    //overwrite/overrun error event
      case CAN_EVENT_ERR_BUS_LOCK:            //Bus lock detected (32 consecutive dominant bits).
      case CAN_EVENT_ERR_CHANNEL:             //Channel error has occurred.
      case CAN_EVENT_TX_ABORTED:              //Transmit abort event.
      case CAN_EVENT_ERR_GLOBAL:              //Global error has occurred.
      case CAN_EVENT_TX_FIFO_EMPTY:           //Transmit FIFO is empty.
      {
            canfd0_err_status_flag = true;  //设置标志位

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

            break;
      }
      default:
      {
            break;
      }
   }
}
代码 36‑6 CANFD1 中断回调函数
/* 要在回调函数中设置的标志 */
volatile bool canfd1_tx_complete_flag = false;
volatile bool canfd1_rx_complete_flag = false;
volatile bool canfd1_err_status_flag = false;
volatile canfd_error_t canfd1_err_status = (canfd_error_t) 0;


/* CANFD1 中断回调函数 */
void canfd1_callback(can_callback_args_t * p_args)
{
   switch (p_args->event)
   {
      case CAN_EVENT_RX_COMPLETE:     //接收完成中断
      {
            canfd1_rx_complete_flag = true; //canfd1接收到数据

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

            break;
      }
      case CAN_EVENT_TX_COMPLETE:     //传输完成中断
      {
            canfd1_tx_complete_flag = true; //canfd0数据发送完成
            break;
      }
      case CAN_EVENT_ERR_WARNING:             //error warning event
      case CAN_EVENT_ERR_PASSIVE:             //error passive event
      case CAN_EVENT_ERR_BUS_OFF:             //error Bus Off event
      case CAN_EVENT_BUS_RECOVERY:            //Bus recovery error event
      case CAN_EVENT_MAILBOX_MESSAGE_LOST:    //overwrite/overrun error event
      case CAN_EVENT_ERR_BUS_LOCK:            //Bus lock detected (32 consecutive dominant bits).
      case CAN_EVENT_ERR_CHANNEL:             //Channel error has occurred.
      case CAN_EVENT_TX_ABORTED:              //Transmit abort event.
      case CAN_EVENT_ERR_GLOBAL:              //Global error has occurred.
      case CAN_EVENT_TX_FIFO_EMPTY:           //Transmit FIFO is empty.
      {
            canfd1_err_status_flag = true;  //设置标志位

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

            break;
      }
      default:
      {
            break;
      }
   }
}

38.6.2.7. CAN FD操作函数

代码 36‑7 CANFD0 操作函数
/* CAN 帧 */
can_frame_t canfd0_tx_frame; //CAN transmit frame
can_frame_t canfd0_rx_frame;


/* CANFD0 操作函数(使用 FD) */
void CANFD0_Operation(void)
{
   fsp_err_t err = FSP_SUCCESS;
   uint32_t time_out = WAIT_TIME;

   /* 更新 FD 帧的参数 */
   canfd0_tx_frame.id = CAN_ID;
   canfd0_tx_frame.id_mode = CAN_ID_MODE_EXTENDED;
   canfd0_tx_frame.type = CAN_FRAME_TYPE_DATA;
   canfd0_tx_frame.data_length_code = CAN_FD_DATA_LENGTH_CODE;
   canfd0_tx_frame.options = CANFD_FRAME_OPTION_FD | CANFD_FRAME_OPTION_BRS;

   /* 填充将要在 FD 帧中发送出去的帧数据 */
   for( uint16_t j = 0; j < CAN_FD_DATA_LENGTH_CODE; j++)
   {
      canfd0_tx_frame.data[j] = (uint8_t) (CAN_FD_DATA_LENGTH_CODE - j);
   }


   CANFD0_MSG_PRINTF("CANFD0 正在使用 CAN FD 帧传输数据");

   /* 通过 mail box #0 传输数据 */
   err = R_CANFD_Write(&g_canfd0_ctrl, CAN_MAILBOX_NUMBER_0, &canfd0_tx_frame);
   assert(FSP_SUCCESS == err);

   /* 等待传输完成 */
   while ((true != canfd0_tx_complete_flag) && (--time_out));
   canfd0_tx_complete_flag = false;
   if (0 == time_out)
   {
      CANFD0_MSG_PRINTF("传输超时!!传输失败!!");
      return;
   }
   CANFD0_MSG_PRINTF("传输完成");
}
代码 36‑8 CANFD1 操作函数
/* CAN 帧 */
can_frame_t canfd1_tx_frame; //CAN transmit frame
can_frame_t canfd1_rx_frame;


/* CANFD1 操作函数(使用 FD) */
void CANFD1_Operation(void)
{
   fsp_err_t err = FSP_SUCCESS;
   uint32_t time_out = WAIT_TIME;

   /* 更新 FD 帧的参数 */
   canfd1_tx_frame.id = CAN_ID;
   canfd1_tx_frame.id_mode = CAN_ID_MODE_EXTENDED;
   canfd1_tx_frame.type = CAN_FRAME_TYPE_DATA;
   canfd1_tx_frame.data_length_code = CAN_FD_DATA_LENGTH_CODE;
   canfd1_tx_frame.options = CANFD_FRAME_OPTION_FD | CANFD_FRAME_OPTION_BRS;

   /* 填充将要在 FD 帧中发送出去的帧数据 */
   for( uint16_t j = 0; j < CAN_FD_DATA_LENGTH_CODE; j++)
   {
      canfd1_tx_frame.data[j] = (uint8_t) (CAN_FD_DATA_LENGTH_CODE - j);
   }


   CANFD1_MSG_PRINTF("CANFD1 正在使用 CAN FD 帧传输数据");

   /* 通过 mail box #0 传输数据 */
   err = R_CANFD_Write(&g_canfd1_ctrl, CAN_MAILBOX_NUMBER_0, &canfd1_tx_frame);
   assert(FSP_SUCCESS == err);

   /* 等待传输完成 */
   while ((true != canfd1_tx_complete_flag) && (--time_out));
   canfd1_tx_complete_flag = false;
   if (0 == time_out)
   {
      CANFD1_MSG_PRINTF("传输超时!!传输失败!!");
      return;
   }
   CANFD1_MSG_PRINTF("传输完成");
}

38.6.2.8. 串口中断回调函数

需要修改串口中断回调函数,实现通过向串口发送“0”或“1”来控制 CANFD0 或 CANFD1 发送数据帧的功能。

代码 36‑9 串口中断回调函数
// CANFD 功能操作允许标志位
volatile bool canfd0_senddata_enable = false;  // 允许 CANFD0 发送数据
volatile bool canfd1_senddata_enable = false;  // 允许 CANFD1 发送数据

/* 发送完成标志 */
volatile bool uart_send_complete_flag = false;


/* 串口中断回调 */
void debug_uart4_callback (uart_callback_args_t * p_args)
{
   switch (p_args->event)
   {
      case UART_EVENT_RX_CHAR:
      {
            /* 根据字符指令进行操作 */
            switch (p_args->data)
            {
               case '0':
                  canfd0_senddata_enable = true;
                  break;
               case '1':
                  canfd1_senddata_enable = true;
                  break;
               default:
                  // input error
                  break;
            }
            break;
      }
      case UART_EVENT_TX_COMPLETE:
      {
            uart_send_complete_flag = true;
            break;
      }
      default:
            break;
   }
}

38.6.2.9. hal_entry入口函数

代码 36‑10 hal_entry入口函数
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
#include "canfd/bsp_canfd0.h"
#include "canfd/bsp_canfd1.h"


/* 外部变量和函数声明 */
extern volatile bool canfd0_rx_complete_flag;
extern can_frame_t canfd0_tx_frame;
extern can_frame_t canfd0_rx_frame;

extern volatile bool canfd1_rx_complete_flag;
extern can_frame_t canfd1_rx_frame;
extern can_frame_t canfd1_tx_frame;

extern volatile bool canfd0_senddata_enable;
extern volatile bool canfd1_senddata_enable;


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

   LED_Init();         // LED 初始化
   Debug_UART4_Init(); // SCI4 UART 调试串口初始化

   /* CAN-FD 模块初始化 */
   CANFD0_Init();
   CANFD1_Init();

   printf("这是一个 CAN FD 通讯例程\r\n");
   printf("打开串口助手发送以下指令,对 CAN-FD 进行相应的操作\r\n");
   printf("\t指令   ------  操作\r\n");
   printf("\t 0   ------  CAN-FD0 发送数据帧\r\n");
   printf("\t 1   ------  CAN-FD1 发送数据帧\r\n");
   printf("\t=======================================\r\n\r\n");


   while(1)
   {
      if (true == canfd0_senddata_enable)
      {
            canfd0_senddata_enable = false; //清零标志位

            /* 测试从 CANFD0 发送到 CANFD1 */
            CANFD0_Operation();

            printf("等待 CANFD1 接收完成中断\r\n");
            while (false == canfd1_rx_complete_flag);
            canfd1_rx_complete_flag = false;

            printf("CANFD0 > CANFD1 开始验证数据\r\n");
            for( uint16_t j = 0; j < canfd0_tx_frame.data_length_code; j++)
            {
               if (canfd0_tx_frame.data[j] != canfd1_rx_frame.data[j])
               {
                  printf("CANFD0发送数据与CANFD1接收数据不一致\r\n");
               }
            }
            printf("CANFD0 > CANFD1 测试完成\r\n\r\n");
      }

      if (true == canfd1_senddata_enable)
      {
            canfd1_senddata_enable = false; //清零标志位

            /* 测试从 CANFD1 发送到 CANFD0 */
            CANFD1_Operation();

            printf("等待 CANFD0 接收完成中断\r\n");
            while (false == canfd0_rx_complete_flag);
            canfd1_rx_complete_flag = false;

            printf("CANFD1 > CANFD0 开始验证数据\r\n");
            for( uint16_t j = 0; j < canfd1_tx_frame.data_length_code; j++)
            {
               if (canfd1_tx_frame.data[j] != canfd0_rx_frame.data[j])
               {
                  printf("CANFD1发送数据与CANFD0接收数据不一致\r\n");
               }
            }
            printf("CANFD1 > CANFD0 测试完成\r\n\r\n");
      }
   }


#if BSP_TZ_SECURE_BUILD
   /* Enter non-secure code */
   R_BSP_NonSecureEnter();
#endif
}

38.6.3. 下载验证

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

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

根据提示发送“0”和“1”,测试两个 CAN 数据传输是否正常,程序的正常运行结果如下图所示:

图