CAN协议通信 学习笔记

文章目录

  • 1.CAN通信简介
  • 2.物理层
    • [2.1 CAN总线的电气特性](#2.1 CAN总线的电气特性)
    • [2.2 CAN的位同步机制(了解,用于理解CAN的初始化参数的配置原理)](#2.2 CAN的位同步机制(了解,用于理解CAN的初始化参数的配置原理))
    • [2.3 CAN对比其他常用协议的优势](#2.3 CAN对比其他常用协议的优势)
  • [3. 数据链路层](#3. 数据链路层)
    • [3.1 CAN协议的数据帧](#3.1 CAN协议的数据帧)
    • [3.2 仲裁机制](#3.2 仲裁机制)
    • [3.3 访问控制](#3.3 访问控制)
    • [3.4 确认机制](#3.4 确认机制)
    • [3.5 错误检测和处理](#3.5 错误检测和处理)
    • [3.6 CAN的时间触发通信](#3.6 CAN的时间触发通信)
  • 4.GD32上的CAN
    • [4.1 特性](#4.1 特性)
    • [4.2 框图](#4.2 框图)
    • [4.3 通信模式](#4.3 通信模式)
    • [4.4 数据发送](#4.4 数据发送)
    • [4.5 数据接收](#4.5 数据接收)
    • [4.6 过滤功能](#4.6 过滤功能)
    • [4.7 GD32的CAN数据收发过程](#4.7 GD32的CAN数据收发过程)
  • 5.GD32的CAN代码编写及测试
    • [5.1 GD32中CAN的初始化](#5.1 GD32中CAN的初始化)
    • [5.2 CAN回环模式代码及测试](#5.2 CAN回环模式代码及测试)
    • [5.3 CAN普通模式代码及测试](#5.3 CAN普通模式代码及测试)
    • [5.4 过滤器的测试代码](#5.4 过滤器的测试代码)
    • [5.5 总结的注意要点](#5.5 总结的注意要点)

1.CAN通信简介

CAN 是指Controller Area Network 的缩写 ,该协议 是一种常用于汽车控制系统的通讯协议,它能够将汽车仪表、变速箱、辅助刹车系统、ECU(Electronic Control Unit)、控制模块、各种传感器等多个控制单元连接在一起,实现信息的实时同步。


该协议由研发和生产汽车电子产品著称的德国 BOSCH 公司 开发的,并最终成为国际标准(ISO11519以及ISO11898)。ISO11519以及ISO11898差异点如下:


对应OSI模型,CAN协议定义了其中的4层的内容,分别是物理层,数据链路层,传输层。

2.物理层

2.1 CAN总线的电气特性

CAN采用双绞线 传输差分信号 ,因此CAN传输的信号抗干扰能力很强。


既然CAN总线用到了差分信号,故其中有两条线 用于传输信号,分别是①CAN高线、②CAN低线。

那么CAN总线中的数字信号是怎么表示的呢?

以高速CAN为例,CAN高为3.5V,CAN低为1.5V,电位差为2V,此时定义为显性,对应数字信号:0 ;CAN高与CAN低 均为2.5V时,电位差为0V,此时定义为隐性,对应数字信号:1。如下图所示

注意混淆点:显性电平表示数字信号0;隐形电平表示是数字信号1.


在了解完CAN总线的信号表示后,我们再了解CAN总线的线路结构。

CAN总线线路结构有闭环开环两种形式。

  • 闭环结构:种CAN总线网络由ISO 11898标准定义,是高速、短距离的CAN 网络结构。在闭环结构的CAN总线网络中,总线两端各连接一个120欧的电阻,两根信号线形成回路。这种结构支持高速通信,通信速率通常在125kbit/s到1Mbit/s之间。
  • 开环结构:这种CAN总线网络由ISO 11519-2标准定义,是低速、远距离的CAN网络结构。在开环结构的CAN总线网络中,两根信号线独立,各自串联一个2.2k欧的电阻 。这种结构支持低速但长距离的通信,通信速率最高可达125kbit/s。

由CAN总线的线路结构,我们再想想CAN总线是什么类型的拓扑结构呢?

can是总线型拓扑结构

百度:拓扑结构定义

总线型拓扑的特点是:结点之间按广播方式通信,一个结点发出的信息,总线上的其它结点均可"收听"到

所有在CAN(Controller Area Network)通信中,并没有严格意义上的"主"和"从"的概念,这与传统的主从式通信有所不同。

CAN总线是一种基于消息广播的通信模式,采用多主竞争式总线结构,网络中各节点均可主动向其他节点发送信息。

有些网页提到CAN还有星型拓扑,树形拓扑,其实只是形式像是星型,树形。

如下图,只是形状是星型,但是没有中间的星型结点,本质还是总线型拓扑。

星型拓扑需要有中间结点。

2.2 CAN的位同步机制(了解,用于理解CAN的初始化参数的配置原理)

在讲CAN的位同步机制之前,我们先想一想CAN的通信方式是什么方式?是同步通信还是异步通信?

首先我们先回顾一下同异步通信的概念。
同步通信 是一种比特同步通信技术,要求发收双方具有同频同相的同步时钟信号,只需在传送报文的最前面附加特定的同步字符,使发收双方建立同步,此后便在同步时钟的控制下逐位发送/接收。
异步通信 是指发送端和接收端没有统一的时钟信号 ,它们各自使用自己的时钟控制数据传输。

由上述内容可知,CAN总线只有两根线:①CAN高线、②CAN低线。

CAN总线上是 没有时钟信号线的,而每个节点都有自己的时钟,故CAN通信是一种异步通信

然而,在 CAN 总线上,虽然每个节点都有自己的时钟,这些时钟并不完全同步,存在一定的偏差,称为时钟漂移。为了解决时钟漂移问题,CAN 协议采用了位同步机制,保证数据传输的准确性。


CAN 的位同步 是通过 硬同步 (Hard Synchronization)重新同步 (Resynchronization) 来实现的,下面是两种手段的简单介绍。

  • 硬同步

    时机: 发生在每个数据帧的起始位置,即检测到起始符 (SOF) 时。

    原理: 接收端检测到 SOF 时,会立即将自己的时钟设置为 SOF 的起始时间点,从而实现与发送端的时钟同步。

    作用: 将接收端的时钟"重置"为发送端的时钟,保证初始的时钟同步。

  • 重新同步

    时机: 发生在数据帧传输过程中,通常是在每个数据位的边缘(位边沿)。

    原理: 接收端会根据每个数据位实际传输时间 ,计算出时钟偏差。如果偏差超过一定阈值,接收端会根据重同步段的位时间调整自己的时钟,以减少偏差。

    作用: 根据数据传输的实际情况,对接收端的时钟进行微调,保证数据传输的准确性。


硬同步方式

为了实现 位同步 的硬同步方式 ,CAN协议定义了位时序这个重要的概念。

位时序 指的是 位时间采样点 的配置。我们在编写CAN驱动程序的时候,就需要对位时序进行配置。


位时间

位时间 是指一个数据位(比特位)的时间,它是由四个周期段组成:同步段传播段相位段 1相位段 2

下图中,Tq可以理解为节点自身CAN时钟一个节拍的时间 。一个完整的位一般由 8~25 个 Tq 组成。

为方便表示,下图中的高低电平直接代表信号逻辑 0 或逻辑 1(不是差分信号)。

  • 同步段 (Synch Segment):

    用来校准接收端时钟,使接收端与发送端同步。

    比如当总线上出现帧起始信号(SOF)时,其它节点上的控制器根据总线上的这个下降沿 ,对自己的位时序进行调整,把该下降沿包含到 SS 段内 ,这样根据起始帧来进行同步的方式称为硬同步

  • 传播段 (Propagation Segment):

    考虑信号在总线上传播的时间,确保所有节点都接收到数据位。

  • 相位段 1 (Phase Segment 1):

    用来灵活调整数据位采样的时间点。

  • 相位段 2 (Phase Segment 2):

    同样用来灵活调整数据位采样的时间点。


采样点

每个数据位传输期间,接收端在特定时间点进行采样,判断该数据位的值是 0 还是 1。
采样点通常位于相位段 1 和相位段 2 的交界处,可以根据实际应用需求进行调整。


硬同步举例

在硬同步阶段,当节点检测到本身SS段并不在总线电平下降沿跳变处,节点则会把自己的位时序中的 SS 段平移至总线出现下降沿的部分,后面三段也跟着上去,以获得同步。(可以理解为节点在检测到帧起始信号时才开始"设置段")


位时序的灵活配置

CAN 协议允许根据实际应用需求,对位时序进行灵活配置,例如调整各个时钟周期的长度、采样点的位置等。

不同的位时序配置会影响数据传输的性能,例如:更长的传播段可以提高抗干扰能力,但会降低数据传输速率。

不同的采样点可以影响数据位的识别精度,影响数据传输的可靠性。

以下是一个常见的位时序配置的例子:

  • 位时间 = 10 个时钟周期(即10Tq)
  • 同步段 = 1 个时钟周期
  • 传播段 = 3 个时钟周期
  • 相位段 1 = 3 个时钟周期
  • 相位段 2 = 3 个时钟周期
  • 采样点 = 相位段 1 和相位段 2 的交界处

重新同步方式

前面的硬同步只是对帧的第一位数据 才起作用,即只对帧起始信号起作用。

但如何保证 帧的第二位数据帧的第三位数据 ,...,帧的第n位数据也是正确的呢?

因而需要引入重新同步方式 ,它利用普通数据位 的高至低电平的跳变沿来同步 (帧起始信号是特殊的跳变沿)。


重新同步硬同步方式相似的地方是它们都使用 SS 段来进行检测,同步的目的都是使节点内的 SS 段把跳变沿包含起来。

重新同步的方式分为超前滞后 两种情况,以总线跳变沿与 SS 段的相对位置进行区分,下面举例设SJW为2Tq

相位超前 ,节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前 2Tq,这时控制器在下一个位时序中的 PBS1 段增加 2Tq 的时间长度,使得节点与总线时序重新同步。

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


在重新同步的时候,PBS1 和 PBS2 中增加或减少的这段时间长度被定义为重新同步补偿宽度SJW*(reSynchronization Jump Width)

一般来说 CAN 控制器会限定 SJW 的最大值,如限定了最大 SJW=3Tq 时,单次同步调整的时候不能增加或减少超过 3Tq 的时间长度,若有需要,控制器会通过多次小幅度调整来实现同步。

当控制器设置的 SJW 极限值较大时,可以吸收的误差加大,但通讯的速度会下降.


2.3 CAN对比其他常用协议的优势

相比于RS485,SPI,IIC,为什么在汽车上常用CAN协议呢?

汽车上常用CAN(Controller Area Network)协议而不是RS485、SPI或IIC协议,主要有以下几个原因:

  • 实时性和可靠性:CAN协议专为汽车设计,支持高速数据传输,并且具有错误检测、通知和恢复功能,能够确保数据的完整性和实时性。这对于汽车控制系统中的关键应用(如发动机控制、制动系统、安全系统等)至关重要。
  • 多主通信:CAN总线是一个多主通信的串行总线,这意味着多个设备可以在没有主机的情况下进行通信,每个设备都可以主动发送数据。这种特性使得CAN总线在汽车应用中特别有用,因为汽车中有许多需要相互通信的设备和系统。
  • 灵活性和可扩展性:CAN协议支持不同的消息格式和优先级设置,可以根据需要进行灵活配置。此外,CAN总线可以连接多个设备,并且易于扩展,这使得它非常适合用于复杂的汽车系统。
  • 成本效益:尽管CAN协议的实现可能比某些其他通信协议更复杂,但由于其广泛的应用和成熟的生态系统,CAN硬件和软件的成本已经相对较低。此外,CAN协议的高效性和可靠性也可以降低系统的总体成本。

RS485虽然也可以用于多设备之间的通信,但它没有CAN协议的实时性和可靠性高,只能是一主多从

SPI和IIC协议则主要用于短距离、低速率的数据传输,不适合汽车中需要高速、实时通信的应用。

3. 数据链路层

数据链路层的功能主要有封装成帧,媒介访问控制,差错控制等功能。

3.1 CAN协议的数据帧

CAN协议的帧格式大概结构如下:

  • 起始符 (SOF)
  • 仲裁域 (arbitration field)
  • 控制域 (control field)
  • 数据域 (data field)
  • CRC校验域 (CRC field)
  • 确认域 (ACK field)
  • 结束符 (EOF)

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

其中,数据帧和遥控帧有标准格式和扩展格式两种格式。

标准格式有 11 个位的标识符(Identifier: 以下称 ID),扩展格式有 29 个位的 ID。


CAN数据帧

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

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

数据帧标准格式各个位的介绍

域段 域段名 位宽:bit 描述
帧起始 SOF(Start Of Frame) 1 数据帧起始标志,固定为1bit显性('b0)
ID Identify(ID) 11 本数据帧的 ID 信息, ID 信息的作用:① 如果同时有多个节点发送数据时,作为优先级依据(仲裁机制);② 目标节点通过 ID 信息来接受数据(验收滤波技术)
RTR Remote Transmission Request BIT 1 RTR标识是否是远程帧(0,数据帧;1,远程帧),在数据帧里这一位为显性('b0)
IDE Identifier Extension Bit 1 IDE用于区分标准格式与扩展格式,在标准格式中 IDE 位为显性('b0),在扩展格式里 IDE 位为隐性('b1)
r0 保留位 1 1bit保留位,固定为1'b0
DLC data length 4 由 4 位组成,MSB 先行(高位先行),它的二进制编码用于表示本报文中的数据段含有多少个字节,DLC 段表示的数字为0到8,若接收方接收到 9~15 的时候并不认为是错误
Data data数据 0~64 据帧的核心内容,它由 0~8 个字节(0 ~ 64位)组成,MSB 先行
CRC段 CRC 15 CRC段用于检查帧传输错误,发送方以一定的方法计算包括:帧起始、仲裁段、控制段、数据段;接收方以同样的算法计算 CRC 值并进行比较,如果不同则会向发送端反馈出错信息,重新发送;计算和出错处理一般由 CAN 控制器硬件完成或由软件控制最大重发数。
CRC界定符 CRC 1 CRC 界定符(用于分隔的位),为隐性位(1'b1),主要作用是把CRC 校验码与后面的 ACK 段间隔起来
ACK 槽 ACK slot 1 在 ACK 槽位中,发送端 发送的为隐性位,而接收端则在这一位中发送显性位以示应答;发送 ACK/返回 ACK这个过程使用到回读机制,即发送方先在 ACK 槽发送隐性位后,回读到的总线上的电平为显性0,发送方才知道它发送成功了,不用重发
ACK界定符 1 在 ACK 槽和帧结束之间由 ACK 界定符间隔开,为隐性位
帧结束 EOF 7 由发送端发送 7 个隐性位表示结束

远程帧

远程帧,相比数据帧,其中缺少了数据域。


其他帧 暂略

3.2 仲裁机制

在总线空闲时,若多个设备同时发送数据,那么哪个设备才能优先占用总线呢?这内容旧涉及到了CAN的仲裁机制。

仲裁机制的工作原理:

  1. 唯一标识符:
    仲裁机制使用到了 仲裁段的 Identify,这个Identify称为节点的标识符
    为了确保每个节点的标识符都是唯一的,CAN 协议规定了每个节点的标识符必须是唯一的。
    CAN 协议规定了节点标识符的优先级,标识符越小,优先级越高。
  2. 总线空闲检测:
    什么情况下,总线是空闲的?
    对于任意一个节点 而言,只要它监听到总线上连续出现了11位隐性电平 (显/隐性电平: 在总线上隐性电平通常表示逻辑1,而显性电平通常表示逻辑0),那么该节点就会认为总线当前处于空闲状态
    在总线一开始工作的时候,所有节点都输出隐性电平。
    当总线处于空闲状态时,多个设备可能同时开始发送数据。
  3. 逐位比较:
    当多个节点同时发送数据时,每个节点都会将自己的标识符发送到总线上。
    在发送每一位ID时,发送节点也会读取总线上的电平状态 ,并将其与自己发送的电平进行比较。
    ①若总线上的电平和本节点要发送的电平一致,则本节点继续发送下一位ID。
    ②若总线上的电平和本节点要发送的电平不一致,则说明由更高优先级的消息正在发送,本节点会立即停止发送并转为监听状态。
  4. 仲裁获胜:
    最终,标识符较小的节点会获胜,继续发送数据。标识符较大的节点则会停止发送数据,并进入接收状态。

为什么 标识符越小,优先级越高 ?(了解)

前面讲过,当CAN_H和CAN_L之间的电压差为某一特定值(如2V)时,表示显性电平;而当两根线的电压几乎相同时(电位差接近0V),则表示隐性电平。

当多个设备同时尝试发送数据时,如果总线上同时出现了显性电平和隐性电平,那么由于显性电平的物理特性(即更高的电压差),它会覆盖隐性电平,使得总线状态被置为显性电平。

而显性电平对应逻辑0,而隐性电平对应逻辑1。标识符采用的是MSB,标识符越小,在逐位比较中先比较的位是更有可能输出显性电平,从而能够优先占用总线资源进行传输。


例子如下图

节点1的ID号是 10110101101,节点2的ID号是 10110101100

在仲裁过程中,在比较到两个节点的ID号的最后一位时,节点2的ID号最后一位是逻辑0,在总线上用显性电平表示;节点2的ID号最后一位是逻辑1,用隐性电平表示。

而此时总线上会表现出显性电平,节点2在读取总线时发现电平与自己发送的隐性电平不一样,这时就让出总线的控制器。节点1仲裁胜利!

总裁核心原理: 将自己要比较的位与总线上的状态相与,只有线与的结果与本身一致时,仲裁才能够通过。

注意:其实在报文发送上去的过程,采用的是广播的方式,在节点1和节点2总裁的同时,总线上所有的节点都能够监听到它们的ID号,只不过也在同时进行验收滤波,只有监听到的ID号存在ID表中,该节点才会选择继续监听该报文后面的数据。

3.3 访问控制

3.4 确认机制

CAN协议中,发送节点如何确认自己发送的数据帧被接收节点正确接收呢?这就涉及到了CAN的确认机制。

在CAN帧结构中,ACK段是一个由两个位组成的区域,包括ACK槽(ACK SLOT)和ACK分隔符(ACK DELIMITER)。发送节点在发送数据或远程帧时,会在ACK槽置入一个隐性位(逻辑1)。


ACK机制的工作原理

  1. 发送过程:
    发送节点在发送数据帧时,会在ACK槽置入一个隐性位(逻辑1)。
    发送节点在发送数据的同时,会对总线上的数据进行回读,以监控ACK槽的状态。
  2. 接收过程:
    接收节点在接收到数据帧后,会检查数据的正确性。
    如果数据正确无误,接收节点会在ACK槽期间发送一个显性位(逻辑0),以此向发送节点确认消息已成功接收。
  3. 确认与重发:
    发送节点通过回读总线,如果检测到ACK槽为显性位(逻辑0),则表示至少有一个接收节点正确接收了数据,发送成功。
    如果检测到ACK槽仍为隐性位(逻辑1),则表示没有接收节点正确接收数据,此时发送节点会重新发送数据帧。

3.5 错误检测和处理

CRC段:

该段用于检查帧传输错误,发送方以一定的方法计算包括:帧起始、仲裁段、控制段、数据段;

接收方以同样的算法计算 CRC 值并进行比较,如果不同则会向发送端反馈出错信息,重新发送;

计算和出错处理一般由 CAN 控制器硬件 完成 或 由 软件 控制最大重发数。

该段由 15 个位的 CRC 顺序和 1 个位的 CRC 界定符(用于分隔的位)组成,它为隐性位(逻辑1),主要作用是把CRC 校验码与后面的 ACK 段间隔起来。

3.6 CAN的时间触发通信

时间触发CAN:建立在标准CAN上的高层协议,通过对网络中所有节点的通信进行同步调度,实现每个节点在固定时间内发送信息,无需再进行优先级仲裁。

其他内容暂略

4.GD32上的CAN

资料参考:GD32F305官方资料GD32F305用户手册

4.1 特性

◼ 支持 CAN 总线协议 2.0A 和 2.0B;

◼ 通信波特率最大为 1Mbit/s;

◼ 支持时间触发通信(Time-triggered communication);

◼ 中断使能和清除。

发送功能

◼ 3 个发送邮箱;

◼ 支持发送优先级;

◼ 支持发送时间戳。

接收功能

◼ 2 个深度为 3 的接收 FIFO;

◼ 在非 GD32F30x CL 系列产品中,具有 14 个过滤器;

◼ 在 GD32F30x CL 系列产品中,具有 28 个过滤器;

◼ FIFO 锁定功能。

时间触发通信

◼ 在时间触发通信模式下禁用自动重传;

◼ 16 位定时器;

◼ 接收时间戳;

◼ 发送时间戳。

4.2 框图

疑问:

1.发送邮箱就是类似与发送的缓存BUF吗

2.两个深度为3的FIFO,即一个CAN控制器就拥有2*3个接收邮箱吗

4.3 通信模式

CAN 总线控制器有 4 种通信模式:

  • 静默(Silent)通信模式;
    在静默通信模式下,可以从 CAN 总线接收数据,但不向总线发送任何数据。
  • 回环(Loopback)通信模式;
    在回环通信模式下,由 CAN 总线控制器发送的数据可以被自己接收并存入接收FIFO,同时这些发送数据也送至CAN 网络。
  • 回环静默(Loopback and Silent)通信模式;
    在回环静默通信模式下,CAN 的 RX 和 TX 引脚与 CAN 网络断开。CAN 总线控制器既不从CAN 网络接收数据,也不向 CAN 网络发送数据,其发送的数据仅可以被自己接收。
  • 正常(Normal)通信模式。
    CAN 总线控制器通常工作在正常通信模式下,可以从 CAN 总线接收数据,也可以向 CAN 总线发送数据。

4.4 数据发送




4.5 数据接收



4.6 过滤功能

一个待接收的数据帧会根据其标识符(Identifier)进行过滤:硬件会将通过过滤的帧送至接收FIFO,并丢弃没有通过过滤的帧。

4.7 GD32的CAN数据收发过程

在GD32芯片中,片内拥有的是CAN控制器。

发送过程:

CAN控制器将CPU传来的信号转换为逻辑电平(即逻辑0-显性电平或者逻辑1-隐性电平)。CAN发射器接收逻辑电平之后,再将其转换为差分电平输出到CAN总线上。

接收过程:

CAN接收器将CAN_H 和CAN_L 线上传来的差分电平转换为逻辑电平输出到CAN控制器,CAN控制器再把该逻辑电平转化为相应的信号发送到CPU上。

5.GD32的CAN代码编写及测试

5.1 GD32中CAN的初始化

gpio的配置

1.1 CAN和GPIO时钟初始化

1.2 CAN管脚复用(需要注意是 完全复用 还是 部分复用)



CAN控制器参数的配置

cpp 复制代码
/* CAN initiliaze parameters structure */
typedef struct
{
    uint8_t working_mode;                         /*!< 配置CAN的工作模式 */ 
    uint8_t resync_jump_width;                    /*!< 重新同步跳跃宽度 */
    uint8_t time_segment_1;                       /*!< 配置 BS1 段长度 */
    uint8_t time_segment_2;                       /*!< 配置 BS2 段长度 */
    ControlStatus time_triggered;                 /*!< 时间触发通信方式 */
    ControlStatus auto_bus_off_recovery;          /*!< 自动离线管理 */
    ControlStatus auto_wake_up;                   /*!< 自动唤醒功能 */
    ControlStatus auto_retrans;                   /*!< 自动重传模式 */
    ControlStatus rec_fifo_overwrite;             /*!< 配置接收 FIFO 锁定 */
    ControlStatus trans_fifo_order;               /*!< 配置 FIFO 优先级 */
    uint16_t prescaler;                           /*!< 配置CAN外设的时钟频率 */
}can_parameter_struct;
  • working_mode

    设置CAN的工作模式,可设置为正常通信模式(CAN_NORMAL_MODE),回环通信模式(CAN_LOOPBACK_MODE),静默通信模式(CAN_SILENT_MODE)以及回环静默通信模式(CAN_SILENT_LOOPBACK_MODE)。

    CAN 总线控制器通常工作在正常通信模式下,可以从 CAN 总线接收数据,也可以向 CAN 总线发送数据。(详情请查看GD32F10x用户手册)

  • resync_jump_width

    设置CAN的再同步补偿宽度SJW,对CAN网络节点同步误差进行补偿占1~4个时间单元。(CAN_BT_SJW_1/2/3/4TQ)

  • time_segment_1

    设置CAN位时序中BS1段长度,可以配置为1~16个时间单元。(CAN_BT_BS1_1 ~ 16TQ)

    注意:time_segment_1是包含了传播时间段和相位缓存段1,所有其范围是1 ~ 16,而不是 1 ~ 8

  • time_segment_2

    设置CAN位时序中BS2段长度,可以配置为1~8个时间单元。(CAN_BT_BS2_1 ~ 8TQ)

  • time_triggered

    用于配置是否使用时间触发功能(ENABLE / DISABLE)。在这种通信模式下,自动重发功能是禁止的。

  • auto_bus_off_recovery

    CAN网络中,当节点因为发送错误计数器(TEC)超过一定阈值(如256)时,会进入总线关闭(Bus Off)状态,此时节点既不能接收总线上的报文,也不能向总线发送报文.

    当auto_bus_off_recovery字段被设置为启用(如ENABLE或TRUE)时,CAN控制器会在节点进入Bus Off状态后,自动尝试恢复通信,不需要软件干预。

  • auto_wake_up

    用于配置是否使用自动唤醒功能(ENABLE / DISABLE),使用自动唤醒功能后会在检测到总线活动后自动唤醒。

  • auto_retrans

    用于配置是否使用自动重传功能(ENABLE / DISABLE),使用自动重传功能时,会一直发送报文直到成功为止,否则只会发送一次报文。

  • rec_fifo_overwrite

    ENABLE(启用):当接收FIFO溢出时,新接收到的数据帧将覆盖FIFO中最旧的数据帧。这意味着,如果FIFO的容量不足以存储所有接收到的数据帧,则最早接收到的数据帧将被丢弃。

    DISABLE(禁用):当接收FIFO溢出时,新接收到的数据帧将被丢弃,而FIFO中已有的数据帧将保持不变。这有助于保护关键数据不被意外覆盖。

  • trans_fifo_order

    用于设置是否使用发送报文的优先级判定方法(ENABLE / DISABLE),使能后,以报文存入FIFO的先后顺序来发送,否则按照报文ID的优先级来发送。

  • prescaler

    设置CAN外设的时钟分频,写入的值即为分频值。可控制时间片的时间长度。

快速计算:BaudRate = APB1_LCK / ( 1 + time_segment_1 +time_segment_2 ) / prescaler

例:APB1总线时钟频率为54MHZ,BS1=5,BS2=3,prescaler=12,实际波特率为500Kbps。


CAN过滤器的配置

cpp 复制代码
/* CAN filter parameters structure */
typedef struct
{
    uint16_t filter_list_high;                 /*!< 过滤器列表高位数 */
    uint16_t filter_list_low;                  /*!< 过滤器列表低位数 */
    uint16_t filter_mask_high;                 /*!< 滤波掩码数高位数 */
    uint16_t filter_mask_low;                  /*!< 滤波掩码数低位数 */
    uint16_t filter_fifo_number;               /*!< 接收与过滤器相关联的FIFO */
    uint16_t filter_number;                    /*!< 筛选器编号 */
    uint16_t filter_mode;                      /*!< 列表或掩码模式 */
    uint16_t filter_bits;                      /*!< 筛选器位宽 */
    ControlStatus filter_enable;               /*!< 是否使能改筛选器 */
}can_filter_parameter_struct;
  • filter_list_high

    用于存储要过滤的ID,若过滤器工作在32位模式,他存储的是所过滤ID的高16位;若过滤器工作在16位模式,它存储的就是一个完整的要过滤的ID。

  • filter_list_low

    用于存储要过滤的ID,若过滤器工作在32位模式,他存储的是所过滤ID的低16位;若过滤器工作在16位模式,它存储的就是一个完整的要过滤的ID。

  • filter_mask_high

    filter_mask_high的存储的内容分两种情况,当过滤器工作在列表模式时,他的功能与filter_list_high相同,都是存储要过滤的ID;当工作在掩码模式时,它存储的是与filter_list_high成员对应的掩码。

  • filter_mask_low

    filter_mask_low的存储的内容分两种情况,当过滤器工作在列表模式时,他的功能与filter_list_low相同,都是存储要过滤的ID;当工作在掩码模式时,它存储的是与filter_list_low成员对应的掩码。

    在掩码模式下,filter_mask_high与filter_mask_low填入的是要筛选的掩码,掩码为1时,筛选的ID必须与ID相同。

    列表模式下,筛选的ID需与ID相同。

  • filter_fifo_number

    用于设置当报文通过过滤器匹配后,该报文会被存储到哪个接收FIFO中(CAN_FIFO0/1)。

  • filter_number

    用于设计过滤器的编号。可以通过设置多个过滤器对特定的ID进行过滤。

  • filter_mode

    设置过滤器的工作模式,可以设置为列表模式(CAN_FILTERMODE_LIST),也可以设置为掩码模式(CAN_FILTERMODE_MASK)。

  • filter_bits

    设置过滤器位宽(32位或16位)。

  • filter_enable

    用于设置是否使能这个过滤器(ENABLE/DISABLE)。

5.2 CAN回环模式代码及测试

调试需要使用回环模式,回环模式下可以不考虑CAN波特率,不考虑ID,便于调试。

正常模式下需要接外部CAN设备,并且双方配置需完全正确,否则一般发送数据就会失败,返回CAN_TRANSMIT_PENDING或者CAN_TRANMIT_FAILED,此时无法确认时双方匹配问题,或者硬件问题,还是自身配置问题,建议使用回环模式调试,先确认自身CAN配置是否正确。

下面代码掩码的过滤模式,过滤全填0,所有数据都会接收,不会过滤,确保接收中断可以接收到数据后,后面再根据实际过滤ID进行填写。

cpp 复制代码
#include "can.h"
#include "gd32f30x.h"
#include "gd32f30x_can.h"
#include <string.h>
#include "log.h"
#include "invt_os.h"


#define CAN_RCU              RCU_CAN0
#define CAN_GPIO_PORT_RCU    RCU_GPIOB

#define CAN_GPIO_PORT        GPIOB
#define CAN_RX_GPIO_PIN      GPIO_PIN_8
#define CAN_TX_GPIO_PIN      GPIO_PIN_9

#define CAN_CONTROLLER       CAN0


int32_t can0_init(void)
{
    can_parameter_struct can_parameter;
    can_filter_parameter_struct can_filter;

    /* enable can clock */
    rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(CAN_RCU);
    rcu_periph_clock_enable(CAN_GPIO_PORT_RCU);

    /* configure CAN0 GPIO, CAN0_TX(PB9) and CAN0_RX(PB8) */
    gpio_init(CAN_GPIO_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CAN_TX_GPIO_PIN);
    gpio_init(CAN_GPIO_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, CAN_RX_GPIO_PIN);
    gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, ENABLE);  /* 注意:PB8,PB9是在部分复用是才是CAN,而不是全复用!!! */

    /* initialize CAN structures */
    can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
    can_struct_para_init(CAN_FILTER_STRUCT, &can_filter);
    /* initialize CAN register */
    can_deinit(CAN_CONTROLLER);

    /* initialize CAN */
    can_parameter.time_triggered = DISABLE;             /* 关闭时间触发通信 */
    can_parameter.auto_bus_off_recovery = ENABLE;       /* 允许离线自行回复 */
    can_parameter.auto_wake_up = ENABLE;                /* 允许自唤醒 */
    can_parameter.auto_retrans = ENABLE;                /* 允许自动重传 */
    can_parameter.rec_fifo_overwrite = DISABLE;         /* FIFO满了,不允许重盖写入 */
    can_parameter.trans_fifo_order = DISABLE;           /* 使能后,以报文存入FIFO的先后顺序来发送,否则按照报文ID的优先级来发送 */
    can_parameter.working_mode = CAN_LOOPBACK_MODE;       /* 回环模式 */
    can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;   /* 对CAN网络节点同步误差进行补偿占1个CAN时钟节拍 */
    can_parameter.time_segment_1 = CAN_BT_BS1_7TQ;      /* 相位1使用7个CAN时钟节拍 */
    can_parameter.time_segment_2 = CAN_BT_BS2_4TQ;      /* 相位2使用4个CAN时钟节拍 */
    /* AHB1 60M ,60000/(20*(7+4+1)) = 250kbps */
    can_parameter.prescaler = 20;                       /* CAN时钟的分配系数 */
    can_init(CAN_CONTROLLER, &can_parameter);

    /* initialize filter */
    /* CAN0 filter number */
    //3.配置CAN过滤器
    // can_filter.filter_list_high = (((uint32_t)0x000<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;   /* 过滤器高字节 */
    // can_filter.filter_list_low =  (((uint32_t)0x000<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);     /* 过滤器低字节 */
    can_filter.filter_list_high = 0x56;
    can_filter.filter_list_low = 0x56;
    can_filter.filter_mask_high = 0;                    /* 过滤器掩码数高位,0表示全盘接收 */
    can_filter.filter_mask_low = 0;                     /* 过滤器掩码数低位,0表示全盘接收 */
    can_filter.filter_fifo_number = CAN_FIFO0;          /* 滤过器关联FIFO0 */
    can_filter.filter_number = 0;                       /* 0号过滤器 */
    can_filter.filter_mode = CAN_FILTERMODE_MASK;       /* 掩码模式 */
    can_filter.filter_bits = CAN_FILTERBITS_32BIT;      /* 32位 */
    can_filter.filter_enable = ENABLE;
    can_filter_init(&can_filter);
    //4.配置中断
    nvic_irq_enable(CAN0_RX0_IRQn, 0, 0); 
    can_interrupt_enable(CAN0,CAN_INT_RFNE0);

    return 0;
}

int32_t can0_deinit(void)
{
    return 0;
}

int32_t can0_write(const char *buffer, size_t len)
{
    uint8_t i,mbox;
    can_trasnmit_message_struct can_tx_msg;
    uint8_t timeout = 1000;

    if (len > 8) {
        len = 8;
    }

    can_tx_msg.tx_efid = 0x123;//扩展帧才起作用,低29位有效,故这里0xffffffff的实际有效值是0b1 1111 1111 1111 1111 1111 1111 1111 = 536870911
    can_tx_msg.tx_sfid = 0x56;//标准帧才其作用,低11位有效,故0xfff的实际有效值是0b111 1111 1111 = 2047
    can_tx_msg.tx_ff = CAN_FF_STANDARD;
    can_tx_msg.tx_ft = CAN_FT_DATA;//数据帧 CAN_FF_STANDARD  CAN_FF_EXTENDED
    can_tx_msg.tx_dlen = len;
    for(i=0;i<len;i++)
    {
        can_tx_msg.tx_data[i] = buffer[i];
    }
    mbox = can_message_transmit(CAN0,&can_tx_msg);				//发送CAN报文

    //等待发送完成
    while(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_PENDING) {
        if(timeout-- == 0)
        {
            LOG_ERRO("CAN transmit failed.\n");
            return 1;
        }
        ios_sleep(1);  // 1ms delay for the CAN bus to be free. If not, the next transmit may fail.
    }

    if(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_OK)
    {
        return 0;
    }
    return 1;
}


can_receive_message_struct can_rx_msg;
int32_t can0_read(char *buffer, size_t len)
{
    LOG_INFO("sfid:%d, rx_efid:%d, rx_ff;%d, rx_dlen:%d\n", 
        can_rx_msg.rx_sfid, can_rx_msg.rx_efid, can_rx_msg.rx_ff, can_rx_msg.rx_dlen);
    memcpy(buffer, can_rx_msg.rx_data, 8);

    return 0;
}

void CAN0_RX0_IRQHandler(void)
{
    /* check the receive message */
    if(can_interrupt_flag_get(CAN0,CAN_INT_FLAG_RFL0))
    {
        memset(&can_rx_msg, 0, sizeof(can_rx_msg));//清空接收结构体
        can_message_receive(CAN0, CAN_FIFO0, &can_rx_msg);
    }
}

测试主函数

cpp 复制代码
    can0_init();
    char recv_buf[10] = {0};

    while(1) {
        can0_write("123578", sizeof("123578")-1);

        ios_sleep(1000);
        memset(recv_buf, 0, sizeof(recv_buf));
        can0_read(recv_buf, 8);
        LOG_INFO("can read:%s\n", recv_buf);
    }

查看输出的日志,可以设备自身自发自收

5.3 CAN普通模式代码及测试

相比与上面的回环模式,下面的发送代码中,加了发送超时则失败的判断

cpp 复制代码
#include "can.h"
#include "gd32f30x.h"
#include "gd32f30x_can.h"
#include <string.h>
#include "log.h"
#include "invt_os.h"


#define CAN_RCU              RCU_CAN0
#define CAN_GPIO_PORT_RCU    RCU_GPIOB

#define CAN_GPIO_PORT        GPIOB
#define CAN_RX_GPIO_PIN      GPIO_PIN_8
#define CAN_TX_GPIO_PIN      GPIO_PIN_9

#define CAN_CONTROLLER       CAN0


int32_t can0_init(void)
{
    can_parameter_struct can_parameter;
    can_filter_parameter_struct can_filter;

    /* enable can clock */
    rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(CAN_RCU);
    rcu_periph_clock_enable(CAN_GPIO_PORT_RCU);

    /* configure CAN0 GPIO, CAN0_TX(PB9) and CAN0_RX(PB8) */
    gpio_init(CAN_GPIO_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CAN_TX_GPIO_PIN);
    gpio_init(CAN_GPIO_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, CAN_RX_GPIO_PIN);
    gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, ENABLE);  /* 注意:PB8,PB9是在部分复用是才是CAN,而不是全复用!!! */

    /* initialize CAN structures */
    can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
    can_struct_para_init(CAN_FILTER_STRUCT, &can_filter);
    /* initialize CAN register */
    can_deinit(CAN_CONTROLLER);

    /* initialize CAN */
    can_parameter.time_triggered = DISABLE;             /* 关闭时间触发通信 */
    can_parameter.auto_bus_off_recovery = ENABLE;       /* 允许离线自行回复 */
    can_parameter.auto_wake_up = ENABLE;                /* 允许自唤醒 */
    can_parameter.auto_retrans = ENABLE;                /* 允许自动重传 */
    can_parameter.rec_fifo_overwrite = DISABLE;         /* FIFO满了,不允许重盖写入 */
    can_parameter.trans_fifo_order = DISABLE;           /* 使能后,以报文存入FIFO的先后顺序来发送,否则按照报文ID的优先级来发送 */
    can_parameter.working_mode = CAN_NORMAL_MODE;       /* 普通模式 */
    can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;   /* 对CAN网络节点同步误差进行补偿占1个CAN时钟节拍 */
    can_parameter.time_segment_1 = CAN_BT_BS1_7TQ;      /* 相位1使用7个CAN时钟节拍 */
    can_parameter.time_segment_2 = CAN_BT_BS2_4TQ;      /* 相位2使用4个CAN时钟节拍 */
    /* AHB1 60M ,60000/(20*(7+4+1)) = 250kbps */
    can_parameter.prescaler = 20;                       /* CAN时钟的分配系数 */
    can_init(CAN_CONTROLLER, &can_parameter);

    /* initialize filter */
    /* CAN0 filter number */
    //3.配置CAN过滤器
    // can_filter.filter_list_high = (((uint32_t)0x000<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;   /* 过滤器高字节 */
    // can_filter.filter_list_low =  (((uint32_t)0x000<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);     /* 过滤器低字节 */
    can_filter.filter_list_high = 0x56;
    can_filter.filter_list_low = 0x56;
    can_filter.filter_mask_high = 0;                    /* 过滤器掩码数高位,0表示全盘接收 */
    can_filter.filter_mask_low = 0;                     /* 过滤器掩码数低位,0表示全盘接收 */
    can_filter.filter_fifo_number = CAN_FIFO0;          /* 滤过器关联FIFO0 */
    can_filter.filter_number = 0;                       /* 0号过滤器 */
    can_filter.filter_mode = CAN_FILTERMODE_MASK;       /* 掩码模式 */
    can_filter.filter_bits = CAN_FILTERBITS_32BIT;      /* 32位 */
    can_filter.filter_enable = ENABLE;
    can_filter_init(&can_filter);
    //4.配置中断
    nvic_irq_enable(CAN0_RX0_IRQn, 0, 0); 
    can_interrupt_enable(CAN0,CAN_INT_RFNE0);

    return 0;
}





int32_t can0_deinit(void)
{
    return 0;
}

int32_t can0_write(const char *buffer, size_t len)
{
    uint8_t i,mbox;
    can_trasnmit_message_struct can_tx_msg;
    uint8_t timeout = 1000;

    if (len > 8) {
        len = 8;
    }

    can_tx_msg.tx_efid = 0x123;//扩展帧才起作用,低29位有效,故这里0xffffffff的实际有效值是0b1 1111 1111 1111 1111 1111 1111 1111 = 536870911
    can_tx_msg.tx_sfid = 0x56;//标准帧才其作用,低11位有效,故0xfff的实际有效值是0b111 1111 1111 = 2047
    can_tx_msg.tx_ff = CAN_FF_STANDARD;
    can_tx_msg.tx_ft = CAN_FT_DATA;//数据帧 CAN_FF_STANDARD  CAN_FF_EXTENDED
    can_tx_msg.tx_dlen = len;
    for(i=0;i<len;i++)
    {
        can_tx_msg.tx_data[i] = buffer[i];
    }
    mbox = can_message_transmit(CAN0,&can_tx_msg);				//发送CAN报文

    //等待发送完成
    while(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_PENDING) {
        if(timeout-- == 0)
        {
            LOG_ERRO("CAN transmit failed.\n");
            return 1;
        }
        ios_sleep(1);  // 1ms delay for the CAN bus to be free. If not, the next transmit may fail.
    }

    if(can_transmit_states(CAN0,mbox) == CAN_TRANSMIT_OK)
    {
        return 0;
    }
    return 1;
}


can_receive_message_struct can_rx_msg;
int32_t can0_read(char *buffer, size_t len)
{
    LOG_INFO("sfid:%d, rx_efid:%d, rx_ff;%d, rx_dlen:%d\n", 
        can_rx_msg.rx_sfid, can_rx_msg.rx_efid, can_rx_msg.rx_ff, can_rx_msg.rx_dlen);
    memcpy(buffer, can_rx_msg.rx_data, 8);

    return 0;
}

void CAN0_RX0_IRQHandler(void)
{
    /* check the receive message */
    if(can_interrupt_flag_get(CAN0,CAN_INT_FLAG_RFL0))
    {
        memset(&can_rx_msg, 0, sizeof(can_rx_msg));//清空接收结构体
        can_message_receive(CAN0, CAN_FIFO0, &can_rx_msg);
    }
}

测试主函数

cpp 复制代码
    can0_init();
    char recv_buf[10] = {0};

    while(1) {
        can0_write("123578", sizeof("123578")-1);

        ios_sleep(1000);
        memset(recv_buf, 0, sizeof(recv_buf));
        can0_read(recv_buf, 8);
        LOG_INFO("can read:%s\n", recv_buf);
    }

测试工具

通过usb-to-can调试器进行调试

插上usb-to-can,安装驱动,设备管理器中如下显示:

打开pcanView调试助手:

选择好CAN的波特率,以及接收的帧格式。这里选择250KBit/s,标准帧。

在没有连接上USB to CAN 模块前,设备日志输出发送错误。

连接上USB to CAN 模块后,设备数据正常输出,并且在PCAN-VIEW软件中查看到接收的数据。


通过PCAN-View软件发送标准数据帧,看出设备日志。

看到设备能够正确收到数据


改变发送帧的配置,使用拓展帧,并改变ID

查看到设备输出日志有所变化,ID变了,rx_ff:4表明是用了扩展帧。

5.4 过滤器的测试代码

把CAN初始化代码改成如下代码,则只有ID=0x56的标准帧才会通过 过滤器,被设备接收到。

cpp 复制代码
int32_t can0_init(void)
{
    uint32_t filter_id = 0;
    can_parameter_struct can_parameter;
    can_filter_parameter_struct can_filter;

    /* enable can clock */
    rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(CAN_RCU);
    rcu_periph_clock_enable(CAN_GPIO_PORT_RCU);

    /* configure CAN0 GPIO, CAN0_TX(PB9) and CAN0_RX(PB8) */
    gpio_init(CAN_GPIO_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CAN_TX_GPIO_PIN);
    gpio_init(CAN_GPIO_PORT, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, CAN_RX_GPIO_PIN);
    gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, ENABLE);  /* 注意:PB8,PB9是在部分复用是才是CAN,而不是全复用!!! */

    /* initialize CAN structures */
    can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
    can_struct_para_init(CAN_FILTER_STRUCT, &can_filter);
    /* initialize CAN register */
    can_deinit(CAN_CONTROLLER);

    /* initialize CAN */
    can_parameter.time_triggered = DISABLE;             /* 关闭时间触发通信 */
    can_parameter.auto_bus_off_recovery = ENABLE;       /* 允许离线自行回复 */
    can_parameter.auto_wake_up = ENABLE;                /* 允许自唤醒 */
    can_parameter.auto_retrans = ENABLE;                /* 允许自动重传 */
    can_parameter.rec_fifo_overwrite = DISABLE;         /* FIFO满了,不允许重盖写入 */
    can_parameter.trans_fifo_order = DISABLE;           /* 使能后,以报文存入FIFO的先后顺序来发送,否则按照报文ID的优先级来发送 */
    can_parameter.working_mode = CAN_LOOPBACK_MODE;       /* 回环模式,自己发出去的数据,自己也能接收得到,用于测试 */
    can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;   /* 对CAN网络节点同步误差进行补偿占1个CAN时钟节拍 */
    can_parameter.time_segment_1 = CAN_BT_BS1_7TQ;      /* 相位1使用7个CAN时钟节拍 */
    can_parameter.time_segment_2 = CAN_BT_BS2_4TQ;      /* 相位2使用4个CAN时钟节拍 */
    /* AHB1 60M ,60000/(20*(7+4+1)) = 250kbps */
    can_parameter.prescaler = 20;                       /* CAN时钟的分配系数 */
    can_init(CAN_CONTROLLER, &can_parameter);

    /* initialize filter */
    /* CAN0 filter number */
    //3.配置CAN过滤器
    // filter_value = (0x111<<3) | CAN_FT_DATA | CAN_FF_STANDARD;
    // can_filter.filter_list_high = filter_value>>16;   /* 过滤器高字节 */
    // can_filter.filter_list_low =  filter_value;     /* 过滤器低字节 */
    filter_id = 0x56;
    can_filter.filter_list_high = (((uint32_t)filter_id<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;   /* 过滤器高字节 */
    can_filter.filter_list_low =  (((uint32_t)filter_id<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);     /* 过滤器低字节 */
    can_filter.filter_mask_high = 0;                    /* 过滤器掩码数高位,0表示全盘接收 */
    can_filter.filter_mask_low = 0;                     /* 过滤器掩码数低位,0表示全盘接收 */
    can_filter.filter_fifo_number = CAN_FIFO0;          /* 滤过器关联FIFO0 */
    can_filter.filter_number = 0;                       /* 0号过滤器 */
    can_filter.filter_mode = CAN_FILTERMODE_LIST;       /* 掩码模式 */
    can_filter.filter_bits = CAN_FILTERBITS_32BIT;      /* 32位 */
    can_filter.filter_enable = ENABLE;
    can_filter_init(&can_filter);
    //4.配置中断
    nvic_irq_enable(CAN0_RX0_IRQn, 0, 0); 
    can_interrupt_enable(CAN0,CAN_INT_RFNE0);

    return 0;
}

主要关注下面这段代码

cpp 复制代码
    filter_id = 0x56;
    can_filter.filter_list_high = (((uint32_t)filter_id<<21|CAN_FT_DATA|CAN_FF_STANDARD)&0xFFFF0000)>>16;   /* 过滤器高字节 */
    can_filter.filter_list_low =  (((uint32_t)filter_id<<21|CAN_FT_DATA|CAN_FF_STANDARD) & 0x0000FFFF);     /* 过滤器低字节 */
    can_filter.filter_mask_high = 0;                    /* 过滤器掩码数高位,0表示全盘接收 */
    can_filter.filter_mask_low = 0;                     /* 过滤器掩码数低位,0表示全盘接收 */
    can_filter.filter_fifo_number = CAN_FIFO0;          /* 滤过器关联FIFO0 */
    can_filter.filter_number = 0;                       /* 0号过滤器 */
    can_filter.filter_mode = CAN_FILTERMODE_LIST;       /* 掩码模式 */
    can_filter.filter_bits = CAN_FILTERBITS_32BIT;      /* 32位 */
    can_filter.filter_enable = ENABLE;
    can_filter_init(&can_filter);

FDATA分为三部分,SFID为标准ID,EFID为扩展ID,FF为数据或遥控帧,FT为标准帧或扩展帧,高16位为filter_list_high,低16位为filter_list_low。

根据FDATA的分段,我们需要注意filter_list_highfilter_list_low两个字段的设置。

若是过滤标准帧ID,则是 (((uint32_t)filter_id<< 21 |CAN_FT_DATA| CAN_FF_STANDARD )&0xFFFF0000)

若是要过滤拓展帧ID,则是 (((uint32_t)filter_id<< 3 |CAN_FT_DATA| CAN_FF_EXTENDED )&0xFFFF0000)

5.5 总结的注意要点

①注意CAN的GPIO口重映射问题,有的是完全映射,有点是部分映射。

②CAN发送数据,需要其他CAN控制器进行确认回复,才能发送成功,否则是发送不成功的

③过滤器标准帧和过滤去拓展帧的区别

相关推荐
keep-learner5 分钟前
Unity Dots理论学习-2.ECS有关的模块(1)
学习·unity·游戏引擎
A懿轩A25 分钟前
C/C++ 数据结构与算法【树和二叉树】 树和二叉树,二叉树先中后序遍历详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·二叉树·
胡楚昊33 分钟前
攻防世界PWN刷题笔记(引导模式)1-3
笔记
虾球xz36 分钟前
游戏引擎学习第62天
学习·游戏引擎
上等猿1 小时前
Ajax笔记
前端·笔记·ajax
lmxnsI1 小时前
docker使用笔记
笔记·docker·容器
程序猿online2 小时前
nvm安装使用,控制node版本
开发语言·前端·学习
lijiachang0307182 小时前
设计模式(一):单例模式
c++·笔记·学习·程序人生·单例模式·设计模式·大学生
Hacker_Oldv2 小时前
网络安全攻防学习平台 - 基础关
网络·学习·web安全
Suwg2093 小时前
《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)
java·数据库·笔记·后端·sql·mybatis·模板方法模式