引言

STM32H7 系列以 400MHz 主频和双 FDCAN 控制器著称,但在高速运行下,FDCAN 的发送超时(TX Timeout)和仲裁丢失(Arbitration Lost)问题频发。这些问题往往源于时钟配置错误、位时序参数不匹配、滤波器误过滤或中断优先级不当。本文基于实际调试经验,提供一套完整的排查方法论。

1. 问题现象与根因分析

1.1 发送超时

发送超时指 FDCAN 在预定时间内未能完成报文发送,通常表现为 HAL_FDCAN_AddMessageToTxMailbox 返回 HAL_BUSY,或 TX Event FIFO 中无对应事件。根因包括:

  • 时钟源错误:FDCAN 内核时钟(FDCAN_KER_CK)未正确分频,导致位时间计算偏差。
  • 位时序过紧:采样点设置不当,尤其在 400MHz 下,APB1 时钟(通常 100MHz)与 FDCAN 时钟(通常 20MHz)分频不匹配。
  • 邮箱占用:三个发送邮箱全部被低优先级报文占满。

1.2 仲裁丢失

仲裁丢失发生在多个节点同时发送时,ID 较小的报文赢得仲裁。若本节点频繁丢失,可能是:

  • 位时序错误:导致位采样偏移,误判总线电平。
  • 滤波器配置:接收滤波器误过滤了本应接收的报文,但发送侧无直接影响。
  • 总线竞争:总线负载过高,本节点发送优先级低。

2. 排查步骤

2.1 检查时钟树配置

在 STM32H7 中,FDCAN 内核时钟源可选 PLL1Q、PLL2Q 或 PLL3Q,最大 80MHz。确保:

  • 使用 CubeMX 或 HAL 配置时,FDCAN_KER_CK 不超过 80MHz。
  • 检查 RCC 时钟配置:
// 示例:配置 FDCAN1 时钟为 PLL1Q(20MHz)
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
PeriphClkInit.Fdcan1ClockSelection = RCC_FDCAN1CLKSOURCE_PLL1Q;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);

验证:在调试器中读取 RCC->D2CCIP2R 的 FDCANSEL 位,确认时钟源。

2.2 位时序参数计算

FDCAN 位时间 = 同步段 + 传播段 + 相位缓冲段1 + 相位缓冲段2。在 400MHz 下,APB1 时钟通常为 100MHz,FDCAN 时钟为 20MHz,则 1 个时间量子(tq)= 50ns。若目标波特率 1Mbps,则位时间 = 1000ns = 20 tq。

推荐采样点:75%~80%。配置示例:

FDCAN_InitTypeDef FdcanInit = {0};
FdcanInit.ClockDivider = FDCAN_CLOCK_DIV1;
FdcanInit.NominalPrescaler = 1;   // 20MHz / 1 = 20MHz
FdcanInit.NominalSyncJumpWidth = 1;
FdcanInit.NominalTimeSeg1 = 14;   // 15 tq (含同步段)
FdcanInit.NominalTimeSeg2 = 5;    // 6 tq
// 采样点 = (1+15)/(1+15+6) = 72.7%

注意:STM32H7 的 FDCAN 位时序寄存器中,TimeSeg1 和 TimeSeg2 的值需加 1 才是实际 tq 数。

2.3 检查滤波器配置

滤波器误配置可能导致本节点无法接收应答帧(ACK),但发送超时通常与滤波器无关。然而,若使用了硬件过滤,确保标准 ID 和扩展 ID 的掩码正确:

FDCAN_FilterTypeDef FilterConfig = {0};
FilterConfig.IdType = FDCAN_STANDARD_ID;
FilterConfig.FilterIndex = 0;
FilterConfig.FilterType = FDCAN_FILTER_MASK;
FilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
FilterConfig.FilterID1 = 0x123;   // 期望 ID
FilterConfig.FilterID2 = 0x7FF;   // 掩码,0x7FF 表示精确匹配
HAL_FDCAN_ConfigFilter(&hfdcan1, &FilterConfig);

2.4 中断优先级与邮箱管理

高优先级中断(如 CAN 发送中断)若被其他中断阻塞,可能导致发送超时。确保:

  • 使用 HAL_FDCAN_ActivateNotification 使能发送中断。
  • 在中断回调中及时释放邮箱。
void HAL_FDCAN_TxMailbox0CompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes) {
    // 处理发送完成,释放邮箱
    HAL_FDCAN_AbortTxRequest(hfdcan, FDCAN_TX_MAILBOX0);
}

3. 完整代码示例

以下代码演示了 FDCAN1 在 20MHz 内核时钟下,1Mbps 波特率的初始化与发送:

#include "stm32h7xx_hal.h"

FDCAN_HandleTypeDef hfdcan1;

void FDCAN1_Init(void) {
    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 = ENABLE;
    hfdcan1.Init.TransmitPause = DISABLE;
    hfdcan1.Init.ProtocolException = DISABLE;
    hfdcan1.Init.NominalPrescaler = 1;
    hfdcan1.Init.NominalSyncJumpWidth = 1;
    hfdcan1.Init.NominalTimeSeg1 = 14;
    hfdcan1.Init.NominalTimeSeg2 = 5;
    hfdcan1.Init.DataPrescaler = 1;
    hfdcan1.Init.DataSyncJumpWidth = 1;
    hfdcan1.Init.DataTimeSeg1 = 4;
    hfdcan1.Init.DataTimeSeg2 = 3;
    hfdcan1.Init.StdFiltersNbr = 1;
    hfdcan1.Init.ExtFiltersNbr = 0;
    hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
    HAL_FDCAN_Init(&hfdcan1);

    // 配置滤波器
    FDCAN_FilterTypeDef FilterConfig = {0};
    FilterConfig.IdType = FDCAN_STANDARD_ID;
    FilterConfig.FilterIndex = 0;
    FilterConfig.FilterType = FDCAN_FILTER_MASK;
    FilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    FilterConfig.FilterID1 = 0x123;
    FilterConfig.FilterID2 = 0x7FF;
    HAL_FDCAN_ConfigFilter(&hfdcan1, &FilterConfig);

    // 启动 FDCAN
    HAL_FDCAN_Start(&hfdcan1);
    HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_TX_MAILBOX_EMPTY, 0);
}

void FDCAN1_Send(uint32_t id, uint8_t *data, uint8_t len) {
    FDCAN_TxHeaderTypeDef TxHeader = {0};
    TxHeader.Identifier = id;
    TxHeader.IdType = FDCAN_STANDARD_ID;
    TxHeader.TxFrameType = FDCAN_DATA_FRAME;
    TxHeader.DataLength = len << 16;  // 长度编码
    TxHeader.FDFormat = FDCAN_FD_CAN;
    TxHeader.BitRateSwitch = DISABLE;
    TxHeader.TxEventFifoControl = FDCAN_NO_TX_EVENTS;

    uint32_t mailbox;
    if (HAL_FDCAN_AddMessageToTxMailbox(&hfdcan1, &TxHeader, data, &mailbox) != HAL_OK) {
        Error_Handler();
    }
}

4. 调试技巧与注意事项

  • 使用逻辑分析仪:抓取 CAN_H/CAN_L 波形,验证位时间是否与配置一致。
  • 查看错误寄存器HAL_FDCAN_GetError 可获取仲裁丢失计数(LEC 字段)。
  • 避免使用自动重传:在调试阶段关闭自动重传,便于观察超时现象。
  • 注意时钟分频:STM32H7 的 FDCAN 时钟源若选择 PLL3Q,需确保 PLL3 配置正确。
  • 中断优先级:FDCAN 中断优先级应高于其他非关键中断,但低于系统滴答。

总结

解决 STM32H7 FDCAN 发送超时与仲裁丢失,核心在于时钟配置、位时序和中断管理。通过系统化排查,90% 的问题可定位到位时序参数错误或时钟源分频不当。建议在项目初期用 CubeMX 生成配置,并利用调试器实时监控寄存器,以快速收敛问题。