基于stm32f103的CAN通讯实验

由于篇幅较大,我在这里就不介绍CAN通信的基础知识了,感兴趣可以自己去学一下。本实验使用STM32F103ZET6 的 CAN 外设实现两个设备之间的通讯,该实验中使用了两个实验板,是在MDK编译环境下调试。可以参考STM32F103的官方参考手册和官方数据手册。

1 硬件设计

原理图

2 软件设计

2.1 bsp_can.h文件代码及讲解

#ifndef __CAN_H
#define	__CAN_H
#include "stm32f10x.h"
#include "./usart/bsp_debug_usart.h"
#define CANx                      CAN1
#define CAN_CLK                   RCC_APB1Periph_CAN1
#define CAN_RX_IRQ								USB_LP_CAN1_RX0_IRQn
#define CAN_RX_IRQHandler					USB_LP_CAN1_RX0_IRQHandler
#define CAN_RX_PIN                GPIO_Pin_8
#define CAN_TX_PIN                GPIO_Pin_9
#define CAN_TX_GPIO_PORT          GPIOB
#define CAN_RX_GPIO_PORT          GPIOB
#define CAN_TX_GPIO_CLK           (RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB)
#define CAN_RX_GPIO_CLK           RCC_APB2Periph_GPIOB
/*debug*/
#define CAN_DEBUG_ON         1
#define CAN_DEBUG_ARRAY_ON   1
#define CAN_DEBUG_FUNC_ON    1  
// Log define
#define CAN_INFO(fmt,arg...)           printf("<<-CAN-INFO->> "fmt"\n",##arg)
#define CAN_ERROR(fmt,arg...)          printf("<<-CAN-ERROR->> "fmt"\n",##arg)
#define CAN_DEBUG(fmt,arg...)          do{\
                                         if(CAN_DEBUG_ON)\
                                         printf("<<-CAN-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
                                       }while(0)

#define CAN_DEBUG_ARRAY(array, num)    do{\
                                         int32_t i;\
                                         uint8_t* a = array;\
                                         if(CAN_DEBUG_ARRAY_ON)\
                                         {\
                                            printf("<<-CAN-DEBUG-ARRAY->>\n");\
                                            for (i = 0; i < (num); i++)\
                                            {\
                                                printf("%02x   ", (a)[i]);\
                                                if ((i + 1 ) %10 == 0)\
                                                {\
                                                    printf("\n");\
                                                }\
                                            }\
                                            printf("\n");\
                                        }\
                                       }while(0)

#define CAN_DEBUG_FUNC()               do{\
                                         if(CAN_DEBUG_FUNC_ON)\
                                         printf("<<-CAN-FUNC->> Func:%s@Line:%d\n",__func__,__LINE__);\
                                       }while(0)
static void CAN_GPIO_Config(void);
static void CAN_NVIC_Config(void);
static void CAN_Mode_Config(void);
static void CAN_Filter_Config(void);
void CAN_Config(void);
void CAN_SetMsg(CanTxMsg *TxMessage);
void Init_RxMes(CanRxMsg *RxMessage);
#endif

以上代码根据硬件连接,把与 CAN 通讯使用的 CAN 号 、引脚号以及时钟都以宏封装
起来,并且定义了接收中断的中断向量和中断服务函数,我们通过中断来获知接收 FIFO
的信息。注意在 GPIO 时钟部分我们还加入了 AFIO 时钟,这是为下面 CAN 进行复用功能重映射而设置的,当使用复用功能重映射时,必须开启 AFIO 时钟。

2.2 bsp_can.c文件代码及讲解

2.2.1 初始化 CAN 的 GPIO

/*
 * 函数名:CAN_GPIO_Config
 * 描述  :CAN的GPIO 配置
 * 输入  :无
 * 输出  : 无
 * 调用  :内部调用
 */
static void CAN_GPIO_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;   	
  /* Enable GPIO clock */
  RCC_APB2PeriphClockCmd(CAN_TX_GPIO_CLK|CAN_RX_GPIO_CLK, ENABLE);
	//重映射引脚
  GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
	 /* Configure CAN TX pins */
  GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		         // 复用推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(CAN_TX_GPIO_PORT, &GPIO_InitStructure);
	/* Configure CAN RX  pins */
  GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN ;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	             // 上拉输入
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(CAN_RX_GPIO_PORT, &GPIO_InitStructure);
}

与所有使用到 GPIO 的外设一样,都要先把使用到的 GPIO 引脚模式初始化,配置好
复用功能,CAN 的两个引脚都配置成通用推挽输出模式即可。根据 CAN 外设的要求,TX引脚要被配置成推挽复用输出、RX 引脚要被设置成浮空输入或上拉输入。另外,由于本实验板中的 CAN 使用的 PB8、PB9 的默认复用功能不是 CAN 外设的引脚,需要使用外设引脚的复用功能重映射才能正常使用,代码中使用 GPIO_PinRemapConfig 函数把 CAN 映射至 PB8、PB9。

引脚的复用功能重映射

2.2.2 配置 CAN 的工作模式

接下来我们配置 CAN 的工作模式,由于我们是自己用的两个板子之间进行通讯,波
特率之类的配置只要两个板子一致即可。如果您要使实验板与某个 CAN 总线网络的通讯
的节点通讯,那么实验板的 CAN 配置必须要与该总线一致。

/*
 * 函数名:CAN_Mode_Config
 * 描述  :CAN的模式 配置
 * 输入  :无
 * 输出  : 无
 * 调用  :内部调用
 */
static void CAN_Mode_Config(void)
{
	CAN_InitTypeDef        CAN_InitStructure;
	/************************CAN通信参数设置**********************************/
	/* Enable CAN clock */
    RCC_APB1PeriphClockCmd(CAN_CLK, ENABLE);
	/*CAN寄存器初始化*/
	CAN_DeInit(CANx);
	CAN_StructInit(&CAN_InitStructure);
	/*CAN单元初始化*/
	CAN_InitStructure.CAN_TTCM=DISABLE;			   //MCR-TTCM  关闭时间触发通信模式使能
	CAN_InitStructure.CAN_ABOM=ENABLE;			   //MCR-ABOM  自动离线管理 
	CAN_InitStructure.CAN_AWUM=ENABLE;			   //MCR-AWUM  使用自动唤醒模式
	CAN_InitStructure.CAN_NART=DISABLE;			   //MCR-NART  禁止报文自动重传	  DISABLE-自动重传
	CAN_InitStructure.CAN_RFLM=DISABLE;			   //MCR-RFLM  接收FIFO 锁定模式  DISABLE-溢出时新报文会覆盖原有报文  
	CAN_InitStructure.CAN_TXFP=DISABLE;			   //MCR-TXFP  发送FIFO优先级 DISABLE-优先级取决于报文标示符 
	CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;  //正常工作模式
	CAN_InitStructure.CAN_SJW=CAN_SJW_2tq;		   //BTR-SJW 重新同步跳跃宽度 2个时间单元 
	/* ss=1 bs1=5 bs2=3 位时间宽度为(1+5+3) 波特率即为时钟周期tq*(1+3+5)  */
	CAN_InitStructure.CAN_BS1=CAN_BS1_5tq;		   //BTR-TS1 时间段1 占用了5个时间单元
	CAN_InitStructure.CAN_BS2=CAN_BS2_3tq;		   //BTR-TS1 时间段2 占用了3个时间单元	
	/* CAN Baudrate = 1 MBps (1MBps已为stm32的CAN最高速率) (CAN 时钟频率为 APB1 = 36 MHz) */
	CAN_InitStructure.CAN_Prescaler =4;		   BTR-BRP 波特率分频器  定义了时间单元的时间长度 36/(1+5+3)/4=1 Mbps
	CAN_Init(CANx, &CAN_InitStructure);
}

这段代码主要是把 CAN 的模式设置成了正常工作模式,如果您阅读的是“CAN—回环测试”的工程,这里是被配置成回环模式的,除此之外,两个工程就没有其它差别了。代码中还把位时序中的 BS1 和 BS2 段分别设置成了 5Tq 和 3Tq,再加上 SYNC_SEG段,一个 CAN 数据位就是 9Tq 了,加上 CAN 外设的分频配置为 4 分频,CAN 所使用的总线时钟 fAPB1 = 36MHz,于是我们可计算出它的波特率:
1Tq = 1/(36M/4)=1/9 us
T1bit=(5+3+1) x Tq =1us
波特率=1/T1bit =1Mbps

2.2.3 配置筛选器

/*
 * 函数名:CAN_Filter_Config
 * 描述  :CAN的过滤器 配置
 * 输入  :无
 * 输出  : 无
 * 调用  :内部调用
 */
static void CAN_Filter_Config(void)
{
	CAN_FilterInitTypeDef  CAN_FilterInitStructure;
	/*CAN筛选器初始化*/
	CAN_FilterInitStructure.CAN_FilterNumber=0;						//筛选器组0
	CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;	//工作在掩码模式
	CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;	//筛选器位宽为单个32位。
	/* 使能筛选器,按照标志的内容进行比对筛选,扩展ID不是如下的就抛弃掉,是的话,会存入FIFO0。 */
	CAN_FilterInitStructure.CAN_FilterIdHigh= ((((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF0000)>>16;		//要筛选的ID高位 
	CAN_FilterInitStructure.CAN_FilterIdLow= (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; //要筛选的ID低位 
	CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0xFFFF;			//筛选器高16位每位必须匹配
	CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0xFFFF;			//筛选器低16位每位必须匹配
	CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0 ;				//筛选器被关联到FIFO0
	CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;			//使能筛选器
	CAN_FilterInit(&CAN_FilterInitStructure);
	/*CAN通信中断使能*/
	CAN_ITConfig(CANx, CAN_IT_FMP0, ENABLE);
}

这段代码把筛选器第 0 组配置成了 32 位的掩码模式,并且把它的输出连接到接收
FIFO0,若通过了筛选器的匹配,报文会被存储到接收 FIFO0。筛选器配置的重点是配置 ID 和掩码,根据我们的配置,这个筛选器工作在下图的模式。
在这里插入图片描述
在该配置中,结构体成员 CAN_FilterIdHigh 和 CAN_FilterIdLow 存储的是要筛选的 ID, 而 CAN_FilterMaskIdHigh 和 CAN_FilterMaskIdLow 存储的是相应的掩码。在赋值时,要注意寄存器位的映射,在 32 位的 ID 中,第 0 位是保留位,第 1 位是 RTR 标志,第 2 位是IDE 标志,从第 3 位起才是报文的 ID(扩展 ID)。因此在上述代码中我们先把扩展ID“0x1314”、IDE 位标志“宏 CAN_ID_EXT”以及RTR 位标志“宏 CAN_RTR_DATA”根据寄存器位映射组成一个 32 位的数据,然后再把它的高 16 位和低 16 位分别赋值给结构体成员 CAN_FilterIdHigh 和 CAN_FilterIdLow。而在掩码部分,为简单起见我们直接对所有位赋值为 1,表示上述所有标志都完全一样的报文才能经过筛选,所以我们这个配置相当于单个 ID 列表的模式,只筛选了一个 ID号,而不是筛选一组 ID 号。这里只是为了演示方便,实际使用中一般会对不要求相等的数据位赋值为 0,从而过滤一组 ID,如果有需要,还可以继续配置多个筛选器组,最多可以配置 28 个,代码中只是配置了筛选器组 0。

2.2.4 配置接收中断

在配置筛选器代码的最后部分我们还调用库函数 CAN_ITConfig 使能了 CAN 的中断,
该函数使用的输入参数宏 CAN_IT_FMP0 表示当 FIFO0 接收到数据时会引起中断,该接收中断的优先级配置如下

/*
 * 函数名:CAN_NVIC_Config
 * 描述  :CAN的NVIC 配置,第1优先级组,0,0优先级
 * 输入  :无
 * 输出  : 无
 * 调用  :内部调用
 */
static void CAN_NVIC_Config(void)
{
   	NVIC_InitTypeDef NVIC_InitStructure;
		/* Configure one bit for preemption priority */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	 	/*中断设置*/
	NVIC_InitStructure.NVIC_IRQChannel = CAN_RX_IRQ;	   //CAN1 RX0中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;		   //抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;			   //子优先级为0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

这部分与我们配置其它中断的优先级无异,都是配置 NVIC 结构体,优先级可根据自己的需要配置,最主要的是中断向量,上述代码中把中断向量配置成了 CAN 的接收中断。

2.2.5 设置发送报文

要使用 CAN 发送报文时,我们需要先定义一个发送报文结构体并向它赋值

/*
 * 函数名:CAN_SetMsg
 * 描述  :CAN通信报文内容设置,设置一个数据内容为0-7的数据包
 * 输入  :发送报文结构体
 * 输出  : 无
 * 调用  :外部调用
 */	 
void CAN_SetMsg(CanTxMsg *TxMessage)
{	  
	uint8_t ubCounter = 0;
  //TxMessage.StdId=0x00;						 
  TxMessage->ExtId=0x1314;					 //使用的扩展ID
  TxMessage->IDE=CAN_ID_EXT;					 //扩展模式
  TxMessage->RTR=CAN_RTR_DATA;				 //发送的是数据
  TxMessage->DLC=8;							 //数据长度为8字节
	/*设置要发送的数据0-7*/
	for (ubCounter = 0; ubCounter < 8; ubCounter++)
  {
    TxMessage->Data[ubCounter] = ubCounter;
  }
}

这段代码是我们为了方便演示而自己定义的设置报文内容的函数,它把报文设置成了扩展模式的数据帧,扩展 ID 为 0x1314,数据段的长度为 8,且数据内容分别为 0-7,实际应用中您可根据自己的需求发设置报文内容。当我们设置好报文内容后,调用库函数CAN_Transmit 即可把该报文存储到发送邮箱,然后 CAN 外设会把它发送出去:
CAN_Transmit(CANx, &TxMessage);

2.3 接收报文(stm32f10x_it.c)

extern __IO uint32_t flag ;		 //用于标志是否接收到数据,在中断函数中赋值
extern CanRxMsg RxMessage;				 //接收缓冲区
/*
 * 函数名:USB_LP_CAN1_RX0_IRQHandler
 * 描述  :USB中断和CAN接收中断服务程序,USB跟CAN公用I/O,这里只用到CAN的中断。
 * 输入  :无
 * 输出  : 无	 
 * 调用  :无
 */
void CAN_RX_IRQHandler(void)
{
	/*从邮箱中读出报文*/
	CAN_Receive(CANx, CAN_FIFO0, &RxMessage);
	/* 比较ID是否为0x1314 */ 
	if((RxMessage.ExtId==0x1314) && (RxMessage.IDE==CAN_ID_EXT) && (RxMessage.DLC==8) )
	{
	flag = 1; 					       //接收成功  
	}
	else
	{
	flag = 0; 					   //接收失败
	}
}

根据我们前面的配置,若 CAN 接收的报文经过筛选器匹配后会被存储到 FIFO0 中,并引起中断进入到这个中断服务函数中,在这个函数里我们调用了库函数 CAN_Receive 把报文从 FIFO 复制到自定义的接收报文结构体 RxMessage 中,并且比较了接收到的报文 ID是否与我们希望接收的一致,若一致就设置标志 flag=1,否则为 0,通过 flag 标志通知主程序流程获知是否接收到数据。
要注意如果设置了接收报文中断,必须要在中断内调用 CAN_Receive 函数读取接收FIFO 的内容,因为只有这样才能清除该 FIFO 的接收中断标志,如果不在中断内调用它清除标志的话,一旦接收到报文,STM32 会不断进入中断服务函数,导致程序卡死。

2.4 bsp_key.h文件代码及讲解

#ifndef __KEY_H
#define	__KEY_H
#include "stm32f10x.h"
//  引脚定义
#define    KEY1_GPIO_CLK     RCC_APB2Periph_GPIOA
#define    KEY1_GPIO_PORT    GPIOA			   
#define    KEY1_GPIO_PIN		 GPIO_Pin_0

#define    KEY2_GPIO_CLK     RCC_APB2Periph_GPIOC
#define    KEY2_GPIO_PORT    GPIOC		   
#define    KEY2_GPIO_PIN		  GPIO_Pin_13
 /** 按键按下标置宏
	*  按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
	*  若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可
	*/
#define KEY_ON	1
#define KEY_OFF	0

void Key_GPIO_Config(void);
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
#endif /* __KEY_H */

2.5 bsp_key.c文件代码及讲解

/**
  * @brief  配置按键用到的I/O口
  * @param  无
  * @retval 无
  */
void Key_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*开启按键端口的时钟*/
	RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK|KEY2_GPIO_CLK,ENABLE);
	
	//选择按键的引脚
	GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; 
	// 设置按键的引脚为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	//使用结构体初始化按键
	GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
	
	//选择按键的引脚
	GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; 
	//设置按键的引脚为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	//使用结构体初始化按键
	GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure);	
}

 /*
 * 函数名:Key_Scan
 * 描述  :检测是否有按键按下
 * 输入  :GPIOx:x 可以是 A,B,C,D或者 E
 *		     GPIO_Pin:待读取的端口位 	
 * 输出  :KEY_OFF(没按下按键)、KEY_ON(按下按键)
 */
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )  
	{	 
		/*等待按键释放 */
		while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);   
		return 	KEY_ON;	 
	}
	else
		return KEY_OFF;
}

2.6 bsp_usart.h文件代码及讲解

#ifndef __DEBUG_USART_H
#define	__DEBUG_USART_H
#include "stm32f10x.h"
#include <stdio.h>
/** 
  * 串口宏定义,不同的串口挂载的总线不一样,移植时需要修改这几个宏
  */
#define  DEBUG_USART                   	USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd        RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE          115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10
void Debug_USART_Config(void);
//int fputc(int ch, FILE *f);
#endif /* __USART1_H */

2.7 bsp_usart.c文件代码及讲解

/**
  * @brief  USART1 GPIO 配置,工作模式配置。115200 8-N-1
  * @param  无
  * @retval 无
  */
void Debug_USART_Config(void)
{
 	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(DEBUG_USART, &USART_InitStructure);	
	// 使能串口
	USART_Cmd(DEBUG_USART, ENABLE);	    
}

///重定向c库函数printf到USART1
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到USART1 */
		USART_SendData(DEBUG_USART, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

///重定向c库函数scanf到USART1
int fgetc(FILE *f)
{
		/* 等待串口1输入数据 */
		while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USART);
}

2.8 main函数

__IO uint32_t flag = 0;		 //用于标志是否接收到数据,在中断函数中赋值
CanTxMsg TxMessage;			     //发送缓冲区
CanRxMsg RxMessage;				 //接收缓冲区
/// 不精确的延时
static void can_delay(__IO u32 nCount)
{
	for(; nCount != 0; nCount--);
} 

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{	
	LED_GPIO_Config();
	
    /*初始化USART1*/
    Debug_USART_Config();
	/*初始化按键*/
	Key_GPIO_Config();
	/*初始化can,在中断接收CAN数据包*/
	CAN_Config();
	
  while(1)
	{
		/*按一次按键发送一次数据*/
		if(	Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
		{
			LED_BLUE;
			/*设置要发送的报文*/
			CAN_SetMsg(&TxMessage);
			/*把报文存储到发送邮箱,发送*/
			CAN_Transmit(CANx, &TxMessage);
			can_delay(10000);//等待发送完毕,可使用CAN_TransmitStatus查看状态
			LED_GREEN;
			printf("\r\n已使用CAN发送数据包!\r\n"); 			
			printf("\r\n发送的报文内容为:\r\n");
			printf("\r\n 扩展ID号ExtId:0x%x \r\n",TxMessage.ExtId);
			CAN_DEBUG_ARRAY(TxMessage.Data,8); 
		}
		if(flag==1)
		{		
			LED_GREEN;
			printf("\r\nCAN接收到数据:\r\n");	
			CAN_DEBUG_ARRAY(RxMessage.Data,8); 
			flag=0;
		}
	}
}

在 main 函数里,我们调用了 CAN_Config 函数初始化 CAN 外设,它包含我们前面解
说的 GPIO 初始化函数 CAN_GPIO_Config、中断优先级设置函数 CAN_NVIC_Config、工作模式设置函数 CAN_Mode_Config 以及筛选器配置函数 CAN_Filter_Config。
初始化完成后,我们在 while 循环里检测按键,当按下实验板的按键 1 时,它就调用CAN_SetMsg 函数设置要发送的报文,然后调用 CAN_Transmit 函数把该报文存储到发送邮箱,等待 CAN 外设把它发送出去。代码中并没有检测发送状态,如果需要,您可以调用库函数 CAN_TransmitStatus 检查发送状态。while 循环中在其它时间一直检查 flag 标志,当接收到报文时,我们的中断服务函数会把它置 1,所以我们可以通过它获知接收状态,当接收到报文时,我们把它使用宏CAN_DEBUG_ARRAY 输出到串口。

3 实验结果

在这里插入图片描述
在这里插入图片描述

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页