基于 STM32 HAL 库的 MCP2515 CAN 驱动开发与数据收发测试
本文将逐步指导您如何基于 STM32 HAL 库开发 MCP2515 CAN 控制器的驱动,并实现数据收发测试。开发过程包括硬件连接、软件初始化、驱动函数编写和测试逻辑。整个流程使用 C 语言实现,确保代码可移植性和可靠性。通过上述步骤,您已实现了基于 STM32 HAL 库的 MCP2515 CAN 驱动开发与数据收发测试。实际应用中,建议使用 STM32CubeMX 生成初始化代码,并参考 M
基于 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.h 和 mcp2515.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 数据手册调整参数。
更多推荐



所有评论(0)