文章目录
- 1.CAN通信简介
- 2.物理层
- [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的仲裁机制。
仲裁机制的工作原理:
- 唯一标识符:
仲裁机制使用到了 仲裁段的 Identify,这个Identify称为节点的标识符 。
为了确保每个节点的标识符都是唯一的,CAN 协议规定了每个节点的标识符必须是唯一的。
CAN 协议规定了节点标识符的优先级,标识符越小,优先级越高。 - 总线空闲检测:
什么情况下,总线是空闲的?
对于任意一个节点 而言,只要它监听到总线上连续出现了11位隐性电平 (显/隐性电平: 在总线上隐性电平通常表示逻辑1,而显性电平通常表示逻辑0),那么该节点就会认为总线当前处于空闲状态 。
在总线一开始工作的时候,所有节点都输出隐性电平。
当总线处于空闲状态时,多个设备可能同时开始发送数据。 - 逐位比较:
当多个节点同时发送数据时,每个节点都会将自己的标识符发送到总线上。
在发送每一位ID时,发送节点也会读取总线上的电平状态 ,并将其与自己发送的电平进行比较。
①若总线上的电平和本节点要发送的电平一致,则本节点继续发送下一位ID。
②若总线上的电平和本节点要发送的电平不一致,则说明由更高优先级的消息正在发送,本节点会立即停止发送并转为监听状态。 - 仲裁获胜:
最终,标识符较小的节点会获胜,继续发送数据。标识符较大的节点则会停止发送数据,并进入接收状态。
为什么 标识符越小,优先级越高 ?(了解)
前面讲过,当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机制的工作原理
- 发送过程:
发送节点在发送数据帧时,会在ACK槽置入一个隐性位(逻辑1)。
发送节点在发送数据的同时,会对总线上的数据进行回读,以监控ACK槽的状态。 - 接收过程:
接收节点在接收到数据帧后,会检查数据的正确性。
如果数据正确无误,接收节点会在ACK槽期间发送一个显性位(逻辑0),以此向发送节点确认消息已成功接收。 - 确认与重发:
发送节点通过回读总线,如果检测到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_high
,filter_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控制器进行确认回复,才能发送成功,否则是发送不成功的
③过滤器标准帧和过滤去拓展帧的区别