3. 配置CANFD

3.1 波特率计算

  • 波特率
波特率 = FDCAN时钟频率 / (NominalPrescaler × (1 + NominalTimeSeg1 + NominalTimeSeg2))
  • 时间量子(TQ)

时间量子也就是一个Tick的时间

时间量子 = FDCAN时钟频率 / NominalPrescaler
  • 位时间

这里的1就是同步段NominalSyncJumpWidth,同步段一般设置为固定的1TQ;

NominalTimeSeg1决定了采样点的位置,采样点在NominalTimeSeg1段的结束处;

NominalTimeSeg2是采样点到位结束的距离,越大对抖动的容忍度越高。

位时间 = 1 + NominalTimeSeg1 + NominalTimeSeg2 个时间量子
  • 采样点

采样点一般设置在75%左右。

采样点位置 = (1 + NominalTimeSeg1) / (1 + NominalTimeSeg1 + NominalTimeSeg2)
  • 波特率设置注意事项
1. NominalPrescaler应该尽可能小,以提高采样精度,减少误差;
2. CANFD推荐仲裁域NominalPrescaler <= 数据域NominalPrescaler;
3. SJW应尽量大,尽量保持与TSEG2一致,以提高位宽容忍度;
4. 波特率大于800K,推荐采样点在75%;波特率大于500K,推荐采样点在80%;波特率小于500K,推荐采样点在87.5%;
5. 尽量保证总线上所有节点的采样点一致,CANFD的仲裁域和数据域采样点不要求一致;
6. CANFD仲裁域与数据域的波特率之比应该大于等于1/8.

3.2 配置参数

在这里我们目标是使用CANFD的加速模式,仲裁域波特率为1M,数据域波特率为2M。

配置CANFD参数1

  • Frame Format

    • Classic mode : 标准的can模式,非CANFD,单帧数据最多8个字节。
    • FD mode without BitRate Switching:CANFD模式,仲裁域和数据域波特率相同,CANFD单帧数据可扩展至64个字节。
    • FD mode with BitRate Switching:CANFD加速模式,数据域波特率可与仲裁域波特率不同,数据域波特率大于仲裁域波特率。
  • Data Prescaler 这里是数据域的波特率设置,在CANFD加速模式下有效。

波特率 = FDCAN时钟频率 / (NominalPrescaler × (1 + NominalTimeSeg1 + NominalTimeSeg2))
------
2000 Kbits/s = 50MHZ / (1 x (1 + 23 + 1))
  • 数据域和仲裁域的波特率不建议差距太大,会导致时钟同步出错,通讯稳定性下降

  • Message Ram Offset: 地址偏移,如果设置两路CANFD的话,第二路CANFD的Message Ram Offset地址需要偏移,避免冲突。具体的偏移值为 Std Filters Nbr * 1 + Ext Filters Nbr*2 + Rx Fifo0 Elmts Nbr * 18 + Rx Fifo1 Elmts Nbr* 18 + Rx Buffers Nbr * 18,需要注意的是 STM32H7的Message RAM最大为10KB,为2560个words(在这里一个word为32个Bits)。

  • Std Filters Nbr: 设置标准帧滤波器的个数,每个滤波器占据1个word;

  • Ext Filters Nbr: 设置扩展帧滤波器的个数,每个滤波器占据2个word;

  • Rx Fifo0 Elmts Nbr:数据接收区域0的个数,遵从先入先出原则,在代码中可以设置哪些帧落入接收区域0;

  • Rx Fifo0 Elmts Size:接收区域0中数据的长度,最大可以接收64个bytes;

  • Rx Fifo1 Elmts Nbr:数据接收区域1的个数,遵从先入先出原则,在代码中可以设置哪些帧落入接收区域1;

  • Rx Fifo1 Elmts Size:接收区域1中数据的长度,最大可以接收64个bytes;

# 滤波设置参考函数
void ConfigureFDCANFilters(void)
{
    FDCAN_FilterTypeDef sFilterConfig;
    
    // 配置1: 标准帧路由到FIFO0 (高优先级)
    sFilterConfig.IdType = FDCAN_STANDARD_ID;
    sFilterConfig.FilterIndex = 0;
    sFilterConfig.FilterType = FDCAN_FILTER_MASK;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    // 拒绝特定ID的消息
	//sFilterConfig.FilterConfig = FDCAN_FILTER_REJECT;  // 拒绝匹配的消息
    sFilterConfig.FilterID1 = 0x100;  // 发动机控制
    sFilterConfig.FilterID2 = 0x7FF;  // 掩码
    HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
    
    // 配置2: 扩展帧路由到FIFO1 (低优先级)
    sFilterConfig.IdType = FDCAN_EXTENDED_ID;
    sFilterConfig.FilterIndex = 0;
    sFilterConfig.FilterType = FDCAN_FILTER_MASK;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
    sFilterConfig.FilterID1 = 0x18FF1000;  // 诊断数据
    sFilterConfig.FilterID2 = 0x1FFFFFFF;  // 掩码
    HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
    
    // 配置3: 紧急消息路由到Buffer0
    sFilterConfig.IdType = FDCAN_STANDARD_ID;
    sFilterConfig.FilterIndex = 1;
    sFilterConfig.FilterType = FDCAN_FILTER_MASK;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;
    sFilterConfig.FilterID1 = 0x300;  // 紧急停止
    sFilterConfig.FilterID2 = 0x7FF;  // 掩码
    sFilterConfig.RxBufferIndex = 0;   // Buffer0
    HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
    
    // 配置4: 安全消息路由到Buffer1
    sFilterConfig.IdType = FDCAN_STANDARD_ID;
    sFilterConfig.FilterIndex = 2;
    sFilterConfig.FilterType = FDCAN_FILTER_MASK;
    sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;
    sFilterConfig.FilterID1 = 0x400;  // 安全气囊
    sFilterConfig.FilterID2 = 0x7FF;  // 掩码
    sFilterConfig.RxBufferIndex = 1;   // Buffer1
    HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig);
}
  • Rx Buffers Nbr: 专用数据缓存区,实时性较高,在代码中可以设置哪些帧落入Buffers区域;
  • Rx Buffers Size: 专用数据缓存区域中数据的长度,最大可以接收64个bytes;

配置CANFD参数2

  • Tx Fifo Queue Elmts Nbr:发送区域FIFO区域数据长度,这里设置为8,意味着我们可以一次性发送8帧数据,而不需要增加延时以等待发送完成;
  • Tx Fifo Queue Mode
    • FIFO mode:先入先出原则;
    • Queue mode:优先级原则;
  • Bit Timings Parameters :仲裁域波特率设置
波特率 = FDCAN时钟频率 / (NominalPrescaler × (1 + NominalTimeSeg1 + NominalTimeSeg2))
------
1000 Kbits/s = 50MHZ / (10 x (1 + 3 + 1))
3.2.1 配置中断线

每路CANFD有两个中断线可以选择使用fdcan1 interrupt 0fdcan1 interrupt 1.项目中根据需求将不同类型的中断分配到不同的中断线上。默认情况下,所有中断都指向了中断线0,fdcan1 interrupt 0.

配置CANFD参数打开中断

配置中断线需要在代码中手动输入。

// 将接收中断分配到中断线0
HAL_FDCAN_ConfigInterruptLines(&hfdcan1, 
                               FDCAN_IT_RX_FIFO0_NEW_MESSAGE | FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 
                               FDCAN_INTERRUPT_LINE0);

// 将错误中断分配到中断线1
HAL_FDCAN_ConfigInterruptLines(&hfdcan1, 
                               FDCAN_IT_ERROR_LOGGING_OVERFLOW | FDCAN_IT_RAM_ACCESS_FAILURE, 
                               FDCAN_INTERRUPT_LINE1);

可供使用配置的中断:

接收相关中断:
FDCAN_IT_RX_FIFO0_NEW_MESSAGE - Rx FIFO 0新消息
FDCAN_IT_RX_FIFO0_WATERMARK - Rx FIFO 0水位标记
FDCAN_IT_RX_FIFO0_FULL - Rx FIFO 0满
FDCAN_IT_RX_FIFO0_MESSAGE_LOST - Rx FIFO 0消息丢失
FDCAN_IT_RX_FIFO1_NEW_MESSAGE - Rx FIFO 1新消息
FDCAN_IT_RX_FIFO1_WATERMARK - Rx FIFO 1水位标记
FDCAN_IT_RX_FIFO1_FULL - Rx FIFO 1满
FDCAN_IT_RX_FIFO1_MESSAGE_LOST - Rx FIFO 1消息丢失
FDCAN_IT_RX_BUFFER_NEW_MESSAGE - Rx缓冲区新消息
FDCAN_IT_RX_HIGH_PRIORITY_MSG - 高优先级消息
发送相关中断:
FDCAN_IT_TX_COMPLETE - 发送完成
FDCAN_IT_TX_ABORT_COMPLETE - 发送中止完成
FDCAN_IT_TX_FIFO_EMPTY - 发送FIFO空
错误相关中断:
FDCAN_IT_RAM_ACCESS_FAILURE - RAM访问失败
FDCAN_IT_ERROR_LOGGING_OVERFLOW - 错误日志溢出
FDCAN_IT_RAM_WATCHDOG - RAM看门狗
FDCAN_IT_ARB_PROTOCOL_ERROR - 仲裁协议错误
FDCAN_IT_DATA_PROTOCOL_ERROR - 数据协议错误
FDCAN_IT_RESERVED_ADDRESS_ACCESS - 保留地址访问

3.2.2 端口设置

配置CANFD配置GPIO

3.3 函数使用

CAN接收函数流程

3.3.1 接收
  • FIFO0回调函数
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
    FDCAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[64];
    
    // 数据存储在FIFO0中
    if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
    {
        // 处理FIFO0中的数据
        printf("收到FIFO0数据: ID=0x%X, 长度=%d\n", 
               RxHeader.Identifier, RxHeader.DataLength);
        
        // 处理数据内容
        for (int i = 0; i < RxHeader.DataLength; i++) {
            printf("数据[%d] = 0x%02X\n", i, RxData[i]);
        }
    }
}
  • FIFO1回调函数
void HAL_FDCAN_RxFifo1Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo1ITs)
{
    FDCAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[64];
    
    // 数据存储在FIFO1中
    if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO1, &RxHeader, RxData) == HAL_OK)
    {
        // 处理FIFO1中的数据
        printf("收到FIFO1数据: ID=0x%X, 长度=%d\n", 
               RxHeader.Identifier, RxHeader.DataLength);
        
        // 处理数据内容
        for (int i = 0; i < RxHeader.DataLength; i++) {
            printf("数据[%d] = 0x%02X\n", i, RxData[i]);
        }
    }
}
  • Buffer回调函数
void HAL_FDCAN_RxBufferNewMessageCallback(FDCAN_HandleTypeDef *hfdcan)
{
    FDCAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[64];
    
    // 检查哪个Buffer有数据
    for (int i = 0; i < hfdcan->Init.RxBuffersNbr; i++)
    {
        if (HAL_FDCAN_GetRxMessage(hfdcan, i, &RxHeader, RxData) == HAL_OK)
        {
            // 数据存储在Buffer i中
            printf("收到Buffer%d数据: ID=0x%X, 长度=%d\n", 
                   i, RxHeader.Identifier, RxHeader.DataLength);
            
            // 处理数据内容
            for (int j = 0; j < RxHeader.DataLength; j++) {
                printf("数据[%d] = 0x%02X\n", j, RxData[j]);
            }
        }
    }
}
  • 批量处理FIFO数据
void ProcessFIFOData(FDCAN_HandleTypeDef *hfdcan, uint32_t FifoLocation)
{
    FDCAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[64];
    uint32_t messageCount = 0;
    
    // 获取FIFO中的消息数量
    uint32_t fifoCount = HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FifoLocation);
    
    printf("FIFO中有 %d 条消息\n", fifoCount);
    
    // 批量处理所有消息
    while (fifoCount > 0)
    {
        if (HAL_FDCAN_GetRxMessage(hfdcan, FifoLocation, &RxHeader, RxData) == HAL_OK)
        {
            messageCount++;
            
            // 根据FIFO位置处理数据
            if (FifoLocation == FDCAN_RX_FIFO0)
            {
                printf("FIFO0消息%d: ID=0x%X\n", messageCount, RxHeader.Identifier);
                // 处理高优先级数据
                ProcessHighPriorityData(&RxHeader, RxData);
            }
            else if (FifoLocation == FDCAN_RX_FIFO1)
            {
                printf("FIFO1消息%d: ID=0x%X\n", messageCount, RxHeader.Identifier);
                // 处理低优先级数据
                ProcessLowPriorityData(&RxHeader, RxData);
            }
        }
        
        fifoCount = HAL_FDCAN_GetRxFifoFillLevel(hfdcan, FifoLocation);
    }
}
  • 处理BUFFER数据
void ProcessBufferData(FDCAN_HandleTypeDef *hfdcan)
{
   FDCAN_RxHeaderTypeDef RxHeader;
   uint8_t RxData[64];
   
   // 检查所有Buffer
   for (int i = 0; i < hfdcan->Init.RxBuffersNbr; i++)
   {
       if (HAL_FDCAN_GetRxMessage(hfdcan, i, &RxHeader, RxData) == HAL_OK)
       {
           printf("Buffer%d: ID=0x%X, 长度=%d\n", 
                  i, RxHeader.Identifier, RxHeader.DataLength);
           
           // 根据Buffer索引处理数据
           switch (i)
           {
               case 0:
                   ProcessEmergencyData(&RxHeader, RxData);  // 紧急数据
                   break;
               case 1:
                   ProcessSafetyData(&RxHeader, RxData);     // 安全数据
                   break;
               default:
                   ProcessGeneralData(&RxHeader, RxData);    // 一般数据
                   break;
           }
       }
   }
}
3.3.2 发送
  • 模板

hfdcan1为can的实例;id为待发送的CANID;data指向待发送的数据指针;发送的数据长度是len。

#define hcan_t FDCAN_HandleTypeDef

uint8_t fdcanx_send_data(hcan_t *hfdcan, uint16_t id, uint8_t *data, uint32_t len)
{	
    FDCAN_TxHeaderTypeDef pTxHeader;
    pTxHeader.Identifier=id;
    pTxHeader.IdType=FDCAN_STANDARD_ID;
    pTxHeader.TxFrameType=FDCAN_DATA_FRAME;
	
	if(len<=8)
		pTxHeader.DataLength = len;
	if(len==12)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_12;
	if(len==16)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_16;
	if(len==20)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_20;
	if(len==24)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_24;
	if(len==32)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_32;
	if(len==48)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_48;
	if(len==64)
		pTxHeader.DataLength = FDCAN_DLC_BYTES_64;
	
    pTxHeader.ErrorStateIndicator=FDCAN_ESI_ACTIVE;
    pTxHeader.BitRateSwitch=FDCAN_BRS_ON;
    pTxHeader.FDFormat=FDCAN_FD_CAN;
    pTxHeader.TxEventFifoControl=FDCAN_NO_TX_EVENTS;
    pTxHeader.MessageMarker=0;
 
	if(HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, &pTxHeader, data)!=HAL_OK) 
		return 1;//·¢ËÍ
	return 0;	
}
  • 使用

hfdcan1为can的实例;0x201为待发送的CANID;tx_data1指向待发送的数据指针;发送的数据长度是8。

uint8_t tx_data1[] = {0,1,2,3,4,5,6,7};
fdcanx_send_data(&hfdcan1, 0x201, tx_data1, 8);
3.3.3 常用的函数

  • 中断触发机制设置
// 每当有新消息进入FIFO时触发中断
// 触发时机:
// 消息1进入 → 中断1
// 消息2进入 → 中断2
// 消息3进入 → 中断3
// ...
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);

// 当FIFO中消息数量达到水位线时触发中断
// 触发时机:
// 消息1-15进入 → 无中断
// 消息16进入 → 中断1
// 消息17-31进入 → 无中断
// 消息32进入 → 中断2
HAL_FDCAN_ConfigFifoWatermark(&hfdcan1, FDCAN_RX_FIFO0, 16);  // 设置水位线为16
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_WATERMARK, 0);

// 当FIFO满时触发中断
// 触发时机:
// 消息1-63进入 → 无中断
// 消息64进入 → 中断1(FIFO满)
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_FULL, 0);