小工具知识网

当前位置: 首页 > 行业知识 > STM32开发(五)STM32F103 通信 —— CAN通信编程详解

行业知识

STM32开发(五)STM32F103 通信 —— CAN通信编程详解

2026-11-04 22:37:26 来源:互联网转载

《上一���》  《主目录》  《下一篇》


文章目录

    • 一、基础知识点
    • 二、开发环境
      • 1、硬件开发准备
      • 2、软件开发准备
      • 三、STM32CubeMX相关配置
        • 1、STM32CubeMX基本配置
        • 2、STM32CubeMX CAN相关配置
        • 四、Vscode代码讲解
        • 五、结果演示
          • CAN 内部回环测试
          • CAN 正常模式测试
          • 使用ADALM2000分析工具解析CAN时序
          • 六、代码下载

            一、基础知识点

            了解CAN通讯协议以及CAN 协议及标准规格 。本实验是基于STM32F103开发的CAN通信,来一起研究下STM32数据手册 中CAN的特色。

            准备好了吗?开始我的show time。


            二、开发环境

            1、硬件开发准备

            主控:STM32F103ZET6

            CAN收发器:TJA1040T

            2、软件开发准备

            软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。

            该部分可参考:软件开发环境构建


            三、STM32CubeMX相关配置

            1、STM32CubeMX基本配置

            本实验基于CubeMX详解构建基本框架 进行开发。

            2、STM32CubeMX CAN相关配置

            (1)时钟配置

            由于CAN在APB1时钟线上,APB1时钟配置36M

            (2)配置CAN参数

            • 开启主CAN配置
            • 位时序配置

              位时序顾名思义就是传输一个位的时序(如0或1)。位时序结构:同步段(SYNC_SEG)、时间段1(BS1)、时间段2(BS2)

              假j把CAN的时钟配置为500KHz

              (1)将系统时间36M进行4分频,则36M/4 = 9M

              (2)位时序中同步段(SYNC_SEG)固定1Tq;STM32时间段1(BS1)包含两部分:传播时间段和相位缓冲时间段1,可以分配11Tq;时间段2(BS2)包含相位缓冲时间段2,可以分配6Tq。

              这样1位由18个 Tq 构成,则9M/18 = 500K

              按照以上的配置可以实现CAN 500K通信。

              • 基本模式配置

                根据自己需要进行配置,这里实验都不需要,直接disable关掉

                自动重发数据:若使能,数据出错了可以重新发送数据

                接收FIFO锁定模式:若使能,FIFO数据不可以重叠,更替

                发送FIFO优先级:若关闭,就按照邮箱的优先级来发送数据;若使能,就按照自己设定的优先级发送。

              • CAN工作模式配置

                模式选择:正常模式、静默模式、环回模式、环回静默模式。

                实验选用正常模式、环回模式测试CAN通信。

              • 中断模式配置

                在NVIC Settings选项卡中将CAN接收中断使能打开

                设置中断优先级


                四、Vscode代码讲解

                1、构建一个can相关结构体

                //定义结构体类型typedef struct{  uint32_t CAN_Work_Mode;         // CAN 工作模式  uint8_t tx_buff[8];             // 发送缓存  uint8_t rx_buff[8];             // 接收缓存    void (*Mycan_Init)(void);        // CAN 初始化               uint8_t (*Mycan_Send_Message)(uint8_t *p_tx_buff, uint32_t *pMycan_MAILBOX_Num);  // 发送信息  void (*Mycan_recevie_Message)(uint8_t *p_rx_buff);                                // 接收信息  uint8_t RX_status_Flag;         // 接收标志位} Mycan_t;

                2、定义can结构体

                Mycan_t Mycan ={    CAN_MODE_NORMAL,            // 正常接收发送模式    {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77},    {0},    Mycan_Init,                         Mycan_Send_Message,    Mycan_recevie_Message,    FALSE            // 默认没有接收到信息};

                3、初始化CAN

                (1)can过滤器配置

                FilterBank:要配置的过滤器0(芯片一共14个,0-13)

                FilterMode:选用标识符屏蔽模式(可以接收一组ID),若选择列表模式,只能接收一个特定的ID

                FilterFIFOAssignment:将配置的过滤器0关联到FIFO0

                FilterActivation:激活过滤器,若不激活接收不到任何数据

                (2)使能接收挂起中断

                在STM32CubeMX里面是时钟了接收的总中断,这里使能的是总中断下的挂起中断

                CAN接收中断包括:挂起中断(只要有信息就触发中断)、满中断(FIFO都满了触发中断)、溢出中断(只有FIFO都满后还接收到数据就会触发中断)

                (3)启动CAN

                  void Mycan_Init(void)  {    CAN_FilterTypeDef Mycan_Filter;    // 配置过滤器    Mycan_Filter.FilterIdHigh         = 0x34;                 // 过滤器需要过滤高ID    Mycan_Filter.FilterIdLow          = 0x00;                 // 过滤器需要过滤低ID    Mycan_Filter.FilterMaskIdHigh     = 0x00;                 // 过滤器掩码 '0'位不限制     Mycan_Filter.FilterMaskIdLow      = 0x00;                 // 过滤器掩码 '0'位不限制    Mycan_Filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;     // 挂在过滤器FIFO0    Mycan_Filter.FilterBank           = 0;                    // 过滤器0    Mycan_Filter.FilterMode           = CAN_FILTERMODE_IDMASK; // ID掩码模式    Mycan_Filter.FilterScale          = CAN_FILTERSCALE_16BIT; // 16位过滤器    Mycan_Filter.FilterActivation     = CAN_FILTER_ENABLE;     // 激活过滤器    Mycan_Filter.SlaveStartFilterBank = 14;                      // 配置过滤器    if (HAL_CAN_ConfigFilter(&hcan, &Mycan_Filter) != HAL_OK)    {        printf("DWB --- can配置过滤器失败\n");        System.Error_handler();    }    // 使能FIFO接收到一个新报文中断    if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)    {        printf("DWB --- can使能接收挂起中断失败\n");        System.Error_handler();    }    if(HAL_CAN_Start(&hcan) != HAL_OK)    {        printf("DWB --- can开启失败\n");        System.Error_handler();    }    printf("DWB --- can配置并开启成功!\n");  }    

                4、CAN发送

                (1)CAN发送数据时序配置

                定义发送时序参数,通过HAL_CAN_AddTxMessage函数发送数据到邮箱

                (2)等待发送数据成功

                延时1s时间,1s内反复通过HAL_CAN_GetTxMailboxesFreeLevel函数检查空邮箱的个数。如果空邮箱个数等于3,则说明数据已经发送成功。

                  uint8_t Mycan_Send_Message(uint8_t *p_tx_buff, uint32_t *pMycan_MAILBOX_Num)  {      CAN_TxHeaderTypeDef Mycan_TxHeader;      // 配置发送头      Mycan_TxHeader.StdId              = 0x34;                    // 发送设备标准ID      Mycan_TxHeader.ExtId              = 0x00;                    // 扩展ID      Mycan_TxHeader.IDE                = CAN_ID_STD;              // can标准ID模式      Mycan_TxHeader.RTR                = CAN_RTR_DATA;            // 数据帧      Mycan_TxHeader.DLC                = 8;                       // 传输长度8      Mycan_TxHeader.TransmitGlobalTime = DISABLE;                 // 时间戳 不使能      // 发送数据到邮箱并判断状态      if(HAL_CAN_AddTxMessage(&hcan, &Mycan_TxHeader, p_tx_buff, pMycan_MAILBOX_Num) != HAL_OK)      {          printf("DWB --- 发送数据到邮箱失败\n");          return send_date_fail;      }      uint8_t rtc_seconds_t = Myrtc.pMyrtc_current_time->Seconds+1;      do      {        if(rtc_seconds_t == Myrtc.pMyrtc_current_time->Seconds)        {            printf("DWB --- 数据未发出 \n");            return send_date_fail;        }      } while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3);      printf("DWB --- 数据发送成功 \n\r");      return send_date_success;  }

                5、主函数中调用发送接收函数

                (1)调用结构体CAN发送函数成员进行数据发送

                (2)通过RX_status_Flag标识符判断是否接收到数据,后调用Mycan_recevie_Message接收

                    res = Mycan.Mycan_Send_Message(Mycan.tx_buff, &MailBox_num);    printf("DWB --- MailBox_num = %ld\n\r", MailBox_num);    if(!res && TRUE == Mycan.RX_status_Flag){        Mycan.Mycan_recevie_Message(Mycan.rx_buff);        Mycan.RX_status_Flag = FALSE;    }

                6、CAN接收中断函数

                在初始化中CAN使能接收挂起中断。当有接收到数据就会调用中断函数

                __weak void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)

                这个函数是弱函数,直接重构就好了。

                void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_t){  CAN_RxHeaderTypeDef pMycan_tx_Head;  // HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]);  if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &pMycan_tx_Head, Mycan.rx_buff) == HAL_OK)    Mycan.RX_status_Flag = TRUE;}

                调用HAL_CAN_GetRxMessage函数接收数据,这里数据从CAN_RX_FIFO0中读取。

                为什么是FIFO0呢?因为在初始化过滤器的时候将其关联到FIFO0上。

                解析接收的过程

                中断初始化中,使能USB_LP_CAN1_RX0_IRQn CAN接收中断

                static void MX_NVIC_Init(void){  /* RTC_Alarm_IRQn interrupt configuration */  HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 1, 0);  HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);  /* USB_LP_CAN1_RX0_IRQn interrupt configuration */  HAL_NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 2, 0);  HAL_NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);            // can接收总中断使能}

                CAN初始化中使能接收挂起中断

                    // 使能FIFO接收到一个新报文中断    if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)    {        printf("DWB --- can使能接收挂起中断失败\n");        System.Error_handler();    }

                CAN接收数据时,

                (1)触发USB_LP_CAN1_RX0_IRQHandler回调函数

                void USB_LP_CAN1_RX0_IRQHandler(void){  /* USER CODE BEGIN USB_LP_CAN1_RX0_IRQn 0 */  /* USER CODE END USB_LP_CAN1_RX0_IRQn 0 */  HAL_CAN_IRQHandler(&hcan);  /* USER CODE BEGIN USB_LP_CAN1_RX0_IRQn 1 */  /* USER CODE END USB_LP_CAN1_RX0_IRQn 1 */}

                (2)在HAL_CAN_IRQHandler函数中判断中断标志位为CAN_IT_RX_FIFO0_MSG_PENDING(挂起中断,在初始化中使能挂起中断)

                USE_HAL_CAN_REGISTER_CALLBACKS宏定义为0,则调用HAL_CAN_RxFifo0MsgPendingCallback回调函数(这个函数是弱化函数,重构该函数之后就会调用重构函数)

                void HAL_CAN_IRQHandler(CAN_HandleTypeDef *hcan){    ......  /* Receive FIFO 0 message pending interrupt management *********************/  if ((interrupts & CAN_IT_RX_FIFO0_MSG_PENDING) != 0U)  {    /* Check if message is still pending */    if ((hcan->Instance->RF0R & CAN_RF0R_FMP0) != 0U)    {      /* Receive FIFO 0 message pending Callback */#if USE_HAL_CAN_REGISTER_CALLBACKS == 1      /* Call registered callback*/      hcan->RxFifo0MsgPendingCallback(hcan);#else      /* Call weak (surcharged) callback */      HAL_CAN_RxFifo0MsgPendingCallback(hcan);#endif /* USE_HAL_CAN_REGISTER_CALLBACKS */    }  }    ......}

                五、结果演示

                CAN 内部回环测试

                代码设置回环测试,can自发自收。

                串口打印发送成功后接收到的数据内容以及发送邮箱号。

                CAN 正常模式测试

                代码模式配置为正常模式

                两块板子CAN相互通信背景:用另一块STM32开发板上的CAN通信与本实验中的板子CAN(打印信息有DWB)通信。

                实验板子CAN发送(左图),STM32开发板CAN接收(右图)。两个板子CANH对应相连;CANL对应相连。

                实验板子CAN接收(左图),STM32开发板CAN发送(右图)。两个板子CANH对应相连;CANL对应相连。

                使用ADALM2000分析工具解析CAN时序

                整体波形:

                开始帧(1位)

                右下角,传输1位的时间为1.998μs,和软件里配置的时间1999.99ns时间一致(500000Hz)

                设备ID位(标准帧ID 11位)

                解析出来的配置为0x34与软件配置一致(00000110100)

                注:由于位补充(在发送数据帧和遥控帧时, SOF~CRC 段间的数据,相同电平如果持续 5 位,在下一个位(第 6 个位)则要插入 1 位与前 5 位反型的电平)的原因,中间有插入一个补充位1(绿色1)

                RTR(1位数据帧)、IDE(1位标准ID模式)、RB0(保留位)、数据长度码(8位)

                由于连续5位0,则中间添加补充位1

                数据(8个字节)

                CRC(校验位15位)、CRC d(CRC 界定符(用于分隔的位)1位)、ACK(用来确认是否正常接收2位)

                结束帧


                六、代码下载

                STM32基础(五)STM32F103 CAN通信代码

上一篇:正规加速服务器怎么用?

下一篇:盛世芳华服务器怎么选不了?