CAN/CANFD 笔记
以 STM32 平台为例,涵盖经典 CAN 2.0 与 CAN FD 的协议原理、外设架构及驱动实现。
目录
- [CAN 总线概述](#CAN 总线概述)
- [CAN 物理层](#CAN 物理层)
- [CAN 协议详解](#CAN 协议详解)
- [3.1 帧类型](#3.1 帧类型)
- [3.2 数据帧结构](#3.2 数据帧结构)
- [3.3 仲裁机制](#3.3 仲裁机制)
- [3.4 位时序与同步](#3.4 位时序与同步)
- [3.5 错误检测与处理](#3.5 错误检测与处理)
- [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))
- [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 系列))
- [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 中断与回调)
- [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.1 经典 CAN 回环测试](#8.1 经典 CAN 回环测试)
- [8.2 CAN FD 双节点通信](#8.2 CAN FD 双节点通信)
- 调试与常见问题
- [CanFestival 驱动集成](#CanFestival 驱动集成)
- 参考资料
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(载波侦听多路访问/冲突避免) 的非破坏性逐位仲裁:
-
总线空闲(连续 11 个隐性位)后,任意节点均可发起发送
-
每发送一位同时回读总线电平
-
若发送隐性(1)但回读到显性(0),说明有其他节点在同时发送且具有更高优先级
-
仲裁失败的节点立即停止发送并转为接收,等待总线空闲后重试
-
仲裁在 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 的
FilterId和FilterMask的值需要左移 到正确位位置- 标准帧 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) 功能解决此问题:
cinit.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宏拼接)。嵌入式平台直接使用canSend、canReceive等名称,编译进固件。
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"
在编译时通过 -D 或 stm32xx_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 节点设计建议 |