【教程】CubeMX配置CAN通讯:全面指南,避免踩坑,附完整工程文件

这两天在学习CAN的过程中,踩过了无数的坑,各种莫名其妙的死机、发送接收无响应差点把我搞疯了,在网上查资料也很难查到针对自己问题的答案。星光不负赶路人,今天终于调好了,本着“人人为我,我为人人!”的精神,现在把过程记录下来,避免后来者踩坑。注意,本文非常合适“快餐式”熟悉CAN的同学们,比如赶着投胎并不想了解CAN通讯的,那恭喜你,为了更细致,篇幅可能有点长;如果你想详细了解CAN通讯,请出门右转。

先说一波,转载我的文章拿去卖钱的,生儿子没PY!!!!

前言

手里有不少板子跟教材,包括正点原子的、硬石科技的,首先非常感谢这些为嵌入式开荒的前辈们。历史惯例,先吹捧一波,再吐槽一波,针对新版HAL库的CAN通讯,他们教材里描述得还是不尽人意。

开始正文之前,先把我踩过的坑罗列一下,加深大家的印象。

踩坑一:

先说正点原子,用他们的HAL库例程调试,其实一点问题没有,但是他们的教材里面用的是非中断接收,我们具体来看他们的接收函数:

uint8_t CAN_Receive_Msg(uint8_t *buf)
{
 	uint32_t i;
	uint8_t	RxData[8];

	if(HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) != 1)
	{
		return 0xF1;
	}

	if(HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
	{
		return 0xF2;
	}
	
  for(i=0;i<RxHeader.DLC;i++)  buf[i]=RxData[i];
	
	return RxHeader.DLC;
}

重点在于这句判定:HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) != 1,从HAL库对这个函数的定义是: allows to know how many Rx message are stored in the Rx Fifo. 用我蹩脚的英语翻译一下:即让你们这群屌丝们清晰地知道Rx Fifo接收信箱里当前存储了多少帧数据。

重点来了,如果你是用中断接收CAN数据(即在CubeMx里面使能了Rx0 interrupts中断接收并配置中断优先级),千万不要用这个判断,不然一旦接收到数据后,就触发return 0xF1了,通过IDE可以看到这个返回值初始状态就是241(十进制)。所以,用中断的话,千万不要这个判定!!!用中断接收,该怎么配置,后文会详细介绍。

踩坑二:

第二个其实不算是坑,算心得吧,就是关于过滤器的配置,相信很多人觉得CAN难的主因就是被过滤器配置搞懵逼的。关键是,CubeMx并不会帮我们生成过滤器配置相关函数,需要我们自己写!!!关于过滤器配置我挑几个重要的来讲,其他的可以看CSDN各路大神的文章。

1、首先了解什么叫标准帧跟扩展帧,简单点理解就是,扩展帧比标准帧可定义的范围更大。

2、其次是理解什么叫列表模式跟掩码模式,列表模式简单说就是从已知库里面寻找匹配的对象(范围窄),而掩码模式则是我只知道目标的局部特征(范围广),我想根据这个特征筛选出目标对象!比如掩码模式下,我只接收帧头ID为0x1800D8D0的数据,不是这个帧头的数据通通无视。

3、最后是过滤器的设置,过滤器是由Filter跟Mask组合形成的,假设你的Filter设置了捕捉的目标ID,当Mask设置成0x0000时,你设置的Filter将形同虚设,不起作用;当Mask设置成0xFFFF时,只有当数据的每一位都要跟你的Filter匹配,才会接收。

下面开始正文~~

一、硬件配置

MCU:STM32F103RC
CAN引脚:PB8、PB9
USART2引脚(串口打印用):PA2、PA3
其他的如LED、KEY,根据你们自己手里的板子设置,不是重点。

我用的是平衡小车之家的STM32小车控制板,上面带了个CAN通讯口,配套实验对象是硬石的F4Pro板子。

二、CubeMX配置

1、时钟及相关配置

时钟配置如下图所示,我这块板子外接是8M晶振,不太会配置的话,可以直接在主频那里设置72M,系统会自动帮你算PLL/PLLM,但是要自己确认一下,因为时钟频率会影响CAN的通讯波特率换算关系。

RCC高速时钟配置,低速时钟没有外接晶振,可以忽略。

SYS配置调试口,我用的是4线制的SWD,配置如下

 2、CAN通讯配置

CAN配置这里主要是波特率的设置,前三项根据公式换算后决定了CAN通讯的波特率,公式大家自己去找吧,我懒得百度了。。

在MCU主频72M的前提下,按我图里面设置出来的CAN通讯波特率为500k,想要其他速率的,自己按公式设置。

配置NVIC中断优先级,使能RX0中断接收,注意,前文我就说过,我用的是中断接收,所以务必使能RX0接收中断。

接下来是GPIO设置,我板子的CAN引脚是PB8/PB9,依次设置,上下拉跟复用,默认即可,新版的CubeMX已经很少坑了,不会给你配置无用的引脚。

 3、USART串口配置

 既然要调试,肯定离不开串口,不然怎么观察数据呢,对吧?左手右手一个慢动作printf,数据就从串口助手上看到了,多开心。串口通讯太细节的就不在这里啰嗦了,设置个115200bit/s的波特率,记得把全局中断勾上,引脚配置可以默认。

 4、NVIC中断优先级确认

最后打开NVIC确认一下中断优先级配置,看看前面我们启用的两个中断被正确勾选没有,注意这里使用的是4bits优先级组。

 5、其他GPIO配置

其他GPIO,根据自己板子来设置了,弄个按键、LED之类的,方便观察,这里不再赘述。

6、工程设置

我用的是keil5,如果你们用其他IDE,就根据实际来设置,勾选好固件版本。

在代码生成这栏里面,按图勾选,解释一下,第一行copy only xxxxxx,意思是只拷贝必须的库文件,不涉及的就不生成,可以缩小工程体积;第二行generate xxxx,意思是独立生成.c跟.h文件,我建议勾选,不然五花八门的东西全给你仍在一个文件里;第三行keep User xxxx,意思是在改动cubemx配置后,重新生成代码时,保留用户在IDE工程文件里面的代码,注意这个保留的前提是你编程的时候把代码敲在了/* USER CODE BEGIN 2 */ 跟/* USER CODE END 2 */之间,放在别的地儿,神仙难救,一重新生成就没了~~~

设置完后,点击右上角的按钮,即可生成工程目录。最终生成的工程目录如下图所示,IDE的工程文件在画圈文件夹内,打开即可。

三、程序编写

终于到了最激动人心的一步,是不是激动坏了,别激动,仔细慢慢看,做错一步可能就要从头看我的文章了。

先说一下我们本次CAN编程所要实现的功能,以汽车电池温度跟电量报警功能举例,具体见下图:

 从开发的需求可知:

(1)CAN接收的标识符ID为:0x1800D8D0,而发送的标识符ID为:0x1800D0D8;

(2)需要对数据帧的第2、3字节进行逻辑判断,数值决定了发送的数据内容;

了解需求后,我们开始编程。

1、USART文件

打开usart.h,添加头文件#include "stdio.h" 。否则下面usart.c里面的File会报错。

再usart.c,重定义printf和getchar,便于用串口助手调试,添加如下两个函数即可:

/* USER CODE BEGIN 1 */

//重定义printf和getchar,便于调试
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}

int fgetc(FILE * f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart2,&ch, 1, 0xffff);
  return ch;
}
/* USER CODE END 1 */

2、CAN文件

我们本次通讯采用扩展帧,而不是标准帧,不了解的话,先补一下CAN的基础知识。首先打开can.h,我们添加两个宏定义,便于规范化编程,第一个是扩展帧接收的标识符,第二个是扩展帧发送的标识符。

/* USER CODE BEGIN Private defines */
#define CAN_RxExtId 0x1800D8D0
#define CAN_TxExtId 0x1800D0D8
/* USER CODE END Private defines */

同时,继续在can.h中添加2个函数,以及3个外部变量声明;

/* USER CODE BEGIN Prototypes */
void CAN_Filter_Init(void);   //过滤器配置函数
uint8_t CAN_Send_Msg(uint8_t* msg,uint8_t len);  //数据发送函数

extern CAN_TxHeaderTypeDef	TxHeader;      //发送
extern CAN_RxHeaderTypeDef	RxHeader;      //接收
extern uint8_t	RxData[8];   //数据接收数组
/* USER CODE END Prototypes */

接着,我们开始编辑can.c文件,在顶部添加句柄、定义数组。

/* USER CODE BEGIN 0 */
CAN_TxHeaderTypeDef	TxHeader;      //发送
CAN_RxHeaderTypeDef	RxHeader;      //接收

uint8_t	RxData[8];  //数据接收数组,can的数据帧只有8帧
/* USER CODE END 0 */

由于初始化函数,cubemx已经帮生成好了,而且相关的中断,我们已经在cubemx里配置过了,所以不需要再动cubemx生成的几个初始化函数。

我们需要自己编写3个函数,第一个是过滤器配置的函数,过滤器配置不算太复杂,前面说了,我们采用的是扩展帧、掩码模式,ID标识符前面也提到了,按部就班地填入即可:

/*CAN过滤器初始化*/
void CAN_Filter_Init(void)
{
	CAN_FilterTypeDef sFilterConfig;
	
  sFilterConfig.FilterBank = 0;                    /* 过滤器组0 */
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  /* 屏蔽位模式 */
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; /* 32位。*/
  
  sFilterConfig.FilterIdHigh         = (((uint32_t)CAN_RxExtId<<3)&0xFFFF0000)>>16;				/* 要过滤的ID高位 */
  sFilterConfig.FilterIdLow          = (((uint32_t)CAN_RxExtId<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0xFFFF; /* 要过滤的ID低位 */
  sFilterConfig.FilterMaskIdHigh     = 0xFFFF;			/* 过滤器高16位每位必须匹配 */
  sFilterConfig.FilterMaskIdLow      = 0xFFFF;			/* 过滤器低16位每位必须匹配 */
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;           /* 过滤器被关联到FIFO 0 */
  sFilterConfig.FilterActivation = ENABLE;          /* 使能过滤器 */ 
  sFilterConfig.SlaveStartFilterBank = 14;
	
	if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
  {
		/* Filter configuration Error */
    Error_Handler();
  }
	
  if (HAL_CAN_Start(&hcan) != HAL_OK)
  {
    /* Start Error */
    Error_Handler();
  }
	
	  /*##-4- Activate CAN RX notification #######################################*/
  if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
  {
    /* Start Error */
    Error_Handler();
  }
	
	TxHeader.ExtId=CAN_TxExtId;        //扩展标识符(29位)
	TxHeader.IDE=CAN_ID_EXT;    //使用标准帧
	TxHeader.RTR=CAN_RTR_DATA;  //数据帧
	TxHeader.DLC=8; 
	TxHeader.TransmitGlobalTime = DISABLE;
}

第二个是can发送数据函数:

/*CAN发送数据,入口参数为要发送的数组指针,数据长度,返回0代表发送数据无异常,返回1代表传输异常*/
uint8_t CAN_Send_Msg(uint8_t* msg,uint8_t len)
{	
  uint8_t i=0;
	uint32_t TxMailbox;
	uint8_t message[8];

	TxHeader.ExtId=CAN_TxExtId;        //扩展标识符(29位)
	TxHeader.IDE=CAN_ID_EXT;    //使用扩展帧
	TxHeader.RTR=CAN_RTR_DATA;  //数据帧
	TxHeader.DLC=len;    
	
  for(i=0;i<len;i++)
  {
		message[i]=msg[i];
	}
	
  if(HAL_CAN_AddTxMessage(&hcan, &TxHeader, message, &TxMailbox) != HAL_OK)//发送
	{
		return 1;
	}
	while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3) {}
    return 0;
}

第三个是中断接收函数,注意,我们can采用的是中断接收,所以务必添加这个函数,否则无法接收数据:

/*CAN接收中断函数*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanNum)
{
	uint32_t i;
	
	Rx_Flag =1;  //接收标志位
	HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, RxData);
	for(i=0;i<RxHeader.DLC;i++)  RxBuf[i]=RxData[i];  //用RxBuf转存RxData的数据
}

3、main文件

定义数组及标志位,同时在main.h中把RxBuf[8]、Rx_Flag声明成外部变量,便于can.c调用

/* USER CODE BEGIN PV */
uint8_t Rx_Flag;
uint8_t Tx_Flag;
uint8_t RxBuf[8];
uint8_t TxBuf[8];
uint8_t key;

/* USER CODE END PV */

在main函数初始化阶段添加几个初始化函数:

/* USER CODE BEGIN 2 */
	CAN_Filter_Init();  //过滤器初始化
	KEY_Init();  //按键初始化
	LED_Init();  //LED初始化
  /* USER CODE END 2 */

while循环中添加数据处理逻辑(逻辑根据前文提到的需求设计):

while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

		if(Rx_Flag)   //如果接收标志位被置1,则开展逻辑判断
		{
			printf("Receive Succeed !\r\n");
			
			//接收数据执行逻辑判断,根据判断结果设置发送内容
			if(RxBuf[2] == 0xAA && RxBuf[3] == 0xBB) TxBuf[2] = 0x01;
			else if(RxBuf[2] == 0xAA && RxBuf[3] == 0xCC) TxBuf[3] = 0x01;
			else if(RxBuf[2] == 0xAA && RxBuf[3] == 0xDD) TxBuf[4] = 0x01;
			else TxBuf[2] = 0x00, TxBuf[3] = 0x00, TxBuf[4] = 0x00;
			
			Tx_Flag = CAN_Send_Msg(TxBuf,8);       //发送数据,根据返回值判定发送是否异常
			if(Tx_Flag) printf("Send failed ,please check your data !\r\n");   //返回1代表数据发送异常
			else printf("Send completed !\r\n");
			
			//清空接收、发送数组,保留Rxbuf内容
			memset(RxData,0,sizeof(RxData));
			memset(TxBuf,0,sizeof(TxBuf));
			HAL_Delay(50);
			
			Rx_Flag = 0;                 //标志位置0,等待下一次中断
		}
		LED0 = !LED0;    //LED闪烁
		HAL_Delay(200);
	
  }

清空数组,我用了memset函数,所以在main文件里记得添加#include "string.h",否则会报错。

至此,本章分享教程到此结束,用板子试验过,完全可以实现前文所提到的需求。

再说一次,本文严禁转载,拿我文章去卖钱的,生儿子没PY。

项目的工程文件我直接开源了,又不是什么核心机密的东西。有疑问的欢迎私信或者留言,看到必定答复,如果本文对你有所帮助,麻烦点个赞、点个收藏、点个关注,感激。~~~

某盘自取:https://pan.baidu.com/s/1-uB9oCQjaLKH-iBJgK0vOA?pwd=4lr0 
提取码:4lr0 
复制这段内容后打开百度网盘手机App,操作更方便哦

物联沃分享整理
物联沃-IOTWORD物联网 » 【教程】CubeMX配置CAN通讯:全面指南,避免踩坑,附完整工程文件

发表评论