STM32H7 FDCAN 驱动笔记
芯片:STM32H743/STM32H753(FDCAN 外设)
HAL 版本:STM32H7xx_HAL_Driver
功能:经典 CAN 2.0B + CAN FD,双波特率,中断接收,硬件过滤器,自动位时序计算,Bus-Off 恢复,TDC
目录
- 架构设计
- [头文件
fdcan_driver.h](#头文件 fdcan_driver.h) - [源文件
fdcan_driver.c](#源文件 fdcan_driver.c) - 应用层示例
- [CubeMX 配置清单](#CubeMX 配置清单)
- 调试记录表
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 帧 | 中断回调只做 push,fdcan_receive 做 pop,无锁 |
| 位时序自动计算 | 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 函数名与启动文件向量表一致 |