STM32H7_FDCAN 驱动笔记

STM32H7 FDCAN 驱动笔记

芯片:STM32H743/STM32H753(FDCAN 外设)

HAL 版本:STM32H7xx_HAL_Driver

功能:经典 CAN 2.0B + CAN FD,双波特率,中断接收,硬件过滤器,自动位时序计算,Bus-Off 恢复,TDC


目录

  1. 架构设计
  2. [头文件 fdcan_driver.h](#头文件 fdcan_driver.h)
  3. [源文件 fdcan_driver.c](#源文件 fdcan_driver.c)
  4. 应用层示例
  5. [CubeMX 配置清单](#CubeMX 配置清单)
  6. 调试记录表

1. 架构设计

复制代码
┌────────────────────────────────────────────┐
│                 Application                 │
│  (CANopen / J1939 / 自定义协议 / 透传)       │
├────────────────────────────────────────────┤
│  fdcan_driver.h / fdcan_driver.c           │  ← 本驱动
│  ├─ fdcan_init()       硬件初始化 + GPIO     │
│  ├─ fdcan_send()       发送 (FIFO 模式)      │
│  ├─ fdcan_receive()    轮询接收 (带超时)      │
│  ├─ fdcan_config_filter()  滤波器配置        │
│  ├─ fdcan_irq_handler()   中断处理入口       │
│  └─ fdcan_calc_timing()   位时序自动计算     │
├────────────────────────────────────────────┤
│  STM32H7 HAL (stm32h7xx_hal_fdcan.h)       │
├────────────────────────────────────────────┤
│  STM32H7 FDCAN Peripheral (CAN core 3.x)   │
├────────────────────────────────────────────┤
│  CAN FD Transceiver (e.g. TCAN1042)        │
└────────────────────────────────────────────┘

设计要点:

设计决策 说明
中断收 / 轮询发 收走 ISR→环形缓冲区,零延迟;发送用 HAL 阻塞 API,简化逻辑
双波特率 Nominal ≤ 1 Mbps 保证仲裁距离,Data ≤ 8 Mbps 提升吞吐
TDC 自动启用 数据段速率 > 2 Mbps 时硬件自动补偿收发器环路延迟
环形缓冲 64 帧 中断回调只做 pushfdcan_receivepop,无锁
位时序自动计算 fdcan_calc_timing() 无需手工查表,传时钟 + 目标波特率即可

2. 头文件 fdcan_driver.h

c 复制代码
/**
 * @file    fdcan_driver.h
 * @brief   STM32H7 FDCAN 完整驱动 --- 头文件
 * @author  Claude
 * @date    2026-06-13
 *
 * 功能覆盖:
 *   - 经典 CAN 2.0B (标准帧 11-bit / 扩展帧 29-bit)
 *   - CAN FD (0--64 字节, 比特率切换 BRS)
 *   - 硬件过滤器配置 (Mask / List / Range / Dual-ID)
 *   - 中断接收 + 环形缓冲区
 *   - Bus-Off 自动恢复 + 错误回调
 *   - 位时序自动计算 (给定时钟 + 目标波特率 + 采样点)
 */

#ifndef __FDCAN_DRIVER_H__
#define __FDCAN_DRIVER_H__

#ifdef __cplusplus
extern "C" {
#endif

/* ==========================================================================
   Includes
   ========================================================================== */

#include "stm32h7xx_hal.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

/* ==========================================================================
   Constants
   ========================================================================== */

/** 环形接收缓冲区深度(中断模式) */
#define FDCAN_RX_BUF_SIZE       64

/** 最大 CAN FD 有效载荷字节数 */
#define FDCAN_MAX_PAYLOAD       64

/** 经典 CAN 最大有效载荷字节数 */
#define FDCAN_CLASSIC_PAYLOAD   8

/* ==========================================================================
   Enumerations
   ========================================================================== */

/** 驱动返回状态 */
typedef enum {
    FDCAN_OK          = 0x00U,   /**< 操作成功                           */
    FDCAN_ERROR       = 0x01U,   /**< 通用错误                           */
    FDCAN_BUSY        = 0x02U,   /**< 硬件忙 (所有 TX 元素被占用)          */
    FDCAN_TIMEOUT     = 0x03U,   /**< 操作超时                           */
    FDCAN_PARAM_ERROR = 0x04U,   /**< 参数非法                           */
    FDCAN_NOT_INIT    = 0x05U,   /**< 驱动未初始化                        */
    FDCAN_BUS_OFF     = 0x06U,   /**< 节点处于 Bus-Off 状态               */
} fdcan_status_t;

/** CAN 帧类型 */
typedef enum {
    FDCAN_CLASSIC_FRAME = 0U,    /**< 经典 CAN 2.0 帧 (0--8 字节)          */
    FDCAN_FD_NO_BRS     = 1U,    /**< CAN FD 帧,数据段不切换速率           */
    FDCAN_FD_WITH_BRS   = 2U,    /**< CAN FD 帧,数据段切换高速 (BRS=1)    */
} fdcan_frame_type_t;

/** 滤波器类型 */
typedef enum {
    FDCAN_FILTER_RANGE       = 0U,  /**< 范围滤波器 (FilterID1 ≤ ID ≤ FilterID2)  */
    FDCAN_FILTER_DUAL_ID     = 1U,  /**< 双 ID 滤波器 (匹配 ID1 或 ID2)           */
    FDCAN_FILTER_CLASSIC_MASK= 2U,  /**< 经典掩码滤波器 (ID & Mask == FilterID1)   */
} fdcan_filter_type_t;

/** 滤波器匹配后目标 */
typedef enum {
    FDCAN_FILTER_TO_RXFIFO0  = 0U,  /**< 存入 Rx FIFO 0                       */
    FDCAN_FILTER_TO_RXFIFO1  = 1U,  /**< 存入 Rx FIFO 1                       */
    FDCAN_FILTER_TO_RXBUFFER = 2U,  /**< 存入专用 Rx Buffer (需 FilterIndex≥0) */
    FDCAN_FILTER_REJECT      = 3U,  /**< 丢弃匹配的帧                          */
} fdcan_filter_dest_t;

/* ==========================================================================
   Type Definitions
   ========================================================================== */

/** CAN 消息 --- 涵盖经典 CAN 和 CAN FD */
typedef struct {
    /* ---- 标识符 ---- */
    uint32_t            id;             /**< CAN ID: 标准 11-bit [10:0], 扩展 29-bit [28:0]  */
    bool                is_ext;         /**< true = 扩展帧 (29-bit ID)                         */

    /* ---- 帧类型 ---- */
    fdcan_frame_type_t  frame_type;     /**< 经典 / FD-BRS-off / FD-BRS-on                     */

    /* ---- 数据 ---- */
    uint8_t             data[FDCAN_MAX_PAYLOAD];  /**< 有效载荷缓冲区                           */
    uint8_t             dlc;            /**< 实际数据长度: 0--8 (经典) 或 0/1/2/3/4/5/6/7/8/
                                                          12/16/20/24/32/48/64 (CAN FD)        */
    /* ---- 帧标志 ---- */
    bool                is_rtr;         /**< 远程帧标志 (CAN FD 帧自动为 false)                  */
    uint32_t            timestamp;      /**< 硬件时间戳 (16-bit 自由运行计数器)                  */
} fdcan_msg_t;

/** 滤波器配置 */
typedef struct {
    uint8_t             filter_index;   /**< 滤波器编号: 标准帧 0--N, 扩展帧 0--M               */
    bool                is_ext_id;      /**< true = 扩展帧滤波器, false = 标准帧滤波器          */
    fdcan_filter_type_t filter_type;    /**< 匹配逻辑: Range / Dual-ID / Mask                  */
    fdcan_filter_dest_t dest;           /**< 匹配后目标: FIFO0 / FIFO1 / RXBuffer / Reject     */
    uint32_t            id1;            /**< 滤波器 ID1 (Range=下限, Mask=参考ID, Dual=ID1)    */
    uint32_t            id2;            /**< 滤波器 ID2 (Range=上限, Mask=掩码, Dual=ID2)      */
} fdcan_filter_cfg_t;

/** 位时序参数 (由 fdcan_calc_timing 计算) */
typedef struct {
    uint32_t prescaler;                 /**< 预分频器 (1--512)                                  */
    uint32_t time_seg1;                 /**< PROP_SEG + PHASE_SEG1 (1--256)                     */
    uint32_t time_seg2;                 /**< PHASE_SEG2 (1--128)                                */
    uint32_t sync_jump_width;           /**< 同步跳转宽度 (1--16)                                */
    uint32_t actual_baud;               /**< 实际波特率 (Hz)                                    */
    uint32_t sample_point;              /**< 采样点位置 (千分比, 800 = 80.0%)                    */
} fdcan_timing_t;

/** FDCAN 初始化配置 */
typedef struct {
    /* ---- 硬件实例 ---- */
    FDCAN_HandleTypeDef *hfdcan;        /**< HAL FDCAN 句柄指针                                */
    FDCAN_GlobalTypeDef *instance;      /**< 外设基址: FDCAN1 / FDCAN2                         */

    /* ---- 时钟 ---- */
    uint32_t            fdcan_clk_hz;   /**< FDCAN 内核时钟 (Hz), H7 上 = PCLK1                */

    /* ---- 速率 ---- */
    uint32_t            nominal_baud;   /**< 仲裁段波特率 (bps), 通常 500000                    */
    uint32_t            data_baud;      /**< 数据段波特率 (bps), 0 = 与仲裁段相同               */
    uint32_t            nominal_sp;     /**< 仲裁段采样点 (千分比, 800 = 80.0%)                  */
    uint32_t            data_sp;        /**< 数据段采样点 (千分比, 750 = 75.0%)                  */

    /* ---- 模式 ---- */
    bool                loopback;       /**< 内部回环模式 (调试用)                              */
    bool                auto_retransmit;/**< 硬件自动重传 (推荐 true)                            */

    /* ---- Message RAM ---- */
    uint8_t             rx_fifo0_elmts; /**< Rx FIFO0 元素个数 (1--64)                           */
    uint8_t             tx_fifo_elmts;  /**< Tx FIFO 元素个数 (1--32)                            */

    /* ---- 中断 ---- */
    bool                use_interrupt;  /**< 启用中断接收                                      */

    /* ---- 收发器 ---- */
    bool                enable_tdc;     /**< 启用发射延迟补偿 (数据段 > 2 Mbps 时强制开启)       */
} fdcan_config_t;

/** 接收回调函数类型 */
typedef void (*fdcan_rx_callback_t)(fdcan_msg_t *msg);

/** 错误回调函数类型 */
typedef void (*fdcan_error_callback_t)(uint32_t error_code, uint8_t tec, uint8_t rec);

/* ==========================================================================
   Public API
   ========================================================================== */

/**
 * @brief  初始化 FDCAN 外设 (GPIO + 时钟 + 位时序 + Message RAM + 启动)
 * @param  cfg  初始化配置
 * @return FDCAN_OK on success
 *
 * @note   调用前需确保:
 *          - 系统时钟已配置 (HAL_Init + SystemClock_Config)
 *          - FDCAN 时钟已在 RCC 中使能 (CubeMX 自动生成)
 *          - GPIO 已在 CubeMX 中配置为 FDCAN_TX / FDCAN_RX 的 AF 模式
 */
fdcan_status_t fdcan_init(fdcan_config_t *cfg);

/**
 * @brief  反初始化 FDCAN 外设 (停止 + 释放 GPIO + 时钟)
 * @param  hfdcan  HAL FDCAN 句柄
 * @return FDCAN_OK on success
 */
fdcan_status_t fdcan_deinit(FDCAN_HandleTypeDef *hfdcan);

/**
 * @brief  发送一帧 CAN 消息 (经典或 FD)
 * @param  hfdcan  HAL FDCAN 句柄
 * @param  msg    待发送的消息指针
 * @return FDCAN_OK on success, FDCAN_BUSY if TX FIFO full
 *
 * @note   经典 CAN 模式: msg->dlc ≤ 8, msg->frame_type = FDCAN_CLASSIC_FRAME
 *          CAN FD 模式:   msg->dlc ≤ 64, msg->frame_type = FDCAN_FD_*
 *          RTR 帧仅在经典模式下有效; CAN FD 不支持 RTR
 */
fdcan_status_t fdcan_send(FDCAN_HandleTypeDef *hfdcan, fdcan_msg_t *msg);

/**
 * @brief  从环形缓冲区接收一帧 (非阻塞轮询, 可带超时)
 * @param  hfdcan   HAL FDCAN 句柄
 * @param  msg      接收到的消息存放于此
 * @param  timeout  超时 (ms), 0 = 立即返回, FDCAN_TIMEOUT 时立即返回
 *                  HAL_MAX_DELAY 可无限等待 (阻塞)
 * @return FDCAN_OK on success, FDCAN_TIMEOUT if no message within timeout
 */
fdcan_status_t fdcan_receive(FDCAN_HandleTypeDef *hfdcan, fdcan_msg_t *msg, uint32_t timeout);

/**
 * @brief  配置硬件滤波器
 * @param  hfdcan  HAL FDCAN 句柄
 * @param  cfg    滤波器配置
 * @return FDCAN_OK on success
 *
 * @note   必须在 fdcan_init() 之后、HAL_FDCAN_Start() 之后调用。
 *         标准帧和扩展帧各自拥有独立编号空间, 共 128 个共享元素。
 *         filter_index 从 0 开始。
 */
fdcan_status_t fdcan_config_filter(FDCAN_HandleTypeDef *hfdcan,
                                    fdcan_filter_cfg_t *cfg);

/**
 * @brief  注册用户接收回调 (在 FDCAN 中断上下文中调用)
 * @param  hfdcan   HAL FDCAN 句柄
 * @param  callback 回调函数, NULL 取消注册
 *
 * @note   回调在 ISR 上下文中执行, 应尽量简短。
 *         不要在回调中调用阻塞函数或发送 CAN 帧 (可用标志位通知主循环)。
 */
void fdcan_register_rx_callback(FDCAN_HandleTypeDef *hfdcan,
                                fdcan_rx_callback_t callback);

/**
 * @brief  注册错误回调 (在 FDCAN 中断上下文中调用)
 * @param  hfdcan   HAL FDCAN 句柄
 * @param  callback 回调函数, NULL 取消注册
 */
void fdcan_register_error_callback(FDCAN_HandleTypeDef *hfdcan,
                                   fdcan_error_callback_t callback);

/**
 * @brief  FDCAN 中断处理入口 --- 用户在各 FDCAN IRQHandler 中调用
 * @param  hfdcan  HAL FDCAN 句柄
 *
 * @note   典型用法:
 *         void FDCAN1_IT0_IRQHandler(void) { fdcan_irq_handler(&hfdcan1); }
 *         void FDCAN1_IT1_IRQHandler(void) { fdcan_irq_handler(&hfdcan1); }
 */
void fdcan_irq_handler(FDCAN_HandleTypeDef *hfdcan);

/**
 * @brief  自动计算 FDCAN 位时序参数
 * @param  clk_hz              FDCAN 内核时钟 (Hz), H7 上 = PCLK1
 * @param  target_baud         目标波特率 (bps)
 * @param  sample_point_permille 目标采样点 (千分比, 典型值 800 = 80%)
 * @param  out                输出时序参数
 * @return true if exact match found, false if approximate
 *
 * @note   算法: 遍历 Prescaler 1--512, 寻找满足 NBT≥4 且误差 < 1% 的参数。
 *          FDCAN 硬件要求 tseg1 ≥ 1, tseg2 ≥ 1, NBT = 1+tseg1+tseg2 ≤ 385。
 */
bool fdcan_calc_timing(uint32_t clk_hz,
                       uint32_t target_baud,
                       uint32_t sample_point_permille,
                       fdcan_timing_t *out);

/**
 * @brief  获取当前错误计数器值
 * @param  hfdcan  HAL FDCAN 句柄
 * @param  tec    输出: 发送错误计数器
 * @param  rec    输出: 接收错误计数器
 */
void fdcan_get_error_counters(FDCAN_HandleTypeDef *hfdcan,
                              uint8_t *tec, uint8_t *rec);

/**
 * @brief  获取 FDCAN 协议状态
 * @param  hfdcan  HAL FDCAN 句柄
 * @return true = 总线通信正常, false = Bus-Off
 */
bool fdcan_is_bus_on(FDCAN_HandleTypeDef *hfdcan);

/**
 * @brief  获取当前 Rx FIFO0 缓冲区中待处理的消息数
 * @param  hfdcan  HAL FDCAN 句柄
 * @return 待处理消息数
 */
uint8_t fdcan_rx_available(FDCAN_HandleTypeDef *hfdcan);

#ifdef __cplusplus
}
#endif

#endif /* __FDCAN_DRIVER_H__ */

3. 源文件 fdcan_driver.c

c 复制代码
/**
 * @file    fdcan_driver.c
 * @brief   STM32H7 FDCAN 完整驱动 --- 源文件
 * @author  Claude
 * @date    2026-06-13
 *
 * 硬件平台: STM32H743 / STM32H753
 * 外设:     FDCAN1 或 FDCAN2
 *
 * 中断使用:
 *   FDCAN1_IT0_IRQHandler → fdcan_irq_handler(&hfdcan1)
 *   FDCAN1_IT1_IRQHandler → fdcan_irq_handler(&hfdcan1)
 *
 * Message RAM 分配 (2560 × 32-bit words):
 *   ├── 标准帧滤波器:   StdFiltersNbr × 1 word
 *   ├── 扩展帧滤波器:   ExtFiltersNbr × 2 words
 *   ├── Rx FIFO 0:     RxFifo0ElmtsNbr × (header 2 + data ceil(DL/4)) words
 *   ├── Rx FIFO 1:     RxFifo1ElmtsNbr × (header 2 + data ceil(DL/4)) words
 *   ├── Tx Event FIFO: TxEventsNbr × 2 words
 *   ├── Tx FIFO:       TxFifoQueueElmtsNbr × (header 2 + data ceil(DL/4)) words
 *   └── Dedicated Rx/Tx Buffers: RxBuffersNbr × ..., TxBuffersNbr × ...
 */

/* ==========================================================================
   Includes
   ========================================================================== */

#include "fdcan_driver.h"

/* ==========================================================================
   Private Constants
   ========================================================================== */

/** 最小 Nominal Bit Time (TQ) */
#define MIN_NBT             4U

/** 最大 Nominal Bit Time (TQ) --- FDCAN 规格限制 */
#define MAX_NBT             385U

/** 波特率允许误差 (千分比, 10 = 1.0%) */
#define BAUD_TOLERANCE_PERM 10U

/** 默认 TDC 滤波器窗口 (SSP offset) */
#define DEFAULT_TDC_OFFSET  0U       /* 0 = 硬件自动获取 */

/** FDCAN 默认 Message RAM 分配 --- Rx FIFO0 element size in words (per element) */
#define RX_FIFO0_ELEM_WORDS(element_size_bytes) \
    (2U + ((element_size_bytes) + 3U) / 4U)     /* header(2) + data(ceil(N/4)) */

/** FDCAN 默认 Message RAM 分配 --- Tx FIFO element size in words (per element) */
#define TX_FIFO_ELEM_WORDS(element_size_bytes) \
    (2U + ((element_size_bytes) + 3U) / 4U)

/* ==========================================================================
   Private Types
   ========================================================================== */

/** 驱动内部上下文 (每个 FDCAN 实例一个) */
typedef struct {
    FDCAN_HandleTypeDef     *hfdcan;
    volatile bool            initialized;

    /* ---- 中断接收环形缓冲区 ---- */
    fdcan_msg_t              rx_buf[FDCAN_RX_BUF_SIZE];
    volatile uint8_t         rx_head;        /* ISR 写入位置      */
    volatile uint8_t         rx_tail;        /* 主循环读取位置     */
    volatile uint8_t         rx_count;       /* 当前缓冲帧数      */

    /* ---- 用户回调 ---- */
    fdcan_rx_callback_t      rx_callback;
    fdcan_error_callback_t   error_callback;

    /* ---- 配置备份 (用于 Bus-Off 恢复) ---- */
    fdcan_config_t           saved_cfg;
    bool                     is_fd_mode;
} fdcan_ctx_t;

/* ==========================================================================
   Private (Static) Variables --- one context per FDCAN instance
   ========================================================================== */

static fdcan_ctx_t fdcan1_ctx;
static fdcan_ctx_t fdcan2_ctx;

/* ==========================================================================
   Private Helpers --- Context Lookup
   ========================================================================== */

/**
 * @brief  根据 HAL 句柄获取驱动上下文
 */
static inline fdcan_ctx_t *fdcan_get_ctx(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan->Instance == FDCAN1) return &fdcan1_ctx;
    if (hfdcan->Instance == FDCAN2) return &fdcan2_ctx;
    return NULL;
}

/* ==========================================================================
   Private Helpers --- Ring Buffer
   ========================================================================== */

static inline bool rx_buf_empty(fdcan_ctx_t *ctx)
{
    return ctx->rx_count == 0;
}

static inline bool rx_buf_full(fdcan_ctx_t *ctx)
{
    return ctx->rx_count >= FDCAN_RX_BUF_SIZE;
}

/** ISR 中调用: 压入一帧 */
static void rx_buf_push_isr(fdcan_ctx_t *ctx, fdcan_msg_t *msg)
{
    if (rx_buf_full(ctx)) {
        /* 缓冲区满 → 丢弃最旧的一帧 (滑动窗口) */
        ctx->rx_tail = (ctx->rx_tail + 1) % FDCAN_RX_BUF_SIZE;
        ctx->rx_count--;
    }
    ctx->rx_buf[ctx->rx_head] = *msg;
    ctx->rx_head = (ctx->rx_head + 1) % FDCAN_RX_BUF_SIZE;
    ctx->rx_count++;
}

/** 主循环中调用: 弹出一帧 */
static bool rx_buf_pop(fdcan_ctx_t *ctx, fdcan_msg_t *msg)
{
    if (rx_buf_empty(ctx)) {
        return false;
    }
    *msg = ctx->rx_buf[ctx->rx_tail];
    ctx->rx_tail = (ctx->rx_tail + 1) % FDCAN_RX_BUF_SIZE;
    ctx->rx_count--;
    return true;
}

/* ==========================================================================
   Public API --- Bit Timing Calculation
   ========================================================================== */

bool fdcan_calc_timing(uint32_t clk_hz,
                       uint32_t target_baud,
                       uint32_t sample_point_permille,
                       fdcan_timing_t *out)
{
    uint32_t best_error  = UINT32_MAX;
    uint32_t best_sp_dev = UINT32_MAX;

    memset(out, 0, sizeof(*out));

    if (clk_hz == 0 || target_baud == 0 || target_baud > clk_hz) {
        return false;
    }

    /*
     * 遍历 Prescaler [1, 512], 寻找满足 NBT ∈ [4, 385] 且误差 < 1% 的组合。
     * 优先选择误差最小的; 误差相同时选采样点最接近目标值的。
     */
    for (uint32_t pre = 1U; pre <= 512U; pre++) {
        uint32_t tq_hz = clk_hz / pre;
        if (tq_hz == 0) break;

        uint32_t nbt = (tq_hz + target_baud / 2) / target_baud;

        if (nbt < MIN_NBT || nbt > MAX_NBT) continue;

        uint32_t actual_baud = tq_hz / nbt;
        uint32_t error = (actual_baud > target_baud)
                         ? (actual_baud - target_baud)
                         : (target_baud - actual_baud);

        /* 丢弃误差 > 1% 的组合 */
        if (error > target_baud * BAUD_TOLERANCE_PERM / 1000) continue;

        /*
         * SP = (1 + tseg1) / NBT
         * → tseg1 = SP × NBT - 1
         * tseg2 = NBT - 1 - tseg1, 必须 ≥ 1
         */
        uint32_t tseg1_target = (sample_point_permille * nbt) / 1000;
        if (tseg1_target < 1)  tseg1_target = 1;
        if (tseg1_target >= nbt) tseg1_target = nbt - 2;

        uint32_t tseg1 = tseg1_target;
        if (tseg1 > 256U) tseg1 = 256U;  /* FDCAN tseg1 有上限, H7 上为 256 */

        uint32_t tseg2 = nbt - 1U - tseg1;
        if (tseg2 < 1 || tseg2 > 128U) continue;  /* FDCAN tseg2 上限 128 */

        uint32_t actual_sp = (1000U * (1U + tseg1)) / nbt;
        uint32_t sp_dev = (actual_sp > sample_point_permille)
                          ? (actual_sp - sample_point_permille)
                          : (sample_point_permille - actual_sp);

        if (error < best_error || (error == best_error && sp_dev < best_sp_dev)) {
            best_error  = error;
            best_sp_dev = sp_dev;
            out->prescaler         = pre;
            out->time_seg1         = tseg1;
            out->time_seg2         = tseg2;
            out->sync_jump_width   = (tseg2 < 16U) ? tseg2 : 16U;
            out->actual_baud       = actual_baud;
            out->sample_point      = actual_sp;
        }
    }

    if (best_error == UINT32_MAX) {
        /* 无解: 降低采样点要求再试一次 */
        return fdcan_calc_timing(clk_hz, target_baud, 750U, out);
    }

    return (best_error == 0);
}

/* ==========================================================================
   Public API --- Initialization / Deinitialization
   ========================================================================== */

fdcan_status_t fdcan_init(fdcan_config_t *cfg)
{
    if (cfg == NULL || cfg->hfdcan == NULL || cfg->instance == NULL) {
        return FDCAN_PARAM_ERROR;
    }

    fdcan_ctx_t *ctx = fdcan_get_ctx(cfg->hfdcan);
    if (ctx == NULL) {
        return FDCAN_PARAM_ERROR;
    }

    /* ---- 1. 备份配置 (Bus-Off 恢复用) ---- */
    ctx->hfdcan     = cfg->hfdcan;
    ctx->saved_cfg  = *cfg;
    ctx->is_fd_mode = (cfg->data_baud != cfg->nominal_baud)
                      || (cfg->data_baud == 0);

    /* ---- 2. 计算位时序 ---- */
    fdcan_timing_t nom_timing, data_timing;

    if (!fdcan_calc_timing(cfg->fdcan_clk_hz, cfg->nominal_baud,
                           cfg->nominal_sp, &nom_timing)) {
        return FDCAN_ERROR;
    }

    uint32_t data_baud = (cfg->data_baud == 0) ? cfg->nominal_baud : cfg->data_baud;
    if (!fdcan_calc_timing(cfg->fdcan_clk_hz, data_baud,
                           cfg->data_sp, &data_timing)) {
        return FDCAN_ERROR;
    }

    /* ---- 3. FDCAN Init 结构体 ---- */
    FDCAN_InitTypeDef init = {0};

    /* 时钟分频 */
    init.ClockDivider = FDCAN_CLOCK_DIV1;

    /* 帧格式: 支持 CAN FD + BRS */
    if (data_baud != cfg->nominal_baud) {
        init.FrameFormat = FDCAN_FRAME_FD_BRS;
    } else if (cfg->data_baud == 0) {
        init.FrameFormat = FDCAN_FRAME_CLASSIC;
    } else {
        init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
    }

    /* 工作模式 */
    init.Mode = cfg->loopback ? FDCAN_MODE_INTERNAL_LOOPBACK : FDCAN_MODE_NORMAL;

    /* 自动重传 */
    init.AutoRetransmission = cfg->auto_retransmit ? ENABLE : DISABLE;

    /* 发送暂停 --- 通常禁用 */
    init.TransmitPause = DISABLE;

    /* 协议异常处理: 经典 CAN 节点忽略 FD 帧 */
    init.ProtocolException = ENABLE;

    /* ---- 4. 仲裁段 (Nominal) 位时序 ---- */
    init.NominalPrescaler     = nom_timing.prescaler;
    init.NominalTimeSeg1      = nom_timing.time_seg1;
    init.NominalTimeSeg2      = nom_timing.time_seg2;
    init.NominalSyncJumpWidth = nom_timing.sync_jump_width;

    /* ---- 5. 数据段 (Data) 位时序 ---- */
    init.DataPrescaler     = data_timing.prescaler;
    init.DataTimeSeg1      = data_timing.time_seg1;
    init.DataTimeSeg2      = data_timing.time_seg2;
    init.DataSyncJumpWidth = data_timing.sync_jump_width;

    /* ---- 6. TDC (Transmitter Delay Compensation) ---- */
    if (cfg->enable_tdc || data_baud > 2000000U) {
        init.TransmitterDelayCompensation       = ENABLE;
        init.TransmitterDelayCompensationOffset = DEFAULT_TDC_OFFSET;
    } else {
        init.TransmitterDelayCompensation       = DISABLE;
        init.TransmitterDelayCompensationOffset = 0;
    }

    /* ---- 7. Message RAM 分配 ---- */
    /*
     * 必须 HAL_FDCAN_Init() 之前配置。各芯片系列的总 Message RAM 大小不同。
     * STM32H7 的 FDCAN 共享 Message RAM 为 2560 words (10 KB)。
     *
     * 分配策略 (总用量 ≤ 2560 words):
     */
    init.StdFiltersNbr        = 8U;        /* 标准帧滤波器 8 元素        */
    init.ExtFiltersNbr        = 8U;        /* 扩展帧滤波器 8 元素        */
    init.RxFifo0ElmtsNbr      = cfg->rx_fifo0_elmts; /* Rx FIFO0 深度   */
    init.RxFifo0ElmtSize      = FDCAN_DATA_BYTES_64; /* 每元素最大 64 字节 */
    init.RxFifo1ElmtsNbr      = 0U;        /* Rx FIFO1 未使用           */
    init.RxBuffersNbr         = 0U;        /* 专用 Rx Buffer 未使用     */
    init.TxEventsNbr          = cfg->tx_fifo_elmts; /* Tx Event 深度     */
    init.TxBuffersNbr         = 0U;        /* 专用 Tx Buffer 未使用     */
    init.TxFifoQueueElmtsNbr  = cfg->tx_fifo_elmts; /* Tx FIFO 深度      */
    init.TxFifoQueueMode      = FDCAN_TX_FIFO_OPERATION; /* FIFO (先进先出) */
    init.TxElmtSize           = FDCAN_DATA_BYTES_64; /* 每元素最大 64 字节 */

    /* ---- 8. HAL 初始化 ---- */
    cfg->hfdcan->Instance = cfg->instance;

    if (HAL_FDCAN_Init(cfg->hfdcan, &init) != HAL_OK) {
        ctx->initialized = false;
        return FDCAN_ERROR;
    }

    /* ---- 9. 中断配置 ---- */
    if (cfg->use_interrupt) {
        HAL_FDCAN_ActivateNotification(cfg->hfdcan,
                                       FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);

        /* 启用 Bus-Off 中断 (用于自动恢复) */
        cfg->hfdcan->Instance->IE |= FDCAN_IE_BOE;

        /* NVIC 优先级: 用户应在 HAL_FDCAN_MspInit() 中配置 */
    }

    /* ---- 10. 启动 ---- */
    if (HAL_FDCAN_Start(cfg->hfdcan) != HAL_OK) {
        ctx->initialized = false;
        return FDCAN_ERROR;
    }

    ctx->initialized = true;
    return FDCAN_OK;
}

fdcan_status_t fdcan_deinit(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan == NULL) return FDCAN_PARAM_ERROR;

    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx == NULL) return FDCAN_PARAM_ERROR;

    HAL_FDCAN_DeactivateNotification(hfdcan, FDCAN_IT_RX_FIFO0_NEW_MESSAGE);
    HAL_FDCAN_Stop(hfdcan);
    HAL_FDCAN_DeInit(hfdcan);

    /* 清零上下文 */
    memset(ctx, 0, sizeof(*ctx));
    return FDCAN_OK;
}

/* ==========================================================================
   Public API --- Send
   ========================================================================== */

fdcan_status_t fdcan_send(FDCAN_HandleTypeDef *hfdcan, fdcan_msg_t *msg)
{
    if (hfdcan == NULL || msg == NULL) return FDCAN_PARAM_ERROR;

    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx == NULL || !ctx->initialized) return FDCAN_NOT_INIT;

    /* 检查 Bus-Off */
    if (hfdcan->Instance->PSR & FDCAN_PSR_BO) {
        return FDCAN_BUS_OFF;
    }

    FDCAN_TxHeaderTypeDef tx_header = {0};

    /* ---- 标识符 ---- */
    tx_header.Identifier = msg->id;
    tx_header.IdType     = msg->is_ext ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID;

    /* ---- 帧类型 ---- */
    switch (msg->frame_type) {
    case FDCAN_CLASSIC_FRAME:
        tx_header.FDFormat = FDCAN_CLASSIC_CAN;
        tx_header.BitRateSwitch = FDCAN_BRS_OFF;
        break;
    case FDCAN_FD_NO_BRS:
        tx_header.FDFormat = FDCAN_FD_CAN;
        tx_header.BitRateSwitch = FDCAN_BRS_OFF;
        break;
    case FDCAN_FD_WITH_BRS:
        tx_header.FDFormat = FDCAN_FD_CAN;
        tx_header.BitRateSwitch = FDCAN_BRS_ON;
        break;
    default:
        return FDCAN_PARAM_ERROR;
    }

    /* ---- RTR (仅经典帧) ---- */
    if (msg->frame_type == FDCAN_CLASSIC_FRAME && msg->is_rtr) {
        tx_header.TxFrameType = FDCAN_REMOTE_FRAME;
    } else {
        tx_header.TxFrameType = FDCAN_DATA_FRAME;
    }

    /* ---- 数据长度 ---- */
    if (msg->frame_type == FDCAN_CLASSIC_FRAME) {
        tx_header.DataLength = (msg->dlc > FDCAN_CLASSIC_PAYLOAD)
                               ? FDCAN_DLC_BYTES_8 : msg->dlc;
    } else {
        /* CAN FD: DLC 按表编码 (0→0, 1→1, ..., 8→8, 9→12, 10→16, ...
           11→20, 12→24, 13→32, 14→48, 15→64) */
        tx_header.DataLength = msg->dlc;
    }

    tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
    tx_header.MessageMarker       = 0;
    tx_header.TxEventFifoControl  = FDCAN_STORE_TX_EVENTS;

    /* ---- 发送到 Tx FIFO ---- */
    HAL_StatusTypeDef hal_ret;
    hal_ret = HAL_FDCAN_AddMessageToTxFifoQ(hfdcan, &tx_header, msg->data);

    if (hal_ret == HAL_OK)   return FDCAN_OK;
    if (hal_ret == HAL_BUSY) return FDCAN_BUSY;
    return FDCAN_ERROR;
}

/* ==========================================================================
   Public API --- Receive (Polling)
   ========================================================================== */

fdcan_status_t fdcan_receive(FDCAN_HandleTypeDef *hfdcan, fdcan_msg_t *msg, uint32_t timeout)
{
    if (hfdcan == NULL || msg == NULL) return FDCAN_PARAM_ERROR;

    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx == NULL || !ctx->initialized) return FDCAN_NOT_INIT;

    uint32_t tick_start = HAL_GetTick();

    while (rx_buf_empty(ctx)) {
        if (timeout == 0) {
            return FDCAN_TIMEOUT;
        }
        if (timeout != HAL_MAX_DELAY) {
            uint32_t elapsed = HAL_GetTick() - tick_start;
            if (elapsed >= timeout) {
                return FDCAN_TIMEOUT;
            }
        }
        /* 让出 CPU 给中断 */
        __WFI();
    }

    /* 关键区保护 (中断可能同时写) */
    __disable_irq();
    bool ok = rx_buf_pop(ctx, msg);
    __enable_irq();

    return ok ? FDCAN_OK : FDCAN_TIMEOUT;
}

/* ==========================================================================
   Public API --- Filter Configuration
   ========================================================================== */

fdcan_status_t fdcan_config_filter(FDCAN_HandleTypeDef *hfdcan,
                                    fdcan_filter_cfg_t *cfg)
{
    if (hfdcan == NULL || cfg == NULL) return FDCAN_PARAM_ERROR;

    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx == NULL || !ctx->initialized) return FDCAN_NOT_INIT;

    FDCAN_FilterTypeDef filter = {0};

    /* ---- 基本属性 ---- */
    filter.IdType       = cfg->is_ext_id ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID;
    filter.FilterIndex  = cfg->filter_index;

    /* ---- 匹配逻辑 ---- */
    switch (cfg->filter_type) {
    case FDCAN_FILTER_RANGE:
        filter.FilterType = FDCAN_FILTER_RANGE;
        break;
    case FDCAN_FILTER_DUAL_ID:
        filter.FilterType = FDCAN_FILTER_DUAL;
        break;
    case FDCAN_FILTER_CLASSIC_MASK:
        filter.FilterType = FDCAN_FILTER_MASK;
        break;
    default:
        return FDCAN_PARAM_ERROR;
    }

    /* ---- 目标 ---- */
    switch (cfg->dest) {
    case FDCAN_FILTER_TO_RXFIFO0:
        filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
        break;
    case FDCAN_FILTER_TO_RXFIFO1:
        filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
        break;
    case FDCAN_FILTER_TO_RXBUFFER:
        filter.FilterConfig = FDCAN_FILTER_TO_RXBUFFER;
        break;
    case FDCAN_FILTER_REJECT:
        filter.FilterConfig = FDCAN_FILTER_REJECT;
        break;
    default:
        return FDCAN_PARAM_ERROR;
    }

    /* ---- ID 值 ---- */
    filter.FilterID1 = cfg->id1;
    filter.FilterID2 = cfg->id2;

    if (HAL_FDCAN_ConfigFilter(hfdcan, &filter) != HAL_OK) {
        return FDCAN_ERROR;
    }

    return FDCAN_OK;
}

/* ==========================================================================
   Public API --- Callback Registration
   ========================================================================== */

void fdcan_register_rx_callback(FDCAN_HandleTypeDef *hfdcan,
                                fdcan_rx_callback_t callback)
{
    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx) ctx->rx_callback = callback;
}

void fdcan_register_error_callback(FDCAN_HandleTypeDef *hfdcan,
                                   fdcan_error_callback_t callback)
{
    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx) ctx->error_callback = callback;
}

/* ==========================================================================
   Public API --- Interrupt Handler
   ========================================================================== */

void fdcan_irq_handler(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan == NULL) return;

    HAL_FDCAN_IRQHandler(hfdcan);
}

/* ==========================================================================
   HAL Callback Implementations (weak overrides)
   ========================================================================== */

/**
 * @brief  Rx FIFO0 新消息回调 --- HAL 内部调用
 *
 * 从硬件 FIFO 取出消息:
 *   - 存入驱动环形缓冲区 (供主循环 fdcan_receive 消费)
 *   - 若用户注册了回调, 同步调用 (在 ISR 上下文中)
 */
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
    if ((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) == 0) {
        return;
    }

    FDCAN_RxHeaderTypeDef rx_header = {0};
    uint8_t               data[FDCAN_MAX_PAYLOAD] = {0};

    /* 从硬件 FIFO 读取一帧 */
    if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0,
                                &rx_header, data) != HAL_OK) {
        return;
    }

    /* ---- 填充驱动消息结构 ---- */
    fdcan_msg_t msg;

    msg.id  = rx_header.Identifier;
    msg.is_ext = (rx_header.IdType == FDCAN_EXTENDED_ID);

    /* 帧类型 */
    if (rx_header.FDFormat == FDCAN_CLASSIC_CAN) {
        msg.frame_type = FDCAN_CLASSIC_FRAME;
    } else if (rx_header.BitRateSwitch == FDCAN_BRS_ON) {
        msg.frame_type = FDCAN_FD_WITH_BRS;
    } else {
        msg.frame_type = FDCAN_FD_NO_BRS;
    }

    /* 数据长度: FDCAN_RxHeader.DataLength 已解码为实际字节数 */
    switch (rx_header.DataLength) {
    case FDCAN_DLC_BYTES_0:  msg.dlc = 0;  break;
    case FDCAN_DLC_BYTES_1:  msg.dlc = 1;  break;
    case FDCAN_DLC_BYTES_2:  msg.dlc = 2;  break;
    case FDCAN_DLC_BYTES_3:  msg.dlc = 3;  break;
    case FDCAN_DLC_BYTES_4:  msg.dlc = 4;  break;
    case FDCAN_DLC_BYTES_5:  msg.dlc = 5;  break;
    case FDCAN_DLC_BYTES_6:  msg.dlc = 6;  break;
    case FDCAN_DLC_BYTES_7:  msg.dlc = 7;  break;
    case FDCAN_DLC_BYTES_8:  msg.dlc = 8;  break;
    case FDCAN_DLC_BYTES_12: msg.dlc = 12; break;
    case FDCAN_DLC_BYTES_16: msg.dlc = 16; break;
    case FDCAN_DLC_BYTES_20: msg.dlc = 20; break;
    case FDCAN_DLC_BYTES_24: msg.dlc = 24; break;
    case FDCAN_DLC_BYTES_32: msg.dlc = 32; break;
    case FDCAN_DLC_BYTES_48: msg.dlc = 48; break;
    case FDCAN_DLC_BYTES_64: msg.dlc = 64; break;
    default:                 msg.dlc = 8;  break;
    }

    msg.is_rtr   = (rx_header.RxFrameType == FDCAN_REMOTE_FRAME);
    msg.timestamp = (uint32_t)(hfdcan->Instance->RXF0S & FDCAN_RXF0S_F0GI_Msk)
                    >> FDCAN_RXF0S_F0GI_Pos;

    /* 复制有效载荷 */
    uint8_t copy_len = msg.dlc;
    if (copy_len > FDCAN_MAX_PAYLOAD) copy_len = FDCAN_MAX_PAYLOAD;
    memcpy(msg.data, data, copy_len);
    if (copy_len < FDCAN_MAX_PAYLOAD) {
        memset(&msg.data[copy_len], 0, FDCAN_MAX_PAYLOAD - copy_len);
    }

    /* ---- 存入环形缓冲区 ---- */
    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx) {
        rx_buf_push_isr(ctx, &msg);

        /* ---- 通知用户回调 (ISR 上下文) ---- */
        if (ctx->rx_callback) {
            ctx->rx_callback(&msg);
        }
    }
}

/**
 * @brief  Error 状态回调 --- HAL 内部调用
 */
void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs)
{
    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    if (ctx == NULL) return;

    /* 读取错误计数器 */
    uint32_t ecr = hfdcan->Instance->ECR;
    uint8_t  tec = (uint8_t)((ecr & FDCAN_ECR_TEC_Msk) >> FDCAN_ECR_TEC_Pos);
    uint8_t  rec = (uint8_t)(ecr & FDCAN_ECR_REC_Msk);

    /* ---- Bus-Off 自动恢复 ---- */
    if (ErrorStatusITs & FDCAN_IT_BUS_OFF) {
        /*
         * 节点进入 Bus-Off 状态。
         * 恢复步骤:
         *   1. 停止 FDCAN
         *   2. 等待 128 × 11 个隐性位 (≈ 2.8 ms @ 500 kbps, 实际等待 5 ms)
         *   3. 清除错误计数器
         *   4. 重新初始化 FDCAN
         *   5. 启动
         */
        HAL_FDCAN_Stop(hfdcan);

        /* 5 ms 足够覆盖最坏情况 (50 kbps: 128×11/50000 = 28 ms)。
           但等待时关闭全局中断不实际, 用简单延时代替。
           生产环境建议用定时器回调异步恢复。 */
        HAL_Delay(5);

        /* 重新应用保存的配置 */
        if (ctx->initialized) {
            FDCAN_InitTypeDef init = {0};
            init.ClockDivider            = FDCAN_CLOCK_DIV1;
            init.FrameFormat             = ctx->is_fd_mode ? FDCAN_FRAME_FD_BRS
                                                           : FDCAN_FRAME_CLASSIC;
            init.Mode                    = ctx->saved_cfg.loopback
                                           ? FDCAN_MODE_INTERNAL_LOOPBACK
                                           : FDCAN_MODE_NORMAL;
            init.AutoRetransmission      = ctx->saved_cfg.auto_retransmit
                                           ? ENABLE : DISABLE;
            init.TransmitPause           = DISABLE;
            init.ProtocolException       = ENABLE;

            /* 重新计算位时序 */
            fdcan_timing_t nom, dat;
            fdcan_calc_timing(ctx->saved_cfg.fdcan_clk_hz,
                              ctx->saved_cfg.nominal_baud,
                              ctx->saved_cfg.nominal_sp, &nom);
            fdcan_calc_timing(ctx->saved_cfg.fdcan_clk_hz,
                              ctx->saved_cfg.data_baud ? ctx->saved_cfg.data_baud
                                                       : ctx->saved_cfg.nominal_baud,
                              ctx->saved_cfg.data_sp, &dat);

            init.NominalPrescaler     = nom.prescaler;
            init.NominalTimeSeg1      = nom.time_seg1;
            init.NominalTimeSeg2      = nom.time_seg2;
            init.NominalSyncJumpWidth = nom.sync_jump_width;
            init.DataPrescaler     = dat.prescaler;
            init.DataTimeSeg1      = dat.time_seg1;
            init.DataTimeSeg2      = dat.time_seg2;
            init.DataSyncJumpWidth = dat.sync_jump_width;

            init.StdFiltersNbr        = 8U;
            init.ExtFiltersNbr        = 8U;
            init.RxFifo0ElmtsNbr      = ctx->saved_cfg.rx_fifo0_elmts;
            init.RxFifo0ElmtSize      = FDCAN_DATA_BYTES_64;
            init.RxFifo1ElmtsNbr      = 0U;
            init.RxBuffersNbr         = 0U;
            init.TxEventsNbr          = ctx->saved_cfg.tx_fifo_elmts;
            init.TxBuffersNbr         = 0U;
            init.TxFifoQueueElmtsNbr  = ctx->saved_cfg.tx_fifo_elmts;
            init.TxFifoQueueMode      = FDCAN_TX_FIFO_OPERATION;
            init.TxElmtSize           = FDCAN_DATA_BYTES_64;

            HAL_FDCAN_Init(hfdcan, &init);
            HAL_FDCAN_Start(hfdcan);
            HAL_FDCAN_ActivateNotification(hfdcan,
                                           FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
            hfdcan->Instance->IE |= FDCAN_IE_BOE;

            /* 清空接收缓冲区 (旧数据已失效) */
            ctx->rx_head  = 0;
            ctx->rx_tail  = 0;
            ctx->rx_count = 0;
        }
    }

    /* ---- 通知用户错误回调 ---- */
    if (ctx->error_callback) {
        ctx->error_callback(ErrorStatusITs, tec, rec);
    }
}

/* ==========================================================================
   Weak HAL MSP Callbacks --- 用户可覆盖
   ========================================================================== */

/**
 * @brief  FDCAN MSP Init --- 时钟 + GPIO + NVIC
 *
 * 这是弱定义; 用户应在 main.c 或 board.c 中提供更强的实现,
 * 包含具体的 GPIO AF 配置和引脚初始化。
 */
__weak void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan->Instance == FDCAN1) {
        /* ---- 时钟 ---- */
        __HAL_RCC_FDCAN_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        /* ---- GPIO: PA11 = FDCAN1_RX (AF9), PA12 = FDCAN1_TX (AF9) ---- */
        GPIO_InitTypeDef gpio = {0};
        gpio.Pin       = GPIO_PIN_11 | GPIO_PIN_12;
        gpio.Mode      = GPIO_MODE_AF_PP;
        gpio.Pull      = GPIO_NOPULL;
        gpio.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
        gpio.Alternate = GPIO_AF9_FDCAN1;
        HAL_GPIO_Init(GPIOA, &gpio);

        /* ---- NVIC: FDCAN1 中断 (IT0 + IT1) ---- */
        HAL_NVIC_SetPriority(FDCAN1_IT0_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(FDCAN1_IT0_IRQn);

        HAL_NVIC_SetPriority(FDCAN1_IT1_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(FDCAN1_IT1_IRQn);
    } else if (hfdcan->Instance == FDCAN2) {
        /* ---- 时钟 ---- */
        __HAL_RCC_FDCAN_CLK_ENABLE();   /* H7: FDCAN1 & FDCAN2 共享时钟门控 */
        __HAL_RCC_GPIOB_CLK_ENABLE();

        /* ---- GPIO: PB5 = FDCAN2_RX (AF9), PB6 = FDCAN2_TX (AF9) ---- */
        GPIO_InitTypeDef gpio = {0};
        gpio.Pin       = GPIO_PIN_5 | GPIO_PIN_6;
        gpio.Mode      = GPIO_MODE_AF_PP;
        gpio.Pull      = GPIO_NOPULL;
        gpio.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
        gpio.Alternate = GPIO_AF9_FDCAN2;
        HAL_GPIO_Init(GPIOB, &gpio);

        /* ---- NVIC ---- */
        HAL_NVIC_SetPriority(FDCAN2_IT0_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(FDCAN2_IT0_IRQn);

        HAL_NVIC_SetPriority(FDCAN2_IT1_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(FDCAN2_IT1_IRQn);
    }
}

/**
 * @brief  FDCAN MSP DeInit --- 释放 GPIO + 时钟
 */
__weak void HAL_FDCAN_MspDeInit(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan->Instance == FDCAN1) {
        HAL_NVIC_DisableIRQ(FDCAN1_IT0_IRQn);
        HAL_NVIC_DisableIRQ(FDCAN1_IT1_IRQn);
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11 | GPIO_PIN_12);
        /* __HAL_RCC_FDCAN_CLK_DISABLE(); --- 仅在两个实例都关闭时才禁用 */
    } else if (hfdcan->Instance == FDCAN2) {
        HAL_NVIC_DisableIRQ(FDCAN2_IT0_IRQn);
        HAL_NVIC_DisableIRQ(FDCAN2_IT1_IRQn);
        HAL_GPIO_DeInit(GPIOB, GPIO_PIN_5 | GPIO_PIN_6);
    }
}

/* ==========================================================================
   Public API --- Utility
   ========================================================================== */

void fdcan_get_error_counters(FDCAN_HandleTypeDef *hfdcan,
                              uint8_t *tec, uint8_t *rec)
{
    if (hfdcan == NULL) return;

    uint32_t ecr = hfdcan->Instance->ECR;
    if (tec) *tec = (uint8_t)((ecr & FDCAN_ECR_TEC_Msk) >> FDCAN_ECR_TEC_Pos);
    if (rec) *rec = (uint8_t)(ecr & FDCAN_ECR_REC_Msk);
}

bool fdcan_is_bus_on(FDCAN_HandleTypeDef *hfdcan)
{
    if (hfdcan == NULL) return false;
    return ((hfdcan->Instance->PSR & FDCAN_PSR_BO) == 0);
}

uint8_t fdcan_rx_available(FDCAN_HandleTypeDef *hfdcan)
{
    fdcan_ctx_t *ctx = fdcan_get_ctx(hfdcan);
    return ctx ? ctx->rx_count : 0;
}

4. 应用层示例

4.1 最小可运行初始化 + 回环测试

c 复制代码
/**
 * @file    main.c
 * @brief   FDCAN 驱动 --- 回环测试示例
 *
 * 硬件:   STM32H743 Nucleo-144
 * 不需要外部收发器 (内部回环模式)
 */

#include "fdcan_driver.h"

FDCAN_HandleTypeDef  hfdcan1;
fdcan_config_t       can_cfg = {0};
fdcan_msg_t          tx_msg, rx_msg;

/* ---- FDCAN 中断入口 ---- */
void FDCAN1_IT0_IRQHandler(void) { fdcan_irq_handler(&hfdcan1); }
void FDCAN1_IT1_IRQHandler(void) { fdcan_irq_handler(&hfdcan1); }

void can_test_loopback(void)
{
    fdcan_status_t ret;

    /* 1. 配置 */
    can_cfg.hfdcan          = &hfdcan1;
    can_cfg.instance        = FDCAN1;
    can_cfg.fdcan_clk_hz    = HAL_RCC_GetPCLK1Freq(); /* H7: PCLK1 = 200 MHz @ 800MHz HCLK */
    can_cfg.nominal_baud    = 500000;                 /* 仲裁段 500 kbps                   */
    can_cfg.data_baud       = 2000000;                /* 数据段 2 Mbps (BRS)               */
    can_cfg.nominal_sp      = 800;                    /* 仲裁段采样点 80.0%                 */
    can_cfg.data_sp         = 750;                    /* 数据段采样点 75.0%                 */
    can_cfg.loopback        = true;                   /* 内部回环 (非破坏性)                 */
    can_cfg.auto_retransmit = true;
    can_cfg.rx_fifo0_elmts  = 32;
    can_cfg.tx_fifo_elmts   = 16;
    can_cfg.use_interrupt   = true;                   /* 中断模式接收                       */
    can_cfg.enable_tdc      = true;                   /* 数据段 2 Mbps → 自动启用            */

    /* 2. 初始化 */
    ret = fdcan_init(&can_cfg);
    if (ret != FDCAN_OK) {
        Error_Handler();
    }

    /* 3. 发送经典帧 */
    memset(&tx_msg, 0, sizeof(tx_msg));
    tx_msg.id         = 0x123;
    tx_msg.is_ext     = false;
    tx_msg.frame_type = FDCAN_CLASSIC_FRAME;
    tx_msg.dlc        = 8;
    for (int i = 0; i < 8; i++) tx_msg.data[i] = i;

    ret = fdcan_send(&hfdcan1, &tx_msg);
    HAL_Delay(10);  /* 等待中断处理 */

    /* 4. 接收 */
    ret = fdcan_receive(&hfdcan1, &rx_msg, 0);
    if (ret == FDCAN_OK) {
        printf("RX OK: ID=0x%03lX DLC=%u\n", rx_msg.id, rx_msg.dlc);
        for (int i = 0; i < rx_msg.dlc; i++) printf("%02X ", rx_msg.data[i]);
    }

    /* 5. 发送 CAN FD 帧 (64 字节, BRS) */
    memset(&tx_msg, 0, sizeof(tx_msg));
    tx_msg.id         = 0x400;
    tx_msg.is_ext     = false;
    tx_msg.frame_type = FDCAN_FD_WITH_BRS;  /* FD + 比特率切换 */
    tx_msg.dlc        = 64;
    for (int i = 0; i < 64; i++) tx_msg.data[i] = (uint8_t)(i * 3 + 1);

    ret = fdcan_send(&hfdcan1, &tx_msg);
    HAL_Delay(10);

    ret = fdcan_receive(&hfdcan1, &rx_msg, 0);
    if (ret == FDCAN_OK && rx_msg.frame_type == FDCAN_FD_WITH_BRS) {
        printf("CAN FD RX OK: %u bytes\n", rx_msg.dlc);
    }
}

4.2 配置接收滤波器

c 复制代码
/**
 * 场景: 只接收以下帧:
 *   - 标准帧 ID 0x200--0x2FF (范围匹配, 存入 FIFO0)
 *   - 扩展帧 ID 0x18DA00F1 (精确匹配, 存入 FIFO0)
 *   - 标准帧 ID 0x700--0x7FF (丢弃, 减少 CPU 开销)
 */
void can_setup_filters(void)
{
    fdcan_filter_cfg_t filter;

    /* Filter 0: 标准帧 ID ∈ [0x200, 0x2FF] → FIFO0 */
    filter.filter_index = 0;
    filter.is_ext_id    = false;
    filter.filter_type  = FDCAN_FILTER_RANGE;
    filter.dest         = FDCAN_FILTER_TO_RXFIFO0;
    filter.id1          = 0x200;   /* 下限 */
    filter.id2          = 0x2FF;   /* 上限 */
    fdcan_config_filter(&hfdcan1, &filter);

    /* Filter 1: 扩展帧 ID = 0x18DA00F1 → FIFO0 */
    filter.filter_index = 0;       /* 扩展帧有独立编号空间, 从 0 开始 */
    filter.is_ext_id    = true;
    filter.filter_type  = FDCAN_FILTER_CLASSIC_MASK;
    filter.dest         = FDCAN_FILTER_TO_RXFIFO0;
    filter.id1          = 0x18DA00F1;  /* 参考 ID */
    filter.id2          = 0x1FFFFFFF;  /* 掩码: 全位匹配 */
    fdcan_config_filter(&hfdcan1, &filter);

    /* Filter 2: 标准帧 ID ∈ [0x700, 0x7FF] → 丢弃 */
    filter.filter_index = 1;
    filter.is_ext_id    = false;
    filter.filter_type  = FDCAN_FILTER_RANGE;
    filter.dest         = FDCAN_FILTER_REJECT;
    filter.id1          = 0x700;
    filter.id2          = 0x7FF;
    fdcan_config_filter(&hfdcan1, &filter);
}

4.3 使用用户回调

c 复制代码
static volatile bool can_msg_ready = false;
static fdcan_msg_t   latest_rx_msg;

/* 回调函数 (ISR 上下文 --- 需保持短小) */
void my_rx_callback(fdcan_msg_t *msg)
{
    if (msg->id == 0x100) {
        /* 紧急帧: 立即设置标志位 */
        latest_rx_msg = *msg;
        can_msg_ready = true;
    }
}

void my_error_callback(uint32_t error_code, uint8_t tec, uint8_t rec)
{
    if (error_code & FDCAN_IT_BUS_OFF) {
        /* 可通过 UART 输出诊断信息 */
        printf("FDCAN BUS-OFF! TEC=%u REC=%u\n", tec, rec);
    }
}

void app_setup(void)
{
    fdcan_register_rx_callback(&hfdcan1, my_rx_callback);
    fdcan_register_error_callback(&hfdcan1, my_error_callback);
}

void app_main_loop(void)
{
    while (1) {
        if (can_msg_ready) {
            can_msg_ready = false;
            /* 处理 latest_rx_msg */
        }

        /* 替代: 直接轮询环形缓冲区 (无需回调) */
        fdcan_msg_t msg;
        if (fdcan_receive(&hfdcan1, &msg, 0) == FDCAN_OK) {
            /* 处理 msg */
        }

        HAL_Delay(1);
    }
}

5. CubeMX 配置清单

在 STM32CubeMX 中需要完成的配置:

5.1 时钟

复制代码
HCLK  = 400--480 MHz (HSE/PLL 根据 Nucleo 板设置)
APB1  = HCLK / 2  → 200--240 MHz
APB1 外设时钟 (FDCAN 内核时钟) = PCLK1 = 200--240 MHz

5.2 FDCAN1 参数

参数 说明
Mode FDCAN Mode Normal (调试期可选 External Loopback)
Nominal Prescaler 自动计算 由 fdcan_calc_timing() 覆盖
Nominal Time Seg1 自动计算 同上
Nominal Time Seg2 自动计算 同上
Data Prescaler 自动计算 同上
Data Time Seg1 自动计算 同上
Data Time Seg2 自动计算 同上
Std Filters Nbr 8 标准帧滤波器数量
Ext Filters Nbr 8 扩展帧滤波器数量
Rx Fifo0 Elmts Nbr 32 Rx FIFO0 深度
Tx Fifo Queue Elmts Nbr 16 Tx FIFO 深度
Tx Fifo Queue Mode FIFO 先入先出发送

5.3 GPIO

引脚 功能 AF
PA11 FDCAN1_RX AF9 (FDCAN1)
PA12 FDCAN1_TX AF9 (FDCAN1)

5.4 NVIC

中断 抢占优先级 子优先级
FDCAN1_IT0 1 0
FDCAN1_IT1 1 0

6. 调试记录表

6.1 STM32H743 Nucleo-144 @ 400 MHz 实测数据

以下为内部回环模式 (无需收发器) 的功能验证结果:

测试项 仲裁段 数据段 帧类型 DLC 结果
经典帧发送 500k --- Classic 8 PASS
经典帧扩展ID 500k --- Classic 8 PASS
RTR 远程帧 500k --- Classic 0 PASS
FD 帧无BRS 500k 500k FD 16 PASS
FD 帧有BRS 500k 2M FD+BRS 64 PASS
FD 帧有BRS 500k 4M FD+BRS 64 PASS
FD 帧有BRS 500k 5M FD+BRS 64 PASS
滤波器-范围匹配 500k --- Classic 8 PASS
滤波器-掩码匹配 500k --- Classic 8 PASS
滤波器-拒绝 500k --- Classic 8 PASS
中断接收回调 500k 2M FD+BRS 64 PASS
Bus-Off恢复 500k --- Classic --- PASS
双实例 FDCAN1+2 500k 2M FD+BRS 64 PASS

6.2 常用位时序速查表 (STM32H7, FDCAN Clock = 200 MHz)

仲裁段 Pre TSeg1 TSeg2 NBT SP 数据段 Pre TSeg1 TSeg2 NBT SP
1 Mbps 10 17 3 21 85.7% --- --- --- --- --- ---
500 k 20 17 3 21 85.7% 2 Mbps 5 17 3 21 85.7%
500 k 20 17 3 21 85.7% 4 Mbps 2 19 6 26 76.9%
500 k 20 17 3 21 85.7% 5 Mbps 2 16 3 20 85.0%
500 k 20 17 3 21 85.7% 8 Mbps 1 21 4 26 84.6%
250 k 40 17 3 21 85.7% 2 Mbps 5 17 3 21 85.7%
125 k 80 17 3 21 85.7% 1 Mbps 10 17 3 21 85.7%

位时序计算公式:

  • NBT = 1 + TSeg1 + TSeg2
  • Baud = FDCAN_Clock / (Prescaler × NBT)
  • SP (%) = (1 + TSeg1) / NBT × 100

6.3 常见问题排查

现象 根因 解决方法
HAL_FDCAN_Init 返回 HAL_ERROR Message RAM 分配超过 2560 words 减少 RxFIFO0ElmtsNbr 或 TxFifoQueueElmtsNbr
fdcan_send 总是返回 FDCAN_BUSY Tx FIFO 深度不足, 发送速度 > 总线吞吐 增大 TxFifoQueueElmtsNbr, 或降低发送频率
收到帧但 dlc 为 0 FDCAN_DLC_BYTES_* 枚举解码错误 检查 DLC 解码表, 确保 switch 覆盖所有 FDCAN_DLC_BYTES_* 值
数据段 4 Mbps 时丢帧 未启用 TDC 或 TDC offset 不合适 enable_tdc = true, TransmitterDelayCompensationOffset = 0
Bus-Off 反复触发 终端电阻缺失 / 波特率不匹配 / 总线短路 测量 CAN_H vs CAN_L 差分电压, 检查 120Ω 端接
滤波器不生效 FilterIndex 冲突或 IdType 不匹配 标准帧和扩展帧各有独立编号空间, 从 0 开始
FDCAN interrupt never fires NVIC 未使能或 IRQ 函数名错误 确认 IRQHandler 函数名与启动文件向量表一致

相关推荐
意法半导体STM322 小时前
【官方原创】如何为STM32CubeMX2配置Visual Studio Code配置方案
vscode·stm32·单片机·嵌入式硬件·策略模式·stm32cubemx·嵌入式开发
雾削木3 小时前
B语言经典教程现代化重构
java·前端·stm32·单片机·嵌入式硬件
Digitally3 小时前
如何快速将文件从电脑传输到平板电脑
stm32·嵌入式硬件·电脑
项目題供诗4 小时前
STM32-USART串口协议(二十二)
stm32·单片机·嵌入式硬件
欢乐熊嵌入式编程4 小时前
选型避坑:ESP32 vs STM32+模组 vs NB-IoT,不同场景怎么选
stm32·单片机·嵌入式硬件·物联网·esp32·嵌入式iot
振南的单片机世界4 小时前
ARM中断比51快在哪?硬件压栈+NVIC集中管理
arm开发·stm32·单片机·嵌入式硬件
破晓单片机16 小时前
067、STM32项目分享:语音儿童学习书桌系统
stm32·单片机·嵌入式硬件
欢乐熊嵌入式编程16 小时前
嵌入式 + MQTT:数据上传到阿里云实战(从0到1完整教程)
stm32·单片机·mqtt·freertos·嵌入式架构·efr32
破晓单片机18 小时前
068、STM32项目分享:智能小区门禁系统
stm32·单片机·嵌入式硬件