STM32-CAN配置与库函数解析,实现环回模式通信指南

STM32-CAN配置与库函数解析

CAN总线介绍:https://blog.csdn.net/weixin_46251230/article/details/129147612

STM32-CAN控制器介绍:https://blog.csdn.net/weixin_46251230/article/details/129150872

STM32CubeMx配置

因为bxCAN是挂载在APB1总线上的,所以设置APB1总线的时钟为36M

选择CAN接口进行配置

勾选主CAN模式,这里并不是主机的意思

配置位时间参数

根据STM32参考手册里位时间特性的介绍,来配置最小时间单位Tq

在位时间参数里可以配置分频系数,这个系数没有下拉列表,需要自己写,APB1 36MHz时钟来到这里经过分频再给后面使用,根据实际通信速度来配置,例如可以设置为4分频,那36MHz/4 = 9MHz

同步段因为固定为1个时间单元,所以不用配置

根据采样点最好在一个位的50% ~ 80%位置采样,所以时间段1可以配置长一点,其范围为1到16个时间单元,这里可根据下拉列表选择11个时间单元

时间段2的设置就要注意不要让总和超过最大Tq数,因为同步段+时间段1+时间段2的Tq数范围是8 ~ 25个,不过配置工具已经把参数规定好了,所以不用担心超出的问题,这里可以设置为6个时间单元,则会自动计算出一个位占用的时间(Time for one Bit)为2000ns

计算过程

36MHz/4 = 9MHz,因为同步段(1个Tq)+时间段1(11个Tq)+时间段2(6个Tq)=18个Tq

所以 9MHz/18 = 0.5MHz,即每一个Tq的频率就是0.5MHz,转为时间就为 1/0.5MHz = 1/500000Hz = 0.000002s = 2us = 2000ns

而2000ns的速率就是500KHz

重新同步跳跃宽度(SJW)可设置范围是1 ~ 4个时间单元,这里可以选择2

配置基础参数

接收FIFO锁定模式:选择Enable(锁定)时,当接收FIFO满时,新接收到的报文就丢弃,软件可以读到FIFO中最早收到的3个报文。

​ 选择Disable(不锁定)时,那么FIFO中最后收到的报文就被新报文所覆盖。这样,最新收到的报文不会被丢弃掉。

发送FIFO优先级:未使能就按邮箱序号进行发送

配置工作模式

正常模式就需要两个或更多的实验板来进行通信

环回模式就只使用一个实验板就可以测试通信

本次实验使用环回模式

NVIC中断配置

CAN发送使用轮询的方式,接收就用RX0中断方式

GPIO配置

因为CAN收发器的STB接到了单片机的PC13引脚,所以将PC13配置为推挽输出模式

上面配置完就可以生成Keil工程进行代码编写

生成的CAN初始化函数如下:

void MX_CAN_Init(void)
{
  hcan.Instance = CAN1;
  hcan.Init.Prescaler = 4;
  hcan.Init.Mode = CAN_MODE_LOOPBACK;
  hcan.Init.SyncJumpWidth = CAN_SJW_2TQ;
  hcan.Init.TimeSeg1 = CAN_BS1_11TQ;
  hcan.Init.TimeSeg2 = CAN_BS2_6TQ;
  hcan.Init.TimeTriggeredMode = DISABLE;
  hcan.Init.AutoBusOff = DISABLE;
  hcan.Init.AutoWakeUp = DISABLE;
  hcan.Init.AutoRetransmission = DISABLE;
  hcan.Init.ReceiveFifoLocked = DISABLE;
  hcan.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan) != HAL_OK)
  {
    Error_Handler();
  }
}

CAN使用到的库函数介绍

1、配置过滤器

CAN_FilterTypeDef结构体就是过滤器的一些参数设置

HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig);

2、发送数据

CAN_TxHeaderTypeDef结构体是对发送报文进行组帧,aData数组存放着要发送的数据,pTxMailbox指针是返回控制器使用了哪个邮箱进行发送

HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox);

3、中止发送请求

HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes);

4、获取空邮箱的个数

uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *hcan);

5、接收数据

RxFifo指定用于接收报文的FIFO缓存,CAN_RxHeaderTypeDef结构体定义接收报文的格式,aData数组存放接收到的报文

HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]);

6、获取接收FIFO满的个数

uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan, uint32_t RxFifo);

CAN通信实现(环回模式)

CAN.h

进行宏定义,定义结构体类型

//发送接收状态宏定义
#define CAN_SEND_OK     0
#define CAN_SEND_FAIL   1

#define CAN_REC_OK      0
#define CAN_REC_FAIL    1

//定义结构体类型
typedef struct
{
    uint32_t uiOperate_Mode;        //操作模式
    uint8_t ucRec_Flag;             //接收标志位
    uint8_t ucSend_Buf[8];          //发送缓存
    uint8_t ucRec_Buf[8];           //接收缓存

    void (*CAN_Init)(void);
    void (*CAN_Config)(void);
    uint8_t (*CAN_Send_Msg)(uint8_t* pSend_Buf,uint8_t LEN);
    uint8_t (*CAN_Rec_Msg)(uint8_t* pRec_Buf);

}CAN_Test_T;

/* extern variables-----------------------------------------------------------*/
extern CAN_Test_T CAN_Test;

CAN.c

配置过滤器并启动CAN

/**
 * @name   CAN_Config
 * @brief  CAN配置
 * @param  None
 * @retval None   
 */
static void CAN_Config(void)
{
    //CAN过滤器参数配置
    CAN_FilterTypeDef CAN_FilterTypeDefSture;

    CAN_FilterTypeDefSture.FilterBank  = 0;                             //配置过滤器0(F1共有14个,0-13)
    CAN_FilterTypeDefSture.FilterScale = CAN_FILTERSCALE_16BIT;         //配置为16位过滤器
    CAN_FilterTypeDefSture.FilterMode  = CAN_FILTERMODE_IDMASK;         //屏蔽位模式
    //ID号为0x00,屏蔽位为0x00,说明任何ID都接收
    CAN_FilterTypeDefSture.FilterIdLow      = 0x00;                     //FR1
    CAN_FilterTypeDefSture.FilterMaskIdLow  = 0x00;
    CAN_FilterTypeDefSture.FilterIdHigh     = 0x00;                     //FR2
    CAN_FilterTypeDefSture.FilterMaskIdHigh = 0x00;   
    CAN_FilterTypeDefSture.FilterFIFOAssignment = CAN_FILTER_FIFO0;     //过滤器0关联到FIFO0
    CAN_FilterTypeDefSture.FilterActivation = ENABLE;                   //激活过滤器0
    CAN_FilterTypeDefSture.SlaveStartFilterBank = 14;

    //启动过滤器
    if(HAL_CAN_ConfigFilter(&hcan,&CAN_FilterTypeDefSture) != HAL_OK)
    {
      printf("CAN配置过滤器成功!");
      System.Error_Handler();
    }

    //使能FIFO0接收到一个新报文中断,具体为FIFO0的挂起中断
    if(HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
    {
      printf("CAN使能FIFO0接收到一个新报文中断");
      System.Error_Handler();
    }

    //启动CAN
    if(HAL_CAN_Start(&hcan) != HAL_OK)
    {
      printf("CAN启动失败!");
      System.Error_Handler();
    }

    printf("配置成功,CAN成功启动!\r\n");
}

CAN发送报文,可在主函数中调用

/**
 * @name   CAN_Send_Msg
 * @brief  CAN发送信息
 * @param  pSend_Buf:发送缓存指针
 *          LEN:发送报文长度
 * @retval CAN_SEND_OK:发送成功
 *          CAN_SEND_FAIL:发送失败
 */
static uint8_t CAN_Send_Msg(uint8_t* pSend_Buf,uint8_t LEN)
{
  uint8_t i = 0;
  static uint8_t ucTestData = 0;
  uint32_t uiTxMailBox;           //接收CAN发送数据成功时返回的邮箱号(0-2)

  //定义CAN TX消息头参数
  CAN_TxHeaderTypeDef CAN_TxHeaderTypeDefStrue = 
  {
    0x88,           //标准标识符-11位
    0x00,           //拓展标识符-29位
    CAN_ID_STD,     //设置为标准格式
    CAN_RTR_DATA,   //设置为数据帧
    8,              //发送数据的长度 0 ~ 8
    DISABLE         //不使用捕获时间戳计数器
  };

  //判断工作模式
  if(CAN_Test.uiOperate_Mode == CAN_MODE_LOOPBACK)
  {
    printf("\r\nCAN工作在环回模式,使用一块实验板来测试\r\n");
  }
  else
  {
    printf("\r\nCAN工作在正常模式,需要两块以上的实验板才能测试\r\n");
  }

  //设置要发送的报文
  printf("CAN要发送的报文如下:\r\n");
  for(i=0;i<8;i++)
  {
    printf("%#.2x ",ucTestData);
    CAN_Test.ucSend_Buf[i] = ucTestData++;
  }
  printf("\r\n");

  //将消息添加到第一个空闲的Tx邮箱并激活相应的传输要求
  if(HAL_CAN_AddTxMessage(&hcan,&CAN_TxHeaderTypeDefStrue,pSend_Buf,&uiTxMailBox) != HAL_OK)
  {
    return CAN_SEND_FAIL;
  }

  //通过检查空闲邮箱个数确认是否发送完成
  Timer6.usDelay_Timer = 0;
  do
  {
    //超时处理
    if(Timer6.usDelay_Timer >= TIMER_1s)
    {
      printf("CAN发送超时\r\n");
      return CAN_SEND_FAIL;
    }
  } while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3); //如果3个发送邮箱都不是空闲的话,就说明数据还在发送
  
  //发送成功
  return CAN_SEND_OK;
}

CallBack.c

重写FIFO0挂起中断,接收CAN消息

因为是环回模式,所以主函数中发送的数据会被中断接收,通过HAL_CAN_GetRxMessage函数放到CAN_Test.ucRec_Buf缓存中

/**
 * @name   HAL_CAN_RxFifo0MsgPendingCallback
 * @brief  CAN接收FIFO0 挂起中断
 * @param  *_hcan:CAN结构体指针
 * @retval None   
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_)
{
  //定义CAN Rx消息头参数
  CAN_RxHeaderTypeDef CAN_RxHeaderTypeDefStrue;

  //CAN接收消息
  if(HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&CAN_RxHeaderTypeDefStrue,CAN_Test.ucRec_Buf) == HAL_OK)
  {
    CAN_Test.ucRec_Flag = TRUE;
  }
  LED.LED_Fun(LED3,LED_Flip);
}

System.c

主函数中,判断触摸按键1是否按下,是则发送一次数据,通过接收标志位判断中断是否已经完成数据接收,是则打印出数据

/**
 * @name   Run
 * @brief  系统运行
 * @param  None
 * @retval None   
 */
static void Run()
{
  //计数
  static uint16_t usCAN_Send_OK_Cnt = 0;
  static uint16_t usCAN_Send_Fail_Cnt = 0;
  static uint16_t usCAN_Rec_OK_Cnt = 0;

  //发送数据
  if(KEY1.KEY_Flag == TRUE)
  {
    KEY1.KEY_Flag = FALSE;

    //发送
    if(CAN_Test.CAN_Send_Msg(CAN_Test.ucSend_Buf,8) == CAN_SEND_OK)
    {
      printf("CAN发送数据成功次数:%u\r\n",++usCAN_Send_OK_Cnt);
    }
    else
    {
      printf("CAN发送数据失败次数:%u\r\n",++usCAN_Send_Fail_Cnt);
    }
  }

  //接收数据
  if(CAN_Test.ucRec_Flag == TRUE)
  {
    CAN_Test.ucRec_Flag = FALSE;
    printf("CAN接收数据成功次数:%u\r\n",++usCAN_Rec_OK_Cnt);
    //打印接收到的数据
    CAN_Test.CAN_Rec_Msg(CAN_Test.ucRec_Buf); 
  }
}

实验结果

每触摸一次按键,就成功发送一次数据,并成功接收一次数据

物联沃分享整理
物联沃-IOTWORD物联网 » STM32-CAN配置与库函数解析,实现环回模式通信指南

发表评论