CAN通信(中)

第二部分:STM32 实现 CAN 通信

第一部分讲了 CAN 协议"是什么",第二部分讲 STM32 “怎么做”。
每个概念都会回溯到第一部分的理论,帮你建立对应关系。


9. STM32 的 CAN 外设长什么样?

9.1 先看全貌

STM32F1 内置的 CAN 控制器叫 bxCAN(Basic Extended CAN)。它把第一部分讲的协议逻辑全部用硬件实现了——你不需要手动拼帧、算 CRC、处理仲裁,只需要告诉硬件"发什么"和"收什么"。

整体框图:

┌─────────────────────────────────────────────────────────────────┐
│                         bxCAN 控制器                             │
│                                                                  │
│   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐      │
│   │   发送邮箱0   │    │   发送邮箱1   │    │   发送邮箱2   │      │
│   └──────┬───────┘    └──────┬───────┘    └──────┬───────┘      │
│          └──────────────┬────┴─────────────────┘                │
│                         ↓                                        │
│               ┌─────────────────┐                                │
│               │   发送调度器     │  ← 按 ID 优先级决定谁先发      │
│               └────────┬────────┘                                │
│                        ↓                                         │
│  ─────────────── CAN TX ──────────────────────────               │
│                                                                  │
│  ─────────────── CAN RX ──────────────────────────               │
│                        ↓                                         │
│            ┌───────────────────────┐                             │
│            │  过滤器组 (14个Bank)   │  ← 硬件筛选,决定收不收      │
│            └─────────┬─────────────┘                             │
│                ┌─────┴─────┐                                     │
│                ↓           ↓                                     │
│         ┌──────────┐ ┌──────────┐                                │
│         │  FIFO 0  │ │  FIFO 1  │  ← 各3层深度                  │
│         │ (3条消息) │ │ (3条消息) │                                │
│         └────┬─────┘ └────┬─────┘                                │
│              ↓            ↓                                      │
│           中断通知 CPU 来取                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

9.2 三大组件详解

发送邮箱(3个)

"邮箱"就是发送缓冲区。你把消息(ID + 数据)写进邮箱,硬件自动完成:

你写入邮箱的内容:              硬件自动完成的事情:
┌──────────────┐              ┌──────────────────────┐
│ ID = 0x100   │              │ 加上 SOF             │
│ DLC = 8      │  ──硬件──→   │ 加上 CRC             │
│ DATA = ...   │              │ 处理仲裁             │
│ RTR = 数据帧  │              │ 等待 ACK             │
└──────────────┘              │ 位填充               │
                              │ 错误重发             │
                              └──────────────────────┘

三个邮箱可以同时排队。当多个邮箱都有待发消息时,由发送调度器决定先发哪个。调度器有两种模式:

模式 行为 配置
ID 优先级(默认) ID 值最小的邮箱先发,和总线仲裁逻辑一致(第 3 章) TransmitFifoPriority = DISABLE
FIFO 顺序 谁先写入邮箱谁先发,先来后到 TransmitFifoPriority = ENABLE

什么时候用 FIFO 模式?当你的应用需要保证消息按发送顺序到达时(比如多帧协议),FIFO 模式更合适。如果用 ID 优先级模式,后写入的低 ID 消息可能插队到前面。

接收 FIFO(2个,各3层深度)

每个 FIFO 能缓存 3 条消息,新消息自动入队:

过滤器通过的消息 → ┌────┬────┬────┐
                   │ 消息1│ 消息2│ 消息3│  ← 3层深度
                   └────┴────┴────┘
                          ↓
                   CPU 从这里读取

如果 FIFO 满了还没取走:

  • 溢出:新消息丢弃(默认),或覆盖最旧的(可配置)

这就是为什么我们在代码中加了环形缓冲区——中断里尽快把 FIFO 的消息搬走,避免溢出。

过滤器组(14个 Bank)

这就是第 8 章讲的"硬件过滤器"在 STM32 上的实现。

每个 Bank 是一个 64 位的寄存器对(两个 32 位寄存器),可以灵活配置为不同模式:

一个 Bank = 64 位

32位模式(常用):
┌────────────────────────────────────┐
│  寄存器1 (32位): ID / 过滤ID       │
│  寄存器2 (32位): MASK / 过滤ID     │
└────────────────────────────────────┘
  → 掩码模式: 1组 (ID + MASK)
  → 列表模式: 2个精确ID

16位模式:
┌────────────────────────────────────┐
│  寄存器1: [ID1(16)] [ID2(16)]      │
│  寄存器2: [MASK1(16)] [MASK2(16)]  │
└────────────────────────────────────┘
  → 掩码模式: 2组 (ID + MASK)
  → 列表模式: 4个精确ID

每个 Bank 可以独立指定把匹配的消息放进 FIFO 0 还是 FIFO 1

9.3 与第一部分的对应关系

第一部分(协议概念) STM32 bxCAN(硬件实现)
帧格式(SOF、ID、CRC、ACK、EOF) 硬件自动处理,你只管填 ID 和 DATA
总线仲裁(ID 优先级) 硬件自动完成;发送调度器也按 ID 排序
位填充 硬件自动插入和去除
位时序 / 波特率 通过 BS1、BS2、Prescaler 寄存器配置
ACK 机制 硬件自动处理,发送失败会自动重发
5 种错误检测 硬件检测,结果记录在 ESR 寄存器
错误状态机(主动/被动/离线) ESR 寄存器的 EWGF、EPVF、BOFF 标志位
硬件过滤器 14 个 Filter Bank,支持掩码/列表模式

一句话总结:第一部分讲的所有机制,bxCAN 都用硬件干了。软件只负责三件事:配置参数、填发送数据、取接收数据。


10. 怎么让 CAN 跑起来?

10.1 CubeMX 配置速览

在写代码之前,先用 CubeMX 生成基础框架。CAN 配置分三块:

位时序参数(Bit Timings Parameters)

┌─────────────────────────────────────────────────────┐
│  Prescaler (for Time Quantum)     9                │
│  Time Quantum                     250.0 ns         │  ← 自动计算
│  Time Quanta in Bit Segment 1     5 Times          │
│  Time Quanta in Bit Segment 2     2 Times          │
│  Time for one Bit                 2000 ns          │  ← 自动计算
│  Baud Rate                        500000 bit/s     │  ← 自动计算
│  ReSynchronization Jump Width     1 Time           │
└─────────────────────────────────────────────────────┘

这些参数直接对应第 4 章讲的位时序概念:

CubeMX 参数 对应概念 说明
Prescaler 分频系数 APB1 时钟 ÷ Prescaler = CAN 时钟
BS1 相位缓冲段1 包含 PROP_SEG,采样点前的时间
BS2 相位缓冲段2 采样点后的时间
SJW 同步跳转宽度 重同步时允许调整的最大 Tq 数

波特率验算(假设 APB1 = 36MHz):

Tq = Prescaler / APB1 = 9 / 36MHz = 250ns

1 bit = (1 + BS1 + BS2) × Tq = (1 + 5 + 2) × 250ns = 2000ns

波特率 = 1 / 2000ns = 500 kbps ✓

采样点 = (1 + BS1) / (1 + BS1 + BS2) = 6/8 = 75%

基础参数(Basic Parameters)

┌─────────────────────────────────────────────────────┐
│  Time Triggered Communication Mode    Disable      │
│  Automatic Bus-Off Management         Enable       │  ← 重要
│  Automatic Retransmission             Enable       │  ← 重要
│  Automatic Wake-Up Mode               Disable      │
│  Receive FIFO Locked Mode             Disable      │
│  Transmit FIFO Priority               Disable      │  ← 第9章讲过
└─────────────────────────────────────────────────────┘
参数 建议值 说明
Automatic Bus-Off Management Enable 离线后自动恢复(第 7 章的离线恢复机制)
Automatic Retransmission Enable 发送失败自动重发,保证可靠性
Receive FIFO Locked Mode Disable FIFO 满时丢新消息(Disable)还是丢旧消息(Enable)
Transmit FIFO Priority 按需 发送调度模式(第 9 章讲过)

工作模式(Advanced Parameters)

┌─────────────────────────────────────────────────────┐
│  Test Mode                            Loopback     │
└─────────────────────────────────────────────────────┘
模式 用途
Normal 正常通信,连接真实总线
Loopback 自发自收,不需要外部连线,调试首选
Silent 只听不发,用于监控/嗅探总线
Silent_Loopback 内部回环 + 不影响总线,纯软件测试

调试技巧:新板子先用 Loopback 模式验证软件逻辑,排除硬件问题。

中断配置(NVIC Settings)

┌──────────────────────────────────────┬─────────┐
│  USB high priority or CAN TX         │   ✓     │  ← 发送完成中断
│  USB low priority or CAN RX0         │   ✓     │  ← FIFO0 接收中断
│  CAN RX1 interrupt                   │   ☐     │  ← FIFO1(没用到可不开)
│  CAN SCE interrupt                   │   ✓     │  ← 错误/状态变化中断
└──────────────────────────────────────┴─────────┘

注意:STM32F1 的 CAN 和 USB 共享中断向量,所以名字带 “USB”。

10.2 CubeMX 生成了什么?

CubeMX 生成的 can.c 主要做两件事:

// 1. 填充 hcan.Init 结构体
hcan.Init.Prescaler = 9;
hcan.Init.Mode = CAN_MODE_LOOPBACK;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_5TQ;
hcan.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan.Init.AutoBusOff = ENABLE;
hcan.Init.AutoRetransmission = ENABLE;
// ...

// 2. 配置 GPIO(在 HAL_CAN_MspInit 中)
// PA11 = CAN_RX, PA12 = CAN_TX(或重映射到 PB8/PB9)

但它没有

  • 配置过滤器(必须手动配,否则收不到任何消息!)
  • 调用 HAL_CAN_Start()
  • 使能中断通知

这些需要我们在应用代码中完成。

10.3 完整的启动流程

结合驱动代码,CAN 启动的正确顺序是:

┌─────────────────────────────────────────────────────────────┐
│                      CAN 启动流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ① HAL_CAN_Init()          CubeMX 自动调用,配置基础参数     │
│         ↓                                                   │
│  ② 配置过滤器               必须!否则所有消息都被丢弃        │
│         ↓                                                   │
│  ③ HAL_CAN_Start()         启动 CAN 外设                    │
│         ↓                                                   │
│  ④ HAL_CAN_ActivateNotification()   使能中断               │
│         ↓                                                   │
│      开始收发                                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

对应代码(can_app.c):

uint8_t can_app_init(void)
{
    // 初始化环形缓冲区
    can_drv_init(rx_buffer, CAN_RX_BUF_SIZE, tx_buffer, CAN_TX_BUF_SIZE);

    // ① 设置波特率(内部会修改 hcan.Init 并调用 HAL_CAN_Init)
    if (!can_set_baudrate(500000))
        return 1;

    // 设置工作模式
    can_set_mode(CAN_DRV_LOOPBACK);

    // ② 配置过滤器
    if (!can_filter_init())
        return 2;

    // ③④ 启动 CAN + 使能中断
    if (!can_drv_start())
        return 3;

    return 0;
}

10.4 波特率的动态配置

CubeMX 只能设置一个固定波特率。如果想在运行时切换(比如支持多种波特率的设备),就需要代码动态计算。

波特率公式回顾

波特率 = APB1时钟 / (Prescaler × (1 + BS1 + BS2))

反过来,给定目标波特率,需要找到合适的 Prescaler、BS1、BS2 组合。

自动搜索算法

驱动中的 can_set_baudrate() 实现了自动搜索:

bool can_set_baudrate(uint32_t baudrate)
{
    uint32_t apb1_clk = HAL_RCC_GetPCLK1Freq();  // 获取实际时钟

    // 遍历所有可能的 Prescaler
    for (prescaler = 1; prescaler <= 1024; prescaler++)
    {
        uint32_t can_clk = apb1_clk / prescaler;
        uint32_t tq_count = can_clk / baudrate;  // 每个 bit 需要多少 Tq

        // 检查是否整除(不能有小数)
        if (can_clk != baudrate * tq_count)
            continue;

        // Tq 数量必须在合理范围(3~22)
        if (tq_count < 3 || tq_count > 22)
            continue;

        // 分配 BS1 和 BS2,优先保证采样点在 75%~80%
        for (bs2 = 5; bs2 >= 1; bs2--)
        {
            bs1 = tq_count - 1 - bs2;  // 1 是 SYNC_SEG

            if (bs1 < 1 || bs1 > 16)
                continue;

            // 确保 BS1 >= 3*BS2 - 1(采样点约 75%+)
            if (bs1 < bs2 * 3 - 1)
                continue;

            goto found;  // 找到合适组合
        }
    }
    return false;  // 无法实现该波特率

found:
    // 应用配置...
}

算法思路

  1. 遍历 Prescaler(1~1024)
  2. 计算每个 bit 需要多少 Tq,必须整除
  3. 分配 BS1/BS2,优先保证采样点 ≥ 75%
  4. 找到第一个合法组合就返回

常用波特率参数参考

假设 APB1 = 36MHz:

波特率 Prescaler BS1 BS2 Tq数 采样点
1 Mbps 4 5 3 9 66.7%
500 kbps 9 5 2 8 75%
250 kbps 9 13 2 16 87.5%
125 kbps 18 13 2 16 87.5%

10.5 工作模式切换

驱动支持运行时切换模式:

typedef enum {
    CAN_DRV_NORMAL,          // 正常模式
    CAN_DRV_LOOPBACK,        // 回环模式
    CAN_DRV_SILENT,          // 静默模式
    CAN_DRV_SILENT_LOOPBACK  // 静默回环
} can_mode_t;

bool can_set_mode(can_mode_t mode)
{
    bool was_running = can_running;

    if (was_running)
        can_hw_stop();       // 先停止

    hcan.Init.Mode = mode_map[mode];  // 修改模式

    if (was_running)
        return can_hw_start();  // 重新启动

    return true;
}

注意:模式切换需要先停止 CAN,修改配置后重新启动。

10.6 初始化常见问题

现象 可能原因
发送成功但收不到 忘记配置过滤器(最常见!
HAL_CAN_Start 返回错误 波特率参数不合法,或 GPIO 未配置
Loopback 正常,Normal 不通 检查终端电阻、CAN_H/CAN_L 接线
波特率不匹配 两端的 Prescaler/BS1/BS2 配置不一致

11. STM32 的过滤器怎么配?

第 8 章讲了过滤器的概念(掩码模式 vs 列表模式),这一章看 STM32 具体怎么实现。

11.1 过滤器的硬件架构

STM32F1 的 bxCAN 有 14 个过滤器组(Filter Bank 0~13),每个 Bank 可以独立配置。

                    收到的 CAN 帧
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│                    过滤器阵列                            │
│                                                         │
│   ┌─────────┐ ┌─────────┐ ┌─────────┐     ┌─────────┐  │
│   │ Bank 0  │ │ Bank 1  │ │ Bank 2  │ ... │ Bank 13 │  │
│   │         │ │         │ │         │     │         │  │
│   │ →FIFO0  │ │ →FIFO1  │ │ →FIFO0  │     │ →FIFO1  │  │
│   └────┬────┘ └────┬────┘ └────┬────┘     └────┬────┘  │
│        │           │           │               │       │
│        ▼           ▼           ▼               ▼       │
│       匹配?       匹配?       匹配?           匹配?     │
│                                                         │
└─────────────────────────────────────────────────────────┘
                         │
            任意一个 Bank 匹配即通过
                         │
              ┌──────────┴──────────┐
              ▼                     ▼
           FIFO 0                FIFO 1

关键点

  • 每个 Bank 可以单独指定把匹配的消息放进 FIFO0 还是 FIFO1
  • 多个 Bank 可以同时激活,任意一个匹配就通过
  • 消息会记录是被哪个 Bank 匹配的(FilterMatchIndex

11.2 一个 Bank 能配成什么样?

每个 Bank 有两个 32 位寄存器,可以灵活组合:

一个 Bank = 64 位 = 2 × 32 位寄存器

┌────────────────────────────────────────────────────────────────┐
│                        配置组合                                  │
├────────────────┬───────────────┬───────────────────────────────┤
│     位宽       │     模式      │           能配多少             │
├────────────────┼───────────────┼───────────────────────────────┤
│   32位         │   掩码模式    │  1 组 (1个ID + 1个MASK)        │
│   32位         │   列表模式    │  2 个精确ID                    │
│   16位         │   掩码模式    │  2 组 (2个ID + 2个MASK)        │
│   16位         │   列表模式    │  4 个精确ID                    │
└────────────────┴───────────────┴───────────────────────────────┘

32 位模式适合扩展帧(29位ID)或需要精确匹配标准帧。
16 位模式只能匹配标准帧的 ID(11位),但数量多。

本驱动采用 32 位掩码模式,最通用。

11.3 寄存器布局的坑

这是配置过滤器最容易踩的坑:ID 不能直接写进寄存器,要按特定格式摆放!

32 位过滤器寄存器的布局

  31  30  29  28  27  26  25  24  23  22  21  20  19  18  17  16  15  14  13  12  11  10  9   8   7   6   5   4   3   2   1   0
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│          STID[10:0] / EXID[28:18]         │                    EXID[17:0]                        │IDE│RTR│ 0 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
 │                                                                                                   │   │   │
 │                                                                                                   │   │   └── 保留位(必须为0)
 │                                                                                                   │   └────── RTR 位
 │                                                                                                   └────────── IDE 位 (0=标准帧, 1=扩展帧)
 │
 └── 标准帧: 11位ID放在[31:21],其余补0
     扩展帧: 29位ID放在[31:3]

标准帧 ID 转换(以 ID = 0x123 为例):

用户给的 ID:  0x123 = 0b001 0010 0011  (11位)

需要放到 [31:21],所以要左移 21 位:
  0x123 << 21 = 0x24600000

再左移 3 位腾出 IDE/RTR/保留位:
  实际上是先左移 18 位变成 29 位格式,再统一左移 3 位

最终寄存器值: 0x24600000 (IDE=0 表示标准帧)

扩展帧 ID 转换(以 ID = 0x12345678 为例):

用户给的 ID:  0x12345678 (29位有效)

直接左移 3 位:
  0x12345678 << 3 = 0x91A2B3C0

然后设置 IDE 位 = 1:
  0x91A2B3C0 | 0x04 = 0x91A2B3C4

驱动中的转换函数

这个坑在驱动里用 format_filter_id() 封装掉了:

static uint32_t format_filter_id(uint32_t id, can_id_type_t id_type, bool is_mask)
{
    uint32_t reg;

    // 标准帧: 先左移 18 位,对齐到 29 位扩展帧格式
    if (id_type == CAN_ID_TYPE_STD)
    {
        id <<= 18;
    }

    // 统一左移 3 位,腾出 IDE/RTR/保留位
    reg = id << 3;

    // 设置 IDE 位
    // - 扩展帧: IDE=1
    // - 掩码寄存器: IDE=1 (表示"需要检查 IDE 位")
    if (is_mask || id_type == CAN_ID_TYPE_EXT)
    {
        reg |= (1 << 2);  // bit2 = IDE
    }

    return reg;
}

为什么掩码也要设置 IDE=1?

掩码的每一位表示"是否关心":

  • 掩码位 = 1:必须匹配
  • 掩码位 = 0:忽略

如果掩码的 IDE 位 = 0,就会同时匹配标准帧和扩展帧。通常我们希望明确区分,所以掩码的 IDE 位要设成 1。

11.4 三种过滤器封装

驱动提供了三个层级的 API:

1. 接收所有消息

bool can_filter_init(void)
{
    // Bank 0: ID=0, MASK=0 (全部不关心) → 接收所有
    can_filter_set_raw(0, 0, 0, CAN_FILTERMODE_IDMASK, true);

    // 关闭其他 Bank
    for (uint8_t i = 1; i < 14; i++)
        can_filter_enable(i, false);

    return true;
}

原理:掩码全 0 表示"所有位都不关心",任何 ID 都能匹配。

收到 ID:     任意
过滤器 ID:   0x00000000
掩码:        0x00000000  (全不关心)

匹配结果: (收到的ID & 掩码) == (过滤器ID & 掩码)
          (任意 & 0) == (0 & 0)
          0 == 0 ✓ 永远通过

2. 掩码模式(范围匹配)

bool can_filter_set_mask(uint8_t bank, uint32_t id, uint32_t mask, can_id_type_t id_type)
{
    uint32_t id_reg   = format_filter_id(id,   id_type, false);
    uint32_t mask_reg = format_filter_id(mask, id_type, true);

    return can_filter_set_raw(bank, id_reg, mask_reg, CAN_FILTERMODE_IDMASK, true);
}

示例:只接收 ID = 0x200 ~ 0x2FF

// 0x200 = 0b010 0000 0000
// 0x2FF = 0b010 1111 1111
//         ↑↑↑
//        前3位必须是 010,后8位任意

can_filter_set_mask(0, 0x200, 0x700, CAN_ID_TYPE_STD);
//                     ID     MASK
// MASK=0x700 = 0b111 0000 0000 (前3位必须匹配)

3. 精确匹配(单个 ID)

bool can_filter_set_id(uint8_t bank, uint32_t id, can_id_type_t id_type)
{
    // 掩码设为全 1,所有位都必须匹配
    uint32_t mask = (id_type == CAN_ID_TYPE_STD) ? 0x7FF : 0x1FFFFFFF;

    return can_filter_set_mask(bank, id, mask, id_type);
}

示例:只接收 ID = 0x123

can_filter_set_id(0, 0x123, CAN_ID_TYPE_STD);

// 等价于:
can_filter_set_mask(0, 0x123, 0x7FF, CAN_ID_TYPE_STD);
// MASK=0x7FF 表示 11 位全部必须匹配

11.5 多个过滤器组合使用

14 个 Bank 可以同时激活,实现复杂的过滤逻辑:

// 场景:接收三类消息
// - 0x100 (控制命令,精确匹配)
// - 0x200~0x2FF (传感器数据,范围匹配)
// - 0x7FF (广播消息,精确匹配)

can_filter_set_id(0, 0x100, CAN_ID_TYPE_STD);           // Bank 0
can_filter_set_mask(1, 0x200, 0x700, CAN_ID_TYPE_STD);  // Bank 1
can_filter_set_id(2, 0x7FF, CAN_ID_TYPE_STD);           // Bank 2

// 其他 Bank 保持关闭

11.6 FilterMatchIndex 的用处

接收消息时,硬件会告诉你是哪个 Bank 匹配的:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
    // ...
    msg.filter_index = rx_header.FilterMatchIndex;  // 记录匹配的 Bank 号
    // ...
}

应用层可以根据 filter_index 快速分类处理,不用再判断 ID:

void can_task(void)
{
    can_msg_t msg;
    while (can_read(&msg))
    {
        switch (msg.filter_index)
        {
            case 0:  handle_control_cmd(&msg);   break;  // Bank 0 = 控制命令
            case 1:  handle_sensor_data(&msg);   break;  // Bank 1 = 传感器
            case 2:  handle_broadcast(&msg);     break;  // Bank 2 = 广播
        }
    }
}

11.7 过滤器配置速查

需求 调用
接收所有消息 can_filter_init()
只收一个 ID can_filter_set_id(bank, id, type)
收一段 ID 范围 can_filter_set_mask(bank, id, mask, type)
关闭某个 Bank can_filter_enable(bank, false)

常见错误

  • 忘记调用 can_filter_init() → 收不到任何消息
  • 掩码写反(0 和 1 的含义搞混)→ 收到的消息不对
  • Bank 号超过 13 → 配置失败

12. 消息怎么收?怎么发?

过滤器配好了,CAN 也启动了,现在看消息怎么流动。

12.1 数据流全景图

                              发送流程
┌─────────────────────────────────────────────────────────────────────┐
│                                                                      │
│   应用层           驱动层                    硬件层                   │
│                                                                      │
│  can_write() ──→ 邮箱有空? ──Y──→ 写入邮箱 ──→ 自动发送到总线        │
│                     │                              │                 │
│                     N                              │                 │
│                     ↓                              ↓                 │
│               存入TX缓冲区 ←────────────── TX完成中断               │
│               (环形队列)       (取出继续发)                          │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

                              接收流程
┌─────────────────────────────────────────────────────────────────────┐
│                                                                      │
│   硬件层                    驱动层                  应用层            │
│                                                                      │
│  总线收到 ──→ 过滤器 ──→ FIFO ──→ RX中断 ──→ RX缓冲区 ──→ can_read()│
│              匹配?         (3层)    触发     (环形队列)    轮询取    │
│               │                                              │       │
│               N                                              ↓       │
│               ↓                                         can_task()   │
│              丢弃                                         处理消息   │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

12.2 发送流程详解

为什么需要 TX 缓冲区?

硬件只有 3 个发送邮箱。如果应用层发送速度超过总线速度:

应用层:  can_write() can_write() can_write() can_write() can_write() ...
              ↓           ↓           ↓           ↓           ↓
硬件邮箱:  [邮箱0满]   [邮箱1满]   [邮箱2满]    ???         ???

没有缓冲区 → 第4条消息丢失!

解决方案:TX 环形缓冲区作为"蓄水池":

应用层:  can_write() × 5
              ↓
          ┌───────────────────────┐
          │   邮箱0  邮箱1  邮箱2  │  ← 前3条直接进邮箱
          └───────────────────────┘
          ┌───────────────────────┐
          │  [msg4] [msg5] [   ]  │  ← 后2条存入缓冲区
          └───────────────────────┘
                     ↑
               TX完成中断自动取出

can_write() 的实现逻辑

bool can_write(const can_msg_t *msg)
{
    bool ret = true;

    // ① 关闭TX中断,防止竞态
    __HAL_CAN_DISABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY);

    // ② 尝试直接写入硬件邮箱
    if (!can_send_to_mailbox(msg))
    {
        // ③ 邮箱满,存入软件缓冲区
        if (!can_ringbuf_push(&tx_ring, msg))
        {
            ret = false;  // 缓冲区也满了,发送失败
        }
    }

    // ④ 恢复TX中断
    __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY);

    return ret;
}

流程图

           can_write(msg)
                 │
                 ▼
        ┌───────────────┐
        │  关闭TX中断    │  ← 防止与中断并发修改缓冲区
        └───────┬───────┘
                │
                ▼
          邮箱有空闲?──Y──→ 写入邮箱 ──→ 返回成功
                │
                N
                ↓
          缓冲区有空?──Y──→ 存入缓冲区 ──→ 返回成功
                │
                N
                ↓
            返回失败
                │
                ▼
        ┌───────────────┐
        │  恢复TX中断    │
        └───────────────┘

TX 完成中断:自动续发

当邮箱发送完成,硬件触发中断,驱动自动从缓冲区取下一条:

static void can_tx_complete_handler(void)
{
    can_msg_t msg;

    // 缓冲区有待发消息?取出来发
    if (can_ringbuf_pop(&tx_ring, &msg))
    {
        can_send_to_mailbox(&msg);
    }
}

// 三个邮箱的回调都调用同一个处理函数
void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan_ptr)
{
    can_tx_complete_handler();
}

发送 5 条消息的时序

时间 ──────────────────────────────────────────────────────────────→

can_write(1) → 邮箱0
can_write(2) → 邮箱1
can_write(3) → 邮箱2
can_write(4) → TX缓冲区[0]
can_write(5) → TX缓冲区[1]
                                    邮箱0发完 → 中断 → 取缓冲区[0]发
                                    邮箱1发完 → 中断 → 取缓冲区[1]发
                                    ...

12.3 接收流程详解

中断接收 + 缓冲区

硬件 FIFO 只有 3 层深度,必须尽快取走,否则溢出。所以用中断 + 环形缓冲区

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
    CAN_RxHeaderTypeDef rx_header;
    can_msg_t msg;

    // 循环取完 FIFO 中所有消息
    while (HAL_CAN_GetRxFifoFillLevel(hcan_ptr, CAN_RX_FIFO0) > 0)
    {
        if (HAL_CAN_GetRxMessage(hcan_ptr, CAN_RX_FIFO0, &rx_header, msg.buf) == HAL_OK)
        {
            // 解析标准帧/扩展帧
            if (rx_header.IDE == CAN_ID_STD)
            {
                msg.id = rx_header.StdId;
                msg.flags.extended = 0;
            }
            else
            {
                msg.id = rx_header.ExtId;
                msg.flags.extended = 1;
            }

            msg.flags.remote = (rx_header.RTR == CAN_RTR_REMOTE) ? 1 : 0;
            msg.len = rx_header.DLC;
            msg.filter_index = rx_header.FilterMatchIndex;  // 哪个过滤器匹配的
            msg.timestamp = rx_header.Timestamp;

            // 存入软件缓冲区
            can_ringbuf_push(&rx_ring, &msg);
        }
    }
}

为什么用 while 循环?

中断触发时,FIFO 里可能不止一条消息(比如总线上连续来了几帧)。用 while 一次性取完,减少中断次数。

应用层轮询消费

应用层通过 can_read() 从缓冲区取消息:

bool can_read(can_msg_t *msg)
{
    // ① 关闭RX中断
    __HAL_CAN_DISABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);

    // ② 从缓冲区取
    bool ret = can_ringbuf_pop(&rx_ring, msg);

    // ③ 恢复RX中断
    __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);

    return ret;
}

典型用法(can_task 周期调用):

void can_task(void)
{
    can_msg_t msg;

    // 取完所有待处理消息
    while (can_read(&msg))
    {
        // 处理消息...
        printf("ID=0x%X LEN=%d\r\n", msg.id, msg.len);
    }
}

12.4 为什么要关中断?(临界区保护)

环形缓冲区的 headtail 指针会被两个上下文同时访问:

操作 上下文 访问的指针
can_read() 主循环(前台) 修改 tail
RX 中断回调 中断(后台) 修改 head

如果不加保护,可能出现竞态条件

场景:缓冲区有1条消息,head=1, tail=0

主循环:                          RX中断:
────────────────────────────────────────────────────
读取 tail=0
读取 head=1
判断: head != tail, 有消息
                                 来了新消息
                                 push: buffer[1] = new_msg
                                 head = 2
读取 buffer[0]  ← 正确
tail = 1        ← 此时 head=2, tail=1, 还剩1条

但如果时序稍微变一下...

主循环:                          RX中断:
────────────────────────────────────────────────────
读取 tail=0
                                 来了新消息
                                 push: buffer[1] = new_msg
                                 head = 2
读取 head=2
判断: head != tail (2 != 0), 有消息
计算数量: (2 - 0) = 2条  ← 错了!实际只有1条有效

解决方案:操作缓冲区时,暂时关闭对应的中断:

// 读取时关闭RX中断
__HAL_CAN_DISABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
can_ringbuf_pop(&rx_ring, msg);
__HAL_CAN_ENABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);

// 写入时关闭TX中断
__HAL_CAN_DISABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY);
can_ringbuf_push(&tx_ring, msg);
__HAL_CAN_ENABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY);

为什么不用全局关中断? 只关特定中断,影响范围最小。如果用 __disable_irq() 会阻塞所有中断,影响系统实时性。

12.5 消息结构体

驱动定义了统一的消息结构:

typedef struct {
    uint32_t id;            // 消息ID(标准帧11位,扩展帧29位)
    uint8_t  len;           // 数据长度 (0~8)
    uint8_t  buf[8];        // 数据内容
    struct {
        uint8_t extended : 1;  // 1=扩展帧, 0=标准帧
        uint8_t remote   : 1;  // 1=远程帧, 0=数据帧
    } flags;
    uint8_t  filter_index;  // 匹配的过滤器 Bank 号
    uint16_t timestamp;     // 时间戳(如果使能)
} can_msg_t;

发送示例

can_msg_t tx_msg = {
    .id = 0x123,
    .len = 4,
    .buf = {0x11, 0x22, 0x33, 0x44},
    .flags.extended = 0,  // 标准帧
    .flags.remote = 0,    // 数据帧
};
can_write(&tx_msg);

12.6 收发流程小结

阶段 发送 接收
入口 can_write() RX 中断回调
硬件资源 3 个邮箱 FIFO(3 层深度)
软件缓冲 TX 环形缓冲区 RX 环形缓冲区
触发续传 TX 完成中断
应用获取 can_read() + can_task()
临界保护 关 TX 中断 关 RX 中断

13. 出错了驱动怎么处理?

第 7 章讲了 CAN 协议的错误检测和状态机。这一章看 STM32 怎么实现,以及驱动怎么封装。

13.1 回顾:CAN 的错误状态机

                 TEC/REC < 96          96 ≤ TEC/REC < 128
                ┌──────────┐          ┌──────────────────┐
                │          │          │                  │
                ▼          │          ▼                  │
┌──────────┐ 错误─→ ┌──────────────┐ 错误─→ ┌─────────────────┐
│ 主动错误  │←─────  │   警告状态   │←─────  │    被动错误      │
│ (Active) │ 恢复   │  (Warning)  │ 恢复   │   (Passive)     │
└──────────┘        └──────────────┘        └────────┬────────┘
     ↑                                               │
     │                                          TEC > 255
     │                                               │
     │              检测到128次                       ▼
     └──────────── 11位连续隐性 ←────────────  ┌──────────┐
                                              │  离线     │
                                              │ (Bus-Off) │
                                              └──────────┘
  • TEC:发送错误计数器
  • REC:接收错误计数器
  • 发送出错 +8,成功 -1;累积到一定程度状态降级

13.2 STM32 的 ESR 寄存器

STM32 用 ESR(Error Status Register) 记录错误状态:

ESR 寄存器关键位:

  位    名称    含义
┌─────┬───────┬────────────────────────────────┐
│  2  │ EWGF  │ 错误警告标志 (TEC/REC ≥ 96)    │
│  1  │ EPVF  │ 错误被动标志 (TEC/REC ≥ 128)   │
│  0  │ BOFF  │ 离线标志 (TEC > 255)           │
├─────┼───────┼────────────────────────────────┤
│23:16│ REC   │ 接收错误计数器值               │
│15:8 │ TEC   │ 发送错误计数器值               │
│ 6:4 │ LEC   │ 最后一次错误码                 │
└─────┴───────┴────────────────────────────────┘

LEC 错误码:
  0 = 无错误
  1 = 填充错误 (Stuff Error)
  2 = 格式错误 (Form Error)
  3 = ACK 错误
  4 = 隐性位错误
  5 = 显性位错误
  6 = CRC 错误
  7 = 由软件设置

13.3 驱动的状态枚举

驱动把 ESR 的标志位映射为简洁的枚举:

typedef enum {
    CAN_BUS_OK,       // 正常(TEC/REC < 96)
    CAN_BUS_WARNING,  // 警告(TEC/REC ≥ 96)
    CAN_BUS_PASSIVE,  // 被动错误(TEC/REC ≥ 128)
    CAN_BUS_OFF,      // 离线(TEC > 255)
} can_bus_state_t;

读取当前状态

can_bus_state_t can_get_bus_state(void)
{
    uint32_t esr = hcan.Instance->ESR;  // 直接读寄存器

    // 按严重程度从高到低判断
    if (esr & CAN_ESR_BOFF)   return CAN_BUS_OFF;      // 离线最严重
    if (esr & CAN_ESR_EPVF)   return CAN_BUS_PASSIVE;  // 其次被动
    if (esr & CAN_ESR_EWGF)   return CAN_BUS_WARNING;  // 再次警告

    return CAN_BUS_OK;  // 都没有就是正常
}

对应关系

协议概念 ESR 标志 驱动枚举
主动错误(Error Active) 无标志 CAN_BUS_OK
警告状态 EWGF = 1 CAN_BUS_WARNING
被动错误(Error Passive) EPVF = 1 CAN_BUS_PASSIVE
离线(Bus-Off) BOFF = 1 CAN_BUS_OFF

13.4 错误回调机制

驱动提供回调接口,当状态发生变化时通知应用层:

注册回调

typedef void (*can_error_cb_t)(can_bus_state_t state, uint32_t error_code);

void can_set_error_callback(can_error_cb_t cb);

使用示例

void my_error_handler(can_bus_state_t state, uint32_t err)
{
    switch (state)
    {
        case CAN_BUS_WARNING:
            printf("警告:错误计数较高\r\n");
            break;

        case CAN_BUS_PASSIVE:
            printf("被动错误:通信能力受限\r\n");
            break;

        case CAN_BUS_OFF:
            printf("离线!需要重启CAN\r\n");
            // 可以在这里触发重新初始化
            break;

        case CAN_BUS_OK:
            printf("恢复正常\r\n");
            break;
    }
}

// 初始化时注册
can_set_error_callback(my_error_handler);

内部实现

static can_error_cb_t error_callback;
static can_bus_state_t last_bus_state = CAN_BUS_OK;

void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan_ptr)
{
    uint32_t err = HAL_CAN_GetError(hcan_ptr);
    can_bus_state_t state = can_get_bus_state();

    // 只在状态变化时通知,避免重复回调
    if (state != last_bus_state)
    {
        last_bus_state = state;

        if (error_callback)
        {
            error_callback(state, err);
        }
    }
}

为什么只在状态变化时回调?

错误中断可能频繁触发(每次出错都触发),如果每次都回调会淹没应用层。只在状态跃迁时通知,应用层才能有效响应。

13.5 自动恢复 vs 手动恢复

自动恢复(推荐)

CubeMX 配置 Automatic Bus-Off Management = Enable 后,离线状态会自动恢复

离线 ──→ 检测到 128 次 (11位隐性) ──→ 自动回到主动错误状态

驱动启动时默认使能了此功能。

手动恢复

如果想手动控制恢复时机:

// CubeMX 配置 AutoBusOff = DISABLE

// 然后在代码中:
if (can_get_bus_state() == CAN_BUS_OFF)
{
    // 执行一些诊断或等待...

    // 触发恢复
    HAL_CAN_Stop(&hcan);
    HAL_CAN_Start(&hcan);
}

13.6 错误计数器的读取

有时候想看具体的 TEC/REC 值:

void print_error_counters(void)
{
    uint32_t esr = hcan.Instance->ESR;

    uint8_t tec = (esr >> 16) & 0xFF;  // 发送错误计数
    uint8_t rec = (esr >> 24) & 0xFF;  // 接收错误计数

    printf("TEC=%d, REC=%d\r\n", tec, rec);
}

注意:这个读取方式是直接访问寄存器,没有封装成驱动 API。如果需要可以自己加。

13.7 常见错误场景

现象 可能原因 排查方法
持续 ACK 错误 没有其他节点 / 波特率不匹配 检查接线,确认对端在线且波特率一致
持续 CRC 错误 信号质量差 / 终端电阻问题 用示波器看波形,检查 120Ω 电阻
快速进入 Bus-Off 总线短路 / 收发器损坏 断开总线测试 Loopback 模式
偶发错误但能恢复 正常现象(干扰) 错误计数器会自动回落,无需处理

13.8 调试建议

调试 CAN 问题的顺序:

1. 先用 Loopback 模式
   └─ 排除软件配置问题

2. 检查硬件
   ├─ 万用表测 CAN_H - CAN_L 阻值 ≈ 60Ω
   ├─ 确认收发器供电正常
   └─ 检查接线(H-H, L-L,不要交叉)

3. 示波器看波形
   ├─ 显性:CAN_H ≈ 3.5V, CAN_L ≈ 1.5V
   ├─ 隐性:CAN_H ≈ CAN_L ≈ 2.5V
   └─ 波形要方正,没有明显过冲/振铃

4. 打印错误信息
   └─ 注册 error_callback,观察状态变化

14. 完整使用示例

把前面所有内容串起来,给出可直接运行的示例。

14.1 示例一:Loopback 自发自收

目标:验证软件配置正确,不需要外部硬件。

代码结构

APP/
  ├── can_app.c      # 应用层封装
  └── can_app.h
Components/can/
  ├── can_drv.c      # 驱动层
  └── can_drv.h
main.c               # 主程序

main.c

#include "can_app.h"
#include "stdio.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    MX_CAN_Init();  // CubeMX 生成的基础初始化

    printf("CAN Loopback Test\r\n");

    // 1. 初始化 CAN(Loopback 模式)
    uint8_t ret = can_app_init();
    if (ret != 0)
    {
        printf("CAN init failed: %d\r\n", ret);
        while (1);
    }
    printf("CAN init OK\r\n");

    // 2. 发送测试消息
    can_msg_t tx_msg = {
        .id = 0x123,
        .len = 4,
        .buf = {0x11, 0x22, 0x33, 0x44},
        .flags.extended = 0,
        .flags.remote = 0,
    };

    if (can_write(&tx_msg))
    {
        printf("TX: ID=0x%lX DATA=%02X %02X %02X %02X\r\n",
               tx_msg.id, tx_msg.buf[0], tx_msg.buf[1],
               tx_msg.buf[2], tx_msg.buf[3]);
    }

    // 3. 主循环
    while (1)
    {
        can_task();  // 处理接收到的消息
        HAL_Delay(10);
    }
}

can_app_init() 配置

uint8_t can_app_init(void)
{
    // 初始化缓冲区
    can_drv_init(rx_buffer, CAN_RX_BUF_SIZE, tx_buffer, CAN_TX_BUF_SIZE);

    // 设置波特率 500kbps
    if (!can_set_baudrate(500000))
        return 1;

    // 关键:Loopback 模式,自发自收
    can_set_mode(CAN_DRV_LOOPBACK);

    // 接收所有消息
    if (!can_filter_init())
        return 2;

    // 启动
    if (!can_drv_start())
        return 3;

    return 0;
}

预期输出

CAN Loopback Test
CAN init OK
TX: ID=0x123 DATA=11 22 33 44
RX: ID=0x123 LEN=4 DATA=11 22 33 44

发送的消息立即被自己收到,说明软件配置正确。

14.2 示例二:双板通信

目标:两块板子互相通信。

硬件连接

    板子 A                           板子 B
┌───────────┐                   ┌───────────┐
│  STM32    │                   │  STM32    │
│           │                   │           │
│  CAN_TX ──┼─→ TJA1050 ──┐ ┌── TJA1050 ←──┼── CAN_TX   │
│  CAN_RX ←─┼── TJA1050 ──┼─┼── TJA1050 ──→┼── CAN_RX   │
└───────────┘              │ │              └───────────┘
                           │ │
               CAN_H ══════╧═╧══════ CAN_H
               CAN_L ═══════════════ CAN_L
                    │             │
                  120Ω          120Ω
                    │             │
                   GND           GND

注意

  • 两端各接 120Ω 终端电阻
  • CAN_H 接 CAN_H,CAN_L 接 CAN_L(不要交叉)
  • 两板共地

板子 A:发送端

// can_app_init 中改为 Normal 模式
can_set_mode(CAN_DRV_NORMAL);

int main(void)
{
    // ... 初始化省略 ...

    can_app_init();

    can_msg_t tx_msg = {
        .id = 0x100,
        .len = 2,
        .buf = {0x00, 0x00},
    };

    uint8_t counter = 0;

    while (1)
    {
        // 每秒发送一次,数据递增
        tx_msg.buf[0] = counter++;
        tx_msg.buf[1] = counter++;

        can_write(&tx_msg);
        printf("TX: %02X %02X\r\n", tx_msg.buf[0], tx_msg.buf[1]);

        HAL_Delay(1000);
    }
}

板子 B:接收端

// can_app_init 中同样改为 Normal 模式
can_set_mode(CAN_DRV_NORMAL);

// 只接收 ID=0x100 的消息
can_filter_set_id(0, 0x100, CAN_ID_TYPE_STD);

int main(void)
{
    // ... 初始化省略 ...

    can_app_init();

    while (1)
    {
        can_task();  // 打印接收到的消息
        HAL_Delay(10);
    }
}

预期输出

板子 A(发送端):

TX: 00 01
TX: 02 03
TX: 04 05
...

板子 B(接收端):

RX: ID=0x100 LEN=2 DATA=00 01
RX: ID=0x100 LEN=2 DATA=02 03
RX: ID=0x100 LEN=2 DATA=04 05
...

14.3 示例三:带错误处理的完整应用

#include "can_app.h"

// 错误回调
void can_error_handler(can_bus_state_t state, uint32_t err)
{
    const char *state_str[] = {"OK", "WARNING", "PASSIVE", "BUS-OFF"};
    printf("CAN State: %s (err=0x%08lX)\r\n", state_str[state], err);

    if (state == CAN_BUS_OFF)
    {
        printf("尝试重新初始化...\r\n");
        // 这里可以触发重新初始化逻辑
    }
}

int main(void)
{
    // ... 硬件初始化 ...

    // CAN 初始化
    if (can_app_init() != 0)
    {
        printf("CAN init failed!\r\n");
        while (1);
    }

    // 注册错误回调
    can_set_error_callback(can_error_handler);

    // 定时发送 + 接收处理
    uint32_t last_tx_time = 0;
    can_msg_t tx_msg = {.id = 0x200, .len = 8};

    while (1)
    {
        // 每 100ms 发送一次
        if (HAL_GetTick() - last_tx_time >= 100)
        {
            last_tx_time = HAL_GetTick();

            // 填充数据(示例:时间戳)
            uint32_t tick = HAL_GetTick();
            tx_msg.buf[0] = (tick >> 24) & 0xFF;
            tx_msg.buf[1] = (tick >> 16) & 0xFF;
            tx_msg.buf[2] = (tick >> 8) & 0xFF;
            tx_msg.buf[3] = tick & 0xFF;

            if (!can_write(&tx_msg))
            {
                printf("TX buffer full!\r\n");
            }
        }

        // 处理接收
        can_task();
    }
}

14.4 API 速查表

初始化相关

函数 说明
can_drv_init(rx_buf, rx_size, tx_buf, tx_size) 初始化驱动(传入缓冲区)
can_set_baudrate(baudrate) 设置波特率(如 500000)
can_set_mode(mode) 设置模式(NORMAL/LOOPBACK/SILENT)
can_filter_init() 初始化过滤器(接收所有)
can_drv_start() 启动 CAN

过滤器相关

函数 说明
can_filter_init() 接收所有消息
can_filter_set_id(bank, id, type) 精确匹配单个 ID
can_filter_set_mask(bank, id, mask, type) 掩码模式匹配范围
can_filter_enable(bank, enabled) 开关指定 Bank

收发相关

函数 说明
can_write(msg) 发送消息(非阻塞)
can_read(msg) 读取一条接收消息
can_rx_count() 获取 RX 缓冲区消息数
can_tx_count() 获取 TX 缓冲区消息数

错误处理相关

函数 说明
can_get_bus_state() 获取当前总线状态
can_set_error_callback(cb) 注册错误回调

消息结构体

typedef struct {
    uint32_t id;           // 消息 ID
    uint8_t  len;          // 数据长度 (0~8)
    uint8_t  buf[8];       // 数据
    struct {
        uint8_t extended : 1;  // 扩展帧标志
        uint8_t remote   : 1;  // 远程帧标志
    } flags;
    uint8_t  filter_index; // 匹配的过滤器号
    uint16_t timestamp;    // 时间戳
} can_msg_t;

14.5 常见问题 FAQ

问题 解答
Loopback 能收到,Normal 模式收不到 检查硬件:终端电阻、接线、收发器供电
发送返回成功但对方没收到 检查两端波特率是否一致
只能收不能发 可能进入了 Silent 模式,检查 can_set_mode()
过滤器配了但还是收不到 检查 Bank 号是否冲突、ID类型是否匹配
缓冲区溢出 增大 CAN_RX_BUF_SIZE,或加快 can_task() 调用频率

附录 C:第二部分知识结构

第二部分:STM32 实现 CAN 通信
│
├─ 9. bxCAN 外设结构
│   ├─ 发送邮箱 (3个)
│   ├─ 接收 FIFO (2个×3层)
│   └─ 过滤器组 (14个Bank)
│
├─ 10. 初始化流程
│   ├─ CubeMX 配置
│   ├─ 波特率计算
│   ├─ 工作模式选择
│   └─ 启动顺序
│
├─ 11. 过滤器配置
│   ├─ 寄存器布局的坑
│   └─ 三种封装 API
│
├─ 12. 消息收发
│   ├─ TX 缓冲区 + 中断续发
│   ├─ RX 缓冲区 + 中断接收
│   └─ 临界区保护
│
├─ 13. 错误处理
│   ├─ ESR 寄存器
│   ├─ 状态枚举
│   └─ 错误回调
│
└─ 14. 完整示例
    ├─ Loopback 自测
    ├─ 双板通信
    └─ API 速查表

#include "can_drv.h"
#include <string.h>
#include "stm32f1xx_hal.h"

extern CAN_HandleTypeDef hcan;

/*---------------------------------------------------------------------------*/
/* RX/TX 缓冲区实例                                                          */
/*---------------------------------------------------------------------------*/
static can_ringbuf_t rx_ring;
static can_ringbuf_t tx_ring;
static bool can_running;

/**
 * @brief 初始化环形缓冲区
 * @param ring 缓冲区指针
 * @param buf  存储数组
 * @param size 数组大小
 */
void can_ringbuf_init(can_ringbuf_t *ring, can_msg_t *buf, uint16_t size)
{
    ring->buffer = buf;
    ring->size = size;
    ring->head = 0;
    ring->tail = 0;
}

/**
 * @brief 消息入队
 * @param ring 缓冲区指针
 * @param msg  待入队的消息
 * @retval true=成功, false=缓冲区满
 */
bool can_ringbuf_push(can_ringbuf_t *ring, const can_msg_t *msg)
{
    uint16_t next = (ring->head + 1) % ring->size;

    if (next == ring->tail)
    {
        return false;
    }

    memcpy(&ring->buffer[ring->head], msg, sizeof(can_msg_t));
    ring->head = next;

    return true;
}

/**
 * @brief 消息出队
 * @param ring 缓冲区指针
 * @param msg  出队消息存放位置
 * @retval true=成功, false=缓冲区空
 */
bool can_ringbuf_pop(can_ringbuf_t *ring, can_msg_t *msg)
{
    if (ring->head == ring->tail)
    {
        return false;
    }

    memcpy(msg, &ring->buffer[ring->tail], sizeof(can_msg_t));
    ring->tail = (ring->tail + 1) % ring->size;

    return true;
}

/**
 * @brief 判断缓冲区是否为空
 * @param ring 缓冲区指针
 * @retval true=空, false=非空
 */
bool can_ringbuf_is_empty(can_ringbuf_t *ring)
{
    return (ring->head == ring->tail);
}

/**
 * @brief 判断缓冲区是否已满
 * @param ring 缓冲区指针
 * @retval true=满, false=未满
 */
bool can_ringbuf_is_full(can_ringbuf_t *ring)
{
    return ((ring->head + 1) % ring->size == ring->tail);
}

/**
 * @brief 获取缓冲区中消息数量
 * @param ring 缓冲区指针
 * @retval 消息数量
 */
uint16_t can_ringbuf_count(can_ringbuf_t *ring)
{
    int16_t count = ring->head - ring->tail;

    if (count < 0)
    {
        count += ring->size;
    }

    return (uint16_t)count;
}

/*---------------------------------------------------------------------------*/
/* CAN 过滤器实现                                                            */
/*---------------------------------------------------------------------------*/

#define CAN_FILTER_BANK_COUNT  14

/**
 * @brief 将用户 ID 转换为 32 位过滤器寄存器格式
 * @param id       用户 ID
 * @param id_type  标准帧/扩展帧
 * @param is_mask  true 时强制置位 IDE 位(掩码需要匹配 IDE)
 * @retval 寄存器值
 * @note  STM32 过滤器寄存器布局:[STID/EXID(31:21)] [EXID(20:8)] [EXID(7:3)] [IDE] [RTR] [0]
 */
static uint32_t format_filter_id(uint32_t id, can_id_type_t id_type, bool is_mask)
{
    uint32_t reg;

    if (id_type == CAN_ID_TYPE_STD)
    {
        id <<= 18;
    }

    reg = id << 3;

    if (is_mask || id_type == CAN_ID_TYPE_EXT)
    {
        reg |= (1 << 2);
    }

    return reg;
}

/**
 * @brief 底层过滤器配置
 * @param bank         过滤器组号 (0~13)
 * @param id_reg       ID 寄存器值(已格式化)
 * @param mask_reg     Mask 寄存器值(已格式化)
 * @param filter_mode  CAN_FILTERMODE_IDMASK 或 CAN_FILTERMODE_IDLIST
 * @param enabled      是否使能
 * @retval true=成功
 */
static bool can_filter_set_raw(uint8_t bank, uint32_t id_reg, uint32_t mask_reg,
                               uint32_t filter_mode, bool enabled)
{
    if (bank >= CAN_FILTER_BANK_COUNT)
    {
        return false;
    }

    CAN_FilterTypeDef filter;

    filter.FilterBank           = bank;
    filter.FilterMode           = filter_mode;
    filter.FilterScale          = CAN_FILTERSCALE_32BIT;
    filter.FilterIdHigh         = (id_reg >> 16) & 0xFFFF;
    filter.FilterIdLow          = id_reg & 0xFFFF;
    filter.FilterMaskIdHigh     = (mask_reg >> 16) & 0xFFFF;
    filter.FilterMaskIdLow      = mask_reg & 0xFFFF;
    filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    filter.FilterActivation     = enabled ? CAN_FILTER_ENABLE : CAN_FILTER_DISABLE;
    filter.SlaveStartFilterBank = CAN_FILTER_BANK_COUNT;

    return (HAL_CAN_ConfigFilter(&hcan, &filter) == HAL_OK);
}

/**
 * @brief 初始化过滤器(Bank0 接收所有,其余关闭)
 * @retval true=成功
 */
bool can_filter_init(void)
{
    if (!can_filter_set_raw(0, 0, 0, CAN_FILTERMODE_IDMASK, true))
    {
        return false;
    }

    for (uint8_t i = 1; i < CAN_FILTER_BANK_COUNT; i++)
    {
        can_filter_enable(i, false);
    }

    return true;
}

/**
 * @brief 32位掩码模式过滤器
 * @param bank    过滤器组号 (0~13)
 * @param id      目标 ID
 * @param mask    掩码(1=必须匹配,0=忽略)
 * @param id_type 标准帧/扩展帧
 * @retval true=成功
 */
bool can_filter_set_mask(uint8_t bank, uint32_t id, uint32_t mask, can_id_type_t id_type)
{
    uint32_t id_reg   = format_filter_id(id,   id_type, false);
    uint32_t mask_reg = format_filter_id(mask, id_type, true);

    return can_filter_set_raw(bank, id_reg, mask_reg, CAN_FILTERMODE_IDMASK, true);
}

/**
 * @brief 精确匹配单个 ID
 * @param bank    过滤器组号 (0~13)
 * @param id      目标 ID
 * @param id_type 标准帧/扩展帧
 * @retval true=成功
 * @note  等价于 mask=0x7FF(STD) 或 mask=0x1FFFFFFF(EXT)
 */
bool can_filter_set_id(uint8_t bank, uint32_t id, can_id_type_t id_type)
{
    uint32_t mask = (id_type == CAN_ID_TYPE_STD) ? 0x7FF : 0x1FFFFFFF;

    return can_filter_set_mask(bank, id, mask, id_type);
}

/**
 * @brief 开关过滤器组
 * @param bank    过滤器组号 (0~13)
 * @param enabled true=使能, false=关闭
 * @retval true=成功
 */
bool can_filter_enable(uint8_t bank, bool enabled)
{
    if (bank >= CAN_FILTER_BANK_COUNT)
    {
        return false;
    }

    CAN_FilterTypeDef filter;

    filter.FilterBank           = bank;
    filter.FilterActivation     = enabled ? CAN_FILTER_ENABLE : CAN_FILTER_DISABLE;
    filter.SlaveStartFilterBank = CAN_FILTER_BANK_COUNT;

    return (HAL_CAN_ConfigFilter(&hcan, &filter) == HAL_OK);
}

/*---------------------------------------------------------------------------*/
/* CAN 错误处理实现                                                          */
/*---------------------------------------------------------------------------*/

static can_error_cb_t error_callback;
static can_bus_state_t last_bus_state = CAN_BUS_OK;

/**
 * @brief 从 ESR 寄存器读取当前总线状态
 * @retval 总线状态枚举
 */
can_bus_state_t can_get_bus_state(void)
{
    uint32_t esr = hcan.Instance->ESR;

    if (esr & CAN_ESR_BOFF)
    {
        return CAN_BUS_OFF;
    }

    if (esr & CAN_ESR_EPVF)
    {
        return CAN_BUS_PASSIVE;
    }

    if (esr & CAN_ESR_EWGF)
    {
        return CAN_BUS_WARNING;
    }

    return CAN_BUS_OK;
}

/**
 * @brief 注册错误回调函数
 * @param cb 回调函数指针(NULL 取消注册)
 */
void can_set_error_callback(can_error_cb_t cb)
{
    error_callback = cb;
}

/**
 * @brief HAL 错误中断回调
 * @param hcan_ptr CAN 句柄
 * @note  仅在状态发生变化时通知上层,避免中断中重复回调
 */
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan_ptr)
{
    uint32_t err = HAL_CAN_GetError(hcan_ptr);
    can_bus_state_t state = can_get_bus_state();

    if (state != last_bus_state)
    {
        last_bus_state = state;

        if (error_callback)
        {
            error_callback(state, err);
        }
    }
}

/*---------------------------------------------------------------------------*/
/* CAN 模式与配置实现                                                        */
/*---------------------------------------------------------------------------*/

static void can_hw_stop(void)
{
    HAL_CAN_DeactivateNotification(&hcan,
        CAN_IT_RX_FIFO0_MSG_PENDING |
        CAN_IT_TX_MAILBOX_EMPTY |
        CAN_IT_ERROR_WARNING |
        CAN_IT_ERROR_PASSIVE |
        CAN_IT_BUSOFF |
        CAN_IT_LAST_ERROR_CODE |
        CAN_IT_ERROR);

    HAL_CAN_Stop(&hcan);
    can_running = false;
}

static bool can_hw_start(void)
{
    if (HAL_CAN_Init(&hcan) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_Start(&hcan) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_ActivateNotification(&hcan,
            CAN_IT_ERROR_WARNING |
            CAN_IT_ERROR_PASSIVE |
            CAN_IT_BUSOFF |
            CAN_IT_LAST_ERROR_CODE |
            CAN_IT_ERROR) != HAL_OK)
    {
        return false;
    }

    can_running = true;
    return true;
}

/**
 * @brief 设置CAN波特率
 * @param baudrate 目标波特率 (如 500000, 250000, 1000000)
 * @retval true=成功, false=无法实现该波特率
 * @note  根据APB1时钟自动计算分频参数,采样点约75-80%
 */
bool can_set_baudrate(uint32_t baudrate)
{
    uint32_t apb1_clk = HAL_RCC_GetPCLK1Freq();
    uint16_t prescaler;
    uint8_t bs1, bs2;

    for (prescaler = 1; prescaler <= 1024; prescaler++)
    {
        uint32_t can_clk = apb1_clk / prescaler;
        uint32_t tq_count = can_clk / baudrate;

        if (can_clk != baudrate * tq_count)
        {
            continue;
        }

        if (tq_count < 3 || tq_count > 22)
        {
            continue;
        }

        for (bs2 = 5; bs2 >= 1; bs2--)
        {
            bs1 = tq_count - 1 - bs2;

            if (bs1 < 1 || bs1 > 16)
            {
                continue;
            }

            if (bs1 < bs2 * 3 - 1)
            {
                continue;
            }

            goto found;
        }
    }

    return false;

found:;
    bool was_running = can_running;

    if (was_running)
    {
        can_hw_stop();
    }

    hcan.Init.Prescaler = prescaler;
    hcan.Init.TimeSeg1 = ((uint32_t)(bs1 - 1)) << 16;
    hcan.Init.TimeSeg2 = ((uint32_t)(bs2 - 1)) << 20;
    hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;

    if (was_running)
    {
        return can_hw_start();
    }

    return true;
}

/**
 * @brief 设置CAN工作模式
 * @param mode CAN_DRV_NORMAL / CAN_DRV_LOOPBACK / CAN_DRV_SILENT / CAN_DRV_SILENT_LOOPBACK
 * @retval true=成功
 * @note  运行中调用会自动重启CAN
 */
bool can_set_mode(can_mode_t mode)
{
    static const uint32_t mode_map[] = {
        CAN_MODE_NORMAL,
        CAN_MODE_LOOPBACK,
        CAN_MODE_SILENT,
        CAN_MODE_SILENT_LOOPBACK,
    };

    if (mode > CAN_DRV_SILENT_LOOPBACK)
    {
        return false;
    }

    bool was_running = can_running;

    if (was_running)
    {
        can_hw_stop();
    }

    hcan.Init.Mode = mode_map[mode];

    if (was_running)
    {
        return can_hw_start();
    }

    return true;
}

/*---------------------------------------------------------------------------*/
/* CAN 驱动接口实现                                                          */
/*---------------------------------------------------------------------------*/

/**
 * @brief 初始化CAN驱动
 * @param rx_buf RX缓冲区数组
 * @param rx_size RX缓冲区大小
 * @param tx_buf TX缓冲区数组
 * @param tx_size TX缓冲区大小
 * @note  仅初始化缓冲区,硬件配置由can_set_baudrate/can_set_mode设置
 */
void can_drv_init(can_msg_t *rx_buf, uint16_t rx_size,
                  can_msg_t *tx_buf, uint16_t tx_size)
{
    can_ringbuf_init(&rx_ring, rx_buf, rx_size);
    can_ringbuf_init(&tx_ring, tx_buf, tx_size);

    hcan.Init.TransmitFifoPriority = ENABLE;
    can_running = false;
}

/**
 * @brief 启动CAN外设
 * @retval true=成功, false=失败
 * @note  应用波特率/模式配置 + 启动CAN + 使能中断
 */
bool can_drv_start(void)
{
    if (HAL_CAN_Init(&hcan) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_Start(&hcan) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY) != HAL_OK)
    {
        return false;
    }

    if (HAL_CAN_ActivateNotification(&hcan,
            CAN_IT_ERROR_WARNING |
            CAN_IT_ERROR_PASSIVE |
            CAN_IT_BUSOFF |
            CAN_IT_LAST_ERROR_CODE |
            CAN_IT_ERROR) != HAL_OK)
    {
        return false;
    }

    can_running = true;
    return true;
}

/**
 * @brief 获取RX缓冲区中消息数量
 * @retval 消息数量
 */
uint16_t can_rx_count(void)
{
    return can_ringbuf_count(&rx_ring);
}

/**
 * @brief 从RX缓冲区读取一条消息
 * @param msg 消息存放位置
 * @retval true=成功, false=缓冲区空
 * @note  关中断保护,防止与RX中断并发访问
 */
bool can_read(can_msg_t *msg)
{
    __HAL_CAN_DISABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);

    bool ret = can_ringbuf_pop(&rx_ring, msg);

    __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);

    return ret;
}

/**
 * @brief 获取TX缓冲区中消息数量
 * @retval 消息数量
 */
uint16_t can_tx_count(void)
{
    return can_ringbuf_count(&tx_ring);
}

/**
 * @brief 内部发送函数,将消息写入硬件邮箱
 * @param msg 待发送消息
 * @retval true=成功写入邮箱, false=邮箱满
 */
static bool can_send_to_mailbox(const can_msg_t *msg)
{
    CAN_TxHeaderTypeDef tx_header;
    uint32_t mailbox;

    if (msg->flags.extended)
    {
        tx_header.ExtId = msg->id;
        tx_header.IDE = CAN_ID_EXT;
    }
    else
    {
        tx_header.StdId = msg->id;
        tx_header.IDE = CAN_ID_STD;
    }

    tx_header.RTR = msg->flags.remote ? CAN_RTR_REMOTE : CAN_RTR_DATA;
    tx_header.DLC = msg->len;
    tx_header.TransmitGlobalTime = DISABLE;

    if (HAL_CAN_AddTxMessage(&hcan, &tx_header, (uint8_t *)msg->buf, &mailbox) != HAL_OK)
    {
        return false;
    }

    return true;
}

/**
 * @brief 非阻塞发送CAN消息
 * @param msg 待发送消息
 * @retval true=成功(已发送或已入队), false=缓冲区满
 * @note  邮箱有空则直接发送,否则存入TX缓冲区等待中断发送
 */
bool can_write(const can_msg_t *msg)
{
    bool ret = true;

    __HAL_CAN_DISABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY);

    if (!can_send_to_mailbox(msg))
    {
        if (!can_ringbuf_push(&tx_ring, msg))
        {
            ret = false;
        }
    }

    __HAL_CAN_ENABLE_IT(&hcan, CAN_IT_TX_MAILBOX_EMPTY);

    return ret;
}

/*---------------------------------------------------------------------------*/
/* CAN 中断回调                                                              */
/*---------------------------------------------------------------------------*/

/**
 * @brief CAN FIFO0接收中断回调
 * @param hcan_ptr CAN句柄
 * @note  从硬件FIFO读取消息存入RX环形缓冲区
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan_ptr)
{
    CAN_RxHeaderTypeDef rx_header;
    can_msg_t msg;

    while (HAL_CAN_GetRxFifoFillLevel(hcan_ptr, CAN_RX_FIFO0) > 0)
    {
        if (HAL_CAN_GetRxMessage(hcan_ptr, CAN_RX_FIFO0, &rx_header, msg.buf) == HAL_OK)
        {
            if (rx_header.IDE == CAN_ID_STD)
            {
                msg.id = rx_header.StdId;
                msg.flags.extended = 0;
            }
            else
            {
                msg.id = rx_header.ExtId;
                msg.flags.extended = 1;
            }

            msg.flags.remote = (rx_header.RTR == CAN_RTR_REMOTE) ? 1 : 0;
            msg.len = rx_header.DLC;
            msg.filter_index = rx_header.FilterMatchIndex;
            msg.timestamp = rx_header.Timestamp;

            can_ringbuf_push(&rx_ring, &msg);
        }
    }
}

/**
 * @brief TX邮箱发送完成回调(内部公共处理)
 * @note  从TX缓冲区取消息继续发送
 */
static void can_tx_complete_handler(void)
{
    can_msg_t msg;

    if (can_ringbuf_pop(&tx_ring, &msg))
    {
        can_send_to_mailbox(&msg);
    }
}

void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan_ptr)
{
    (void)hcan_ptr;
    can_tx_complete_handler();
}

void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan_ptr)
{
    (void)hcan_ptr;
    can_tx_complete_handler();
}

void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan_ptr)
{
    (void)hcan_ptr;
    can_tx_complete_handler();
}

#ifndef __CAN_DRV_H__
#define __CAN_DRV_H__

#include <stdint.h>
#include <stdbool.h>

/** @brief CAN消息结构体 */
typedef struct {
    uint32_t id;
    uint8_t  len;
    uint8_t  buf[8];
    struct {
        uint8_t extended : 1;
        uint8_t remote   : 1;
    } flags;
    uint8_t  filter_index;
    uint16_t timestamp;
} can_msg_t;

/** @brief CAN环形缓冲区结构体 */
typedef struct {
    can_msg_t *buffer;
    volatile uint16_t head;
    volatile uint16_t tail;
    uint16_t size;
} can_ringbuf_t;

/** @brief 初始化环形缓冲区 */
void can_ringbuf_init(can_ringbuf_t *ring, can_msg_t *buf, uint16_t size);

/** @brief 消息入队 */
bool can_ringbuf_push(can_ringbuf_t *ring, const can_msg_t *msg);

/** @brief 消息出队 */
bool can_ringbuf_pop(can_ringbuf_t *ring, can_msg_t *msg);

/** @brief 判断缓冲区是否为空 */
bool can_ringbuf_is_empty(can_ringbuf_t *ring);

/** @brief 判断缓冲区是否已满 */
bool can_ringbuf_is_full(can_ringbuf_t *ring);

/** @brief 获取缓冲区中消息数量 */
uint16_t can_ringbuf_count(can_ringbuf_t *ring);

/*---------------------------------------------------------------------------*/
/* CAN 过滤器接口                                                            */
/*---------------------------------------------------------------------------*/

typedef enum {
    CAN_ID_TYPE_STD = 0,
    CAN_ID_TYPE_EXT = 1,
} can_id_type_t;

/** @brief 初始化过滤器(默认接收所有ID) */
bool can_filter_init(void);

/** @brief 32位掩码模式过滤器 */
bool can_filter_set_mask(uint8_t bank, uint32_t id, uint32_t mask, can_id_type_t id_type);

/** @brief 精确匹配单个ID */
bool can_filter_set_id(uint8_t bank, uint32_t id, can_id_type_t id_type);

/** @brief 开关过滤器组 */
bool can_filter_enable(uint8_t bank, bool enabled);

/*---------------------------------------------------------------------------*/
/* CAN 错误处理接口                                                          */
/*---------------------------------------------------------------------------*/

typedef enum {
    CAN_BUS_OK,
    CAN_BUS_WARNING,
    CAN_BUS_PASSIVE,
    CAN_BUS_OFF,
} can_bus_state_t;

typedef void (*can_error_cb_t)(can_bus_state_t state, uint32_t error_code);

/** @brief 查询当前总线状态 */
can_bus_state_t can_get_bus_state(void);

/** @brief 注册错误回调(状态变化时由中断调用) */
void can_set_error_callback(can_error_cb_t cb);

/*---------------------------------------------------------------------------*/
/* CAN 模式与配置接口                                                        */
/*---------------------------------------------------------------------------*/

typedef enum {
    CAN_DRV_NORMAL = 0,
    CAN_DRV_LOOPBACK,
    CAN_DRV_SILENT,
    CAN_DRV_SILENT_LOOPBACK,
} can_mode_t;

/** @brief 设置波特率(运行中调用会自动重启CAN) */
bool can_set_baudrate(uint32_t baudrate);

/** @brief 设置工作模式(运行中调用会自动重启CAN) */
bool can_set_mode(can_mode_t mode);

/*---------------------------------------------------------------------------*/
/* CAN 驱动接口                                                              */
/*---------------------------------------------------------------------------*/

/** @brief 初始化CAN驱动(使用外部缓冲区) */
void can_drv_init(can_msg_t *rx_buf, uint16_t rx_size,
                  can_msg_t *tx_buf, uint16_t tx_size);

/** @brief 启动CAN外设(Start + 使能中断) */
bool can_drv_start(void);

/** @brief 获取RX缓冲区中消息数量 */
uint16_t can_rx_count(void);

/** @brief 从RX缓冲区读取一条消息 */
bool can_read(can_msg_t *msg);

/** @brief 获取TX缓冲区中消息数量 */
uint16_t can_tx_count(void);

/** @brief 非阻塞发送CAN消息 */
bool can_write(const can_msg_t *msg);

#endif

#include "can_app.h"
#include "bsp_system.h"

/*---------------------------------------------------------------------------*/
/* 缓冲区配置                                                                */
/*---------------------------------------------------------------------------*/
#define CAN_RX_BUF_SIZE  16
#define CAN_TX_BUF_SIZE  8

static can_msg_t rx_buffer[CAN_RX_BUF_SIZE];
static can_msg_t tx_buffer[CAN_TX_BUF_SIZE];

/**
 * @brief CAN应用层初始化
 * @note  初始化驱动 + 配置波特率/模式 + 配置过滤器 + 启动CAN
 * @retval 0=成功, 1=波特率配置失败, 2=过滤器配置失败, 3=启动失败
 */
uint8_t can_app_init(void)
{
    can_drv_init(rx_buffer, CAN_RX_BUF_SIZE, tx_buffer, CAN_TX_BUF_SIZE);

    if (!can_set_baudrate(500000))
    {
        return 1;
    }

    can_set_mode(CAN_DRV_NORMAL);

    if (!can_filter_init())
    {
        return 2;
    }

    if (!can_drv_start())
    {
        return 3;
    }

    return 0;
}

/**
 * @brief CAN发送数据
 * @param id  帧ID (标准帧11位, 扩展帧29位)
 * @param msg 数据指针
 * @param len 数据长度 (0~8)
 * @param extended 帧类型 (false=标准帧, true=扩展帧)
 * @retval 0=成功, 1=失败
 */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len, bool extended)
{
    can_msg_t frame = {0};

    frame.id = id;
    frame.len = len;
    frame.flags.extended = extended ? 1 : 0;
    frame.flags.remote = 0;

    for (uint8_t i = 0; i < len; i++)
    {
        frame.buf[i] = msg[i];
    }

    return can_write(&frame) ? 0 : 1;
}

/**
 * @brief CAN轮询接收
 * @param id  期望的标准ID
 * @param buf 接收缓冲区
 * @retval 0=无数据, >0=接收到的数据长度
 */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf)
{
    CAN_RxHeaderTypeDef rx_header;

    if (HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) == 0)
    {
        return 0;
    }

    if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rx_header, buf) != HAL_OK)
    {
        return 0;
    }

    if (rx_header.StdId != id ||
        rx_header.IDE != CAN_ID_STD ||
        rx_header.RTR != CAN_RTR_DATA)
    {
        return 0;
    }

    return rx_header.DLC;
}

/**
 * @brief CAN任务
 * @note  从RX缓冲区读取消息并处理,由调度器周期调用
 */
void can_task(void)
{
    can_msg_t msg;

    while (can_read(&msg))
    {
        my_printf(&huart1, "RX: ID=0x%X LEN=%d DATA=", msg.id, msg.len);
        for (uint8_t i = 0; i < msg.len; i++)
        {
            my_printf(&huart1, "%02X ", msg.buf[i]);
        }
        my_printf(&huart1, "\r\n");
    }
}

#ifndef __CAN_APP_H__
#define __CAN_APP_H__

#include "can_drv.h"

/** @brief CAN应用层初始化 (过滤器+启动+使能中断) */
uint8_t can_app_init(void);

/** @brief CAN发送数据 (支持标准帧/扩展帧) */
uint8_t can_send_msg(uint32_t id, uint8_t *msg, uint8_t len, bool extended);

/** @brief CAN轮询接收 (标准帧, 数据帧) */
uint8_t can_receive_msg(uint32_t id, uint8_t *buf);

/** @brief CAN任务 (从缓冲区读取并处理消息) */
void can_task(void);

#endif