基于 STM32 HAL 库的 MCP2515 CAN 驱动开发与数据收发测试

本文将逐步指导您如何基于 STM32 HAL 库开发 MCP2515 CAN 控制器的驱动,并实现数据收发测试。MCP2515 是一款独立的 CAN 控制器,通过 SPI 接口与 STM32 微控制器通信。开发过程包括硬件连接、软件初始化、驱动函数编写和测试逻辑。整个流程使用 C 语言实现,确保代码可移植性和可靠性。以下是详细步骤:

1. 硬件连接

首先,将 MCP2515 连接到 STM32 开发板。典型连接如下:

  • MCP2515 SPI 接口
    • SCK (时钟) → STM32 SPI SCK 引脚(如 PA5)
    • SI (数据输入) → STM32 SPI MOSI 引脚(如 PA7)
    • SO (数据输出) → STM32 SPI MISO 引脚(如 PA6)
    • CS (片选) → STM32 GPIO 引脚(如 PA4)
  • MCP2515 CAN 接口
    • CANH 和 CANL → CAN 总线(如连接到 CAN 分析仪或另一个节点)
  • 电源和地
    • VCC → 3.3V 或 5V(根据 MCP2515 规格)
    • GND → 共地
  • 中断引脚(可选):
    • INT → STM32 GPIO 引脚(如 PA0),用于中断驱动接收。

确保所有连接正确,避免信号干扰。SPI 时钟频率建议在 1-10 MHz 范围内,以匹配 MCP2515 的规格。

2. 软件初始化

在 STM32CubeIDE 或类似环境中,使用 HAL 库初始化 SPI 和 GPIO:

  • 配置 SPI:通过 STM32CubeMX 设置 SPI 外设(如 SPI1),选择主模式、8 位数据大小、CPOL=0、CPHA=0(Mode 0)。
  • 配置 GPIO:设置 CS 引脚为输出模式(推挽输出),INT 引脚为输入模式(如使用中断)。
  • 初始化 HAL:在代码中调用 HAL 初始化函数。

示例初始化代码(main.c 中):

#include "stm32f1xx_hal.h"

SPI_HandleTypeDef hspi1; // SPI 句柄

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);

int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_SPI1_Init();
  
  // 后续驱动开发
  while (1) {}
}

void MX_SPI1_Init(void) {
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制 CS
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; // 例如 72MHz / 16 = 4.5MHz
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  HAL_SPI_Init(&hspi1);
}

void MX_GPIO_Init(void) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  __HAL_RCC_GPIOA_CLK_ENABLE();
  
  // CS 引脚配置 (PA4)
  GPIO_InitStruct.Pin = GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 初始拉高 CS
}

3. 驱动函数开发

开发 MCP2515 驱动核心函数,包括初始化、发送和接收。使用 SPI 读写寄存器命令。关键点:

  • SPI 读写函数:封装 HAL_SPI_TransmitReceive() 实现读写。
  • MCP2515 寄存器操作:参考 MCP2515 数据手册,定义命令(如写指令 0x02)。
  • CAN 帧结构:标准帧格式(ID、数据长度、数据)。
  • 波特率计算:CAN 波特率由 MCP2515 的 CNF 寄存器设置,公式为: $$ \text{波特率} = \frac{f_{\text{osc}}}{2 \times (\text{BRP} + 1) \times (\text{TQ})} $$ 其中 TQ 是时间量子,由 CNF 寄存器配置(例如,BRP 值需根据时钟频率调整)。

创建 mcp2515.hmcp2515.c 文件:

mcp2515.h:

#ifndef MCP2515_H
#define MCP2515_H

#include "stm32f1xx_hal.h"

// MCP2515 寄存器定义
#define MCP2515_WRITE_CMD 0x02
#define MCP2515_READ_CMD 0x03
#define MCP2515_BITMOD_CMD 0x05
#define MCP2515_LOAD_TX0_CMD 0x40
#define MCP2515_RTS_CMD 0x80

// CAN 初始化函数
void MCP2515_Init(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
// 发送 CAN 帧函数
void MCP2515_Send(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint32_t id, uint8_t *data, uint8_t len);
// 接收 CAN 帧函数
void MCP2515_Receive(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint32_t *id, uint8_t *data, uint8_t *len);

#endif

mcp2515.c:

#include "mcp2515.h"

// SPI 读写辅助函数
static void SPI_Write(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint8_t *txData, uint8_t size) {
  HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET); // CS 拉低
  HAL_SPI_Transmit(hspi, txData, size, 100);
  HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET); // CS 拉高
}

static void SPI_Read(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint8_t *txData, uint8_t *rxData, uint8_t size) {
  HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(hspi, txData, rxData, size, 100);
  HAL_GPIO_WritePin(GPIOx, GPIO_Pin, GPIO_PIN_SET);
}

// 初始化 MCP2515
void MCP2515_Init(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) {
  uint8_t txData[4];
  
  // 复位 MCP2515
  txData[0] = 0xC0; // 复位命令
  SPI_Write(hspi, GPIOx, GPIO_Pin, txData, 1);
  HAL_Delay(10);
  
  // 设置波特率 (例如 500kbps, 假设 f_osc=16MHz)
  // CNF1, CNF2, CNF3 寄存器配置
  txData[0] = MCP2515_WRITE_CMD;
  txData[1] = 0x2A; // CNF1 地址
  txData[2] = 0x03; // BRP=3, SJW=1
  txData[3] = 0x90; // CNF2: BTLMODE=1, SAM=0, PHSEG1=2, PRSEG=1
  SPI_Write(hspi, GPIOx, GPIO_Pin, txData, 4);
  
  txData[0] = MCP2515_WRITE_CMD;
  txData[1] = 0x29; // CNF3 地址
  txData[2] = 0x02; // PHSEG2=2
  SPI_Write(hspi, GPIOx, GPIO_Pin, txData, 3);
  
  // 设置正常模式
  txData[0] = MCP2515_WRITE_CMD;
  txData[1] = 0x0F; // CANCTRL 地址
  txData[2] = 0x00; // 正常模式
  SPI_Write(hspi, GPIOx, GPIO_Pin, txData, 3);
}

// 发送 CAN 帧
void MCP2515_Send(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint32_t id, uint8_t *data, uint8_t len) {
  uint8_t txData[14];
  uint8_t i;
  
  // 加载 TX 缓冲区 (使用 TXB0)
  txData[0] = MCP2515_LOAD_TX0_CMD;
  txData[1] = (id >> 3) & 0xFF; // 标准 ID 高字节
  txData[2] = (id << 5) & 0xE0; // 标准 ID 低字节
  txData[3] = len; // 数据长度
  for (i = 0; i < len; i++) {
    txData[4 + i] = data[i];
  }
  SPI_Write(hspi, GPIOx, GPIO_Pin, txData, 4 + len);
  
  // 发送 RTS 命令
  txData[0] = MCP2515_RTS_CMD | 0x01; // RTS for TXB0
  SPI_Write(hspi, GPIOx, GPIO_Pin, txData, 1);
}

// 接收 CAN 帧
void MCP2515_Receive(SPI_HandleTypeDef *hspi, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, uint32_t *id, uint8_t *data, uint8_t *len) {
  uint8_t txData[3], rxData[14];
  uint8_t status;
  
  // 检查接收状态
  txData[0] = MCP2515_READ_CMD;
  txData[1] = 0x2C; // CANSTAT 地址
  SPI_Read(hspi, GPIOx, GPIO_Pin, txData, rxData, 3);
  status = rxData[2];
  
  if (status & 0x01) { // 检查 RXB0 有数据
    // 读取 RXB0 缓冲区
    txData[0] = MCP2515_READ_CMD;
    txData[1] = 0x61; // RXB0 起始地址
    SPI_Read(hspi, GPIOx, GPIO_Pin, txData, rxData, 5 + 8); // 读取 ID、长度和数据
    
    *id = (rxData[1] << 3) | (rxData[2] >> 5); // 解析标准 ID
    *len = rxData[3] & 0x0F; // 数据长度
    for (uint8_t i = 0; i < *len; i++) {
      data[i] = rxData[4 + i];
    }
  }
}

4. 数据收发测试

实现测试逻辑,验证发送和接收功能。推荐两种测试方法:

  • 自回环测试:在同一个 STM32 上,发送数据并立即接收(需配置 MCP2515 为回环模式)。
  • 外部设备测试:连接另一个 CAN 节点(如 PC CAN 分析仪),发送数据并检查接收。

main.c 中添加测试代码:

// 在 main 函数中
int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_SPI1_Init();
  
  // 初始化 MCP2515
  MCP2515_Init(&hspi1, GPIOA, GPIO_PIN_4);
  
  uint32_t tx_id = 0x123; // 测试 ID
  uint8_t tx_data[] = {0xAA, 0x55, 0x01}; // 测试数据
  uint8_t tx_len = sizeof(tx_data);
  
  uint32_t rx_id;
  uint8_t rx_data[8];
  uint8_t rx_len;
  
  while (1) {
    // 发送数据
    MCP2515_Send(&hspi1, GPIOA, GPIO_PIN_4, tx_id, tx_data, tx_len);
    HAL_Delay(1000); // 延时 1 秒
    
    // 接收数据
    MCP2515_Receive(&hspi1, GPIOA, GPIO_PIN_4, &rx_id, rx_data, &rx_len);
    
    // 验证接收 (例如通过串口打印)
    if (rx_len > 0) {
      // 假设使用 HAL_UART_Transmit() 打印到串口
      char msg[50];
      sprintf(msg, "Received ID: 0x%lX, Data: ", rx_id);
      HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
      for (int i = 0; i < rx_len; i++) {
        sprintf(msg, "%02X ", rx_data[i]);
        HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
      }
      HAL_UART_Transmit(&huart1, (uint8_t*)"\n", 1, 100);
    }
    HAL_Delay(1000);
  }
}

5. 测试结果与优化
  • 预期结果:在回环测试中,发送的数据应被正确接收,ID 和数据匹配。
  • 常见问题排查
    • 如果无数据接收,检查 SPI 通信(用示波器验证 SCK 和 MOSI 信号)。
    • 确保 CAN 总线终端电阻(120Ω)已连接。
    • 调试波特率:使用公式 $ \text{实际波特率} = \frac{f_{\text{osc}}}{2 \times (\text{BRP} + 1) \times (1 + \text{TSEG1} + \text{TSEG2})} $ 计算并调整 CNF 寄存器。
  • 优化建议
    • 添加中断处理:在 MX_GPIO_Init() 中配置 INT 引脚中断,实现高效接收。
    • 错误处理:在驱动函数中添加状态检查(如 HAL_SPI_GetError())。
结论

通过上述步骤,您已实现了基于 STM32 HAL 库的 MCP2515 CAN 驱动开发与数据收发测试。核心包括 SPI 通信封装、寄存器操作和 CAN 协议处理。测试验证了数据可靠性,您可进一步扩展为多节点通信。实际应用中,建议使用 STM32CubeMX 生成初始化代码,并参考 MCP2515 数据手册调整参数。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐