STM32 f407 CAN收发 基于HAL库和Cubemx配置

记录配置CAN1和CAN2实现CAN收发的过程

实现功能:给CAN1和CAN2发送信息时,将收到的报文以相同类型的ID返回(发送标准ID则返回标准ID,发送扩展ID则返回扩展ID)

1.激活CAN外设并配置波特率

af984ac1a3d24b598c44dabb5af64837.png

这里的波特率设置和别的外设都是类似的,首先给一个分频系数确定外设的时钟频率,然后cubemx会自动计算出当前的时钟周期。然后乘以设置的(Segment1+Segment2)就是can报文发送一个字节的时间4000ns,包括波特率cubemx也自动计算出来了。

目前本人所在行业应用中最广泛使用的波特率就是250k,所以一般这组参数并不需要改变。

另外如果没有can收发芯片可以使用回环模式来实验,具体实现可以查查资料,这里不赘述。因为从实际应用出发,调试CAN通信时遇到的很多问题都是硬件连接上的问题,手里有外设也可以方便我们在学习的过程中发现这些容易出错的地方,得到能力的实质提升。

3f968024da9a4e859731b4aec50d3323.png

2.使能CAN接收中断a18854072c9e44c79c5d76f4c70e3026.png

d40ece7ad1d64660a93569c2e83bbee2.png

这里的RX0和RX1对应的是单片机中的两个接收邮箱FIFO1和FIFO2,这里设置CAN1使用FIFO0,CAN2使用FIFO2

3.生成代码并配置过滤器

过滤器就是一个进行报文ID判断的东西,让CPU不用来一条报文就判断一遍ID。实际应用中很多项目并没有太多发送CAN信号的外设,或者说单片机并不会接收到那么多无关的报文,所以其实不过滤也没事。但是HAL库要求必须配置过滤器才能开启CAN接收,所以我们就配置一个什么ID都不过滤的过滤器。(不要因为一时没吃透这个过滤器而担心,当需要用到过滤器时你再查下资料立马就会使用了,尤其是在你把CAN收发都调通了的情况下)

在can.c的MX_CAN1_Init()中配置过滤器:

/* USER CODE BEGIN CAN1_Init 2 */
// 过滤器结构体
  CAN_FilterTypeDef  sFilterConfig;
// 过滤器参数
sFilterConfig.FilterBank = 1;                      // 过滤器序号
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 掩码模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位掩码
sFilterConfig.FilterIdHigh = 0x0000;               // 
sFilterConfig.FilterIdLow = 0x0000;                // 
sFilterConfig.FilterMaskIdHigh = 0x0000;           // 
sFilterConfig.FilterMaskIdLow = 0x0000;            // 
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 给邮箱0配置的过滤器
sFilterConfig.FilterActivation = ENABLE;           // 滤波器使能
sFilterConfig.SlaveStartFilterBank = 13;           // 分配滤波器资源
// 配置并自检
while(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK);
/* USER CODE END CAN1_Init 2 */

这里FilterIdHigh、FilterIdLow、FilterMaskIdHigh、FilterMaskIdLow都被设置为0x0000,所有报文就都能通过滤波器了。HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig)将定义的这些参数配置给CAN1,若配置成功则继续往下执行

配置CAN2也是一样,注意邮箱要设置为CAN_RX_FIFO1

// 过滤器结构体
  CAN_FilterTypeDef  sFilterConfig;
// 过滤器参数
sFilterConfig.FilterBank = 14;                     // 过滤器序号
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;  // 掩码模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位掩码
sFilterConfig.FilterIdHigh = 0x0000;               // 
sFilterConfig.FilterIdLow = 0x0000;                // 
sFilterConfig.FilterMaskIdHigh = 0x0000;           // 
sFilterConfig.FilterMaskIdLow = 0x0000;            // 
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO1; // 给邮箱1配置的过滤器
sFilterConfig.FilterActivation = ENABLE;           // 滤波器使能
sFilterConfig.SlaveStartFilterBank = 13;           // 分配滤波器资源
// 配置并自检
while(HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig) != HAL_OK);

当你需要使用到过滤器时可以参考这个教程,有详细的讲解(很简单,一看就会)39. CAN—通讯实验 — [野火]STM32 HAL库开发实战指南——基于野火F4系列开发板 文档

过滤器配置最核心的内容就是下面这两张图

961cd104fb264d4f9dd6949c5add49cf.png

463be50120534e3598180bd634e84adb.png

4.配置发送函数

void CAN1_Transmit(uint32_t ID, uint8_t* Data)
{
	static uint32_t TxMailbox;
	static CAN_TxHeaderTypeDef TxMessage;
	TxMessage.StdId = ID;
	TxMessage.ExtId = ID;
	TxMessage.IDE = CAN_ID_STD;    // 标准ID
	TxMessage.RTR = CAN_RTR_DATA;
	TxMessage.DLC = 8;
	// Send message and self-check
	while(HAL_CAN_AddTxMessage(&hcan1, &TxMessage, Data, &TxMailbox) !=HAL_OK);	
}
void CAN2_Transmit(uint32_t ID, uint8_t* Data)
{
	static uint32_t TxMailbox;
	static CAN_TxHeaderTypeDef TxMessage;
	TxMessage.StdId = ID;
	TxMessage.ExtId = ID;
	TxMessage.IDE = CAN_ID_EXT;    // 扩展ID
	TxMessage.RTR = CAN_RTR_DATA;
	TxMessage.DLC = 8;
	// Send message and self-check
	while(HAL_CAN_AddTxMessage(&hcan2, &TxMessage, Data, &TxMailbox) !=HAL_OK);	
}

注意IDE要根据实际项目需要去设置,RTR一般都设置为数据帧。DLC直接写死8,因为实际应用中,就算你传输的数据没有8字节,也是以8字节传输,比如要传输0xF1,其实是传输0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xF1。(绝大部分情况是这样的)

小声BB:看到有些教程在发送函数里写for循环里先定义一个8字节的变量,再一个一个字节把Data存入变量,最后把变量送到HAL_CAN_AddTxMessage()中的,惊呆了,完全无法理解这种做法,简直脱裤子放屁。

5.重定义FIFO0和FIFO1的消息等待回调函数

现在主程序中定义大小为8个字节的全局变量,以便我们在主程序中对CAN1和CAN2接收到的报文进行读取

uint8_t CAN1_Rx_data[8] = {0};
uint8_t CAN2_Rx_data[8] = {0};

不要忘了在main.h中声明,不然can.c文件无法访问变量

extern uint8_t CAN1_Rx_data[8];
extern uint8_t CAN2_Rx_data[8];

重定义消息等待回调函数(即邮箱收到信息等待读取时触发的中断)

// FIFO0消息等待回调
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
	static CAN_RxHeaderTypeDef CAN_RxHeader; 
  while(HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &CAN_RxHeader, (uint8_t *)CAN1_Rx_data) != HAL_OK);	
	if(CAN_RxHeader.IDE == CAN_ID_STD)
	{
		CAN1_Transmit(CAN_RxHeader.StdId, (uint8_t *)CAN1_Rx_data);
	}
	else if(CAN_RxHeader.IDE == CAN_ID_EXT)
	{
		CAN1_Transmit(CAN_RxHeader.ExtId, (uint8_t *)CAN1_Rx_data);
	}	
}

// FIFO1消息等待回调
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
  static CAN_RxHeaderTypeDef CAN_RxHeader; 
  while(HAL_CAN_GetRxMessage(&hcan2, CAN_RX_FIFO1, &CAN_RxHeader, (uint8_t *)CAN1_Rx_data) != HAL_OK);	
	if(CAN_RxHeader.IDE == CAN_ID_STD)
	{
		CAN2_Transmit(CAN_RxHeader.StdId, (uint8_t *)CAN2_Rx_data);
	}
	else if(CAN_RxHeader.IDE == CAN_ID_EXT)
	{
		CAN2_Transmit(CAN_RxHeader.ExtId, (uint8_t *)CAN2_Rx_data);
	}
}

使用HAL_CAN_RxFifo1MsgPendingCallback()接收数据后对接收到的数据的IDE进行判断,如果是标准ID则使用标准ID将信息发回,扩展ID则同理

注意养成static修饰局部变量的习惯(代码洁癖)

6.开启CAN与FIFO消息等待中断

HAL_CAN_Start(&hcan1);   
HAL_CAN_Start(&hcan2);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);

7.测试

这个每个人使用的外设不同,根据使用的外设进行测试即可,基本都大差不差。

8.注意事项

如果Status一直是heavy状态,注意使用的CAN收发芯片是否有需要下拉/上拉的引脚!!

 

 

作者:电工小王(全国可飞)

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 f407 CAN收发 基于HAL库和Cubemx配置

发表回复