主页 > 软件开发  > 

【STM32项目实战系列】基于STM32G474的FDCAN驱动配置

【STM32项目实战系列】基于STM32G474的FDCAN驱动配置

前言:本周工作中用到了CANFD的驱动,由于以前都是用到的CAN2.0,所以过程并不是特别的顺利,所以中间遇到几个比较小的问题导致自己卡住了一段时间,特此记录一下并完全奉上自己的配置的源码。


1,CANFD配置与简介

先简单介绍一下CANFD:

FDCAN(Flexible Data-Rate CAN,灵活数据速率 CAN)是 CAN-FD(CAN with Flexible Data-Rate)协议的实现,支持更高的传输速率和更大的数据负载。FDCAN 通信主要由 仲裁域(Arbitration Phase) 和 数据域(Data Phase) 组成,它们在波特率和位定时参数上有所不同。

这里配置的FDCAN外设的时钟为100MHZ

1,相较于传统的CAN,CANFD仲裁域与数据域的波特率可以不同也可以相同,

仲裁阶段:与传统 CAN 相同(≤ 1 Mbps)数据阶段:可以使用更高的速率(典型值 2 Mbps、5 Mbps,甚至 8 Mbps

2,数据传输特点:

传输 更长的数据(64 字节),减少协议开销,提高带宽利用率。数据阶段 速率更快,提升整车网络通信性能。

3,仲裁域特点:

低 ID 优先级高(0 优先级高于 1)。发送过程中,如果节点检测到比自己更低的 ID(更高优先级),则自动停止发送。传统 CAN 与 CAN FD 可以共存,但 如果 CAN FD 设备检测到传统 CAN 帧,会降级为传统 CAN 模式。

4,波特率的计算方式

Baud_rate = FDCAN_Clock / (Prescaler * (Seg_1 + Seg_2 + Sync_Jump_Width))

FDCAN_Clock(FDCAN 时钟)Prescaler(分频系数)Phase Segment 1(相位段 1)Phase Segment 2(相位段 2,用于接收器同步和误差修正)Sync_Jump_Width(同步跳宽)

5,采样率的计算方式

sampling_rate = (Seg_1 + 1) / (1 + Seg_1 + Seg_2)


2,FDCAN代码生成

这里先用的CUBEMX生成的源驱动代码,但是烧录进板子里面发现无法使用,后面就有改了一下,同样的把这个源码也搬过来

fdcan.c

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file fdcan.c * @brief This file provides code for the configuration * of the FDCAN instances. ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "fdcan.h" /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ FDCAN_HandleTypeDef hfdcan1; /* FDCAN1 init function */ void MX_FDCAN1_Init(void) { /* USER CODE BEGIN FDCAN1_Init 0 */ /* USER CODE END FDCAN1_Init 0 */ /* USER CODE BEGIN FDCAN1_Init 1 */ /* USER CODE END FDCAN1_Init 1 */ hfdcan1.Instance = FDCAN1; hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1; hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS; hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; hfdcan1.Init.AutoRetransmission = DISABLE; hfdcan1.Init.TransmitPause = DISABLE; hfdcan1.Init.ProtocolException = DISABLE; hfdcan1.Init.NominalPrescaler = 5; hfdcan1.Init.NominalSyncJumpWidth = 1; hfdcan1.Init.NominalTimeSeg1 = 15; hfdcan1.Init.NominalTimeSeg2 = 4; hfdcan1.Init.DataPrescaler = 5; hfdcan1.Init.DataSyncJumpWidth = 1; hfdcan1.Init.DataTimeSeg1 = 2; hfdcan1.Init.DataTimeSeg2 = 1; hfdcan1.Init.StdFiltersNbr = 28; hfdcan1.Init.ExtFiltersNbr = 8; hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN FDCAN1_Init 2 */ /* USER CODE END FDCAN1_Init 2 */ } void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; if(fdcanHandle->Instance==FDCAN1) { /* USER CODE BEGIN FDCAN1_MspInit 0 */ /* USER CODE END FDCAN1_MspInit 0 */ /** Initializes the peripherals clocks */ PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } /* FDCAN1 clock enable */ __HAL_RCC_FDCAN_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**FDCAN1 GPIO Configuration PA11 ------> FDCAN1_RX PA12 ------> FDCAN1_TX */ GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* FDCAN1 interrupt Init */ HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_SetPriority(FDCAN1_IT1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(FDCAN1_IT1_IRQn); /* USER CODE BEGIN FDCAN1_MspInit 1 */ /* USER CODE END FDCAN1_MspInit 1 */ } } void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef* fdcanHandle) { if(fdcanHandle->Instance==FDCAN1) { /* USER CODE BEGIN FDCAN1_MspDeInit 0 */ /* USER CODE END FDCAN1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_FDCAN_CLK_DISABLE(); /**FDCAN1 GPIO Configuration PA11 ------> FDCAN1_RX PA12 ------> FDCAN1_TX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12); /* FDCAN1 interrupt Deinit */ HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_DisableIRQ(FDCAN1_IT1_IRQn); /* USER CODE BEGIN FDCAN1_MspDeInit 1 */ /* USER CODE END FDCAN1_MspDeInit 1 */ } } /* USER CODE BEGIN 1 */ /* USER CODE END 1 */

fdcan.h

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file fdcan.h * @brief This file contains all the function prototypes for * the fdcan.c file ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __FDCAN_H__ #define __FDCAN_H__ #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include "main.h" /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ extern FDCAN_HandleTypeDef hfdcan1; /* USER CODE BEGIN Private defines */ /* USER CODE END Private defines */ void MX_FDCAN1_Init(void); /* USER CODE BEGIN Prototypes */ /* USER CODE END Prototypes */ #ifdef __cplusplus } #endif #endif /* __FDCAN_H__ */

改进后的代码,增加一个发送与接收CAN报文的接口与一些驱动接口。尝试之后就可以发送与接收到报文了,亲测有效。另外,当数据域设置成最大的时候,使用CAN工具设置数据域的那个波特率都是可以接受到报文的。

 fdcan.c

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file fdcan.c * @brief This file provides code for the configuration * of the FDCAN instances. ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "fdcan.h" /* USER CODE BEGIN 0 */ RxDataLen_t RxData_len[16] = { {SEND_BYTES_0, 0}, {SEND_BYTES_1, 1}, {SEND_BYTES_2, 2}, {SEND_BYTES_3, 3}, {SEND_BYTES_4, 4}, {SEND_BYTES_5, 5}, {SEND_BYTES_6, 6}, {SEND_BYTES_7, 7}, {SEND_BYTES_8, 8}, {SEND_BYTES_12, 12}, {SEND_BYTES_16, 16}, {SEND_BYTES_20, 20}, {SEND_BYTES_24, 24}, {SEND_BYTES_32, 32}, {SEND_BYTES_48, 48}, {SEND_BYTES_64, 64} }; /* USER CODE END 0 */ FDCAN_HandleTypeDef hfdcan1; /* FDCAN1 init function */ void MX_FDCAN1_Init(void) { FDCAN_FilterTypeDef sFilterConfig = {0}; /* USER CODE BEGIN FDCAN1_Init 0 */ /* USER CODE END FDCAN1_Init 0 */ /* USER CODE BEGIN FDCAN1_Init 1 */ /* USER CODE END FDCAN1_Init 1 */ // 仲裁域波特率为1Mbps 80% 数据域波特率为5Mbps 75% // 波特率 Baud rate = FDCAN Clock / (Prescaler * (Seg_1 + Seg_2 + Sync_Jump_Width)) // 采样率 sampling rate = (Seg_1 + 1) / (1 + Seg_1 + Seg_2) hfdcan1.Instance = FDCAN1; hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1; // 外设时钟分频 hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS; // 使用FD BRS格式 hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; // 正常模式 hfdcan1.Init.AutoRetransmission = DISABLE; // 禁止自动重发 hfdcan1.Init.TransmitPause = DISABLE; // 禁止暂停传输 hfdcan1.Init.ProtocolException = DISABLE; // 禁用协议异常 hfdcan1.Init.NominalPrescaler = 5; // 仲裁域分频系数(1Mbps) hfdcan1.Init.NominalSyncJumpWidth = 1; // 同步跳跃宽度 hfdcan1.Init.NominalTimeSeg1 = 15; // 时间段1 hfdcan1.Init.NominalTimeSeg2 = 4; // 时间段2 hfdcan1.Init.DataPrescaler = 5; // 数据域分频系数 hfdcan1.Init.DataSyncJumpWidth = 1; // 数据同步跳跃宽度 hfdcan1.Init.DataTimeSeg1 = 2; // 数据时间段1 hfdcan1.Init.DataTimeSeg2 = 1; // 数据时间段2 hfdcan1.Init.StdFiltersNbr = 28; // 标准过滤器数量 hfdcan1.Init.ExtFiltersNbr = 8; // 扩展过滤器数量 hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // 发送FIFO操作模式 if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK) { printf("Error_Handler:HAL_FDCAN_Init\r\n"); Error_Handler(); } sFilterConfig.IdType = FDCAN_STANDARD_ID; sFilterConfig.FilterIndex = 0; sFilterConfig.FilterType = FDCAN_FILTER_RANGE; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterID1 = 0x00; sFilterConfig.FilterID2 = 0x7FF; if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); } sFilterConfig.IdType = FDCAN_EXTENDED_ID; sFilterConfig.FilterIndex = 0; sFilterConfig.FilterType = FDCAN_FILTER_RANGE; sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0; sFilterConfig.FilterID1 = 0x00; sFilterConfig.FilterID2 = 0x1FFFFFFF; if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK) { Error_Handler(); } /* Configure global filter on both FDCAN instances: Filter all remote frames with STD and EXT ID Reject non matching frames with STD ID and EXT ID */ if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK) { Error_Handler(); } /* Activate Rx FIFO 0 new message notification on both FDCAN instances */ if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK) { Error_Handler(); } if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_BUS_OFF, 0) != HAL_OK) { Error_Handler(); } /* Configure and enable Tx Delay Compensation, required for BRS mode. TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler TdcFilter default recommended value: 0 */ HAL_FDCAN_ConfigTxDelayCompensation(&hfdcan1, hfdcan1.Init.DataPrescaler * hfdcan1.Init.DataTimeSeg1, 0); HAL_FDCAN_EnableTxDelayCompensation(&hfdcan1); HAL_FDCAN_Start(&hfdcan1); /* USER CODE END FDCAN1_Init 2 */ } void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; if(fdcanHandle->Instance==FDCAN1) { /* USER CODE BEGIN FDCAN1_MspInit 0 */ /* USER CODE END FDCAN1_MspInit 0 */ /** Initializes the peripherals clocks */ PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN; PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); } /* FDCAN1 clock enable */ __HAL_RCC_FDCAN_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**FDCAN1 GPIO Configuration PA11 ------> FDCAN1_RX PA12 ------> FDCAN1_TX */ GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF9_FDCAN1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* FDCAN1 interrupt Init */ HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 0, 0); HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_SetPriority(FDCAN1_IT1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(FDCAN1_IT1_IRQn); /* USER CODE BEGIN FDCAN1_MspInit 1 */ /* USER CODE END FDCAN1_MspInit 1 */ } } void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef* fdcanHandle) { if(fdcanHandle->Instance==FDCAN1) { /* USER CODE BEGIN FDCAN1_MspDeInit 0 */ /* USER CODE END FDCAN1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_FDCAN_CLK_DISABLE(); /**FDCAN1 GPIO Configuration PA11 ------> FDCAN1_RX PA12 ------> FDCAN1_TX */ HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12); /* FDCAN1 interrupt Deinit */ HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn); HAL_NVIC_DisableIRQ(FDCAN1_IT1_IRQn); /* USER CODE BEGIN FDCAN1_MspDeInit 1 */ /* USER CODE END FDCAN1_MspDeInit 1 */ } } // /* USER CODE BEGIN 1 */ /* FDCAN发送报文函数 */ HAL_StatusTypeDef FDCAN_SendMessage(uint32_t id, uint8_t Txdata[], FDCAN_DLC_T dataLength) { FDCAN_TxHeaderTypeDef TxHeader = {0}; TxHeader.Identifier = id; // 设置CAN报文的ID // 检查发送帧ID的有效性 if(id < 0x800) { TxHeader.IdType = FDCAN_STANDARD_ID; } else { TxHeader.IdType = FDCAN_EXTENDED_ID; if(id > 0x1FFFFFFF) { printf("Error_Handler:id > 0x1FFFFFFF\r\n"); return HAL_ERROR; } } // CAN发送格式的识别 if (dataLength > FDCAN_DLC_BYTES_8) { TxHeader.FDFormat = FDCAN_FD_CAN; } else { TxHeader.FDFormat = FDCAN_CLASSIC_CAN; } TxHeader.TxFrameType = FDCAN_DATA_FRAME; // 数据帧 FDCAN_REMOTE_FRAME//FDCAN_DATA_FRAME // 数据长度(0-8字节,还有12,16,20,24,32,48,64) TxHeader.DataLength = dataLength; TxHeader.BitRateSwitch = FDCAN_BRS_OFF; // 不使用数据速率切换 // 发送报文 if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader, Txdata) != HAL_OK) { printf("FDCAN send msg fail\r\n"); return HAL_ERROR; // 发送失败 } return HAL_OK; // 发送成功 } // FDCAN1 中断接收回调函数 void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { FDCAN_RxHeaderTypeDef RxHeader = {0}; uint8_t RxData[64] = {0}; // 支持最大64字节数据 uint8_t len = 0; // 接收到的数据长度 // 从FIFO0中读取接收到的消息 if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK) { Error_Handler(); } for(int i = 0; i < (sizeof(RxData_len)/sizeof(RxData_len[0])); i++) { if (RxData_len[i].dataLength == RxHeader.DataLength) { len = RxData_len[i].datalen_num; } } #if 1 printf("id: %d, DataLength: %d\r\n", RxHeader.Identifier, len); printf("Received Data: "); for (uint8_t i = 0; i < len; i++) { printf("0x%02X ", RxData[i]); } printf("\n"); #endif // 可以根据需要检查其他中断标志位进行不同的处理 // 如果接收FIFO0已满 if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_FULL) != RESET) { // 处理FIFO满的情况 printf("FIFO 0 is full\n"); } // 如果接收FIFO0消息丢失 if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_MESSAGE_LOST) != RESET) { // 处理消息丢失的情况 printf("Message lost in FIFO 0\n"); } } /* USER CODE END 1 */

  fdcan.h

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file fdcan.h * @brief This file contains all the function prototypes for * the fdcan.c file ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Define to prevent recursive inclusion -------------------------------------*/ #ifndef __FDCAN_H__ #define __FDCAN_H__ #ifdef __cplusplus extern "C" { #endif /* Includes ------------------------------------------------------------------*/ #include "main.h" /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ extern FDCAN_HandleTypeDef hfdcan1; /* USER CODE BEGIN Private defines */ typedef enum { SEND_BYTES_0 = FDCAN_DLC_BYTES_0, SEND_BYTES_1 = FDCAN_DLC_BYTES_1, SEND_BYTES_2 = FDCAN_DLC_BYTES_2, SEND_BYTES_3 = FDCAN_DLC_BYTES_3, SEND_BYTES_4 = FDCAN_DLC_BYTES_4, SEND_BYTES_5 = FDCAN_DLC_BYTES_5, SEND_BYTES_6 = FDCAN_DLC_BYTES_6, SEND_BYTES_7 = FDCAN_DLC_BYTES_7, SEND_BYTES_8 = FDCAN_DLC_BYTES_8, SEND_BYTES_12 = FDCAN_DLC_BYTES_12, SEND_BYTES_16 = FDCAN_DLC_BYTES_16, SEND_BYTES_20 = FDCAN_DLC_BYTES_20, SEND_BYTES_24 = FDCAN_DLC_BYTES_24, SEND_BYTES_32 = FDCAN_DLC_BYTES_32, SEND_BYTES_48 = FDCAN_DLC_BYTES_48, SEND_BYTES_64 = FDCAN_DLC_BYTES_64 } FDCAN_DLC_T; typedef struct { FDCAN_DLC_T dataLength; uint8_t datalen_num; } RxDataLen_t; /* USER CODE END Private defines */ /* USER CODE BEGIN Prototypes */ void MX_FDCAN1_Init(void); HAL_StatusTypeDef FDCAN_SendMessage(uint32_t id, uint8_t Txdata[], FDCAN_DLC_T dataLength); /* USER CODE END Prototypes */ #ifdef __cplusplus } #endif #endif /* __FDCAN_H__ */

参考文章:

CAN总线采样点原理与测试方法详解-CSDN博客

嵌入式Linux中的CAN(FD)总线——驱动配置 - 知乎 (zhihu )

标签:

【STM32项目实战系列】基于STM32G474的FDCAN驱动配置由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【STM32项目实战系列】基于STM32G474的FDCAN驱动配置