📚 CAN总线技术系列 · 持续更新中
本系列从差分电气层一路打到 Linux SocketCAN,把"两根线"背后的电气/协议/控制器/驱动/实战全拧成一根线。
👉 想系统追更,戳这里查看完整专栏:
👉 其他技术系列专栏,欢迎关注、交流、共同成长:
【SPI 技术系列】 【I2C 技术系列】 【串口技术系列】【RISC-V 架构系列】【Linux内核系列】 【操作系统设计思想系列】 【 Android 系统全景专栏】
本篇定位:L3 驱动实现层。寄存器讲完,本篇讲三种传输模式:轮询(死等标志)、中断(状态机+ISR)、DMA(大块传输)。CAN 和 SPI 不同:CAN 有多邮箱/多 FIFO,可 DMA 接收大块。本篇讲三种模式代码、发送邮箱队列、接收 FIFO 处理、RTOS 集成。读完你能写生产级 CAN 驱动。
!important CAN 传输模式比 SPI 复杂SPI 三种模式(轮询/中断/DMA)相对简单(全双工一缓冲)。CAN 有 3 发送邮箱 + 多接收 FIFO + 验收过滤 + 错误中断,传输模式要管邮箱队列、FIFO 溢出、错误处理、RTOS 集成。你做 AEGIX BSP,CAN 驱动要中断 + RTOS 信号量 + 消息队列。对照 \[09-SPI传输模式-轮询-中断-DMA] 看,CAN 驱动更复杂但更实用。
目录
- 一、三种模式对比
-
- [1.1 选型原则](#1.1 选型原则)
- [1.2 混合模式](#1.2 混合模式)
- 二、轮询模式
-
- [2.1 轮询发送](#2.1 轮询发送)
- [2.2 轮询接收](#2.2 轮询接收)
- [2.3 轮询优缺点](#2.3 轮询优缺点)
- 三、中断模式
-
- [3.1 中断模式架构](#3.1 中断模式架构)
- [3.2 发送状态机](#3.2 发送状态机)
- [3.3 接收 ISR](#3.3 接收 ISR)
- [3.4 错误 ISR](#3.4 错误 ISR)
- [3.5 中断使能](#3.5 中断使能)
- [3.6 中断优缺点](#3.6 中断优缺点)
- [四、DMA 模式](#四、DMA 模式)
-
- [4.1 CAN DMA 概述](#4.1 CAN DMA 概述)
- [4.2 DMA 接收(FDCAN)](#4.2 DMA 接收(FDCAN))
- [4.3 DMA 发送(FDCAN)](#4.3 DMA 发送(FDCAN))
- [4.4 DMA 完成](#4.4 DMA 完成)
- [4.5 DMA 优缺点](#4.5 DMA 优缺点)
- [4.6 何时用 DMA](#4.6 何时用 DMA)
- 五、发送邮箱队列
-
- [5.1 3 邮箱队列](#5.1 3 邮箱队列)
- [5.2 软件队列](#5.2 软件队列)
- [5.3 发送优先级](#5.3 发送优先级)
- [5.4 FDCAN TX FIFO](#5.4 FDCAN TX FIFO)
- [六、接收 FIFO 处理](#六、接收 FIFO 处理)
-
- [6.1 FIFO 中断读取](#6.1 FIFO 中断读取)
- [6.2 FIFO 溢出处理](#6.2 FIFO 溢出处理)
- [6.3 FIFO 满中断](#6.3 FIFO 满中断)
- [6.4 FDCAN 水线中断](#6.4 FDCAN 水线中断)
- [七、RTOS 集成](#七、RTOS 集成)
-
- [7.1 信号量 + 消息队列](#7.1 信号量 + 消息队列)
- [7.2 接收任务](#7.2 接收任务)
- [7.3 发送任务](#7.3 发送任务)
- [7.4 错误任务](#7.4 错误任务)
- [7.5 互斥](#7.5 互斥)
- [7.6 RTOS 集成要点](#7.6 RTOS 集成要点)
- [八、关键问题 Q&A](#八、关键问题 Q&A)
-
- [Q1:CAN 三种传输模式怎么选?](#Q1:CAN 三种传输模式怎么选?)
- [Q2:3 发送邮箱不够怎么办?](#Q2:3 发送邮箱不够怎么办?)
- [Q3:接收 FIFO 溢出怎么处理?](#Q3:接收 FIFO 溢出怎么处理?)
- [Q4:CAN DMA 什么时候值得用?](#Q4:CAN DMA 什么时候值得用?)
- [Q5:RTOS 下 CAN 驱动怎么集成?](#Q5:RTOS 下 CAN 驱动怎么集成?)
- Q6:轮询模式有什么用?
- 九、实战案例
-
- [案例 1:中断模式接收丢帧](#案例 1:中断模式接收丢帧)
- [案例 2:3 邮箱满,发送阻塞](#案例 2:3 邮箱满,发送阻塞)
- [案例 3:CAN FD 64B DMA 配错](#案例 3:CAN FD 64B DMA 配错)
- [案例 4:总线关闭中断没处理](#案例 4:总线关闭中断没处理)
- [案例 5:多任务发送竞态](#案例 5:多任务发送竞态)
- [案例 6:FIFO 锁定模式丢关键帧](#案例 6:FIFO 锁定模式丢关键帧)
- [案例 7:中断优先级太低,响应慢](#案例 7:中断优先级太低,响应慢)
- [案例 8:DMA 发送缓冲对齐错](#案例 8:DMA 发送缓冲对齐错)
- 十、本篇小结与速查
- 延伸阅读
- 参考资料
一、三种模式对比
| 模式 | CPU 占用 | 实时性 | 复杂度 | 适用 |
|---|---|---|---|---|
| 轮询 | 高(死等) | 差 | 低 | 初始化/调试 |
| 中断 | 低(事件驱动) | 好 | 中 | 主流 |
| DMA | 极低 | 最好 | 高 | CAN FD 大块 |
1.1 选型原则
- 轮询:调试/初始化,简单
- 中断:主流,事件驱动,CPU 解放
- DMA:CAN FD 64B 大块,高吞吐
1.2 混合模式
实际驱动混合用:
- 发送:中断(小报文)/ DMA(大报文 FD)
- 接收:中断(低频)/ DMA(高频)
- 错误:中断(必)
二、轮询模式
2.1 轮询发送
c
int can_send_pio(uint32_t id, uint8_t *data, uint8_t len) {
uint32_t timeout = 10000;
// 1. 找空邮箱
uint8_t mbox;
while (!(CAN->TSR & (CAN_TSR_TME0 | CAN_TSR_TME1 | CAN_TSR_TME2))) {
if (--timeout == 0) return -ETIMEDOUT; // 无空邮箱超时
}
mbox = (CAN->TSR & CAN_TSR_CODE) >> CAN_TSR_CODE_Pos;
// 2. 写 ID
CAN->sTxMailBox[mbox].TIR = (id << 21) | CAN_TI0R_TXRQ; // 标准 ID + 请求发送
// 3. 写数据
CAN->sTxMailBox[mbox].TDLR = *(uint32_t*)&data[0];
CAN->sTxMailBox[mbox].TDHR = *(uint32_t*)&data[4];
// 4. 写长度(TDTR)
CAN->sTxMailBox[mbox].TDTR = (CAN->sTxMailBox[mbox].TDTR & 0xFFFFFFF0) | len;
// 5. 等发送完成
timeout = 10000;
while (!(CAN->TSR & (CAN_TSR_TXOK0 << (mbox * 8)))) {
if (--timeout == 0) return -ETIMEDOUT;
}
return 0;
}
2.2 轮询接收
c
int can_recv_pio(uint32_t *id, uint8_t *data, uint8_t *len) {
// 1. 查 FIFO0 是否有消息
if (!(CAN->RF0R & CAN_RF0R_FMP0)) return -EAGAIN; // 空
// 2. 读 ID
*id = (CAN->sFIFOMailBox[0].RIR >> 21) & 0x7FF; // 标准 ID
// 3. 读数据
*(uint32_t*)&data[0] = CAN->sFIFOMailBox[0].RDLR;
*(uint32_t*)&data[4] = CAN->sFIFOMailBox[0].RDHR;
// 4. 读长度
*len = CAN->sFIFOMailBox[0].RDTR & 0x0F;
// 5. 释放 FIFO
CAN->RF0R |= CAN_RF0R_RFOM0;
return 0;
}
2.3 轮询优缺点
- 优点:简单,无中断,调试方便
- 缺点:CPU 死等,实时性差,高频不行
三、中断模式
3.1 中断模式架构
CAN 帧到达 → FIFO 挂起中断 → ISR 读帧 → 推消息队列 → 任务处理
发送请求 → 写邮箱 → 发送完成中断 → ISR 释放邮箱 → 信号量通知
错误 → 错误中断 → ISR 处理 → 通知应用
3.2 发送状态机
c
typedef struct {
uint8_t *tx_buf[3]; // 3 邮箱待发数据
uint8_t tx_len[3];
uint32_t tx_id[3];
volatile uint8_t tx_busy; // 邮箱占用位图
} can_tx_t;
can_tx_t can_tx;
int can_send_irq(uint32_t id, uint8_t *data, uint8_t len) {
// 找空邮箱
uint8_t mbox = can_find_free_mbox();
if (mbox >= 3) return -EBUSY;
can_tx.tx_id[mbox] = id;
can_tx.tx_len[mbox] = len;
memcpy(can_tx.tx_buf[mbox], data, len);
can_tx.tx_busy |= (1 << mbox);
// 写邮箱 + 请求发送
can_write_mbox(mbox, id, data, len);
return 0;
}
void can_tx_isr(void) {
uint8_t mbox = can_get_completed_mbox(); // TSR TXOK
can_tx.tx_busy &= ~(1 << mbox); // 释放邮箱
// 通知应用发送完成
xSemaphoreGiveFromISR(can_tx_sem, ...);
}
3.3 接收 ISR
c
void can_rx0_isr(void) { // FIFO0 接收中断
while (CAN->RF0R & CAN_RF0R_FMP0) { // FIFO 有消息
can_msg_t msg;
msg.id = (CAN->sFIFOMailBox[0].RIR >> 21) & 0x7FF;
msg.len = CAN->sFIFOMailBox[0].RDTR & 0x0F;
*(uint32_t*)&msg.data[0] = CAN->sFIFOMailBox[0].RDLR;
*(uint32_t*)&msg.data[4] = CAN->sFIFOMailBox[0].RDHR;
// 释放 FIFO
CAN->RF0R |= CAN_RF0R_RFOM0;
// 推消息队列
xQueueSendFromISR(can_rx_queue, &msg, ...);
}
}
3.4 错误 ISR
c
void can_err_isr(void) {
uint32_t esr = CAN->ESR;
if (esr & CAN_ESR_BOFF) {
// 总线关闭
xTaskNotifyFromISR(can_task, CAN_EVENT_BUSOFF, ...);
}
if (esr & CAN_ESR_EPVF) {
// 错误被动
xTaskNotifyFromISR(can_task, CAN_EVENT_ERROR_PASSIVE, ...);
}
if (esr & CAN_ESR_EWGF) {
// 错误警告
}
uint8_t lec = (esr >> 4) & 0x7;
if (lec) {
// 记录最后错误码
log_error("CAN LEC=%d", lec);
}
}
3.5 中断使能
c
void can_enable_irq(void) {
CAN->IER = CAN_IER_FMPIE0 // FIFO0 消息挂起
| CAN_IER_TMEIE // 发送邮箱空
| CAN_IER_ERRIE // 错误
| CAN_IER_BOFIE // 总线关闭
| CAN_IER_EPVIE // 错误被动
| CAN_IER_EWGIE // 错误警告
| CAN_IER_LECIE; // 最后错误码
NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn);
NVIC_EnableIRQ(USB_LP_CAN1_TX_IRQn);
NVIC_EnableIRQ(USB_LP_CAN1_SCE_IRQn); // 错误
}
3.6 中断优缺点
- 优点:CPU 解放,实时性好,主流
- 缺点:中断频次高(CPU 上下文切换开销),高频要 DMA
四、DMA 模式
4.1 CAN DMA 概述
- 部分控制器支持 DMA(STM32 FDCAN 部分 / N300 M_CAN)
- DMA 接收:CAN 帧进 FIFO → DMA 搬到内存缓冲
- DMA 发送:内存缓冲 → DMA 搬到 TX Buffer → 触发发送
- CAN FD 64B 大块,DMA 优势明显
4.2 DMA 接收(FDCAN)
c
// FDCAN DMA 接收配置
void can_dma_rx_init(void) {
// 配置 FDCAN DMA
FDCAN->RXF0A = (uint32_t)rx_buf_addr; // FIFO0 水线 DMA
// DMA 通道配置
DMA->CH[CAN_DMA_CH].MAR = (uint32_t)rx_buf;
DMA->CH[CAN_DMA_CH].PAR = (uint32_t)&FDCAN->RXF0D;
DMA->CH[CAN_DMA_CH].NDTR = RX_BUF_SIZE;
DMA->CH[CAN_DMA_CH].CR = DMA_DIR_PERI2MEM // 外设→内存
| DMA_MINC_ENABLE // 内存自增
| DMA_PSIZE_WORD
| DMA_MSIZE_WORD;
DMA->CH[CAN_DMA_CH].CR |= DMA_EN;
// FDCAN DMA 使能
FDCAN->TXBTIE = 0xFFFFFFFF; // 发送缓冲中断
FDCAN->IE |= FDCAN_IE_RF0NE; // FIFO0 新消息
}
4.3 DMA 发送(FDCAN)
c
int can_send_dma(uint32_t id, uint8_t *data, uint8_t len) {
// 1. 找空 TX Buffer
uint8_t buf_idx = find_free_txbuf();
if (buf_idx >= 32) return -EBUSY;
// 2. DMA 搬数据到消息 RAM
DMA->CH[TX_DMA_CH].MAR = (uint32_t)data;
DMA->CH[TX_DMA_CH].PAR = (uint32_t)TXBUF_ADDR(buf_idx);
DMA->CH[TX_DMA_CH].NDTR = len;
DMA->CH[TX_DMA_CH].CR |= DMA_EN;
// 3. 写 ID + 长度
TXBUF->TIR[buf_idx] = (id << 18) | FDCAN_IDE; // FD 扩展
TXBUF->TDTR[buf_idx] = len;
// 4. 请求发送
FDCAN->TXBAR = (1 << buf_idx);
return 0;
}
4.4 DMA 完成
c
void can_dma_isr(void) {
if (DMA->ISR & DMA_TCIF(CAN_DMA_CH)) {
// DMA 接收完成(一批)
xSemaphoreGiveFromISR(can_rx_sem, ...);
}
}
4.5 DMA 优缺点
- 优点:CPU 极低,大块高效,适合 CAN FD 64B
- 缺点:复杂,不是所有控制器支持,小报文不值得
4.6 何时用 DMA
- CAN FD 大块(64B/帧):DMA 高效
- 高频接收(总线负载高):DMA 减中断
- 小报文 + 低频:中断够,DMA 反而复杂
五、发送邮箱队列
5.1 3 邮箱队列
bxCAN 3 发送邮箱,可同时排 3 帧:
应用层 邮箱层 总线
│ 帧1 ──→ 邮箱0 ──→ 发送
│ 帧2 ──→ 邮箱1 ──→ 等
│ 帧3 ──→ 邮箱2 ──→ 等
│ 帧4 ──→ 满,等或入软件队列
5.2 软件队列
3 邮箱不够,加软件队列:
c
typedef struct {
can_msg_t buf[CAN_TX_QUEUE_SIZE];
uint16_t head, tail;
} can_tx_queue_t;
can_tx_queue_t tx_queue;
int can_tx_enqueue(can_msg_t *msg) {
if (queue_full(&tx_queue)) return -ENOMEM;
tx_queue.buf[tx_queue.tail] = *msg;
tx_queue.tail = (tx_queue.tail + 1) % CAN_TX_QUEUE_SIZE;
// 尝试立即发(有空邮箱)
can_try_send();
return 0;
}
void can_try_send(void) {
while (!queue_empty(&tx_queue)) {
uint8_t mbox = can_find_free_mbox();
if (mbox >= 3) break; // 无空邮箱,等中断
can_msg_t msg = tx_queue.buf[tx_queue.head];
tx_queue.head = (tx_queue.head + 1) % CAN_TX_QUEUE_SIZE;
can_write_mbox(mbox, msg.id, msg.data, msg.len);
}
}
void can_tx_isr(void) {
// 邮箱空中断 → 尝试发队列下一帧
can_try_send();
}
5.3 发送优先级
- TXFP=0(ID 优先):硬件按 ID 小的先发(邮箱里的帧)
- TXFP=1(请求顺序):先入邮箱的先发
5.4 FDCAN TX FIFO
- FDCAN TX Buffer 可配 FIFO 模式
- 自动按入队顺序发,无需软件队列
六、接收 FIFO 处理
6.1 FIFO 中断读取
c
void can_rx0_isr(void) {
while (CAN->RF0R & CAN_RF0R_FMP0) { // 读完所有挂起
can_msg_t msg;
// 读 ID/数据/长度(见 3.3)
read_fifo0(&msg);
CAN->RF0R |= CAN_RF0R_RFOM0; // 释放
xQueueSendFromISR(can_rx_queue, &msg, ...);
}
}
6.2 FIFO 溢出处理
- RFLM=0(覆盖):FIFO 满新帧覆盖,记溢出计数
- RFLM=1(锁定):FIFO 满丢新帧,记丢失计数
c
void can_rx0_isr(void) {
if (CAN->RF0R & CAN_RF0R_FOVR0) {
// FIFO 溢出
can_rx_overrun_cnt++;
CAN->RF0R |= CAN_RF0R_FOVR0; // 清标志
log_warning("CAN RX FIFO0 overrun");
}
// ... 读消息
}
6.3 FIFO 满中断
- FIFO 满(3 级)触发中断,优先处理
- 防止溢出
6.4 FDCAN 水线中断
- FDCAN 可配水线(如 FIFO 半满触发)
- 提前通知,减少溢出风险
七、RTOS 集成
7.1 信号量 + 消息队列
ISR → 消息队列(can_rx_queue)→ 接收任务处理
ISR → 信号量(can_tx_sem)→ 发送任务继续发
ISR → 任务通知(can_task)→ 错误任务处理
7.2 接收任务
c
void can_rx_task(void *arg) {
can_msg_t msg;
while (1) {
if (xQueueReceive(can_rx_queue, &msg, portMAX_DELAY) == pdTRUE) {
// 处理消息
can_dispatch(&msg);
}
}
}
void can_dispatch(can_msg_t *msg) {
switch (msg->id) {
case 0x100: handle_engine_status(msg); break;
case 0x200: handle_sensor_data(msg); break;
case 0x7E8: handle_uds_response(msg); break;
default: log_unknown(msg);
}
}
7.3 发送任务
c
void can_tx_task(void *arg) {
can_msg_t msg;
while (1) {
// 等应用层发送请求
xQueueReceive(can_tx_request, &msg, portMAX_DELAY);
// 入软件队列
can_tx_enqueue(&msg);
// 等发送完成(信号量,可选)
// xSemaphoreTake(can_tx_sem, 100);
}
}
7.4 错误任务
c
void can_error_task(void *arg) {
uint32_t event;
while (1) {
xTaskNotifyWait(0, 0xFFFFFFFF, &event, portMAX_DELAY);
if (event & CAN_EVENT_BUSOFF) {
log_error("CAN Bus-Off, recovering...");
// 等自动恢复 or 软件恢复
// 恢复后重新同步应用状态
}
if (event & CAN_EVENT_ERROR_PASSIVE) {
log_warning("CAN Error Passive");
// 检查 TEC/REC,排查
}
}
}
7.5 互斥
- 多任务发送 CAN,用互斥锁保护软件队列:
c
void can_send(can_msg_t *msg) {
xSemaphoreTake(can_mutex, portMAX_DELAY);
can_tx_enqueue(msg);
xSemaphoreGive(can_mutex);
}
7.6 RTOS 集成要点
- ISR 用 FromISR API(不阻塞)
- 消息队列缓冲(削峰)
- 信号量同步(发送完成)
- 任务通知(错误事件)
- 互斥锁(多任务共享)
八、关键问题 Q&A
Q1:CAN 三种传输模式怎么选?
A:
- 轮询:调试/初始化,简单但 CPU 死等
- 中断:主流,事件驱动,CPU 解放,适合多数场景
- DMA:CAN FD 64B 大块/高频接收,CPU 极低但复杂
- 实际混合:发送中断(小)/DMA(FD大块),接收中断(低频)/DMA(高频),错误中断
Q2:3 发送邮箱不够怎么办?
A :加软件队列。3 邮箱同时排 3 帧,满后入软件队列(环形缓冲)。邮箱空中断触发从队列取下一帧发。FDCAN 的 TX Buffer 可配 FIFO 模式(32 缓冲),自动按序发,无需软件队列。
Q3:接收 FIFO 溢出怎么处理?
A :两种模式:RFLM=0 覆盖 (满后新覆盖旧,留最新,适合实时数据)/ RFLM=1 锁定(满后丢新留旧,适合关键数据)。记溢出计数,排查:① CPU 处理慢(提高优先级);② 总线负载高(减负载);③ FIFO 深度不够(用 FDCAN 可配深度);④ 用 DMA。
Q4:CAN DMA 什么时候值得用?
A :CAN FD 64B 大块 + 高频接收。DMA 搬 64B 比 CPU 快,CPU 极低。小报文(8B)+ 低频,中断够,DMA 反而复杂(配置开销)。不是所有控制器支持 CAN DMA(查手册)。
Q5:RTOS 下 CAN 驱动怎么集成?
A :ISR + 消息队列 + 信号量 + 任务。ISR 读帧推消息队列(FromISR API),接收任务阻塞读队列处理;发送任务等请求入队列,邮箱空中断触发发;错误 ISR 用任务通知错误任务处理。多任务发送用互斥锁。ISR 绝不阻塞。
Q6:轮询模式有什么用?
A :调试和初始化。轮询简单(无中断),初始化阶段验证硬件(发一帧看回环)。调试时轮询查寄存器状态。生产环境用中断/DMA。轮询也用于极简场景(无 RTOS/无中断的裸机简单应用)。
九、实战案例
案例 1:中断模式接收丢帧
现象:CAN 中断接收,高频率丢帧。
分析:ISR 处理慢(中断里做太多),FIFO 溢出覆盖。
解决:ISR 只读帧推队列(最小化),处理放任务。提高 ISR 优先级。
案例 2:3 邮箱满,发送阻塞
现象:连续发 4 帧,第 4 帧阻塞。
分析:3 邮箱都在发,第 4 帧无邮箱。
解决:加软件队列(环形缓冲),邮箱空中断发队列下一帧。或用 FDCAN TX FIFO。
案例 3:CAN FD 64B DMA 配错
现象:CAN FD DMA 接收,数据错乱。
分析:DMA 数据宽度/地址对齐错(64B 要 8 字节对齐)。
解决:DMA 配 WORD(32bit),缓冲 8 字节对齐,NDTR 按 64B 算(16 个 word)。
案例 4:总线关闭中断没处理
现象:CAN 总线关闭,应用无响应。
分析:错误中断没使能,或没处理 BOFF。
解决:使能 BOFIE,ISR 通知错误任务,进安全状态等恢复。
案例 5:多任务发送竞态
现象:两个任务同时发 CAN,数据错乱。
分析:软件队列没保护,竞态。
解决:互斥锁保护发送队列(can_mutex)。
案例 6:FIFO 锁定模式丢关键帧
现象:RFLM=1 锁定,FIFO 满后新帧丢(关键报文)。
分析:锁定模式留旧丢新,高频时新关键帧丢。
解决:提高处理速度(中断/DMA),或 RFLM=0 覆盖(看应用),或加深 FIFO(FDCAN)。
案例 7:中断优先级太低,响应慢
现象:CAN 接收中断响应慢,FIFO 偶发溢出。
分析:CAN 中断优先级低于其他中断,被抢占。
解决:CAN 接收中断优先级调高(NVIC),关键实时场景最高。
案例 8:DMA 发送缓冲对齐错
现象:CAN DMA 发送,偶发数据错。
分析:DMA 内存地址没对齐(Cache line/字对齐),DMA 搬错。
解决 :缓冲 attribute((aligned(32))),发前 flush cache(如需)。
十、本篇小结与速查
核心要点
- 三种模式:轮询(调试)/ 中断(主流)/ DMA(FD大块)
- 轮询:死等标志,简单但 CPU 占用高
- 中断:ISR 读帧推队列,发送邮箱空中断,错误中断,主流
- DMA:CAN FD 64B 大块/高频,不是所有控制器支持
- 发送邮箱:3 个(bxCAN)/ 32 TX Buffer(FDCAN),加软件队列
- 发送优先级:TXFP=0 ID 优先 / TXFP=1 请求顺序
- 接收 FIFO:2×3 级(bxCAN),RFLM 覆盖/锁定
- FIFO 溢出:计数 + 排查(CPU慢/负载高/深度不够)
- RTOS:ISR + 消息队列 + 信号量 + 任务通知 + 互斥锁
- ISR 最小化(只读推队列),处理放任务
- FDCAN:TX Buffer 可配 FIFO,接收水线中断,消息 RAM
传输模式选型
| 场景 | 模式 |
|---|---|
| 调试/初始化 | 轮询 |
| 低频小报文 | 中断 |
| 高频接收 | 中断 + 队列 或 DMA |
| CAN FD 64B | DMA |
| 错误处理 | 中断(必) |
中断使能速查
| 中断 | 位 | 用途 |
|---|---|---|
| FIFO0 挂起 | FMPIE0 | 接收 |
| 发送邮箱空 | TMEIE | 发送完成 |
| 错误 | ERRIE | 错误总 |
| 总线关闭 | BOFIE | Bus-Off |
| 错误被动 | EPVIE | 进/出被动 |
| 错误警告 | EWGIE | 警告 |
| 最后错误码 | LECIE | 错误类型 |
| FIFO0 满 | FFIE0 | FIFO 满 |
| FIFO0 溢出 | FOVIE0 | 丢帧 |
RTOS 集成清单
- ISR 用 FromISR API
- 接收消息队列(削峰)
- 发送软件队列 + 邮箱空中断
- 信号量(发送完成同步)
- 任务通知(错误事件)
- 互斥锁(多任务共享)
- ISR 最小化(只读推队列)
- 错误任务(总线关闭/被动)
- 优先级(CAN 接收高)
驱动分层
应用层(业务)
↓ can_send/can_recv API
驱动层(队列/邮箱/中断)
↓ 寄存器
硬件层(CAN 控制器)
延伸阅读
- \[08-CAN控制器寄存器与IP架构] ------ 寄存器操作
- \[06-CAN错误处理与错误帧] ------ 错误中断
- \[10-CAN错误恢复与总线关闭] ------ 总线关闭恢复
- \[11-CAN Linux驱动框架] ------ Linux CAN 驱动
- \[12-CAN设备驱动实战] ------ 实战驱动
- \[09-SPI传输模式-轮询-中断-DMA] ------ 对比 SPI 传输
- \[09-I2C传输模式-轮询-中断-DMA] ------ 对比 I2C 传输
参考资料
- STM32 RM0090(bxCAN 中断/DMA)
- STM32 RM0433(FDCAN DMA)
- Bosch M_CAN User Manual(TX Buffer/FIFO)
- FreeRTOS(CAN 驱动集成)
\[00-CAN技术系列总目录] · 上一篇:\[08-CAN控制器寄存器与IP架构] · 下一篇:\[10-CAN错误恢复与总线关闭]
✨ 感谢你的阅读 ✨
技术之路漫漫,分享是为了更好地交流。
如果本文的内容对你有启发,希望能得到你的支持:
👍 点赞 | ⭐ 收藏 | 👀 关注
如果你在调试过程中遇到了其他问题,
欢迎在 评论区 💬 留言,我们一起探讨。
关注我,一起交流底层开发的那些事儿!🚀