目录
概述
1 认识bxCAN Loopback
1.1 环回模式
1.2 环回模式特点
2 使用STM32CubeMX 生成工程
2.1 参数配置
2.2 生成工程代码
2.4 了解can.c
3 认识Hal库中的bxCAN
3.1 认识3个重要数据结构
3.2 函数组
3.2.1 初始化函数组
3.2.2 控制函数组
3.2.3 中断管理函数组
3.2.4 中断回调函数组
4 实现bxCAN Loopback功能
4.1 初始化筛选器结构
4.2 发送数据函数
4.3 中断回调函数
4.5 完整代码
5 测试
5.1 编写测试代码
5.2 测试
源代码下载地址: stm32-bxCAN-Loopback测试程序,使用MCU:STM32F407IGX资源-CSDN文库
概述
本文主要介绍STM32F4XX的外设bxCAN的一个重要测试功能: Loopback。包括其实现原理,寄存器配置,代码实现,且还介绍了实现该功能的一些函数接口。并编写测试程序验证其功能。
1 认识bxCAN Loopback
1.1 环回模式
配置环回模式方法可以通过将 CAN_BTR 寄存器的 LBKM 位置 1,将 bxCAN 置于环回模式。
CAN 位时序寄存器 (CAN_BTR)

1.2 环回模式特点
1)在环回模式下,bxCAN 将其自身发送的消息作为接收的消息来处理并存储(如果这些消息通过了验收筛选)在接收邮箱中。
2)该模式为自检功能提供。为了不受外部事件的影响, CAN 内核在环回模式下将忽略确认错误(在数据/远程帧的确认时隙不对显性位采样)。
3)在此模式下, bxCAN 将执行从发送输出到接收输入的内部反馈。 bxCAN 将忽略 CANRX 输入引脚的实际值。从 CANTX 引脚可以监视发送的消息

2 使用STM32CubeMX 生成工程
2.1 参数配置
1) 配置RCC,使能外部晶振

2) GPIO Setting
本测试使用CAN1,与之相关的IO为PH13和PI9

3)CAN Parameter Settings

4) NVIC Setting

2.2 生成工程代码
在Project Manger选项卡中作如下配置,点击GENERATE CODE 生成工程代码。

打开工程文件如下:

2.4 了解can.c
使用STM32CubeMX 生成的工程文件,已经将需要的外设已经做了配置。本文重点介绍和CAN相关的接口,打开can.c可以看到:
在MX_CAN1_Init()中,主要实现can相关参数的配置

在HAL_CAN_MspInit()中配置IO,使其复用到CAN功能。

3 认识Hal库中的bxCAN
3.1 认识3个重要数据结构
1) CAN筛选器结构体
typedef struct {
    uint16_t CAN_FilterIdHigh;            /*CAN_FxR1寄存器的高16位 */
    uint16_t CAN_FilterIdLow;             /*CAN_FxR1寄存器的低16位*/
    uint16_t CAN_FilterMaskIdHigh;        /*CAN_FxR2寄存器的高16位*/
    uint16_t CAN_FilterMaskIdLow;         /*CAN_FxR2寄存器的低16位 */
    uint16_t CAN_FilterFIFOAssignment;     /*设置经过筛选后数据存储到哪个接收FIFO */
    uint8_t CAN_FilterNumber;              /*筛选器编号,范围0-27*/
    uint8_t CAN_FilterMode;                /*筛选器模式 */
    uint8_t CAN_FilterScale;               /*设置筛选器的尺度 */
    FunctionalState CAN_FilterActivation;  /*是否使能本筛选器*/
} CAN_FilterInitTypeDef;2) 发送/接收结构体
/**
* 发送结构体
*/
typedef struct {
    uint32_t StdId;  /*存储报文的标准标识符11位,0-0x7FF. */
    uint32_t ExtId;  /*存储报文的扩展标识符29位,0-0x1FFFFFFF. */
    uint8_t IDE;     /*存储IDE扩展标志 */
    uint8_t RTR;     /*存储RTR远程帧标志*/
    uint8_t DLC;     /*存储报文数据段的长度,0-8 */
    uint8_t Data[8]; /*存储报文数据段的内容 */
} CanTxMsg;
/**
* 接收结构体
*/
typedef struct {
    uint32_t StdId;  /*存储了报文的标准标识符11位,0-0x7FF. */
    uint32_t ExtId;  /*存储了报文的扩展标识符29位,0-0x1FFFFFFF. */
    uint8_t IDE;     /*存储了IDE扩展标志 */
    uint8_t RTR;     /*存储了RTR远程帧标志*/
    uint8_t DLC;     /*存储了报文数据段的长度,0-8 */
    uint8_t Data[8]; /*存储了报文数据段的内容 */
    uint8_t FMI;     /*存储了 本报文是由经过筛选器存储进FIFO的,0-0xFF */
} CanRxMsg;3.2 函数组
3.2.1 初始化函数组
使用STM32CubeMX 生成工程中,这部分接口STM32CubeMX已经将其在合适的位置,无需程序员在重新调用。

3.2.2 控制函数组

3.2.3 中断管理函数组

3.2.4 中断回调函数组

4 实现bxCAN Loopback功能
创建stm32f4_drv_can.c 文件,编写代码

4.1 初始化筛选器结构
static void CAN_Drv_fitter( CAN_HandleTypeDef *hcan )
{
    CAN_FilterTypeDef filterStru;
    
    filterStru.FilterActivation  = ENABLE;
    filterStru.FilterBank = 0;
    filterStru.FilterFIFOAssignment = CAN_FILTER_FIFO0;   //帅选后的数据存储在FIFO0
    
    filterStru.FilterIdHigh = 0;
    filterStru.FilterIdLow = 0;
    
    filterStru.FilterMaskIdHigh = 0;
    filterStru.FilterMaskIdLow = 0;
    
    filterStru.FilterMode = CAN_FILTERMODE_IDMASK;   //配置过滤模式:IDMASK
    filterStru.FilterScale = CAN_FILTERSCALE_32BIT;  //设置筛选器的尺度: 32 bit
    
    filterStru.SlaveStartFilterBank = 0;
    
    HAL_CAN_ConfigFilter( hcan, &filterStru);
}4.2 发送数据函数
void CAN_Drv_sendMessage( CAN_HandleTypeDef *hcan, 
                          uint8 *databuff, uint8 datalen, 
                          uint32 stdId , uint32 extId, uint32 ide)
{
    uint32 txMailbox;
    CAN_TxHeaderTypeDef txHdrStru;  
    
    
    txHdrStru.DLC = datalen;  /*发送的数据长度*/
    
    txHdrStru.ExtId = extId;  /*报文的扩展标识符 ID */
    txHdrStru.StdId = stdId;  /*报文的标准标识符 ID */
    
     /*
        ide = CAN_ID_STD  标准帧
        ide = CAN_ID_EXT  扩展帧
      */
    txHdrStru.IDE = ide;  
    txHdrStru.RTR = CAN_RTR_DATA;  /*RTR远程帧标志 */
    
    txHdrStru.TransmitGlobalTime = DISABLE;
    
    HAL_CAN_AddTxMessage( hcan, &txHdrStru, databuff, &txMailbox);
}4.3 中断回调函数
CAN0的中断函数void CAN1_RX0_IRQHandler(void)会自动调用该函数。主要实现数据接收和解析
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    uint8_t recv_data[8];
    CAN_RxHeaderTypeDef header;
    // 从FIFO读取数据
    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &header, recv_data);
    if(header.IDE == CAN_ID_STD)
    {
        printf("StdId ID: 0x%x\n",header.StdId);
    }
    else
    {
        printf("ExtId ID:%d\n",header.ExtId);
    }
    
    printf("CAN IDE:0x%x\n",header.IDE);
    printf("CAN RTR:0x%x\n",header.RTR);
    printf("CAN DLC:0x%x\n",header.DLC);
    printf("RECV DATA:");
    
    printf("%s \n",recv_data);
    printf("\n");
}4.5 完整代码
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * File Name          : stm32f4_drv_can.c
 * Description        : Code for board can driver
 ******************************************************************************
 * @attention
 *
 * Copyright (c) mingfei.tang
 * All rights reserved.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
#include "can.h"
#include "stm32f4_drv_can.h"
static void CAN_Drv_fitter( CAN_HandleTypeDef *hcan );
void CAN_Drv_Init( CAN_HandleTypeDef *hcan )
{
    CAN_Drv_fitter( hcan );    //初始化过滤器
    HAL_CAN_Start( hcan );     //启动CAN
    HAL_CAN_ActivateNotification(hcan,CAN_IT_RX_FIFO0_MSG_PENDING);  //接收触发方式 
}
void CAN_Drv_sendMessage( CAN_HandleTypeDef *hcan, 
                          uint8 *databuff, uint8 datalen, 
                          uint32 stdId , uint32 extId, uint32 ide)
{
    uint32 txMailbox;
    CAN_TxHeaderTypeDef txHdrStru;
    
    txHdrStru.DLC = datalen;
    
    txHdrStru.ExtId = extId;
    txHdrStru.StdId = stdId;
    
    txHdrStru.IDE = ide;
    txHdrStru.RTR = CAN_RTR_DATA;
    
    txHdrStru.TransmitGlobalTime = DISABLE;
    
    HAL_CAN_AddTxMessage( hcan, &txHdrStru, databuff, &txMailbox);
}
static void CAN_Drv_fitter( CAN_HandleTypeDef *hcan )
{
    CAN_FilterTypeDef filterStru;
    
    filterStru.FilterActivation  = ENABLE;
    filterStru.FilterBank = 0;
    filterStru.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    
    filterStru.FilterIdHigh = 0;
    filterStru.FilterIdLow = 0;
    
    filterStru.FilterMaskIdHigh = 0;
    filterStru.FilterMaskIdLow = 0;
    
    filterStru.FilterMode = CAN_FILTERMODE_IDMASK;
    filterStru.FilterScale = CAN_FILTERSCALE_32BIT;
    
    filterStru.SlaveStartFilterBank = 0;
    
    HAL_CAN_ConfigFilter( hcan, &filterStru);
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    uint8_t recv_data[8];
    CAN_RxHeaderTypeDef header;
    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &header, recv_data);
    if(header.IDE == CAN_ID_STD)
    {
        printf("StdId ID: 0x%x \n",header.StdId);
    }
    else
    {
        printf("ExtId ID:0x%x \n",header.ExtId);
    }
    
    printf("CAN IDE:0x%x\n",header.IDE);
    printf("CAN RTR:0x%x\n",header.RTR);
    printf("CAN DLC:0x%x\n",header.DLC);
    printf("RECV DATA:");
    
    printf("%s \n",recv_data);
    printf("\n");
}
/* End of this file */5 测试
5.1 编写测试代码
在main.c文件中实现如下函数:
1) 发送接口
void test_can1_send( void )
{
    static uint8 i = 0;
    uint32 stdId , extId, ide;
    uint8 sendBuff[8] ="can-1:";
    sendBuff[6] = 0X30 + i; 
    sendBuff[7] = 0X30 + i +1; 
    stdId = 0x321;
    extId = 2;
    ide = CAN_ID_STD;
    CAN_Drv_sendMessage( &hcan1, sendBuff, sizeof(sendBuff), stdId, extId, ide);
    i++;
}2)执行函数中,主要做键盘扫描,当按键按下时,发送一次数据
void tick_action( void )
{
   static bool flag_1s = 0;
   static unsigned int tick_cnt = 0;
   static unsigned int beforTick = 0;
   unsigned int currentTick;
   unsigned char val;
    
   currentTick = HAL_GetTick();
   if(beforTick != currentTick )
   {
       beforTick = currentTick;
       tick_cnt++;
       
       // 1s action
       if( (tick_cnt % 1000) == 0) 
       {
          flag_1s = true;;
       }
       
       //1ms action
       bsp_KeyMonitor();
   }
   
    if( flag_1s )
    {
        flag_1s = false;
        HAL_GPIO_TogglePin(SYS_RUN_LED_GPIO_Port, SYS_RUN_LED_Pin);
    }
    
    // send CAN packet 
    val = bsp_KeyGetValue( KEY_1 );
    if( val  )
    {
        test_can1_send();
    }
}3) main()函数中初始化CAN配置,在while(1)下执行tick_action()

5.2 测试
编译代码,将代码下载到板卡中测试。每按下一次按键,发送数据的值都会加1,接收中断函数也能正确的接受到数据。



![[c++] c++ 中的顺序(构造,析构,初始化列表,继承)](https://img-blog.csdnimg.cn/direct/839bf38b9a014664b66372bed6ef34a6.png)
















