CAN 技术系列 09 · CAN 传输模式:轮询 / 中断 / DMA

📚 CAN总线技术系列 · 持续更新中

本系列从差分电气层一路打到 Linux SocketCAN,把"两根线"背后的电气/协议/控制器/驱动/实战全拧成一根线。

👉 想系统追更,戳这里查看完整专栏:

【CAN 技术系列】

👉 其他技术系列专栏,欢迎关注、交流、共同成长:

【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错误恢复与总线关闭]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
ccbw165 小时前
CH9444X多芯片组合的通道扩展架构与损耗叠加分析
驱动开发·架构·带宽·采集
AI模型调用笔记2 天前
Copilot Fusion:用 Python 跑通模型级联
人工智能·驱动开发·语言模型·copilot
mounter6253 天前
深入理解 gccrs:GCC 如何实现 Rust 编译器,以及编译 Linux 内核面临的挑战
linux·驱动开发·rust
硅基手札3 天前
CAN 技术系列 04 · CAN 帧格式
驱动开发·嵌入式开发·通信协议·can总线
硅基手札4 天前
操作系统设计思想系列 13 · NuttX 深度 — POSIX 飞控底座 + RTOS 群像
架构·操作系统·嵌入式开发·ecos·sel4·nuttx
万物智能信息科技4 天前
六轴传感器Motion驱动框架开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·华为·开源·harmonyos·鸿蒙
硅基手札4 天前
CAN 技术系列 03 · CAN 位定时与同步
嵌入式开发·通信协议·can总线·bug定位调试
晓蛋5 天前
嵌入式开发中的C语言特性
嵌入式开发·陷阱·编程技巧·缺陷·c语言特性
AMASENSYS艾马斯5 天前
变频器滤波器选型与EMC合规建议
驱动开发·单片机·其他·硬件工程·精益工程