0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

聚沃科技 2024-07-05 11:13 次阅读
wKgaomZVdiiAfR9BAB3mDFhHnZc972.png

30.1实验内容

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

30.2实验原理

30.2.1CAN概述

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

30.2.2CAN总线拓扑

CAN总线拓扑图如下:

wKgZomaHYsiAWvRyAAItASjUeEI275.png

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位数:

wKgaomaHYueAOKkkAAEiZnRurC4328.png
  • 帧开始

每次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 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.pngwKgaomaHYxOAdPkOAABNcg7mVtE895.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.pngwKgZomaHZAGAabTOAAB7w3KehXQ105.png

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

wKgZomaHZBGAUmrCAAEPkOnzGdo094.png

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

wKgZomaHZByAGidBAAAY-Wxxy5o701.pngwKgZomaHZCSAZIoWAABYjoPbXuI530.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.pngwKgaomaHZGSAfBwKAADxhmnrmN8267.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教程,关注聚沃科技官网

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6012

    文章

    44173

    浏览量

    624653
  • CAN通信
    +关注

    关注

    4

    文章

    92

    浏览量

    17686
  • CAN
    CAN
    +关注

    关注

    57

    文章

    2561

    浏览量

    461258
  • 通信
    +关注

    关注

    18

    文章

    5789

    浏览量

    134882
  • 开发板
    +关注

    关注

    25

    文章

    4618

    浏览量

    95186
  • GD32
    +关注

    关注

    7

    文章

    368

    浏览量

    23851
收藏 人收藏

    评论

    相关推荐

    STM32CUBEMX开发GD32F303(6)----GPIO输入函数说明

    本章STM32CUBEMX配置STM32F103,并且在GD32F303中进行开发,同时通过GD32303C_START开发板内进行验证。
    的头像 发表于 11-25 16:06 1279次阅读
    STM32CUBEMX<b class='flag-5'>开发</b><b class='flag-5'>GD32F303</b>(6)----GPIO输入函数说明

    GD32F303】星空介绍

    一、开发板介绍星空GD开发板是由旗点科技推出的一款GD32开发板,板载
    发表于 09-11 17:55

    【星空GD32F303开发板试用体验】开发记录汇总

    本帖最后由 申小林一号 于 2021-10-15 14:47 编辑 开贴日期:2021年10月11日该贴主要是用于记录星空开发板使用记录以及学习GD32F303芯片的一个过程,将所有的
    发表于 10-11 16:09

    【星空GD32F303开发板试用体验】开箱+环境搭建

    本帖最后由 lustao 于 2021-10-19 09:29 编辑 感谢 发烧友学院以及广州旗点智能科技有限公司为我和孩子提供此产品星空GD32F303开发板。收到了星空
    发表于 10-18 14:15

    【星空GD32F303开发板试用体验】开箱+环境搭建

    https://bbs.elecfans.com/jishu_2179209_1_1.html感谢 发烧友学院以及广州旗点智能科技有限公司为我和孩子提供此产品星空gd32F303开发板。收到了星空
    发表于 11-02 15:36

    【星空GD32F303开发板试用体验】+板卡概览

    本帖最后由 cooldog123pp 于 2021-11-6 21:07 编辑 星空GD开发板是由旗点科技推出的一款GD32开发板
    发表于 11-06 21:05

    星空GD32F303开发板的相关资料下载

    一、开发板介绍星空GD开发板是由旗点科技推出的一款GD32开发板,板载
    发表于 12-10 08:27

    GD32F303开发板介绍

    目录如下,持续更新~~【1】星空GD32F303开发板介绍 与 文章目录1. 串口基础概念USART数据格式一般分为启动位、数据帧、可能的奇偶校验位、停止位,如图4.34所示。启动位:发送方想要
    发表于 01-17 08:06

    STM32CUBEMX开发GD32F303(14)----IIC之配置OLED

    本章STM32CUBEMX配置STM32F103,并且在GD32F303中进行开发,同时通过开发板内进行验证。
    的头像 发表于 07-26 13:52 1519次阅读
    STM32CUBEMX<b class='flag-5'>开发</b><b class='flag-5'>GD32F303</b>(14)----IIC之配置OLED

    GD32F303固件库开发

    的可以加群申请:615061293 。 GD32F303固件库开发(1)----前期准备与烧录 使用GDLINK、jlink、串口下载程序到GD芯片。 [https://blog.csdn.net
    的头像 发表于 07-27 09:27 782次阅读
    <b class='flag-5'>GD32F303</b>固件库<b class='flag-5'>开发</b>

    GD32F303红枫开发板使用手册】第二 GPIO-流水灯实验

    GD32F303系列MCU最多可支持 112 个通用I/O 引脚(GPIO),分别为 PA0 ~ PA15, PB0 ~ PB15, PC0 ~ PC15,PD0 ~ PD15, PE0
    的头像 发表于 05-29 10:02 399次阅读
    【<b class='flag-5'>GD32F303</b><b class='flag-5'>红枫</b><b class='flag-5'>派</b><b class='flag-5'>开发板</b><b class='flag-5'>使用手册</b>】第二<b class='flag-5'>讲</b> GPIO-流水灯<b class='flag-5'>实验</b>

    GD32F303红枫开发板使用手册第三讲 GPIO-按键查询检测实验

    GD32F303系列MCU GPIO输入配置结构如下图所示,输入可配置上下拉电阻,通过施密特触发器后可通过备用功能输入或者通过输入状态寄存器进行读取。
    的头像 发表于 05-30 10:02 350次阅读
    【<b class='flag-5'>GD32F303</b><b class='flag-5'>红枫</b><b class='flag-5'>派</b><b class='flag-5'>开发板</b><b class='flag-5'>使用手册</b>】<b class='flag-5'>第三讲</b> GPIO-按键查询检测<b class='flag-5'>实验</b>

    GD32F303红枫开发板使用手册】第五 FMC-片内Flash擦写读实验

    MC即Flash控制器,其提供了片上Flash操作所需要的所有功能,在GD32F303系列MCU中,Flash前256K字节空间内, CPU执行指令零等待,具有相同主频下最快的代码执行效率。FMC也
    的头像 发表于 06-02 10:05 258次阅读
    【<b class='flag-5'>GD32F303</b><b class='flag-5'>红枫</b><b class='flag-5'>派</b><b class='flag-5'>开发板</b><b class='flag-5'>使用手册</b>】第五<b class='flag-5'>讲</b> FMC-片内Flash擦写读<b class='flag-5'>实验</b>

    GD32F303红枫开发板使用手册】第十六 USART-DMA串口收发实验

    在前面ADC章节中,我们介绍了DMA的工作原理,这里就不多做介绍。从GD32F303用户手册中可以查到,各串口的TX和RX分别对应DMA的不同通道,比如USART0的TX对应DMA0的通道3,而RX对应DMA0的通道4。
    的头像 发表于 06-15 09:54 289次阅读
    【<b class='flag-5'>GD32F303</b><b class='flag-5'>红枫</b><b class='flag-5'>派</b><b class='flag-5'>开发板</b><b class='flag-5'>使用手册</b>】第十六<b class='flag-5'>讲</b> USART-DMA串口收发<b class='flag-5'>实验</b>

    GD32F303红枫开发板使用手册】第二十 SPI-SPI NAND FLASH读写实验

    通过本实验主要学习以下内容: •SPI通信协议,参考19.2.1东方红开发板使用手册GD32F303 SPI操作方式,参考19.2.2
    的头像 发表于 06-20 09:50 158次阅读
    【<b class='flag-5'>GD32F303</b><b class='flag-5'>红枫</b><b class='flag-5'>派</b><b class='flag-5'>开发板</b><b class='flag-5'>使用手册</b>】第二十<b class='flag-5'>讲</b> SPI-SPI NAND FLASH读写<b class='flag-5'>实验</b>