打印
[研电赛技术支持]

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

[复制链接]
181|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
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_HCAN_L。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 当CAN总线上的电位差为0V时,表示隐性电平,隐性电平代表逻辑“1”;当CAN总线上有电位差时(大概在2.5V左右),表示显性电平,显性电平代表逻辑“0”。总线空闲时,默认为隐性电平,即总线电位差为0
关于电位差、隐性/显性电平及逻辑电平,非常容易弄混,读者需要熟记。
CAN总线的特点可以总结为:
1. 多主
USART-485这种一主多从类型总线不同,CAN总线是多主控制,即总线上没有主机从机之分,所有设备都是处于平等的地位。
2. 消息格式
CAN总线上的消息都以固定格式发送。当两个以上的单元同时开始发送消息时,根据标识符( Identifier 以下称为 ID)决定优先级。ID 并不是表示发送的目的地址,而是表示访问总线的消息的优先级。   
3. 通信速度快,通信距离远
CAN最高可达1Mbps波特率,理论最远距离可达10Km,当然此时通讯速率较低,只有5Kbps以下。  
4. 具有错误检测、错误通知和错误恢复功能
CAN总线具有强大的错误检测、通知和恢复功能。当一个单元发生错误时其他单元会进行报错,正在发送的单元检测到错误后,会立即强制结束当前发送,并尝试重新发送(功能可配置),当发送错误的次数达到一定值后,该单元会自动从总线中退出,直到应用程序让其重新加入总线为止。
5. 半双工异步通讯
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通讯都始于帧开始段,帧开始是1bit位的显性电平(逻辑“0”),由发送方发出,接收方检测到一个bit逻辑“0”,开始准备接收数据。
仲裁段
好了,我们终于看到了帧ID的庐山真面目了。数据帧分为两种——标准帧和扩展帧,标准帧的帧ID11bit组成,扩展帧有11+1829bit组成。需要注意,无论是标准帧ID还是扩展帧ID,都不允许设置最高7bit1(即不允许帧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单元同时发送数据,其中单元一发出的数标准帧的ID0b,00110000000,而单元二发出的标准帧ID0b,00100000000,可以看到发送到第4位时,单元一发出的是逻辑“1”,单元二的是逻辑“0”,因为总线上“0”“1”优先级搞,所以此时单元二获得总线发送权,单元一将自动停止发送。
控制段
控制段由6bit组成,其中DLC占用4bit,表示要发送的字节数。
数据段
数据段即帧载有的有效数据了,CAN最多一次可发送8个字节(CANFD最多可以发送64字节,这个后面介绍带CANFD功能的开发板时再细说),也就是说上面描述的DLC最大值为0b,1000
• CRC
CRC段总共有16bit,其中15bit表示CRC值,另1bitCRC界定符。此段 CRC 的值计算范围包括:帧起始、仲裁段、控制段、数据段。发送会根据发送的数据来计算出一个CRC值并通过CRC段发到总线,接收方以同样的算法计算CRC值并和发送方发出的CRC段进行比较,当不一致时接收方会向总线报错。
• ACK
ACK段用来确认接收方是否正常接收。ACK段由 ACK (ACK Slot)ACK 界定符 2 个位组成。
发送单元的 ACK,发送 2 个位的隐性位,而接收到正确消息的单元在 ACK 槽(ACK Slot)发送显性位,通知发单元正常接收结束,这个过程叫发送 ACK/返回 ACK。发送 ACK 的是在既不处于总线关闭态也不处于休眠态的所有收单元中,接收到正常消息的单元(发送单元不发送 ACK)。
帧结束
帧结束由7bit的逻辑“1”组成,表示该帧结束。
30.2.5 CAN的波特率
前面有提到CAN的波特率,波特率代表每秒钟传输的位数(1位就表示1bit),我们来看下GD32F303的波特率是怎么计算的。
首先需要了解一个概念,CAN时序的最小单位是一个叫Tq的东西,CAN的每个位即每个bit是由若干个Tq组成的,那么这个Tq的长度是多少呢?GD32F303CAN是挂载在APB1总线的:
如果读者将GD32F303的主频配置为最高120M,且将APB1的时钟配置为最高60M的话,那么CAN的时钟就是60M,然后CAN有个分频系数,在位时序寄存器CAN_BT中:
一个Tq占用的时间计算公式:分频系数/CAN时钟,举个例子,我们设置这个分频系数为6的话,一个Tq的时间就是6/60M = 100us,也可以说Tq的传输速率为10M。那么由多少个Tq组成CAN的一个位呢?还是看CAN_BT寄存器:
寄存器中有BS1BS2,这两个位域用于设置位段1和位段2(关于位段后面会介绍),一个CAN位占用的Tq个数等于位段1+位段2+1,举个例子,设置位段15(即BS1=4),设置位段24(即BS2=3),那么一个CAN位占用的Tq个数为5+4+1=10。好,现在就可以来算CAN的波特率了,按照CAN分频系数为6,位段15,位段24,一个位占用的时间为6/60M*10 = 1ms,也就是波特率=1M。我们可以把这个计算转化为公式:
30.2.6 CAN的位时序和采样点
我们现在来看下上一节提到的位段1和位段2CAN总线控制器将位时间分为3个部分。
同步段(Synchronization segment),记为SYNC_SEG。该段占用1个时间单元(1 × ����)。
位段1Bit segment 1),记为BS1。该段占用116Tq(由位时序寄存器配置)。相对于CAN协议而言, BS1相当于传播时间段(Propagation delay segment)和相位缓冲段1Phase buffer segment 1)。
位段2Bit segment 2),记为BS2。该段占用18Tq(由位时序寄存器配置)。相对于CAN协议而言, BS2相当于相位缓冲段2Phase buffer segment 2)。
位时序图:
这里提到的BS1(即位段1)和CAN时序寄存器中的BS1[3:0]位域不是一个概念,位段1=BS1[3:0]+1
说完位时序我们来介绍下GD32F303的采样点。
对于接收方来说,需要对发送方发出的每个bit进行采样,那么具体是采样哪个点呢?按照CAN标准来说,采样点为BS1BS2的交界处,即:
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]FTFFEFID[17:15]


过滤器可以设置为两种模式——掩码模式和列表模式:
掩码模式
对于一个待过滤的数据帧的标识符(Identifier),掩码模式用来指定哪些位必须与预设的标识符相同,哪些位无需判断。掩码模式有两种位宽:32bit16bit。  
一个 32-bit 位宽掩码模式过滤器如下:
可以看到,在掩码模式下,FDATA0用于目标IDFDATA1用于Mask。举个例子,设置FDATA00x55550000FDATA10xFF00FF00(第31~24以及第15~8位为1),那么意味着总线上的报文ID的第31~24以及第15~8位必须和FDATA0相应位相同,就可以通过这个过滤器,而其他位则不需要关心,也就是说帧ID0x55xx00xx可以通过过滤器。
明白了32bit掩码模式过滤器,16位位宽就很好理解了。一个16-bit位宽掩码模式过滤器如下:
32-bit的不用,16-bit位宽掩码过滤器的IDFDATA0的高16bitMaskFDATA0的低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,下面我们分别介绍数据发送和数据接收。
数据发送
发送寄存器的框图:
三个发送邮箱对于三组发送寄存器TMIxTMPxTMDATA0xTMDATA1xx=0,1,2):
发送邮箱标识符寄存器(CAN_TMIx
发送邮箱属性寄存器(CAN_TMPx)
发送邮箱 data0 寄存器(CAN_TMDATA0x):
发送邮箱 data1 寄存器(CAN_TMDATA1x):
当需要发送数据时,选择一个空闲(empty)的邮箱(读取CAN_TSTAT寄存器获取),然后将该邮箱对应TMIxTMPxTMDATA0xTMDATA1x寄存器填好后,使能TMIxTEN位,寄存器中的数据就自动转移到邮箱。
实际上数据到邮箱后也不一定就马上发送到总线,因为有可能总线上正有数据发送,或者其他的邮箱中也有数据,这就涉及到CAN发送邮箱的调度:
当发送邮箱被填入新的数据后,邮箱状态从empty转到pending状态。当超过1 个邮箱处于 pending 状态时,需要对多个邮箱进行调度,这时发送邮箱处于 scheduled 状态。当调度完成后,发送邮箱中的数据开始向 CAN 总线上发送,这时发送邮箱处于 transmit 状态。当数据发送完成,邮箱变为空闲,可以再次交给应用程序使用,这时发送邮箱重新变为 empty 状态。
发送邮箱状态转换图:
当多个发送邮箱处于等待状态下时,可以通过CAN_CTLTFO位的值可以决定发送顺序:
TFO1,所有等待发送的邮箱按照先来先发送(FIFO)的顺序进行。
TFO0,具有最小标识符(Identifier)的邮箱最先发送。如果所有的标识符(Identifier)相等,具有最小邮箱编号的邮箱最先发送。  
数据接收
接收寄存器的框图:
两个接收FIFO对应了两组接收寄存器RFIFOMIxRFIFOMPxRFIFOMDATA0xRFIFOMDATA1xx=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_RFIFOMIxCAN_RFIFOMPxCAN_RFIFOMDATA0xCAN_RFIFOMDATA1x读取数据,之后将寄存器CAN_RFIFOxRFD1释放邮箱。
用户可以通过读取寄存器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位置1SLPWMOD位清0
由睡眠模式进入正常工作模式:将CAN_CTL寄存器的IWMOD位和SLPWMOD位清0
初始化工作模式
如果需要配置 CAN 总线通信参数, CAN 总线控制器必须进入初始化工作模式。将 CAN_CTL寄存器的 IWMOD 位置 1,使 CAN 总线控制器进入初始化工作模式,将其清 0 则离开初始化 工作模式。在进入初始化工作模式后, CAN_STAT 寄存器的IWS 位将被硬件置 1
由初始化模式进入睡眠模式: CAN_CTL 寄存器的 SLPWMOD 位置 1IWMOD 位清 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的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_BAUDRATEdriver_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 FIFO0FIFO1的中断接收处理函数:
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 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,分别发送帧ID0x300,0x5000x400的文到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接收,而由于过滤器的配置,ID0x300的会被接收到FIFO0中,ID0x500的会被接收到FIFO1中,而ID0x400的由于无法通过过滤器,被CAN舍弃。
30.5 实验结果
使用USB-TypeC线,连接电脑和板上USB to UART口后,配置好串口调试助手,即可看到CAN发送和接收数据的情况:

本教程由GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网,GD32MCU技术交流群:859440462

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

90

主题

110

帖子

2

粉丝