CAN/CANFD 笔记

CAN/CANFD 笔记

以 STM32 平台为例,涵盖经典 CAN 2.0 与 CAN FD 的协议原理、外设架构及驱动实现。


目录

  1. [CAN 总线概述](#CAN 总线概述)
  2. [CAN 物理层](#CAN 物理层)
  3. [CAN 协议详解](#CAN 协议详解)
    • [3.1 帧类型](#3.1 帧类型)
    • [3.2 数据帧结构](#3.2 数据帧结构)
    • [3.3 仲裁机制](#3.3 仲裁机制)
    • [3.4 位时序与同步](#3.4 位时序与同步)
    • [3.5 错误检测与处理](#3.5 错误检测与处理)
  4. [CAN FD 协议](#CAN FD 协议)
    • [4.1 CAN FD 与经典 CAN 的核心差异](#4.1 CAN FD 与经典 CAN 的核心差异)
    • [4.2 CAN FD 帧结构](#4.2 CAN FD 帧结构)
    • [4.3 比特率切换 (BRS)](#4.3 比特率切换 (BRS))
  5. [STM32 CAN 外设架构](#STM32 CAN 外设架构)
    • [5.1 bxCAN (F1/F4 系列)](#5.1 bxCAN (F1/F4 系列))
    • [5.2 FDCAN (H7/G4/G0/L5 系列)](#5.2 FDCAN (H7/G4/G0/L5 系列))
  6. [CAN 驱动设计](#CAN 驱动设计)
    • [6.1 驱动分层架构](#6.1 驱动分层架构)
    • [6.2 HAL 层 CAN 初始化](#6.2 HAL 层 CAN 初始化)
    • [6.3 发送与接收](#6.3 发送与接收)
    • [6.4 过滤器配置](#6.4 过滤器配置)
    • [6.5 中断与回调](#6.5 中断与回调)
  7. [CAN FD 驱动设计](#CAN FD 驱动设计)
    • [7.1 FDCAN 初始化](#7.1 FDCAN 初始化)
    • [7.2 FDCAN 发送与接收](#7.2 FDCAN 发送与接收)
    • [7.3 FDCAN FIFO 与专用缓冲区](#7.3 FDCAN FIFO 与专用缓冲区)
  8. 驱动实战示例
    • [8.1 经典 CAN 回环测试](#8.1 经典 CAN 回环测试)
    • [8.2 CAN FD 双节点通信](#8.2 CAN FD 双节点通信)
  9. 调试与常见问题
  10. [CanFestival 驱动集成](#CanFestival 驱动集成)
  11. 参考资料

1. CAN 总线概述

CAN(Controller Area Network)是 Bosch 于 1986 年提出的串行通信协议,现已成为汽车和工业控制领域事实上的总线标准:

特性 说明
拓扑 多主总线,任一节点可主动发送
仲裁 非破坏性逐位仲裁(CSMA/CA)
帧优先级 由 CAN ID 决定,ID 越小优先级越高
错误处理 5 种错误检测机制 + 自动重传
故障隔离 自动将故障节点从总线隔离
最大速率 经典 CAN: 1 Mbps(40m 内);CAN FD: 最高 8 Mbps(数据段)
数据长度 经典 CAN: 0--8 字节;CAN FD: 0--64 字节

CAN 总线的核心设计哲学:追求高实时性和高可靠性,在发生冲突时不会丢失数据------优先级高的消息总能无延迟地赢得仲裁。


2. CAN 物理层

2.1 总线电平

CAN 使用差分信号传输,两根线分别为 CAN_H 和 CAN_H(实际应为 CAN_H 和 CAN_L),终端各接 120Ω 电阻:

复制代码
        CAN_H ──┬───────────────┬──
                │               │
               120Ω             120Ω
                │               │
        CAN_L ──┴───────────────┴──
状态 CAN_H CAN_L 差分电压
显性 (Dominant) 3.5V 1.5V 2.0V
隐性 (Recessive) 2.5V 2.5V 0.0V
  • 显性 = 逻辑 0,任何节点驱动显性时总线即为显性(线与逻辑)
  • 隐性 = 逻辑 1,需所有节点都释放总线才为隐性
  • 显性覆盖隐性是仲裁机制的物理基础

2.2 收发器

常见的 CAN 收发器:

型号 特性
TJA1050 经典 CAN,高速,5V
TJA1042 经典 CAN,低功耗待机
TJA1043 经典 CAN,支持唤醒
TJA1057 CAN FD 就绪,最高 5 Mbps
TCAN1042 CAN FD 就绪,保护功能完善

2.3 典型硬件连接 (STM32)

复制代码
  STM32F407             TJA1050
  ┌──────────┐         ┌──────────┐
  │          │         │          │
  │  CAN_TX  ├────────►│   TXD    │    CAN_H ──────┐
  │  CAN_RX  │◄────────│   RXD    │                │
  │          │         │          │    CAN_L ──────┤
  └──────────┘         └──────────┘               120Ω

3. CAN 协议详解

3.1 帧类型

CAN 2.0 定义了 4 种帧类型:

帧类型 用途
数据帧 (Data Frame) 发送节点向接收节点传输数据
远程帧 (Remote Frame) 请求其他节点发送相同 ID 的数据帧
错误帧 (Error Frame) 任一节点检测到总线错误时发送
过载帧 (Overload Frame) 通知其他节点自身尚未准备好接收

注意:CAN FD 中远程帧已被移除,远程请求改用普通数据帧 + 上层协议实现。

3.2 数据帧结构

经典 CAN 标准帧(11-bit ID)结构:

复制代码
 ┌──────┬───────┬───────┬────┬─────┬─────┬──────┬─────┬──────┬──────┬──────┐
 │ SOF  │Arb(11)│ RTR  │IDE │ r0  │ DLC │ Data │ CRC │ ACK  │ EOF  │ IFS  │
 │ 1bit │ 11bit │ 1bit │1bit│1bit │4bit │0-64  │15bit│ 2bit │ 7bit │ 3bit │
 └──────┴───────┴───────┴────┴─────┴─────┴──────┴─────┴──────┴──────┴──────┘

经典 CAN 扩展帧(29-bit ID)结构:

复制代码
 ┌──────┬────────┬──────┬────┬──────────┬─────┬──────┬─────┬────┬─────┬──────┬──────┐
 │ SOF  │ID[28:18]│ SRR │IDE │ID[17:0]  │ RTR │ r1,r0│ DLC │Data│ CRC │ ACK  │ EOF  │
 └──────┴────────┴──────┴────┴──────────┴─────┴──────┴─────┴────┴─────┴──────┴──────┘

各字段说明:

字段 长度 说明
SOF 1 bit 帧起始,显性位,用于同步所有节点
Arbitration Field 11/29 bit ID + RTR/SRR + IDE,用于仲裁
Control Field 6 bit IDE/r0 + DLC (4 bit),指示数据长度
Data Field 0--8 byte 有效载荷
CRC Field 15 bit + 1 bit 界定 循环冗余校验,多项式 x¹⁵+x¹⁴+x¹⁰+x⁸+x⁷+x⁴+x³+1
ACK Field 2 bit ACK Slot + ACK Delimiter,正确接收节点在 Slot 位置发显性
EOF 7 bit 帧结束,隐性位序列
IFS 3 bit 帧间间隔,用于控制器内部处理

3.3 仲裁机制

CAN 采用 CSMA/CA(载波侦听多路访问/冲突避免) 的非破坏性逐位仲裁:

  1. 总线空闲(连续 11 个隐性位)后,任意节点均可发起发送

  2. 每发送一位同时回读总线电平

  3. 若发送隐性(1)但回读到显性(0),说明有其他节点在同时发送且具有更高优先级

  4. 仲裁失败的节点立即停止发送并转为接收,等待总线空闲后重试

  5. 仲裁在 ID 段完成,不会破坏任何有效数据

    示例:两个节点同时发送

    节点A发送: SOF 1 0 1 1 0 0 1 0 1 1 0 (ID = 0x59A)
    节点B发送: SOF 1 0 1 1 0 0 1 1 0 1 1 (ID = 0x59B)
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │ │ │ └── A发1(隐性),读到B发的0(显性),A仲裁失败!
    A转为接收 ───────┘ │ │ │ │ │ │ │
    B继续发送 ─────────────────────────────►

关键推论:CAN ID 越小且显性位越多,优先级越高。设计系统时需为高实时性消息分配小 ID。

3.4 位时序与同步

每个 CAN 位被划分为多个时间量子 (Time Quantum, TQ):

复制代码
           ┌───────────── Nominal Bit Time (NBT) ──────────────┐
           │                                                    │
           ├───── SYNC_SEG ─────┤◄───── PROP_SEG ─────┤
           │                    │                      │
           │    1 TQ            │    1--8 TQ            │
           │                    │                      │
           └──────────────────────────────────────────────────┘
           ◄────────── PHASE_SEG1 ──────────┤◄── PHASE_SEG2 ──┤
           1--8 TQ                           │    1--8 TQ       │
                                             │                 │
                                             ◄── Sample Point ─┘
参数 说明
SYNC_SEG 同步段,固定 1 TQ,位边沿应落在本段内
PROP_SEG 传播段,补偿物理传输延迟,1--8 TQ
PHASE_SEG1 相位缓冲段 1,可用于正相位漂移补偿,1--8 TQ
PHASE_SEG2 相位缓冲段 2,可用于负相位漂移补偿,1--8 TQ
SJW 同步跳转宽度,1--4 TQ,限制每次重同步的最大调整量
Sample Point 采样点,接收器在此位置判定总线电平,通常设在 75%--87.5%

STM32 CAN 简化模型:

STM32 的 bxCAN 将 PROP_SEG 和 PHASE_SEG1 合为 BS1,PHASE_SEG2 为 BS2:

复制代码
    TQ = 1 / (PCLK1 / Prescaler)

    NBT = (1 + BS1 + BS2) × TQ

    Sample Point = (1 + BS1) / (1 + BS1 + BS2)

常见波特率配置示例(APB1 = 42 MHz,Sample Point ~87.5%):

波特率 Prescaler BS1 BS2 SJW
1 Mbps 3 11 2 1
500 kbps 6 11 2 1
250 kbps 12 11 2 1
125 kbps 24 11 2 1

3.5 错误检测与处理

CAN 协议规定了 5 种错误检测机制

错误类型 检测方式 检测节点
位错误 (Bit Error) 发送器比较发出的位与总线实际电平 发送节点
格式错误 (Form Error) 检查固定格式位是否有违协议定义的值 任意节点
填充错误 (Stuff Error) 连续 5 个相同电平后应有反向填充位 任意节点
ACK 错误 (ACK Error) 发送器在 ACK Slot 未检测到显性位 发送节点
CRC 错误 (CRC Error) 接收方计算 CRC 与收到的 CRC 不匹配 接收节点

错误状态机(3 级故障隔离):

复制代码
            错误计数

  Error Active ◄──── TEC/REC ────► Error Passive ◄──── TEC/REC ────► Bus Off
  (TEC≤127      count < 128       (TEC≥128 或       count ≥ 256     (TEC≥256,
   REC≤127)                        REC>127)                          节点脱离总线)
  • TEC:发送错误计数器(Transmit Error Counter)
  • REC:接收错误计数器(Receive Error Counter)
  • Error Active:可正常收发,检测到错误时发送 6 位显性主动错误标志
  • Error Passive:仍可收发,但错误时发送 6 位隐性被动错误标志,发送后需等 8 位暂停
  • Bus Off:节点完全脱离总线,通常需软件干预恢复

4. CAN FD 协议

CAN FD(CAN with Flexible Data-Rate)是 Bosch 2012 年发布的 CAN 协议扩展,主要解决经典 CAN 两个痛点:带宽上限数据长度受限

4.1 CAN FD 与经典 CAN 的核心差异

特性 经典 CAN (2.0) CAN FD
最大数据长度 8 字节 64 字节
仲裁段速率 同数据段 同经典 CAN(通常 ≤1 Mbps)
数据段速率 同仲裁段 可切换到高速(最高 8 Mbps+)
CRC 多项式 15 位 17 位(≤16 字节)或 21 位(>16 字节)
填充方式 5 bits 固定填充 前 4 位固定填充 + 动态填充
远程帧 支持 不支持
DLC 编码 直接映射 0--8 特殊编码映射 0--64

CAN FD DLC 编码表:

DLC 值 数据字节数 DLC 值 数据字节数
0 0 8 8
1 1 9 12
2 2 10 16
3 3 11 20
4 4 12 24
5 5 13 32
6 6 14 48
7 7 15 64

4.2 CAN FD 帧结构

复制代码
  ┌──────┬──────────┬──────┬───────┬───────┬───────────┬──────┬──────┬─────┬─────┬──────┬──────┐
  │ SOF  │Arbitration│ Ctrl │ Data  │ Stuff │  CRC      │ CRC  │ ACK  │ EOF │ IFS │      │      │
  │      │   Field   │ Field│ Field │ Count │  Field    │ Delim│ Field│     │     │      │      │
  └──────┴──────────┴──────┴───────┴───────┴───────────┴──────┴──────┴─────┴─────┴──────┴──────┘
                                               ◄── 数据段可用高速传输 ──►
                                               ◄──         BRS 位控制        ──►

CAN FD 在 Control Field 中新增了 3 个标志位:

标志 含义
FDF (FD Format) 隐性位表示 CAN FD 帧,显性表示经典 CAN 帧
BRS (Bit Rate Switch) 是否在数据段切换到高速率
ESI (Error State Indicator) 发送节点的错误状态(Active = 显性, Passive = 隐性)

4.3 比特率切换 (BRS)

CAN FD 的关键创新:仲裁段保持低速(≤1 Mbps)确保兼容性和总线长度,数据段切换到高速(如 4--8 Mbps)提升吞吐量

复制代码
        仲裁段 (500 kbps)          数据段 (4 Mbps)         仲裁段 (500 kbps)
        ◄────────────────►        ◄────────────►          ◄────────────►
 ┌──────┬─────────────────┬───────┬───────────┬──────┬──────────────────┐
 │ SOF  │   ID + Control  │ BRS=1 │ 0-64 byte │ CRC  │ ACK + EOF + IFS  │
 └──────┴─────────────────┴───────┴───────────┴──────┴──────────────────┘
                                         │
                                         └── 此处切换速率

STM32 FDCAN 外设支持两套独立的位时序配置:一套用于仲裁段(Nominal),一套用于数据段(Data)。


5. STM32 CAN 外设架构

STM32 家族中,CAN 外设分为两代:

系列 CAN 外设 协议支持
F1, F2, F4, L1 bxCAN (Basic Extended CAN) CAN 2.0A/B
F7 bxCAN 增强版 CAN 2.0A/B
G0, G4, H7, L5, U5 FDCAN CAN 2.0 + CAN FD

5.1 bxCAN (F1/F4 系列)

复制代码
                        bxCAN Core
    ┌──────────────────────────────────────────────────┐
    │                                                    │
    │  ┌──────────────┐    ┌──────────────┐              │
    │  │   CAN 2.0B   │    │    Filters   │              │
    │  │  Protocol    │◄──►│  (28 Bank)   │              │
    │  │   Engine     │    │              │              │
    │  └──────────────┘    └──────────────┘              │
    │         │                                          │
    │    ┌────┴────┐                                      │
    │    │         │                                      │
    │  ┌──▼──┐ ┌──▼──┐                                   │
    │  │ 3 TX│ │ 2 RX│  Mailboxes                        │
    │  │ Mail │ │ FIFO │                                  │
    │  └─────┘ └─────┘                                   │
    └──────────────────────────────────────────────────┘

关键参数:

参数 说明
发送邮箱 3 个,按优先级发送(ID 低或 FIFO 先出)
接收 FIFO 2 个(FIFO0, FIFO1),每个深 3 帧
过滤器 28 个 Bank(互联型),14 个 Bank(基本型)

过滤器模式:

模式 说明
屏蔽位模式 指定哪些 ID 位需要比较,哪些位忽略
标识符列表模式 精确匹配一个或多个 ID

每个 Bank 可配置为 32-bit 或 16-bit,从而组合出不同的过滤能力。

5.2 FDCAN (H7/G4/G0/L5 系列)

复制代码
                          FDCAN Core
    ┌─────────────────────────────────────────────────────┐
    │                                                      │
    │  ┌────────────────┐    ┌────────────────┐            │
    │  │  CAN/CAN FD    │    │  128 Filter    │            │
    │  │  Protocol      │◄──►│  Elements      │            │
    │  │  Engine        │    │ (共享)          │            │
    │  └────────────────┘    └────────────────┘            │
    │         │                                            │
    │    ┌────┴──────────────┐                              │
    │    │    Message RAM    │                              │
    │    │  (可配置划分为)    │                              │
    │    │  ├─Tx FIFO/Q/专用 │                              │
    │    │  ├─Rx FIFO 0/1    │                              │
    │    │  ├─Rx Buffer      │                              │
    │    │  └─专用 Rx Buffer  │                              │
    │    └───────────────────┘                              │
    └──────────────────────────────────────────────────────┘

相比 bxCAN 的重大升级:

特性 bxCAN FDCAN
CAN FD 支持
最大数据长度 8 字节 64 字节
标准过滤器 28 Bank (max) 128 元素(共享)
过滤器类型 Mode + Scale 固定 5 种类型可选
发送模式 3 个固定邮箱 Tx FIFO / Tx Queue / 专用 Tx Buffer
接收方式 2 个 FIFO(深3) Rx FIFO 0/1 + 专用 Rx Buffer
Message RAM 固定 软件可配置 2560 words
时间戳 16-bit 自由计数器 16/32-bit 可选,外部 TS 输入
Pretended Networking ✅(睡眠时可过滤唤醒帧)
收发延迟补偿 ✅(高数据段速率下必需)

6. CAN 驱动设计

6.1 驱动分层架构

推荐的驱动分层:

复制代码
 ┌──────────────────────────────────┐
 │      Application Layer           │  用户业务逻辑
 │  (protocol-specific, e.g. CANopen)│
 ├──────────────────────────────────┤
 │      Middleware Layer            │  协议栈(如 CANopenNode)
 ├──────────────────────────────────┤
 │      Driver Abstraction (CAN_if) │  统一 API,屏蔽硬件差异
 ├──────────────────────────────────┤
 │      HAL/BSP Layer               │  STM32 HAL CAN API
 ├──────────────────────────────────┤
 │      Hardware (bxCAN / FDCAN)    │  STM32 外设
 └──────────────────────────────────┘

本章重点放在 HAL 层的驱动实现和设计要点。

6.2 HAL 层 CAN 初始化

以 STM32F407 (bxCAN) 为例,APB1 = 42 MHz,目标波特率 500 kbps,采样点 ~87.5%。

c 复制代码
/* can_driver.h */
#ifndef CAN_DRIVER_H
#define CAN_DRIVER_H

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

/* CAN 消息结构 */
typedef struct {
    uint32_t id;             /* CAN ID (标准帧 bit[10:0], 扩展帧 bit[28:0]) */
    uint8_t  data[8];        /* 数据 */
    uint8_t  dlc;            /* 数据长度 0--8 */
    bool     is_ext;         /* true = 扩展帧, false = 标准帧 */
    bool     is_rtr;         /* true = 远程帧 */
} can_msg_t;

/* CAN 配置 */
typedef struct {
    CAN_HandleTypeDef *hcan;
    uint32_t            nominal_baud;  /* 仲裁波特率, e.g. 500000 */
    bool                loopback;      /* 回环模式 */
} can_config_t;

/* API */
bool can_init(can_config_t *cfg);
bool can_send(can_msg_t *msg);
bool can_recv(can_msg_t *msg, uint32_t timeout_ms);
bool can_config_filter(uint32_t filter_id, uint32_t mask, bool is_ext);

#endif
c 复制代码
/* can_driver.c */
#include "can_driver.h"

static CAN_HandleTypeDef *can_handle;

/**
 * 根据目标波特率自动计算位时序参数
 * APB1 时钟默认为 42 MHz (STM32F407, HCLK=168MHz, APB1 prescaler=4)
 */
static void can_calc_timing(uint32_t baud, CAN_HandleTypeDef *hcan)
{
    uint32_t pclk1 = HAL_RCC_GetPCLK1Freq();

    /*
     * 目标:采样点 ≈ 87.5%
     * NBT = (1 + BS1 + BS2) TQ, SamplePoint = (1 + BS1) / (1 + BS1 + BS2)
     * BS1 = 11, BS2 = 2 → NBT = 14, SP = 12/14 = 85.7% (接近)
     * 其他常用组合:
     *   BS1=13, BS2=2 → NBT=16, SP=14/16=87.5%
     *   BS1=15, BS2=2 → NBT=18, SP=16/18=88.9%
     */
    uint32_t bs1, bs2, prescaler;

    if (baud >= 1000000) {
        bs1 = 11; bs2 = 2;   /* 14 TQ, SP = 85.7% */
    } else {
        bs1 = 13; bs2 = 2;   /* 16 TQ, SP = 87.5% */
    }

    uint32_t nbt = 1 + bs1 + bs2;
    prescaler = pclk1 / (baud * nbt);

    hcan->Init.Prescaler     = prescaler;
    hcan->Init.TimeSeg1      = bs1;     /* BS1 = TS1 */
    hcan->Init.TimeSeg2      = bs2;     /* BS2 = TS2 */
    hcan->Init.SyncJumpWidth = CAN_SJW_1TQ;
}

bool can_init(can_config_t *cfg)
{
    can_handle = cfg->hcan;

    /* 计算位时序 */
    can_calc_timing(cfg->nominal_baud, can_handle);

    can_handle->Init.Mode                = cfg->loopback
                                           ? CAN_MODE_LOOPBACK
                                           : CAN_MODE_NORMAL;
    can_handle->Init.AutoBusOff          = ENABLE;
    can_handle->Init.AutoWakeUp          = DISABLE;
    can_handle->Init.AutoRetransmission  = ENABLE;  /* 硬件自动重传 */
    can_handle->Init.ReceiveFifoLocked   = DISABLE; /* FIFO 满时新消息覆盖旧 */
    can_handle->Init.TransmitFifoPriority= ENABLE;  /* 按 ID 优先级发送 */
    can_handle->Init.TimeTriggeredMode   = DISABLE;

    if (HAL_CAN_Init(can_handle) != HAL_OK) {
        return false;
    }

    /* 配置 CAN 滤波器:关闭滤波,先接收所有帧 */
    CAN_FilterTypeDef filter = {0};
    filter.FilterMode    = CAN_FILTERMODE_IDMASK;
    filter.FilterScale   = CAN_FILTERSCALE_32BIT;
    filter.FilterIdHigh  = 0x0000;
    filter.FilterIdLow   = 0x0000;
    filter.FilterMaskIdHigh = 0x0000;
    filter.FilterMaskIdLow  = 0x0000;
    filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    filter.FilterActivation    = ENABLE;
    filter.FilterBank          = 0;

    if (HAL_CAN_ConfigFilter(can_handle, &filter) != HAL_OK) {
        return false;
    }

    /* 启动 CAN 外设 */
    if (HAL_CAN_Start(can_handle) != HAL_OK) {
        return false;
    }

    return true;
}

6.3 发送与接收

c 复制代码
bool can_send(can_msg_t *msg)
{
    CAN_TxHeaderTypeDef tx_header = {0};
    uint32_t            tx_mailbox;

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

    tx_header.RTR   = msg->is_rtr ? CAN_RTR_REMOTE : CAN_RTR_DATA;
    tx_header.DLC   = msg->dlc;
    tx_header.TransmitGlobalTime = DISABLE;

    /*
     * HAL_CAN_AddTxMessage 将消息放入 3 个发送邮箱的空闲项
     * 如果全部满 → HAL_CAN_STATE_BUSY → 需重试或丢弃
     */
    if (HAL_CAN_AddTxMessage(can_handle, &tx_header, msg->data, &tx_mailbox) != HAL_OK) {
        return false;
    }

    return true;
}

bool can_recv(can_msg_t *msg, uint32_t timeout_ms)
{
    CAN_RxHeaderTypeDef rx_header = {0};
    uint32_t            tickstart = HAL_GetTick();

    /* 轮询等待 FIFO 中有消息 */
    while (HAL_CAN_GetRxFifoFillLevel(can_handle, CAN_RX_FIFO0) == 0) {
        if ((HAL_GetTick() - tickstart) > timeout_ms) {
            return false;  /* 超时 */
        }
    }

    if (HAL_CAN_GetRxMessage(can_handle, CAN_RX_FIFO0, &rx_header, msg->data) != HAL_OK) {
        return false;
    }

    msg->is_ext = (rx_header.IDE == CAN_ID_EXT);
    msg->id     = msg->is_ext ? rx_header.ExtId : rx_header.StdId;
    msg->dlc    = rx_header.DLC;
    msg->is_rtr = (rx_header.RTR == CAN_RTR_REMOTE);

    return true;
}

6.4 过滤器配置

过滤器的设置直接影响接收效率------未通过过滤器匹配的帧在硬件层即被丢弃,不占用 CPU。

c 复制代码
bool can_config_filter(uint32_t filter_id, uint32_t mask, bool is_ext)
{
    CAN_FilterTypeDef filter = {0};
    uint32_t bank = 0;  /* 根据实际占用情况分配 Bank 编号 */

    filter.FilterMode  = CAN_FILTERMODE_IDMASK;
    filter.FilterScale = CAN_FILTERSCALE_32BIT;
    filter.FilterBank  = bank;
    filter.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    filter.FilterActivation    = ENABLE;

    if (is_ext) {
        filter.FilterIdHigh      = (filter_id << 3) & 0xFFFF;
        filter.FilterIdLow       = ((filter_id << 3) >> 16) | CAN_ID_EXT;
        filter.FilterMaskIdHigh  = (mask << 3) & 0xFFFF;
        filter.FilterMaskIdLow   = ((mask << 3) >> 16) | CAN_ID_EXT;
    } else {
        filter.FilterIdHigh      = (filter_id << 5) & 0xFFFF;
        filter.FilterIdLow       = 0;
        filter.FilterMaskIdHigh  = (mask << 5) & 0xFFFF;
        filter.FilterMaskIdLow   = 0;
    }

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

bxCAN 过滤器注意事项:

  • 每个 Bank 的 FilterIdFilterMask 的值需要左移 到正确位位置
    • 标准帧 11-bit ID 需左移 5 位(对齐到 bit15:5
    • 扩展帧 29-bit ID 需左移 3 位(对齐到 bit31:3
  • 对于 16-bit 模式,每个 Bank 可存 4 个标准帧 ID(列表模式)或 2 组 ID+Mask

6.5 中断与回调

对于实时性要求较高的场景(如 CANopen 同步帧处理),建议使用中断模式:

c 复制代码
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef rx_header;
    uint8_t             data[8];

    HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, data);

    /* 将数据存入环形缓冲区或发信号给处理任务 */
    uint32_t id = (rx_header.IDE == CAN_ID_EXT) ? rx_header.ExtId : rx_header.StdId;
    rx_buffer_push(id, data, rx_header.DLC, rx_header.IDE == CAN_ID_EXT);
}

/* 错误回调 */
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
    uint32_t error = HAL_CAN_GetError(hcan);

    if (error & HAL_CAN_ERROR_BOF) {
        /* 节点进入 Bus-Off,需要复位重新初始化 */
        HAL_CAN_Stop(hcan);
        HAL_CAN_ResetError(hcan);
        // 等待至少 128 次 11 个隐性位后再启动
        HAL_CAN_Start(hcan);
    }
}

/* 使能中断 */
HAL_CAN_ActivateNotification(can_handle,
    CAN_IT_RX_FIFO0_MSG_PENDING |
    CAN_IT_ERROR_WARNING        |
    CAN_IT_ERROR_PASSIVE        |
    CAN_IT_BUSOFF               |
    CAN_IT_LAST_ERROR_CODE);

7. CAN FD 驱动设计

7.1 FDCAN 初始化

以 STM32H743 为例(或 G473/G474),使用 STM32 HAL FDCAN 驱动。

c 复制代码
/* fdcan_driver.h */
#ifndef FDCAN_DRIVER_H
#define FDCAN_DRIVER_H

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

/* CAN FD 消息结构 */
typedef struct {
    uint32_t id;
    uint8_t  data[64];   /* 最多 64 字节 */
    uint8_t  dlc;        /* 实际长度 0--64 */
    bool     is_ext;
    bool     is_fd;      /* true = CAN FD 帧, false = 经典 CAN */
    bool     brs;        /* 比特率切换使能 */
} fdcan_msg_t;

/* FDCAN 配置 */
typedef struct {
    FDCAN_HandleTypeDef *hfdcan;
    uint32_t nominal_baud;    /* 仲裁段波特率, e.g. 500000 */
    uint32_t data_baud;       /* 数据段波特率, e.g. 4000000 */
    bool     loopback;
} fdcan_config_t;

bool fdcan_init(fdcan_config_t *cfg);
bool fdcan_send(fdcan_msg_t *msg);
bool fdcan_recv(fdcan_msg_t *msg, uint32_t timeout_ms);

#endif
c 复制代码
/* fdcan_driver.c */
#include "fdcan_driver.h"

static FDCAN_HandleTypeDef *fdcan_handle;

/*
 * FDCAN 初始化 --- 配置双波特率
 * FDCAN 外设时钟来源在 H7 系列为 PCLK1 (APB1),G4 系列为 PCLK1
 */
bool fdcan_init(fdcan_config_t *cfg)
{
    fdcan_handle = cfg->hfdcan;

    FDCAN_InitTypeDef init = {0};
    init.ClockDivider           = FDCAN_CLOCK_DIV1;
    init.FrameFormat            = FDCAN_FRAME_FD_BRS;  /* 启用 CAN FD + BRS */
    init.Mode                   = cfg->loopback
                                  ? FDCAN_MODE_INTERNAL_LOOPBACK
                                  : FDCAN_MODE_NORMAL;
    init.AutoRetransmission     = ENABLE;
    init.TransmitPause          = DISABLE;
    init.ProtocolException      = ENABLE;  /* 经典 CAN 模式下忽略 FD 帧 */

    /* ---- 仲裁段 (Nominal) 位时序 ---- */
    init.NominalPrescaler       = 1;  /* 需要根据实际时钟计算 */
    init.NominalTimeSeg1        = 29;
    init.NominalTimeSeg2        = 4;
    init.NominalSyncJumpWidth   = 4;

    /* ---- 数据段 (Data) 位时序 ---- */
    init.DataPrescaler          = 1;
    init.DataTimeSeg1           = 4;
    init.DataTimeSeg2           = 1;
    init.DataSyncJumpWidth      = 1;

    /*
     * 位时序计算参考(H743 APB1 = 200 MHz 示例):
     *
     * Nominal 500 kbps: NBT=34, TQ=1/(200M/1)=5ns, BR=1/(34×5ns)≈588kbps≈500kbps
     * Data 4 Mbps:     DBT=6,  TQ=5ns, BR=1/(6×5ns)≈3.33Mbps  需要调整 Prescaler
     *
     * 推荐工具:使用 STM32CubeMX 的 CAN 位时序计算器
     */

    if (HAL_FDCAN_Init(fdcan_handle, &init) != HAL_OK) {
        return false;
    }

    /* ---- 配置过滤器:接收所有帧 ---- */
    FDCAN_FilterTypeDef filter = {0};
    filter.IdType       = FDCAN_STANDARD_ID;
    filter.FilterIndex  = 0;
    filter.FilterType   = FDCAN_FILTER_MASK;
    filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    filter.FilterID1    = 0x0000;
    filter.FilterID2    = 0x0000;

    if (HAL_FDCAN_ConfigFilter(fdcan_handle, &filter) != HAL_OK) {
        return false;
    }

    /* 配置全局过滤器(扩展帧) */
    filter.IdType      = FDCAN_EXTENDED_ID;
    filter.FilterIndex = 1;
    filter.FilterID1   = 0x00000000;
    filter.FilterID2   = 0x00000000;

    if (HAL_FDCAN_ConfigFilter(fdcan_handle, &filter) != HAL_OK) {
        return false;
    }

    /* ---- 启动 FDCAN ---- */
    if (HAL_FDCAN_Start(fdcan_handle) != HAL_OK) {
        return false;
    }

    return true;
}

FDCAN 位时序计算关键点:

FDCAN 的位时序与 bxCAN 有所不同------NominalSyncJumpWidth 需要 ≤ min(4, NominalTimeSeg2),且对于高仲裁速率需要缩小:

复制代码
  Nominal Bit Time (NBT) = (1 + NominalTimeSeg1 + NominalTimeSeg2) × NominalPrescaler / f_FDCAN

  Data Bit Time (DBT)    = (1 + DataTimeSeg1 + DataTimeSeg2) × DataPrescaler / f_FDCAN

Sample Point 位置:

复制代码
  Nominal SP = (1 + NominalTimeSeg1) / (1 + NominalTimeSeg1 + NominalTimeSeg2)
  Data SP    = (1 + DataTimeSeg1)    / (1 + DataTimeSeg1 + DataTimeSeg2)

收发延迟补偿 :数据段高速运行时,收发器的环路延迟可能超过一个 TQ,导致发送器在采样点无法正确检测自身的位。FDCAN 提供 Transmitter Delay Compensation (TDC) 功能解决此问题:

c 复制代码
init.TransmitterDelayCompensation = ENABLE;
init.TransmitterDelayCompensationOffset = 0; /* 通常由 SSP 自动确定 */

启用 TDC 后,发送器使用次采样点 (SSP) 而非正常采样点检查自身发出的位。

7.2 FDCAN 发送与接收

c 复制代码
bool fdcan_send(fdcan_msg_t *msg)
{
    FDCAN_TxHeaderTypeDef tx_header = {0};

    tx_header.Identifier = msg->id;

    if (msg->is_ext) {
        tx_header.IdType = FDCAN_EXTENDED_ID;
    } else {
        tx_header.IdType = FDCAN_STANDARD_ID;
    }

    tx_header.TxFrameType = FDCAN_DATA_FRAME;

    if (msg->is_fd) {
        tx_header.FDFormat = FDCAN_FD_CAN;        /* CAN FD 帧 */
    } else {
        tx_header.FDFormat = FDCAN_CLASSIC_CAN;
    }

    tx_header.BitRateSwitch = msg->brs ? FDCAN_BRS_ON : FDCAN_BRS_OFF;
    tx_header.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
    tx_header.DataLength    = msg->dlc;  /* HAL 自动根据 DLC 编码 */
    tx_header.MessageMarker = 0;
    tx_header.TxEventFifoControl = FDCAN_STORE_TX_EVENTS;

    if (HAL_FDCAN_AddMessageToTxFifoQ(fdcan_handle, &tx_header,
                                      msg->data) != HAL_OK) {
        return false;
    }

    return true;
}

bool fdcan_recv(fdcan_msg_t *msg, uint32_t timeout_ms)
{
    FDCAN_RxHeaderTypeDef rx_header = {0};
    uint32_t tickstart = HAL_GetTick();

    while (HAL_FDCAN_GetRxFifoFillLevel(fdcan_handle, FDCAN_RX_FIFO0) == 0) {
        if ((HAL_GetTick() - tickstart) > timeout_ms) {
            return false;
        }
    }

    if (HAL_FDCAN_GetRxMessage(fdcan_handle, FDCAN_RX_FIFO0,
                                &rx_header, msg->data) != HAL_OK) {
        return false;
    }

    msg->id     = rx_header.Identifier;
    msg->is_ext = (rx_header.IdType == FDCAN_EXTENDED_ID);
    msg->is_fd  = (rx_header.FDFormat == FDCAN_FD_CAN);
    msg->brs    = (rx_header.BitRateSwitch == FDCAN_BRS_ON);
    msg->dlc    = rx_header.DataLength;  /* HAL 已解码为实际字节数 */

    return true;
}

7.3 FDCAN FIFO 与专用缓冲区

FDCAN 的 Message RAM 可灵活配置,以下展示在初始化前配置 RAM 分配:

c 复制代码
/*
 * 必须在 HAL_FDCAN_Init() 之前完成配置!
 */

void fdcan_config_message_ram(FDCAN_HandleTypeDef *hfdcan)
{
    /*
     * Message RAM 共 2560 words(32-bit),需手动分配各区域:
     *  - Standard Filter List (11-bit)
     *  - Extended Filter List (29-bit)
     *  - Rx FIFO 0 / Rx FIFO 1
     *  - Rx Buffer (dedicated)
     *  - Tx Event FIFO
     *  - Tx FIFO / Tx Queue / Dedicated Tx Buffers
     */

    /* 示例:分配 64 个 Rx FIFO0 元素 + 32 个 Tx FIFO 元素 */
    hfdcan->Init.StdFiltersNbr       = 2;      /* 标准帧滤波器元素数 */
    hfdcan->Init.ExtFiltersNbr       = 2;      /* 扩展帧滤波器元素数 */
    hfdcan->Init.RxFifo0ElmtsNbr     = 64;     /* Rx FIFO0 深 64 */
    hfdcan->Init.RxFifo0ElmtSize     = FDCAN_DATA_BYTES_64;  /* 每元素 64 字节 */
    hfdcan->Init.RxFifo1ElmtsNbr     = 0;
    hfdcan->Init.RxBuffersNbr        = 0;
    hfdcan->Init.TxEventsNbr         = 32;     /* 发送事件 FIFO 深 32 */
    hfdcan->Init.TxBuffersNbr        = 0;
    hfdcan->Init.TxFifoQueueElmtsNbr = 32;     /* Tx FIFO 深 32 */
    hfdcan->Init.TxFifoQueueMode     = FDCAN_TX_FIFO_OPERATION; /* FIFO 模式 */
    hfdcan->Init.TxElmtSize          = FDCAN_DATA_BYTES_64;
}

Tx Queue 模式 vs Tx FIFO 模式:

模式 行为
Tx FIFO 先入先出,按存入顺序发送
Tx Queue 按 CAN ID 优先级发送(类似 bxCAN 的 TransmitFifoPriority)

8. 驱动实战示例

8.1 经典 CAN 回环测试

硬件:STM32F407 + CAN1,无需外部收发器,回环模式内部自测。

c 复制代码
#include "stm32f4xx_hal.h"
#include "can_driver.h"

CAN_HandleTypeDef hcan1;
can_config_t      can_cfg;
can_msg_t         tx_msg, rx_msg;

void test_can_loopback(void)
{
    /* ---- 1. 初始化 CAN ---- */
    hcan1.Instance = CAN1;

    can_cfg.hcan         = &hcan1;
    can_cfg.nominal_baud = 500000;   /* 500 kbps */
    can_cfg.loopback     = true;

    if (!can_init(&can_cfg)) {
        /* 初始化失败处理 */
        return;
    }

    /* ---- 2. 发送测试帧 ---- */
    tx_msg.id     = 0x123;           /* 标准帧 ID = 0x123 */
    tx_msg.is_ext = false;
    tx_msg.is_rtr = false;
    tx_msg.dlc    = 8;
    tx_msg.data[0] = 0xDE;
    tx_msg.data[1] = 0xAD;
    tx_msg.data[2] = 0xBE;
    tx_msg.data[3] = 0xEF;
    tx_msg.data[4] = 0xCA;
    tx_msg.data[5] = 0xFE;
    tx_msg.data[6] = 0xBA;
    tx_msg.data[7] = 0xBE;

    if (!can_send(&tx_msg)) {
        /* 发送失败处理 */
        return;
    }

    /* ---- 3. 接收验证 ---- */
    if (can_recv(&rx_msg, 100)) {    /* 100ms 超时 */
        /* 验证接收到的数据与发送数据一致 */
        if (rx_msg.id == tx_msg.id &&
            rx_msg.dlc == tx_msg.dlc &&
            memcmp(rx_msg.data, tx_msg.data, tx_msg.dlc) == 0) {
            /* 测试通过! */
        }
    }
}

8.2 CAN FD 双节点通信

硬件:2× STM32H743 Nucleo,通过 CAN FD 收发器(如 TCAN1042)连接。

节点 A --- 发送:

c 复制代码
#include "fdcan_driver.h"

FDCAN_HandleTypeDef hfdcan1;
fdcan_config_t      fdcan_cfg;
fdcan_msg_t         tx_msg;

void node_a_transmit(void)
{
    /* FDCAN1 初始化 */
    hfdcan1.Instance = FDCAN1;

    fdcan_cfg.hfdcan       = &hfdcan1;
    fdcan_cfg.nominal_baud = 500000;    /* 仲裁段 500 kbps */
    fdcan_cfg.data_baud    = 4000000;   /* 数据段 4 Mbps */
    fdcan_cfg.loopback     = false;

    fdcan_init(&fdcan_cfg);

    /* 构造 CAN FD 帧:64 字节数据 */
    tx_msg.id     = 0x200;
    tx_msg.is_ext = false;
    tx_msg.is_fd  = true;
    tx_msg.brs    = true;               /* 启用比特率切换 */
    tx_msg.dlc    = 64;                 /* 64 字节有效载荷 */

    for (int i = 0; i < 64; i++) {
        tx_msg.data[i] = i;
    }

    /* 周期发送 (1 Hz) */
    while (1) {
        if (fdcan_send(&tx_msg)) {
            /* 发送成功 --- 切换 LED */
            HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
        }
        HAL_Delay(1000);
    }
}

节点 B --- 接收:

c 复制代码
#include "fdcan_driver.h"

FDCAN_HandleTypeDef hfdcan1;
fdcan_config_t      fdcan_cfg;
fdcan_msg_t         rx_msg;

void node_b_receive(void)
{
    hfdcan1.Instance = FDCAN1;

    fdcan_cfg.hfdcan       = &hfdcan1;
    fdcan_cfg.nominal_baud = 500000;
    fdcan_cfg.data_baud    = 4000000;
    fdcan_cfg.loopback     = false;

    fdcan_init(&fdcan_cfg);

    /* 配置滤波器:仅接收 ID 0x200 */
    FDCAN_FilterTypeDef filter = {0};
    filter.IdType       = FDCAN_STANDARD_ID;
    filter.FilterIndex  = 0;
    filter.FilterType   = FDCAN_FILTER_MASK;
    filter.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
    filter.FilterID1    = 0x200;
    filter.FilterID2    = 0x7FF;  /* 标准帧全位掩码 */
    HAL_FDCAN_ConfigFilter(&hfdcan1, &filter);

    while (1) {
        if (fdcan_recv(&rx_msg, HAL_MAX_DELAY)) {
            /* 处理接收数据 */
            if (rx_msg.id == 0x200 && rx_msg.dlc == 64) {
                /* 数据完整性校验 */
                bool valid = true;
                for (int i = 0; i < 64; i++) {
                    if (rx_msg.data[i] != i) {
                        valid = false;
                        break;
                    }
                }
                if (valid) {
                    HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
                }
            }
        }
    }
}

9. 调试与常见问题

9.1 常见问题排查

现象 可能原因 排查方法
CAN 初始化失败 时钟未使能,GPIO 未配置 AF 检查 __HAL_RCC_CANx_CLK_ENABLE() 调用
发送始终失败 总线无应答(ACK Error) 测量终端电阻(两端各 120Ω),检查线缆
接收始终为空 滤波器不匹配,FIFO 满 先配置全通滤波器测试,检查 FillLevel
错误中断不断触发 波特率不匹配 用示波器测量最窄位宽,反算波特率
Bus-Off 发送错误计数 ≥256 检查终端电阻 / 线缆 / 地线,重启 CAN 外设
CAN FD 通信异常 BRS 不匹配,TDC 未配置 确认双方 FD 配置一致,数据段 > 1 Mbps 时启用 TDC
采样点配置不当 边沿抖动时误判 推荐采样点 75%--87.5%,星形拓扑用高值

9.2 调试工具

工具 用途
逻辑分析仪 + CAN 解码 抓取总线波形,验证帧格式和内容
CAN 分析仪 (PCAN / Kvaser) 作为参考节点收发,验证通信
STM32CubeMonitor 实时观测 STM32 内部变量和 CAN 状态
示波器 (差分探头) 检查信号质量、反射、振铃
CAN in Automation (CiA) 官方规范、应用笔记

9.3 设计建议清单

  • 终端电阻:总线两端各并接 120Ω(±1%),不可多接。未正确端接将导致信号反射和仲裁失败。
  • 线缆选择:使用 120Ω 特性阻抗双绞线(如 NXP TJA 应用笔记推荐的 AWG22--AWG28 双绞线)。
  • 总线长度 vs 速率
    • 1 Mbps → ≤ 40 m
    • 500 kbps → ≤ 100 m
    • 250 kbps → ≤ 250 m
    • 125 kbps → ≤ 500 m
    • 50 kbps → ≤ 1000 m
  • CAN FD 数据段高速:一旦数据段速率超过 2 Mbps,强烈建议启用 TDC。
  • 收发器匹配:CAN FD 应用务必选用支持 FD 的收发器(如 TCAN1042、TJA1057)。

10. CanFestival 驱动集成

CanFestival-3 是一个开源的 CANopen 协议栈。它与 CAN 硬件驱动之间通过标准化的 5 函数接口解耦。本章说明如何将上述 HAL 驱动集成到 CanFestival 中。

10.1 CanFestival 驱动架构

复制代码
 ┌───────────────────────────────────┐
 │   CANopen 协议栈 (src/*.c)         │  对象字典、PDO、SDO、NMT、SYNC、EMCY
 ├───────────────────────────────────┤
 │   canSend() / canReceive()         │  统一 API (include/can_driver.h)
 ├───────────────────────────────────┤
 │   平台适配层 (src/driver.c)         │  编译时选择具体驱动
 ├──────────────┬────────────────────┤
 │  bxCAN 驱动   │  FDCAN 驱动         │  drivers/STM32/stm32.c
 │  (F1/F4)     │  (H7/G4/G0)        │  drivers/STM32/stm32_fdcan.c
 └──────────────┴────────────────────┘

10.2 驱动 API 约定

所有硬件驱动必须实现以下 5 个函数(在 include/can_driver.h 中声明):

函数 签名 功能
canSend UNS8 (CAN_PORT, Message *) 发送一帧 CAN 消息,0 = 成功
canReceive UNS8 (CAN_PORT, Message *) 从缓冲区取一帧,0 = 成功
canOpen CAN_HANDLE (s_BOARD *, CO_Data *) 初始化硬件并返回句柄
canClose int (CAN_HANDLE) 关闭硬件,释放资源
canChangeBaudRate UNS8 (CAN_HANDLE, char *) 运行时修改波特率

Message 结构体(include/can.h):

c 复制代码
typedef struct {
  UNS16 cob_id;     /* CAN ID (11-bit 标准帧或 29-bit 扩展帧) */
  UNS8  rtr;        /* 远程帧标志 (0 = 数据帧, 1 = 远程帧)   */
  UNS8  len;        /* 数据长度 (0--8 字节)                    */
  UNS8  data[8];    /* 有效载荷                                */
} Message;

命名约定 :在 Windows/Linux 平台上使用动态加载(.dll/.so),函数实际名为 canSend_driver 等(通过 DLL_CALL 宏拼接)。嵌入式平台直接使用 canSendcanReceive 等名称,编译进固件。

10.3 平台选择

src/driver.c 通过预处理器宏选择驱动:

c 复制代码
/* STM32 FDCAN 平台 (H7 / G4 / G0 / L5 / U5) */
#elif defined(STM32H7)   || defined(STM32G4)   || defined(STM32G0) \
   || defined(STM32L5)   || defined(STM32U5)
#include "../drivers/STM32/stm32_fdcan.c"

/* STM32 bxCAN 平台 (F1 / F2 / F3 / F4 / F7) */
#elif defined(STM32F4) || defined(STM32F1) || defined(STM32F3) \
   || defined(STM32F7) || defined(STM32L1)
#include "../drivers/STM32/stm32.c"

在编译时通过 -Dstm32xx_hal_conf.h 传入对应宏即可。

10.4 配置文件

每个平台在 include/<PLATFORM>/ 下有 4 个配置文件:

文件 内容
config.h 栈资源限制(SDO_BLOCK_SIZE, MAX_NB_TIMER, NMT_MAX_NODE_ID 等)
applicfg.h 类型定义(UNS8, UNS16, UNS32, UNS64 等),CAN_HANDLE, CAN_PORT
timerscfg.h 定时器参数(TIMEVAL 类型,TIMER_MAX_COUNT,时间转换宏)
canfestival.h 公共 API 声明(canSend, canOpen, setTimer 等)

STM32 配置文件位于 include/STM32/,同时适用于 bxCAN 和 FDCAN 驱动。

10.5 用户需要提供的硬件初始化

驱动文件(stm32.c / stm32_fdcan.c)假设以下 HAL 初始化由用户在 main.c 中完成:

c 复制代码
/* main.c --- 必须的硬件初始化 */

CAN_HandleTypeDef   hcan;   /* 或 FDCAN_HandleTypeDef hfdcan */
TIM_HandleTypeDef   htim7;

void SystemClock_Config(void);  /* CubeMX 生成,确保 CAN 时钟已使能 */

void MX_GPIO_Init(void)         /* CubeMX 生成,配置 CAN_RX/CAN_TX 的 AF 模式 */
{
    /* PA11 = CAN1_RX (AF9), PA12 = CAN1_TX (AF9) */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    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_HIGH;
    gpio.Alternate = GPIO_AF9_CAN1;
    HAL_GPIO_Init(GPIOA, &gpio);
}

/* CAN MSP 初始化 (时钟 + NVIC) */
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcan)
{
    if (hcan->Instance == CAN1) {
        __HAL_RCC_CAN1_CLK_ENABLE();
        HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
        HAL_NVIC_SetPriority(CAN1_TX_IRQn, 1, 0);
        /* TX 中断通常不使能------发送使用轮询 */
    }
}

void MX_TIM7_Init(void)         /* 1 ms 时基 */
{
    __HAL_RCC_TIM7_CLK_ENABLE();
    htim7.Instance = TIM7;
    htim7.Init.Prescaler   = 83;      /* 84 MHz / 84 = 1 MHz */
    htim7.Init.Period      = 999;     /* 1 MHz / 1000 = 1 kHz */
    htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_Base_Init(&htim7);
    HAL_NVIC_SetPriority(TIM7_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(TIM7_IRQn);
    HAL_TIM_Base_Start_IT(&htim7);
}

10.6 应用程序示例

完整的 CanFestival + STM32 CAN 驱动初始化流程:

c 复制代码
#include "canfestival.h"

CO_Data     canopen_node;

void canopen_app_init(void)
{
    Message m = Message_Initializer;

    /* 1. 初始化硬件 */
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM7_Init();

    /* 2. 打开 CAN */
    s_BOARD board = {
        .busname  = "CAN1",
        .baudrate = "500000"
    };

    if (canOpen(&board, &canopen_node) == NULL) {
        /* 初始化失败处理 */
        Error_Handler();
    }

    /* 3. 设置节点 ID 并初始化 CANopen 栈 */
    setNodeId(&canopen_node, 0x02);  /* Node ID = 2 */

    /* 4. 进入 NMT Operational (通过发送 NMT 启动命令) */
    UNS8 nmt_start[2] = {0x01, 0x02};  /* NMT Start, Node 2 */
    m.cob_id = 0x000;                   /* NMT 功能码 */
    m.rtr    = 0;
    m.len    = 2;
    m.data[0] = nmt_start[0];
    m.data[1] = nmt_start[1];
    canSend(NULL, &m);
}

/* 主循环:调用 CanFestival 定时任务 */
int main(void)
{
    canopen_app_init();

    while (1) {
        /* 处理收到的 CAN 消息 */
        Message m;
        if (canReceive(&m) == 0) {
            canDispatch(&canopen_node, &m);
        }
        /* TIM7 中断驱动 TimeDispatch(),无需轮询 */
    }
}

10.7 使用 CAN FD 扩展 API(FDCAN 驱动)

对于需要 CAN FD 大帧(> 8 字节)的应用,FDCAN 驱动提供了扩展接口:

c 复制代码
/* 发送 64 字节 CAN FD 帧 */
fdcan_msg_t fd_msg;
fd_msg.id     = 0x300;
fd_msg.is_ext = false;
fd_msg.is_fd  = true;
fd_msg.brs    = true;          /* 数据段切换到 4 Mbps */
fd_msg.dlc    = 64;

for (int i = 0; i < 64; i++) {
    fd_msg.data[i] = (uint8_t)(i * 3);
}

fdcan_send_ext(&fd_msg);

/* 接收 */
fdcan_msg_t rx_fd;
if (fdcan_recv_ext(&rx_fd) == 0) {
    /* 处理 rx_fd.data[0..rx_fd.dlc-1] */
}

通过 #define FDCAN_CLASSIC_ONLY 可禁用扩展接口,减小代码体积。


11. 参考资料

来源 说明
Bosch CAN Specification 2.0 (1991) 经典 CAN 协议权威文档
Bosch CAN FD Specification 1.0 (2012) CAN FD 协议官方规范
ISO 11898-1:2015 CAN 国际标准(含 CAN FD)
ISO 11898-2:2016 CAN 物理层标准
STM32F4 Reference Manual (RM0090) bxCAN 章节
STM32H7 Reference Manual (RM0433) FDCAN 章节
STM32G4 Reference Manual (RM0440) FDCAN 章节
AN5348 (STM32) FDCAN 外设应用笔记
CiA 301 CANopen 应用层协议
CiA 601-4 CAN FD 节点设计建议

相关推荐
proware21 天前
rk3588适配MCP2515 SPI转CAN
can·spi·3588
时光の尘1 个月前
【嵌入式大厂面经】·CAN总线常见考点(持续更新中···)
stm32·单片机·mcu·物联网·can·ack
不知秋8801 个月前
PCAN-View核心功能使用手册
can·以太网·codesys·peakcan·pcan-view
嵌软小白呗1 个月前
Autosar-SecOC功能详解(一)
e2e·can·autosar·crc·secoc
【ql君】qlexcel2 个月前
STM32 移植 CANopenNode 主站、控制伺服器位置模式运行
pdo·canopen·canopennode·伺服器·sdo
zmj3203242 个月前
UDS 0x27 安全访问(种子 / 密钥 Seed-Key) 的用法、流程、算法、存储位置、安全机制
安全·can·诊断·uds·27服务
南金研高新科技(南京)有限公司2 个月前
南金研CAN数采仪助力吉利远程商用车售后问题处理
can·硬件·记录仪
zmj3203242 个月前
CAN FD CRC17 真实完整示例 + CRC15 / CRC17 详细对比
网络·crc·canfd