股票 投资 增持 经济 金融 银行 汽车 法律 法制 大学 创业 求职 科普 文化 大数据 新能源 社会万象 消费金融 金融机构 美元指数 食品安全 科技新闻

【GD32F303红枫派开发板使用手册】第三十讲 CAN -CAN通信实验

时间:2024-07-09作者:chy123分类:时尚科技浏览:41109评论:0
wKgaomZVdiiAfR9BAB3mDFhHnZc972.png

30.1实验内容

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

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

30.2实验原理

30.2.1CAN概述

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

30.2.2CAN总线拓扑

CAN总线拓扑图如下:

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

wKgZomaHYtaALFZOAAEmmitVmDM170.png

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

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

  1. 多主

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

  1. 消息格式

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

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

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

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

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

  1. 半双工异步通讯

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

30.2.3CAN帧的种类

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

  • 数据帧

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

  • 遥控帧

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

  • 错误帧

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

  • 过载帧

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

  • 帧间隔

用于帧和帧之间分离的帧

30.2.4CAN协议的解析

介绍了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.5CAN的波特率

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

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

wKgZomaHYveACdiOAAIqpf39r5k384.png

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

wKgaomaHYwWAIqX7AABG1YrFoNU787.png

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

wKgZomaHYxOAQQL5AABGOo6dri0581.png wKgaomaHYxOAdPkOAABNcg7mVtE895.png

寄存器中有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。我们可以把这个计算转化为公式:

wKgaomaHYzqATn9qAAAGbJRLotk609.png

30.2.6CAN的位时序和采样点

我们现在来看下上一节提到的位段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)。

位时序图:

wKgZomaHY0KALQA5AABVIE_iwlo786.png

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

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

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

wKgaomaHY0-AaNzdAABc6t4ouE4782.png

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

wKgZomaHY16AYABLAABjuUUrmQM028.png

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

30.2.7GD32F303 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可用):

wKgZomaHY2uAJlXuAABMuCbXevE963.png

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

wKgaomaHY3eAe2HwAAAhNZ3Tim0128.png

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

wKgZomaHY4SAeIeRAAAtaDgj7FQ620.png

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

  • 掩码模式

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

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

wKgaomaHY5KACpdwAAAy6dqf76Q390.png

可以看到,在掩码模式下,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位宽掩码模式过滤器如下:

wKgaomaHY6CAGowuAAA6Uo_eCBQ260.png

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

  • 列表模式

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

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

wKgZomaHY8uAN2RKAAAuLzQpXGg893.png

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

wKgZomaHY9aAXNpAAAAtj4s1ug0204.png

30.2.8GD32F303 CAN的发送和接收

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

wKgZomaHY-GAJv_8AACNsuw6NGA543.png

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

  • 数据发送

发送寄存器的框图:

wKgaomaHY-uAZ6VYAACIC-gnEik130.png

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

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

wKgaomaHY_eAT4v6AAD4yWEK4Pk539.png wKgZomaHZAGAabTOAAB7w3KehXQ105.png

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

wKgZomaHZBGAUmrCAAEPkOnzGdo094.png

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

wKgZomaHZByAGidBAAAY-Wxxy5o701.png wKgZomaHZCSAZIoWAABYjoPbXuI530.png

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

wKgaomaHZC-AIKgZAAB3SpRHy5g121.png

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

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

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

发送邮箱状态转换图:

wKgZomaHZDyAeHDHAAAsGboqfE0615.png

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

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

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

  • 数据接收

接收寄存器的框图:

wKgZomaHZEaAdrflAACO3xSjmEs052.png

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

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

wKgaomaHZFGAfmC_AAAoaswpmgY135.png wKgaomaHZGSAfBwKAADxhmnrmN8267.png

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

wKgaomaHZFuAcilCAACjELkA7GA755.png

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

wKgZomaHZHuAfYs2AAByWfoIkh8957.png

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

wKgaomaHZIWAIMkYAAB0UWW1Nao537.png

当总线上报文通过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.9GD32F303 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.10GD32F303 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的硬件设计如下:

wKgaomaHZJSABaSJAAD3k2bG5s8279.png

30.4代码解析

30.4.1CAN 配置函数

在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的deinit

can_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]实际为4

can_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 == 500

can_general.can_parameter.prescaler = 12;

/* 250KBps */

#elif CAN_BAUDRATE == 250

can_general.can_parameter.prescaler = 24;

/* 125KBps */

#elif CAN_BAUDRATE == 125

can_general.can_parameter.prescaler = 48;

/* 100KBps */

#elif CAN_BAUDRATE == 100

can_general.can_parameter.prescaler = 60;

/* 50KBps */

#elif CAN_BAUDRATE == 50

can_general.can_parameter.prescaler = 120;

/* 20KBps */

#elif CAN_BAUDRATE == 20

can_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.2CAN 发送函数

在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.3CAN中断接收函数

在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.4main函数实现

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 NVIC

nvic_irq_enable(CAN0_RX1_IRQn,0,0);//使能CAN0 FIFO1 NVIC

while (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); //延时1s

bsp_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发送和接收数据的情况:

wKgZomaHZKSAV2MXAAAjvK87Umw242.png

教程GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网

相关推荐

猜你喜欢