3. CAN通信底层驱动开发

任务目标

本案例实现一个基于STM32F103 + MCP2515(SPI接口外部CAN控制器)的CAN底层驱动,完成报文发送、报文接收以及错误状态处理三个核心功能。驱动以独立模块形式提供,不依赖具体应用逻辑,可直接集成到Bootloader的UDS刷写流程中作为传输层基础。

硬件连接与依赖

  • MCU:STM32F103C8T6,SPI1连接MCP2515
  • MCP2515引脚:CS→PA4,SCK→PA5,MOSI→PA7,MISO→PA6,INT→PA8(中断接收)
  • 晶振:MCP2515外部8MHz晶振,CAN波特率配置为500kbps
  • 依赖:STM32标准外设库(SPI、GPIO、EXTI),无需RTOS

驱动头文件(can_drv.h)

#ifndef CAN_DRV_H
#define CAN_DRV_H

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

#define CAN_DRV_TX_BUFFER_SIZE   8
#define CAN_DRV_RX_BUFFER_SIZE   8

typedef struct {
    uint32_t id;           // 标准帧ID (0x000-0x7FF)
    uint8_t  data[8];      // 数据场
    uint8_t  dlc;          // 数据长度 (0-8)
} CanFrame_t;

typedef enum {
    CAN_DRV_OK = 0,
    CAN_DRV_TX_BUSY,       // 发送缓冲区满
    CAN_DRV_RX_EMPTY,      // 接收缓冲区空
    CAN_DRV_BUS_OFF,       // 总线关闭
    CAN_DRV_ERR_PASSIVE,   // 错误被动
    CAN_DRV_TIMEOUT        // 等待超时
} CanDrvStatus_t;

void     CanDrv_Init(void);
CanDrvStatus_t CanDrv_SendFrame(const CanFrame_t *frame, uint32_t timeoutMs);
CanDrvStatus_t CanDrv_ReceiveFrame(CanFrame_t *frame, uint32_t timeoutMs);
CanDrvStatus_t CanDrv_GetErrorStatus(void);
uint32_t CanDrv_GetTxErrorCount(void);
uint32_t CanDrv_GetRxErrorCount(void);
void     CanDrv_Task(void);   // 周期调用,处理错误恢复

#endif

驱动实现(can_drv.c)

#include "can_drv.h"
#include "mcp2515.h"   // MCP2515底层寄存器操作函数

static volatile CanFrame_t txQueue[CAN_DRV_TX_BUFFER_SIZE];
static volatile CanFrame_t rxQueue[CAN_DRV_RX_BUFFER_SIZE];
static volatile uint8_t txHead = 0, txTail = 0, txCount = 0;
static volatile uint8_t rxHead = 0, rxTail = 0, rxCount = 0;
static volatile uint32_t txErrCnt = 0, rxErrCnt = 0;
static volatile uint8_t busOffFlag = 0;

void CanDrv_Init(void) {
    MCP2515_Init(500);   // 500kbps
    MCP2515_SetMode(NORMAL_MODE);
    MCP2515_EnableInterrupt(RX0IE | RX1IE | TX0IE | ERRIF);
    txHead = txTail = txCount = 0;
    rxHead = rxTail = rxCount = 0;
    txErrCnt = rxErrCnt = 0;
    busOffFlag = 0;
}

CanDrvStatus_t CanDrv_SendFrame(const CanFrame_t *frame, uint32_t timeoutMs) {
    uint32_t startTick = HAL_GetTick();
    
    // 等待发送缓冲区有空位
    while (txCount >= CAN_DRV_TX_BUFFER_SIZE) {
        if ((HAL_GetTick() - startTick) > timeoutMs) return CAN_DRV_TX_BUSY;
        CanDrv_Task();
    }
    
    // 入队
    txQueue[txHead] = *frame;
    txHead = (txHead + 1) % CAN_DRV_TX_BUFFER_SIZE;
    txCount++;
    
    // 尝试立即发送
    MCP2515_LoadTxBuffer(0, frame->id, frame->data, frame->dlc);
    if (MCP2515_RequestSend(0) != MCP2515_OK) {
        // 发送失败,回滚队列
        txHead = (txHead + CAN_DRV_TX_BUFFER_SIZE - 1) % CAN_DRV_TX_BUFFER_SIZE;
        txCount--;
        return CAN_DRV_BUS_OFF;
    }
    return CAN_DRV_OK;
}

CanDrvStatus_t CanDrv_ReceiveFrame(CanFrame_t *frame, uint32_t timeoutMs) {
    uint32_t startTick = HAL_GetTick();
    
    while (rxCount == 0) {
        if ((HAL_GetTick() - startTick) > timeoutMs) return CAN_DRV_RX_EMPTY;
        CanDrv_Task();
    }
    
    *frame = rxQueue[rxTail];
    rxTail = (rxTail + 1) % CAN_DRV_RX_BUFFER_SIZE;
    rxCount--;
    return CAN_DRV_OK;
}

CanDrvStatus_t CanDrv_GetErrorStatus(void) {
    if (busOffFlag) return CAN_DRV_BUS_OFF;
    
    uint8_t errFlags = MCP2515_ReadErrorFlags();
    if (errFlags & MCP2515_ERR_PASSIVE) return CAN_DRV_ERR_PASSIVE;
    return CAN_DRV_OK;
}

uint32_t CanDrv_GetTxErrorCount(void) { return txErrCnt; }
uint32_t CanDrv_GetRxErrorCount(void) { return rxErrCnt; }

void CanDrv_Task(void) {
    uint8_t intFlags = MCP2515_ReadInterruptFlags();
    
    // 接收中断处理
    if (intFlags & (MCP2515_INT_RX0 | MCP2515_INT_RX1)) {
        CanFrame_t frame;
        if (MCP2515_ReadRxBuffer(&frame.id, frame.data, &frame.dlc) == MCP2515_OK) {
            if (rxCount < CAN_DRV_RX_BUFFER_SIZE) {
                rxQueue[rxHead] = frame;
                rxHead = (rxHead + 1) % CAN_DRV_RX_BUFFER_SIZE;
                rxCount++;
            }
        }
    }
    
    // 发送完成中断
    if (intFlags & MCP2515_INT_TX0) {
        if (txCount > 0) {
            txTail = (txTail + 1) % CAN_DRV_TX_BUFFER_SIZE;
            txCount--;
        }
        MCP2515_ClearInterrupt(MCP2515_INT_TX0);
    }
    
    // 错误中断处理
    if (intFlags & MCP2515_INT_ERR) {
        uint8_t errFlags = MCP2515_ReadErrorFlags();
        txErrCnt = MCP2515_GetTxErrorCount();
        rxErrCnt = MCP2515_GetRxErrorCount();
        
        if (errFlags & MCP2515_ERR_BUSOFF) {
            busOffFlag = 1;
            MCP2515_SetMode(CONFIG_MODE);
            MCP2515_SetMode(NORMAL_MODE);  // 恢复总线
            busOffFlag = 0;
        }
        MCP2515_ClearInterrupt(MCP2515_INT_ERR);
    }
}

// 中断服务函数(放在stm32f10x_it.c中调用)
void EXTI9_5_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line8) != RESET) {
        CanDrv_Task();
        EXTI_ClearITPendingBit(EXTI_Line8);
    }
}

MCP2515底层接口说明

上述代码依赖的MCP2515寄存器操作函数(MCP2515_Init、MCP2515_LoadTxBuffer等)为SPI通信的标准实现,包含寄存器读写、波特率配置(基于8MHz晶振的BRP=1,TQ=16,500kbps)、TX/RX缓冲管理。该层约300行,属于标准外设驱动,此处不展开。若需完整源码,可参考Microchip官方MCP2515驱动或STM32CubeMX生成的SPI底层。

验证方法

  1. 回环测试:将MCP2515设置为Loopback模式(MCP2515_SetMode(LOOPBACK_MODE)),调用CanDrv_SendFrame()发送一帧ID=0x123、数据[0x01,0x02,0x03,0x04],随后调用CanDrv_ReceiveFrame()接收,断言收到的帧ID和数据与发送一致。
  2. 外部通信测试:将两个STM32+ MCP2515节点通过CAN总线连接(120Ω终端电阻),节点A发送,节点B接收,验证双向收发。
  3. 错误注入测试:断开总线终端电阻,连续发送大量帧,观察CanDrv_GetErrorStatus()返回CAN_DRV_ERR_PASSIVE或CAN_DRV_BUS_OFF,确认错误计数递增且总线恢复机制生效。

通过标准:回环测试中收发数据完全一致;外部通信测试中1000帧连续传输无丢失;错误注入测试中总线关闭后能自动恢复并继续通信。