当前位置: 首页 > news >正文

【GD32F303红枫派使用手册】第三十节 CAN -CAN通信实验

30.1 实验内容

通过本实验主要学习以下内容:

  • CAN的简介
  • GD32F303 CAN工作原理
  • 通过CAN实现回环收发

30.2 实验原理

30.2.1 CAN概述

CAN 是 Controller Area Network 的缩写,是由德国BOSCH公司开发的,已成为ISO 国际标准化的串行通信协议。其主要应用场合为汽车和工业控制。 CAN具有传输距离长,传输可靠、强大的纠错机制等特点,其高性能和可靠性已被广泛认同,现在已经成为汽车、工业自动化、医疗设备等领域应用最广泛的总线之一。

30.2.2 CAN总线拓扑

CAN总线拓扑图如下:

CAN 控制器根据两根线上的电位差来判断总线电平,一般将两根线分别命名为CAN_H和CAN_L。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 当CAN总线上的电位差为0V时,表示隐性电平,隐性电平代表逻辑“1”;当CAN总线上有电位差时(大概在2.5V左右),表示显性电平,显性电平代表逻辑“0”。总线空闲时,默认为隐性电平,即总线电位差为0。

关于电位差、隐性/显性电平及逻辑电平,非常容易弄混,读者需要熟记。

CAN总线的特点可以总结为:

  1. 多主

与USART-485这种一主多从类型总线不同,CAN总线是多主控制,即总线上没有主机从机之分,所有设备都是处于平等的地位。

  1. 消息格式

CAN总线上的消息都以固定格式发送。当两个以上的单元同时开始发送消息时,根据标识符( Identifier 以下称为 ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。    

  1. 通信速度快,通信距离远

CAN最高可达1Mbps波特率,理论最远距离可达10Km,当然此时通讯速率较低,只有5Kbps以下。  

  1. 具有错误检测、错误通知和错误恢复功能

CAN总线具有强大的错误检测、通知和恢复功能。当一个单元发生错误时其他单元会进行报错,正在发送的单元检测到错误后,会立即强制结束当前发送,并尝试重新发送(功能可配置),当发送错误的次数达到一定值后,该单元会自动从总线中退出,直到应用程序让其重新加入总线为止。

  1. 半双工异步通讯

CAN的总线的查分信号,决定了CAN总线实际为半双工通讯,另外由于CAN总线没有时钟线,所以是异步通讯,故要求CAN总线上的所有单元的波特率都要设置一致。

30.2.3 CAN帧的种类

CAN总共有如下五种类型的帧种类:

  • 数据帧

用于数据传输的帧,也是最常用的一种帧种类

  • 遥控帧

接收单元向具有相同ID的发送单元请求数据时所需要的帧种类

  • 错误帧

一种非常重要的帧种类,错误帧是当总线上有错误时,检测到错误的单元向其他单元通知错误的帧。

  • 过载帧

用于接收单元通知其他单元其还没有准备好的帧种类

  • 帧间隔  

用于帧和帧之间分离的帧

30.2.4 CAN协议的解析

介绍了CAN的一些基本指示后,可能读者还是不太明白帧ID是什么,CAN的发送和接收是怎么实现的,是否就像串口一样发送数据就可以?实际上CAN需要遵循CAN协议,这样每个CAN单元才可以准确无误的发送和接收数据,CAN强大的错误检测、错误通知等机制也是依托于标准CAN协议。下面以数据帧来解析下CAN的协议。

数据帧的格式如下图,其中D表示显性位,即逻辑“0”,R表示隐性位,即逻辑“1”,D/R表示隐性位或显性位,另外下图中的数字表示bit位数:

 

  • 帧开始

每次CAN通讯都始于帧开始段,帧开始是1个bit位的显性电平(逻辑“0”),由发送方发出,接收方检测到一个bit逻辑“0”,开始准备接收数据。

  • 仲裁段

好了,我们终于看到了帧ID的庐山真面目了。数据帧分为两种——标准帧和扩展帧,标准帧的帧ID由11bit组成,扩展帧有11+18共29bit组成。需要注意,无论是标准帧ID还是扩展帧ID,都不允许设置最高7bit为1(即不允许帧ID=0b,1111111xxx··),以为帧结束段就是7个“1”组成。

冲裁段中的 RTR 位用于标识是否是远程帧(0:数据帧; 1:远程帧),IDE 位为标识符选择位( 0:使用标准标符; 1:使用扩展标识符),SRR 位为代替远程请求位,为隐性位,它代替了标准帧中的 RTR 位。

我们需要先明确一个概念,CAN总线上的每个单元并不是只能发送固定帧ID,而是可以发送任意帧ID,帧ID不代表CAN设备号,代表的是当前数据帧/远程帧的ID号。当一帧数据发送到总线后,所有接收方会对帧ID进行识别,当识别到是自己需要的ID时,则会将该帧数据收取到内部;而当识别到不是自己需要的ID时,则不会接收数据。

因为有可能出现两个或更多CAN单元同时发送数据的情况,此时帧ID还起到仲裁的作用,各发送单元从帧ID的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送,比如两个CAN单元同时发送数据,其中单元一发出的数标准帧的ID为0b,00110000000,而单元二发出的标准帧ID为0b,00100000000,可以看到发送到第4位时,单元一发出的是逻辑“1”,单元二的是逻辑“0”,因为总线上“0”比“1”优先级搞,所以此时单元二获得总线发送权,单元一将自动停止发送。

  • 控制段

控制段由6个bit组成,其中DLC占用4个bit,表示要发送的字节数。

  • 数据段

数据段即帧载有的有效数据了,CAN最多一次可发送8个字节(CANFD最多可以发送64字节,这个后面介绍带CANFD功能的开发板时再细说),也就是说上面描述的DLC最大值为0b,1000。

  • CRC段

CRC段总共有16bit,其中15个bit表示CRC值,另1个bit为CRC界定符。此段 CRC 的值计算范围包括:帧起始、仲裁段、控制段、数据段。发送会根据发送的数据来计算出一个CRC值并通过CRC段发到总线,接收方以同样的算法计算CRC值并和发送方发出的CRC段进行比较,当不一致时接收方会向总线报错。

  • ACK段

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

发送单元的 ACK,发送 2 个位的隐性位,而接收到正确消息的单元在 ACK 槽(ACK Slot)发送显性位,通知发单元正常接收结束,这个过程叫发送 ACK/返回 ACK。发送 ACK 的是在既不处于总线关闭态也不处于休眠态的所有收单元中,接收到正常消息的单元(发送单元不发送 ACK)。

  • 帧结束

帧结束由7个bit的逻辑“1”组成,表示该帧结束。

30.2.5 CAN的波特率

前面有提到CAN的波特率,波特率代表每秒钟传输的位数(1位就表示1bit),我们来看下GD32F303的波特率是怎么计算的。

首先需要了解一个概念,CAN时序的最小单位是一个叫Tq的东西,CAN的每个位即每个bit是由若干个Tq组成的,那么这个Tq的长度是多少呢?GD32F303的CAN是挂载在APB1总线的:

 如果读者将GD32F303的主频配置为最高120M,且将APB1的时钟配置为最高60M的话,那么CAN的时钟就是60M,然后CAN有个分频系数,在位时序寄存器CAN_BT中:

一个Tq占用的时间计算公式:分频系数/CAN时钟,举个例子,我们设置这个分频系数为6的话,一个Tq的时间就是6/60M = 100us,也可以说Tq的传输速率为10M。那么由多少个Tq组成CAN的一个位呢?还是看CAN_BT寄存器:

寄存器中有BS1和BS2,这两个位域用于设置位段1和位段2(关于位段后面会介绍),一个CAN位占用的Tq个数等于位段1+位段2+1,举个例子,设置位段1为5(即BS1=4),设置位段2为4(即BS2=3),那么一个CAN位占用的Tq个数为5+4+1=10。好,现在就可以来算CAN的波特率了,按照CAN分频系数为6,位段1为5,位段2为4,一个位占用的时间为6/60M*10 = 1ms,也就是波特率=1M。我们可以把这个计算转化为公式: 

30.2.6 CAN的位时序和采样点

我们现在来看下上一节提到的位段1和位段2。CAN总线控制器将位时间分为3个部分。

  • 同步段(Synchronization segment),记为SYNC_SEG。该段占用1个时间单元(1 × ����)。
  • 位段1(Bit segment 1),记为BS1。该段占用1到16个Tq(由位时序寄存器配置)。相对于CAN协议而言, BS1相当于传播时间段(Propagation delay segment)和相位缓冲段1(Phase buffer segment 1)。
  • 位段2(Bit segment 2),记为BS2。该段占用1到8个Tq(由位时序寄存器配置)。相对于CAN协议而言, BS2相当于相位缓冲段2(Phase buffer segment 2)。

位时序图:

这里提到的BS1(即位段1)和CAN时序寄存器中的BS1[3:0]位域不是一个概念,位段1=BS1[3:0]+1

说完位时序我们来介绍下GD32F303的采样点。

对于接收方来说,需要对发送方发出的每个bit进行采样,那么具体是采样哪个点呢?按照CAN标准来说,采样点为BS1和BS2的交界处,即:

而GD32F303为了更好的容错性,会在标准采样点前一个以及前前一个Tq加了两个采样点,取两个有效位,所以GD32F303的采样点为:

如果三个采样点分别采样到的为010,则认为该位为“1”。

30.2.7  GD32F303 CAN过滤器

前面提到CAN节点发送数据的时候,帧ID是任意的,那么接收方是不是任意ID都可以接收呢?当然是可以的,但一般不会这么做,一个CAN节点一般只接收一个ID或几个ID的报文,那么如何实现呢?这就要介绍CAN的过滤器了,只有总线上的报文帧ID通过了CAN节点的过滤,才会被接收。

GD32F303共有14个过滤器组(对于互联性GD32F305/F307,是28个过滤器组),每个过滤器组有两个过滤器寄存器。程序中需要设置过滤器对应哪个接收FIFO(接收FIFO会在下一节中介绍)。

GD32F303过滤器( x) 数据( y) 寄存器(CAN_FxDATAy)( x= 0...13, y = 0,1)( 仅 CAN0可用):

过滤器可以配置为2种位宽:32-bit位宽和16-bit位宽。 32-bit 位宽 CAN_FDATAx 包含字段: SFID[10:0], EFID[17:0], FF 和FT。

16-bit 位宽 CAN_FDATAx 包含字段: SFID[10:0], FT, FF和 EFID[17:15]。 

过滤器可以设置为两种模式——掩码模式和列表模式:

  • 掩码模式

对于一个待过滤的数据帧的标识符(Identifier),掩码模式用来指定哪些位必须与预设的标识符相同,哪些位无需判断。掩码模式有两种位宽:32bit和16bit。  

 一个 32-bit 位宽掩码模式过滤器如下:

可以看到,在掩码模式下,FDATA0用于目标ID,FDATA1用于Mask。举个例子,设置FDATA0为0x55550000,FDATA1为0xFF00FF00(第31~24以及第15~8位为1),那么意味着总线上的报文ID的第31~24以及第15~8位必须和FDATA0相应位相同,就可以通过这个过滤器,而其他位则不需要关心,也就是说帧ID为0x55xx00xx可以通过过滤器。

明白了32bit掩码模式过滤器,16位位宽就很好理解了。一个16-bit位宽掩码模式过滤器如下:

和32-bit的不用,16-bit位宽掩码过滤器的ID为FDATA0的高16bit,Mask为FDATA0的低16bit,这也意味着16-bit位宽掩码模式可以设置28个过滤ID掩码类型。

  • 列表模式

列表模式和掩码模式不同,列表模式设置了一个个具体ID,只有和这些ID完全相同的帧才可以通过过滤器,同样分成两种位宽模式。

32-bit位宽列表模式过滤器:

16-bit位宽列表模式过滤器:

30.2.8 GD32F303 CAN的发送和接收

通过上面的学习,我们已经基本了解了CAN的工作原理了,这节我们来讲GD32F303 CAN的收发。首先我们需要了解GD32F303 CAN的结构框图:

可以看到,GD32F303是有3个发送邮箱和两个深度为3的接收FIFO,下面我们分别介绍数据发送和数据接收。

  • 数据发送

发送寄存器的框图:

三个发送邮箱对于三组发送寄存器TMIx、TMPx、TMDATA0x和TMDATA1x(x=0,1,2):

发送邮箱标识符寄存器(CAN_TMIx)

发送邮箱属性寄存器(CAN_TMPx):

发送邮箱 data0 寄存器(CAN_TMDATA0x):

发送邮箱 data1 寄存器(CAN_TMDATA1x):

当需要发送数据时,选择一个空闲(empty)的邮箱(读取CAN_TSTAT寄存器获取),然后将该邮箱对应TMIx、TMPx、TMDATA0x和TMDATA1x寄存器填好后,使能TMIx的TEN位,寄存器中的数据就自动转移到邮箱。

实际上数据到邮箱后也不一定就马上发送到总线,因为有可能总线上正有数据发送,或者其他的邮箱中也有数据,这就涉及到CAN发送邮箱的调度:

当发送邮箱被填入新的数据后,邮箱状态从empty转到pending状态。当超过1 个邮箱处于 pending 状态时,需要对多个邮箱进行调度,这时发送邮箱处于 scheduled 状态。当调度完成后,发送邮箱中的数据开始向 CAN 总线上发送,这时发送邮箱处于 transmit 状态。当数据发送完成,邮箱变为空闲,可以再次交给应用程序使用,这时发送邮箱重新变为 empty 状态。

发送邮箱状态转换图:

当多个发送邮箱处于等待状态下时,可以通过CAN_CTL的TFO位的值可以决定发送顺序:

当TFO为1,所有等待发送的邮箱按照先来先发送(FIFO)的顺序进行。

当TFO为0,具有最小标识符(Identifier)的邮箱最先发送。如果所有的标识符(Identifier)相等,具有最小邮箱编号的邮箱最先发送。  

  • 数据接收

接收寄存器的框图:

两个接收FIFO对应了两组接收寄存器RFIFOMIx, RFIFOMPx,RFIFOMDATA0x和RFIFOMDATA1x(x=0,1):

接收 FIFO 邮箱标识符寄存器(CAN_RFIFOMIx):

接收 FIFO 邮箱属性寄存器(CAN_RFIFOMPx) :

接收 FIFO 邮箱 data0 寄存器(CAN_RFIFOMDATA0x) :

接收 FIFO 邮箱 data1 寄存器(CAN_RFIFOMDATA1x) :

当总线上报文通过CAN接收过滤器后(过滤器需要设置对应的FIFO号),数据就会保存到接收邮箱中,每个接收FIFO包含3个接收邮箱,用来接收存储数据帧。这些邮箱按照先进先出方式进行组织,最早从CAN网络接收的数据,最早被应用程序处理。

寄存器CAN_RFIFOx包含FIFO状态信息和帧的数量。当FIFO中包含数据时,可以通过寄存器CAN_RFIFOMIx, CAN_RFIFOMPx, CAN_RFIFOMDATA0x和CAN_RFIFOMDATA1x读取数据,之后将寄存器CAN_RFIFOx的RFD置1释放邮箱。

用户可以通过读取寄存器CAN_RFIFOx来获取FIFO的一些信息,比如接收FIFO中目前还有多少个邮箱内容没有被读取,是否有FIFO溢出的情况等。关于溢出时的处理方式,可以通过CAN_CTL寄存器的RFOD位来进行设置(读者可阅读GD32F30x用户手册来查看相关寄存器含义)。

30.2.9 GD32F303 CAN工作模式

CAN 总线控制器有 3 种工作模式:

  • 睡眠工作模式;
  • 初始化工作模式;
  • 正常工作模式。

睡眠工作模式

芯片复位后, CAN总线控制器处于睡眠工作模式。该模式下CAN总线控制器的时钟停止工作并处于一种低功耗状态。

将CAN_CTL寄存器的SLPWMOD位置1,可以使CAN总线控制器进入睡眠工作模式。 当进入睡眠工作模式后, CAN_STAT寄存器的SLPWS位将被硬件置1。

将CAN_CTL寄存器的AWU位置1,并当CAN检测到总线活动时, CAN总线控制器将自动退出睡眠工作模式。将CAN_CTL寄存器的SLPWMOD位清0,也可以退出睡眠工作模式。

由睡眠模式进入初始化工作模式:将CAN_CTL寄存器的IWMOD位置1, SLPWMOD位清0。

由睡眠模式进入正常工作模式:将CAN_CTL寄存器的IWMOD位和SLPWMOD位清0。

初始化工作模式

如果需要配置 CAN 总线通信参数, CAN 总线控制器必须进入初始化工作模式。将 CAN_CTL寄存器的 IWMOD 位置 1,使 CAN 总线控制器进入初始化工作模式,将其清 0 则离开初始化 工作模式。在进入初始化工作模式后, CAN_STAT 寄存器的IWS 位将被硬件置 1。

由初始化模式进入睡眠模式: CAN_CTL 寄存器的 SLPWMOD 位置 1, IWMOD 位清 0。

由初始化模式进入正常工作模式: CAN_CTL 寄存器的 SLPWMOD 位和 IWMOD 位清0。

正常工作模式
在初始化工作模式中配置完CAN 总线通信参数后,将 CAN_CTL 寄存器的IWMOD位清0可以进入正常工作模式并与 CAN 总线网络中的节点进行正常通信。

由正常工作模式进入睡眠工作模式: CAN_CTL 寄存器的 SLPWMOD 位置 1,并等待当前数据收发过程结束。

由正常工作模式初始化工作模式: CAN_CTL 寄存器的 IWMOD 位置 1,并等待当前数据收发过程结束。

30.2.10 GD32F303 CAN通信模式

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

  • 静默(Silent)通信模式;
  • 回环(Loopback)通信模式;
  • 回环静默(Loopback and Silent)通信模式;
  • 正常(Normal)通信模式。

静默(Silent)通信模式

在静默通信模式下,可以从 CAN 总线接收数据,但不向总线发送任何数据。将 CAN_BT寄存器中的 SCMOD 位置 1,使 CAN 总线控制器进入静默通信模式,将其清0 可以退出静默通信模式。

静默通信模式可以用来监控CAN 网络上的数据传输。

回环(Loopback)通信模式

在回环通信模式下,由 CAN 总线控制器发送的数据可以被自己接收并存入接收FIFO,同时这些发送数据也送至CAN 网络。将CAN_BT 寄存器中的 LCMOD 位置 1,使 CAN总线控制器进入回环通信模式,将其清0 可以退出回环通信模式。本实验中就用到了CAN的回环通讯模式。

回环通信模式通常用来进行CAN 通信自测。

回环静默(Loopback and Silent)通信模式

在回环静默通信模式下, CAN 的 RX 和 TX 引脚与 CAN 网络断开。 CAN 总线控制器既不从CAN 网络接收数据,也不向 CAN 网络发送数据,其发送的数据仅可以被自己接收。将CAN_BT寄存器中的 LCMOD 位和 SCMOD 位置 1,使 CAN 总线控制器进入回环静默通信模式,将它们清 0 可以退出回环静默通信模式。  

回环静默通信模式通常用来进行CAN 通信自测。对外 TX 引脚保持隐性状态(逻辑1), RX 引脚保持高阻态。

正常(Normal)通信模式

CAN 总线控制器通常工作在正常通信模式下,可以从 CAN 总线接收数据,也可以向 CAN 总线发送数据。这时需要将 CAN_BT 寄存器的LCMOD 位和 SCMOD 位清0。  

30.3 硬件设计

本实验CAN的硬件设计如下:

30.4 代码解析

30.4.1 CAN 配置函数

在driver_can.c中定义了driver_can_config函数,用于CAN的基本参数和过滤器配置:

C
void driver_can_config(typdef_can_general can_general)
{rcu_periph_clock_enable(can_general.rcu_can); //CAN时钟使能rcu_periph_clock_enable(can_general.rcu_IO_port); //IO时钟使能if(can_general.can_remap != 0) //如IO有remap,需要配置remap功能{rcu_periph_clock_enable(RCU_AF);gpio_pin_remap_config(can_general.can_remap,ENABLE);}   gpio_init(can_general.IO_port,GPIO_MODE_IPU,can_general.gpio_speed,can_general.pin_rx); //CAN RX IO配置gpio_init(can_general.IO_port,GPIO_MODE_AF_PP,can_general.gpio_speed,can_general.pin_tx); //CAN TX IO配置can_struct_para_init(CAN_INIT_STRUCT, &can_general.can_parameter); //CAN初始化结构体的初始化can_struct_para_init(CAN_INIT_STRUCT, &can_general.can_filter); //CAN过滤器结构体的初始化can_deinit(can_general.can_port); //CAN的deinitcan_general.can_parameter.time_triggered = DISABLE;  //时间触发功能can_general.can_parameter.auto_bus_off_recovery = DISABLE;//busoff自恢复功能can_general.can_parameter.auto_wake_up = DISABLE; //自动唤醒功能can_general.can_parameter.no_auto_retrans = DISABLE;//自动重发功能,需要注意DISABLE为使能自动重发can_general.can_parameter.rec_fifo_overwrite = DISABLE;//接收溢出模式can_general.can_parameter.trans_fifo_order = DISABLE;//发送邮箱顺序配置can_general.can_parameter.working_mode = CAN_LOOPBACK_MODE;//回环模式can_general.can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;//再同步补偿can_general.can_parameter.time_segment_1 = CAN_BT_BS1_5TQ;//BS1设置,注意这里设置为5,寄存器BS1[3:0]实际为4can_general.can_parameter.time_segment_2 = CAN_BT_BS2_4TQ;//BS2设置,注意这里设置为4,寄存器BS2[2:0]实际为3/* 1MBps */
#if CAN_BAUDRATE == 1000 //波特率设置can_general.can_parameter.prescaler = 6;/* 500KBps */
#elif CAN_BAUDRATE == 500can_general.can_parameter.prescaler = 12;/* 250KBps */
#elif CAN_BAUDRATE == 250can_general.can_parameter.prescaler = 24;/* 125KBps */
#elif CAN_BAUDRATE == 125can_general.can_parameter.prescaler = 48;/* 100KBps */
#elif  CAN_BAUDRATE == 100can_general.can_parameter.prescaler = 60;/* 50KBps */
#elif  CAN_BAUDRATE == 50can_general.can_parameter.prescaler = 120;/* 20KBps */
#elif  CAN_BAUDRATE == 20can_general.can_parameter.prescaler = 300;
#else#error "please select list can baudrate in private defines in main.c "
#endif  /* initialize CAN */can_init(can_general.can_port, &can_general.can_parameter);//CAN初始化/* initialize filter */ can_general.can_filter.filter_number=0; //过滤器号can_general.can_filter.filter_mode = CAN_FILTERMODE_MASK;//掩码模式can_general.can_filter.filter_bits = CAN_FILTERBITS_32BIT;//掩码位宽can_general.can_filter.filter_list_high = 0x3000<<1; //掩码和ID设置can_general.can_filter.filter_list_low = 0x0000;can_general.can_filter.filter_mask_high = 0x3000<<1;can_general.can_filter.filter_mask_low = 0x0000;can_general.can_filter.filter_fifo_number = CAN_FIFO0; //过滤器关联接收FIFO号can_general.can_filter.filter_enable = ENABLE; //过滤器使能    can_filter_init(&can_general.can_filter); //过滤器初始化can_general.can_filter.filter_number=1;can_general.can_filter.filter_list_high = 0x5000<<1;can_general.can_filter.filter_list_low = 0x0000;can_general.can_filter.filter_mask_high = 0x5000<<1;can_general.can_filter.filter_mask_low = 0x0000;can_general.can_filter.filter_fifo_number = CAN_FIFO1;can_filter_init(&can_general.can_filter);if(can_general.can_rx_use_interrupt == SET)//打开CAN接收中断{can_interrupt_enable(can_general.can_port, CAN_INT_RFNE0);can_interrupt_enable(can_general.can_port, CAN_INT_RFNE1);}
}

其中波特率CAN_BAUDRATE在driver_can.h中预定义:

C
/* select CAN baudrate */
/* 1MBps */
#define CAN_BAUDRATE  1000
/* 500kBps */
/* #define CAN_BAUDRATE  500 */
/* 250kBps */
/* #define CAN_BAUDRATE  250 */
/* 125kBps */
/* #define CAN_BAUDRATE  125 */
/* 100kBps */ 
/* #define CAN_BAUDRATE  100 */
/* 50kBps */ 
/* #define CAN_BAUDRATE  50 */
/* 20kBps */ 
/* #define CAN_BAUDRATE  20 */

30.4.2 CAN 发送函数

在driver_can.c中定义了CAN发送函数:

C
void driver_can_transmit(typdef_can_general can_general,can_trasnmit_message_struct *transmit_message)
{can_message_transmit(can_general.can_port,transmit_message);
}

30.4.3 CAN中断接收函数

在bsp_can.c中定义了CAN FIFO0和FIFO1的中断接收处理函数:

C
void can0_rx0_interrupt_handler(void)
{can_message_receive(CAN0, CAN_FIFO0, &can0_receive_message_fifo0);//将数据从FIFO中转移到接收寄存器组中    if((0x300 == can0_receive_message_fifo0.rx_sfid)&&(CAN_FF_STANDARD == can0_receive_message_fifo0.rx_ff)&&(2 == can0_receive_message_fifo0.rx_dlen)){can0_receive_fifo0_flag = SET; }else{can0_receive_fifo0_flag = RESET; }
}
C
void can0_rx1_interrupt_handler(void)
{can_message_receive(CAN0, CAN_FIFO1, &can0_receive_message_fifo1);//将数据从FIFO中转移到接收寄存器组中   if((0x500 == can0_receive_message_fifo1.rx_sfid)&&(CAN_FF_STANDARD == can0_receive_message_fifo1.rx_ff)&&(2 == can0_receive_message_fifo1.rx_dlen)){can0_receive_fifo1_flag = SET; }else{can0_receive_fifo1_flag = RESET; }
}

 30.4.4 main函数实现

main函数实现如下:

C
int main(void)
{driver_init();//delay函数初始化bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化bsp_can_config(BSP_CAN);//BOARD_CAN初始化nvic_irq_enable(USBD_LP_CAN0_RX0_IRQn,0,0);//使能CAN0 FIFO0 NVICnvic_irq_enable(CAN0_RX1_IRQn,0,0);//使能CAN0 FIFO1 NVICwhile (1){bsp_can_transmit(BSP_CAN,&bsp_can_transmit_message_1);//发送一帧数据printf("\r\n can0 transmit data:%x,%x", bsp_can_transmit_message_1.tx_data[0], bsp_can_transmit_message_1.tx_data[1]);//发送数据打印delay_ms(1000); //延时1sbsp_can_transmit(BSP_CAN,&bsp_can_transmit_message_2);//发送一帧数据printf("\r\n can0 transmit data:%x,%x", bsp_can_transmit_message_2.tx_data[0], bsp_can_transmit_message_2.tx_data[1]);//发送数据打印delay_ms(1000);bsp_can_transmit(BSP_CAN,&bsp_can_transmit_message_3);//发送一帧数据printf("\r\n can0 transmit data:%x,%x", bsp_can_transmit_message_3.tx_data[0], bsp_can_transmit_message_3.tx_data[1]);//发送数据打印delay_ms(1000);                if(can0_receive_fifo0_flag == SET){printf("\r\n can0_fifo0 receive ID = %x data:%x,%x", can0_receive_message_fifo0.rx_sfid,can0_receive_message_fifo0.rx_data[0], can0_receive_message_fifo0.rx_data[1]);//接收数据打印can0_receive_fifo0_flag = RESET; //标志位清除}if(can0_receive_fifo1_flag == SET){printf("\r\n can0_fifo1 receive ID = %x data:%x,%x", can0_receive_message_fifo1.rx_sfid,can0_receive_message_fifo1.rx_data[0], can0_receive_message_fifo1.rx_data[1]);//接收数据打印can0_receive_fifo1_flag = RESET;}}
}

 BSP_CAN实参结构体初始化在bsp_can.c中:

C
typdef_can_general BSP_CAN = 
{.can_port = CAN0,.rcu_can = RCU_CAN0,                .rcu_IO_port = RCU_GPIOB,.IO_port = GPIOB,                                        .pin_tx = GPIO_PIN_9,                                                        .pin_rx = GPIO_PIN_8,                                                .can_remap = GPIO_CAN_PARTIAL_REMAP,.gpio_speed = GPIO_OSPEED_50MHZ        ,                .can_rx_use_interrupt = SET
};

 main函数中实现的功能是每隔1s,分别发送帧ID为0x300,0x500和0x400的文到CAN总线,每帧发送两个数据,数据结构体初始化在bsp_can.c中:

C
can_trasnmit_message_struct bsp_can_transmit_message_1 = {.tx_sfid = 0x300,.tx_efid = 0x00,.tx_ft = CAN_FT_DATA,.tx_ff = CAN_FF_STANDARD,.tx_dlen = 2,.tx_data[0] = 0x55,.tx_data[1] = 0xAA,
};can_trasnmit_message_struct bsp_can_transmit_message_2 = {.tx_sfid = 0x500,.tx_efid = 0x00,.tx_ft = CAN_FT_DATA,.tx_ff = CAN_FF_STANDARD,.tx_dlen = 2,.tx_data[0] = 0x01,.tx_data[1] = 0x02,
};can_trasnmit_message_struct bsp_can_transmit_message_3 = {.tx_sfid = 0x400,.tx_efid = 0x00,.tx_ft = CAN_FT_DATA,.tx_ff = CAN_FF_STANDARD,.tx_dlen = 2,.tx_data[0] = 0x02,.tx_data[1] = 0x01,
};

 因为使用了回环模式,故发送的报文同时也会被CAN接收,而由于过滤器的配置,ID为0x300的会被接收到FIFO0中,ID为0x500的会被接收到FIFO1中,而ID为0x400的由于无法通过过滤器,被CAN舍弃。

30.5 实验结果

使用USB-TypeC线,连接电脑和板上USB to UART口后,配置好串口调试助手,即可看到CAN发送和接收数据的情况:

  1. 由聚沃科技原创,来源于【红枫派开发板】第三十讲 CAN -CAN通信实验 - 苏州聚沃电子科技有限公司 (gd32bbs.com)

相关文章:

  • 完全理解C语言函数
  • Linux 查看修改系统时间| date -s
  • 昇思25天学习打卡营第6天|关于函数与神经网络梯度相关技术探讨
  • 算法基础-----【动态规划】
  • 基于上一篇博客,用阻塞队列实现异步下单
  • mac中文件夹怎么显示.git隐藏文件
  • opengauss与pg核心关键技术对比
  • 统计信号处理基础 习题解答11-1
  • 【算法】冒泡排序
  • 白骑士的Python教学高级篇 3.4 Web开发
  • Python学习篇:Python基础知识(三)
  • Elasticsearch实战教程:如何使用集群索引数据来进行统计多个数据?
  • 通义千问接入进阶:流式、文件、图片、上下文
  • BAT批处理运行项目
  • 微信小程序毕业设计-社区门诊管理系统项目开发实战(附源码+论文)
  • [deviceone开发]-do_Webview的基本示例
  • [译] React v16.8: 含有Hooks的版本
  • 2018一半小结一波
  • 3.7、@ResponseBody 和 @RestController
  • Angular 响应式表单 基础例子
  • iOS | NSProxy
  • iOS 系统授权开发
  • Java IO学习笔记一
  • Meteor的表单提交:Form
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • Python语法速览与机器学习开发环境搭建
  • Redis中的lru算法实现
  • ubuntu 下nginx安装 并支持https协议
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 关于字符编码你应该知道的事情
  • 开源SQL-on-Hadoop系统一览
  • 坑!为什么View.startAnimation不起作用?
  • 前端代码风格自动化系列(二)之Commitlint
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 容器服务kubernetes弹性伸缩高级用法
  • 网页视频流m3u8/ts视频下载
  • 应用生命周期终极 DevOps 工具包
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 云大使推广中的常见热门问题
  • FaaS 的简单实践
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • # SpringBoot 如何让指定的Bean先加载
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (五)c52学习之旅-静态数码管
  • (转)fock函数详解
  • (转)LINQ之路
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • ./configure、make、make install 命令