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底层。
验证方法
- 回环测试:将MCP2515设置为Loopback模式(
MCP2515_SetMode(LOOPBACK_MODE)),调用CanDrv_SendFrame()发送一帧ID=0x123、数据[0x01,0x02,0x03,0x04],随后调用CanDrv_ReceiveFrame()接收,断言收到的帧ID和数据与发送一致。 - 外部通信测试:将两个STM32+ MCP2515节点通过CAN总线连接(120Ω终端电阻),节点A发送,节点B接收,验证双向收发。
- 错误注入测试:断开总线终端电阻,连续发送大量帧,观察
CanDrv_GetErrorStatus()返回CAN_DRV_ERR_PASSIVE或CAN_DRV_BUS_OFF,确认错误计数递增且总线恢复机制生效。
通过标准:回环测试中收发数据完全一致;外部通信测试中1000帧连续传输无丢失;错误注入测试中总线关闭后能自动恢复并继续通信。