STM32 CAN通信 HAL库实战教程:从零到测试成功

STM32 CAN通信 HAL库实战教程:从零到测试成功

<我打印的是陀螺仪的数据>

目录

  1. 简介:为什么学习CAN通信
  2. CAN通信基础概念
  3. [STM32 CAN硬件配置](#STM32 CAN硬件配置)
  4. CAN初始化详解
  5. CAN数据发送实现
  6. CAN数据接收实现
  7. 测试与验证方法
  8. 常见问题与解决
  9. 总结与拓展

1. 简介:为什么学习CAN通信

在嵌入式系统中,CAN(Controller Area Network)总线是汽车电子、工业控制和物联网设备中不可或缺的通信协议。与UART、SPI等串行通信相比,CAN总线具有以下显著优势:

  • 多主多从架构,支持多节点通信
  • 内置错误检测和自动重传机制
  • 高可靠性,适合嘈杂的工业环境
  • 基于优先级的报文仲裁机制

本教程将基于STM32 HAL库,手把手教你实现CAN通信功能。我们不讲空洞的理论,直接上代码,目标是让你的开发板在1小时内实现可靠的CAN数据传输。

2. CAN通信基础概念

在深入代码之前,我们需要掌握几个关键概念:

2.1 CAN帧结构

CAN协议定义了两种帧格式:

  • 标准帧(11位标识符)
  • 扩展帧(29位标识符)

每帧数据包含:

  • 标识符(决定数据优先级)
  • 控制字段(包含数据长度码DLC)
  • 数据字段(最多8字节)
  • CRC校验字段
  • 应答字段(接收节点在此确认收到)

2.2 CAN通信模式

  • 正常模式:完整参与总线通信和错误检测
  • 只听模式:接收数据但不参与总线活动
  • 回环模式:发送的数据直接回传到接收缓冲区(用于自测试)

2.3 波特率计算

CAN波特率由以下参数决定:

  • 时钟预分频(Prescaler)
  • 同步跳变宽度(SJW)
  • 时间段1(TimeSeg1,采样点前时间)
  • 时间段2(TimeSeg2,采样点后时间)

公式:Tbit = (Prescaler × (TimeSeg1 + TimeSeg2 + 1)) / APB Clock

3. STM32 CAN硬件配置

3.1 CAN引脚配置

STM32F1系列的CAN引脚映射:

  • CAN1_RX:PA11 或 PB8
  • CAN1_TX:PA12 或 PB9

推荐使用PA11和PA12,因为它们属于低密度引脚,冲突可能性更低。

3.2 电路连接

基本连接要求:

  • CAN_H和CAN_L需要通过终端电阻(通常120Ω)连接
  • STM32的CAN引脚通过收发器芯片(如TJA1050)连接到CAN总线
  • 电源部分需要做好滤波处理

3.3 外围设备配置

在STM32CubeMX中:

  1. 使能CAN1外设
  2. 配置CAN引脚为复用功能
  3. 设置CAN过滤器模式

4. CAN初始化详解

c 复制代码
// CAN 初始化函数
void MyCAN_Init(void)
{
    // 1. 使能时钟
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_CAN1_CLK_ENABLE();

    // 2. 配置GPIO引脚
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // RX引脚配置 (PA11)
    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // TX引脚配置 (PA12)
    GPIO_InitStruct.Pin = GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;  // 复用推挽输出
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 3. 配置CAN参数
    CAN_HandleTypeDef hcan;
    hcan.Instance = CAN1;
    hcan.Init.Prescaler = 48;  // 时钟预分频
    hcan.Init.Mode = CAN_MODE_NORMAL;
    hcan.Init.SyncJumpWidth = CAN_SJW_2TQ;
    hcan.Init.TimeSeg1 = CAN_BS1_2TQ;
    hcan.Init.TimeSeg2 = CAN_BS2_3TQ;
    HAL_CAN_Init(&hcan);

    // 4. 配置过滤器
    CAN_FilterTypeDef canfilterconfig;
    canfilterconfig.FilterBank = 0;
    canfilterconfig.FilterIdHigh = 0x0000;
    canfilterconfig.FilterIdLow = 0x0000;
    canfilterconfig.FilterMaskIdHigh = 0x0000;
    canfilterconfig.FilterMaskIdLow = 0x0000;
    canfilterconfig.FilterMode = CAN_FILTERMODE_IDMASK;
    canfilterconfig.FilterScale = CAN_FILTERSCALE_32BIT;
    canfilterconfig.FilterFIFOAssignment = CAN_FILTER_FIFO0;
    canfilterconfig.FilterActivation = CAN_FILTER_ENABLE;
    HAL_CAN_ConfigFilter(&hcan, &canfilterconfig);

    // 5. 启动CAN并启用中断
    HAL_CAN_Start(&hcan);
    HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
}

关键参数解释

  • Prescaler:时钟预分频系数,决定位时钟周期
  • SyncJumpWidth:同步跳变宽度,影响位定时同步
  • TimeSeg1和TimeSeg2:决定采样点位置,通常TimeSeg1 + TimeSeg2 = 8TQ(时间量子)

5. CAN数据发送实现

c 复制代码
void MyCAN_Transmit(uint32_t ID, uint8_t Length, uint8_t *Data)
{
    CAN_TxHeaderTypeDef TxHeader;
    uint32_t TxMailbox;

    // 配置发送帧头
    TxHeader.StdId = ID;
    TxHeader.ExtId = ID;
    TxHeader.IDE = CAN_ID_STD;  // 标准帧
    TxHeader.RTR = CAN_RTR_DATA;  // 数据帧
    TxHeader.DLC = Length;
    TxHeader.TransmitGlobalTime = DISABLE;

    // 发送数据并等待完成
    if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, Data, &TxMailbox) != HAL_OK)
        return;

    // 等待发送完成或超时
    uint32_t Timeout = 0;
    while (HAL_CAN_IsTxMessagePending(&hcan, TxMailbox))
    {
        if (++Timeout > 100000)
            break;
    }
}

使用方法

c 复制代码
uint8_t data[] = {0x11, 0x22, 0x33, 0x44};
MyCAN_Transmit(0x123, 4, data);

6. CAN数据接收实现

c 复制代码
uint8_t MyCAN_ReceiveFlag(void)
{
    return HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0) > 0;
}

void MyCAN_Receive(uint32_t *ID, uint8_t *Length, uint8_t *Data)
{
    CAN_RxHeaderTypeDef RxHeader;

    if (HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, Data) != HAL_OK)
        return;

    // 解析标识符
    *ID = RxHeader.IDE == CAN_ID_STD ? RxHeader.StdId : RxHeader.ExtId;
    
    // 获取数据长度
    *Length = RxHeader.DLC;
}

使用方法

c 复制代码
uint32_t rx_id;
uint8_t rx_len, rx_data[8];

if (MyCAN_ReceiveFlag()) {
    MyCAN_Receive(&rx_id, &rx_len, rx_data);
    // 处理接收到的数据
}

7. 测试与验证方法

测试环境搭建

  1. 使用两块STM32开发板
  2. 通过CAN总线连接(确保终端电阻正确)
  3. 使用PCAN分析工具或串口调试工具监控通信

测试步骤

  1. 在主函数中调用MyCAN_Init()初始化CAN
  2. 使用MyCAN_Transmit()发送测试数据
  3. 在接收端使用MyCAN_ReceiveFlag()和MyCAN_Receive()接收数据
  4. 验证数据完整性、标识符和顺序

常见测试场景

  • 单次发送接收测试
  • 连续发送压力测试
  • 不同波特率兼容性测试
  • 异常帧处理测试

8. 常见问题与解决

问题1:无法接收到数据

  • 检查引脚配置是否正确(注意复用功能是否使能)
  • 确认波特率设置是否一致
  • 检查过滤器配置是否允许接收该ID
  • 确认CAN总线终端电阻是否连接正确

问题2:数据发送后无响应

  • 检查Tx引脚是否配置为复用推挽输出
  • 确认发送函数中TxMailbox是否正确
  • 检查CAN初始化是否成功
  • 使用CAN分析工具观察总线状态

问题3:接收数据错误

  • 检查数据长度码(DLC)是否正确
  • 确认接收缓冲区是否足够大
  • 检查发送端数据是否正确填充
  • 确认接收函数是否正确解析标识符

调试技巧

  • 使用HAL_CAN_ActivateNotification()启用中断通知
  • 实现CAN错误回调函数进行错误处理
  • 使用逻辑分析仪观察CAN_H和CAN_L信号

9. 总结与拓展

通过本教程,我们完整实现了STM32基于HAL库的CAN通信功能。从硬件配置到软件实现,再到测试验证,我们掌握了CAN通信的核心技术。

拓展方向

  1. 实现基于CAN的自定义协议栈
  2. 开发CAN数据加密传输功能
  3. 设计CAN网络故障诊断系统
  4. 优化CAN通信的实时性能

希望本教程能帮助你在嵌入式开发中高效利用CAN总线技术。如果有任何问题,欢迎在评论区交流!

开发环境:STM32CubeIDE 1.10.0 | 目标芯片:STM32F103C8T6 | 波特率:500kbps (APB Clock = 72MHz)

通过实际项目验证的代码,直接复制到你的工程即可运行!

相关推荐
电鱼智能的电小鱼5 分钟前
EFISH-SBC-RK3588 —— 厘米级定位 × 旗舰算力 × 工业级可靠‌
linux·人工智能·嵌入式硬件·边缘计算
ltqshs11 分钟前
STM32标准库和HAL库SPI发送数据的区别-即SPI_I2S_SendData()和HAL_SPI_Transmit()互换
stm32·单片机·嵌入式硬件
程序员JerrySUN28 分钟前
驱动开发硬核特训 · Day 22(上篇): 电源管理体系完整梳理:I2C、Regulator、PMIC与Power-Domain框架
linux·驱动开发·嵌入式硬件
xyd陈宇阳1 小时前
STM32(M4)入门:定时器延时与系统滴答(价值 3w + 的嵌入式开发指南)
stm32·单片机·嵌入式硬件
优信电子2 小时前
STM32 驱动 INA226 测量电流电压功率
stm32·单片机·嵌入式硬件
BW.SU2 小时前
单片机 + 图像处理芯片 + TFT彩屏 复选框控件
单片机·嵌入式硬件·gpu·ra8889·ra6809·液晶控制芯片·图形处理芯片
小智学长 | 嵌入式3 小时前
单片机-89C51部分:5、点亮LED
单片机·嵌入式硬件
嵌入式学习之旅3 小时前
单片机之间的双向通信
单片机·嵌入式硬件
零零刷3 小时前
德州仪器(TI)—TDA4VM芯片详解(1)—产品特性
人工智能·嵌入式硬件·深度学习·神经网络·自动驾驶·硬件架构·硬件工程