基于STM32的CAN通信完整例程(HAL库实现)

一、系统概述

CAN(Controller Area Network,控制器局域网) 是一种高可靠、多主机的串行通信协议,广泛应用于工业控制、汽车电子、航空航天等领域。本例程基于STM32F103C8T6(Cortex-M3,72MHz)和HAL库,实现CAN总线通信,支持标准帧(11位标识符) 和扩展帧(29位标识符),核心功能包括:CAN控制器初始化、报文发送(单帧/多帧)、报文接收(中断/轮询)、错误处理(总线关闭、错误计数)。

二、硬件设计

2.1 核心组件

模块 型号/参数 功能说明
主控 STM32F103C8T6(64KB Flash,20KB RAM) CAN控制器(bxCAN)、报文处理、数据调度
CAN收发器 TJA1050(高速CAN,5V供电) 转换TTL电平与CAN差分信号(CAN_H/CAN_L)
终端电阻 120Ω(两端节点) 消除信号反射,确保总线稳定
电源 5V USB供电(或3.3V LDO) 为STM32、TJA1050供电

2.2 硬件连接

模块 引脚(STM32F103C8T6) TJA1050引脚 说明
CAN_TX PA12(CAN1_TX) TXD 发送数据(TTL→CAN)
CAN_RX PA11(CAN1_RX) RXD 接收数据(CAN→TTL)
TJA1050 VCC(5V) 5V 电源正极
GND GND 电源负极
CAN_H CAN_H 差分信号正线(接另一节点CAN_H)
CAN_L CAN_L 差分信号负线(接另一节点CAN_L)
STB(待机) GND 正常工作模式(低电平)

三、软件设计(STM32CubeIDE + HAL库)

3.1 开发环境

  • IDE:STM32CubeIDE 1.13.0+

  • :STM32Cube_FW_F1_V1.8.0(HAL库)

  • 工具链:GCC ARM Embedded

3.2 CubeMX配置(关键步骤)

  1. 时钟配置

    • 选择HSE(8MHz),配置系统时钟72MHz,APB1时钟36MHz(CAN控制器挂载于APB1)。
  2. CAN配置

    • 使能CAN1(全功能模式),配置参数:

      • 模式:正常模式(Normal);

      • 波特率:500kbps(计算公式:波特率=APB1时钟/(分频系数×(BS1+BS2+SJW)),如36MHz/(6×(5+3+1))=36MHz/54≈666kbps,需调整分频系数=8,BS1=5,BS2=3,SJW=1→36MHz/(8×(5+3+1))=36MHz/72=500kbps);

      • 工作模式:自发自收(Loopback,测试用)或正常模式(双节点通信);

      • 自动重传:使能(Auto Retransmission)。

  3. GPIO配置

    • PA11(CAN_RX):输入模式(下拉);

    • PA12(CAN_TX):复用推挽输出。

  4. NVIC配置

    • 使能CAN1_RX0中断(接收中断),优先级配置为中等。
  5. 生成代码:选择"Generate peripheral initialization as a pair of .c/.h files per peripheral",生成工程。

3.3 核心代码实现

3.3.1 CAN初始化(自动生成+补充)

CubeMX生成的can.c文件包含CAN初始化函数,需补充波特率计算和过滤器配置:

c 复制代码
#include "can.h"
#include "stm32f1xx_hal.h"

CAN_HandleTypeDef hcan1;

// CAN初始化(500kbps,正常模式)
void MX_CAN1_Init(void) {
  hcan1.Instance = CAN1;
  hcan1.Init.Prescaler = 8;         // 分频系数=8(36MHz/8=4.5MHz)
  hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常模式(双节点通信)
  hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度=1TQ
  hcan1.Init.TimeSeg1 = CAN_BS1_5TQ; // BS1=5TQ
  hcan1.Init.TimeSeg2 = CAN_BS2_3TQ; // BS2=3TQ
  hcan1.Init.TimeTriggeredMode = DISABLE;
  hcan1.Init.AutoBusOff = DISABLE;
  hcan1.Init.AutoWakeUp = DISABLE;
  hcan1.Init.AutoRetransmission = ENABLE; // 自动重传
  hcan1.Init.ReceiveFifoLocked = DISABLE;
  hcan1.Init.TransmitFifoPriority = DISABLE;
  if (HAL_CAN_Init(&hcan1) != HAL_OK) {
    Error_Handler();
  }

  // 配置CAN过滤器(接收所有标准帧)
  CAN_FilterTypeDef filter;
  filter.FilterBank = 0;              // 过滤器组0
  filter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
  filter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽
  filter.FilterIdHigh = 0x0000;       // 标识符高16位(标准帧:11位ID)
  filter.FilterIdLow = 0x0000;        // 标识符低16位
  filter.FilterMaskIdHigh = 0x0000;   // 掩码高16位(0=不关心)
  filter.FilterMaskIdLow = 0x0000;    // 掩码低16位
  filter.FilterFIFOAssignment = CAN_RX_FIFO0; // 接收FIFO0
  filter.FilterActivation = ENABLE;   // 使能过滤器
  filter.SlaveStartFilterBank = 14;   // 单CAN控制器,无需从机
  if (HAL_CAN_ConfigFilter(&hcan1, &filter) != HAL_OK) {
    Error_Handler();
  }

  // 启动CAN
  if (HAL_CAN_Start(&hcan1) != HAL_OK) {
    Error_Handler();
  }

  // 使能接收中断
  if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) {
    Error_Handler();
  }
}
3.3.2 CAN报文发送函数
c 复制代码
// 发送CAN报文(标准帧,11位ID)
HAL_StatusTypeDef CAN_SendMessage(uint16_t id, uint8_t *data, uint8_t len) {
  CAN_TxHeaderTypeDef tx_header;
  uint32_t tx_mailbox;

  tx_header.StdId = id;              // 标准标识符(11位)
  tx_header.ExtId = 0x00;            // 扩展标识符(29位,不用)
  tx_header.RTR = CAN_RTR_DATA;      // 数据帧(非远程帧)
  tx_header.IDE = CAN_ID_STD;        // 标准帧
  tx_header.DLC = len;               // 数据长度(0~8字节)
  tx_header.TransmitGlobalTime = DISABLE;

  return HAL_CAN_AddTxMessage(&hcan1, &tx_header, data, &tx_mailbox);
}
3.3.3 CAN报文接收(中断回调)
c 复制代码
// CAN接收中断回调函数(FIFO0消息 pending)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
  CAN_RxHeaderTypeDef rx_header;
  uint8_t rx_data[8];

  if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
    // 打印接收信息(示例:ID=0x123,数据=0x01 0x02)
    printf("CAN Received: ID=0x%03X, Len=%d, Data=", rx_header.StdId, rx_header.DLC);
    for (uint8_t i=0; i<rx_header.DLC; i++) {
      printf("%02X ", rx_data[i]);
    }
    printf("\r\n");
  }
}
3.3.4 主函数(发送测试报文)
c 复制代码
#include "main.h"
#include "can.h"
#include "usart.h"  // 用于printf调试(重定向USART1)

int main(void) {
  HAL_Init();
  SystemClock_Config();  // 时钟配置(72MHz)
  MX_GPIO_Init();
  MX_CAN1_Init();        // CAN初始化(500kbps)
  MX_USART1_UART_Init(); // USART1初始化(115200bps,调试用)

  uint8_t tx_data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 测试数据

  while (1) {
    // 发送CAN报文(ID=0x123,数据长度8字节)
    if (CAN_SendMessage(0x123, tx_data, 8) == HAL_OK) {
      printf("CAN Sent: ID=0x123, Data=01 02 03 04 05 06 07 08\r\n");
    } else {
      printf("CAN Send Failed!\r\n");
    }
    HAL_Delay(1000); // 每秒发送一次
  }
}

参考代码 基于STM32的一个完整CAN通信例程 www.youwenfan.com/contentcsu/56567.html

四、关键问题与解决方案

4.1 波特率计算错误

  • 现象:通信乱码或无数据。

  • 原因:APB1时钟配置错误(如未配置为36MHz)、分频系数/BS1/BS2设置不当。

  • 解决

  • 确认APB1时钟:SystemClock_Config()RCC_CFGR_SW_PLL,APB1分频系数=2(72MHz/2=36MHz);

  • 波特率公式:波特率=APB1时钟/(Prescaler×(BS1+BS2+SJW)),如500kbps=36MHz/(8×(5+3+1))=36MHz/72=500kbps(Prescaler=8,BS1=5,BS2=3,SJW=1)。

4.2 接收不到数据

  • 现象:发送成功但接收回调不触发。

  • 原因:CAN过滤器配置错误(未接收目标ID)、终端电阻缺失(120Ω)、总线电平异常。

  • 解决

  • 配置过滤器为"接收所有帧"(掩码=0x0000);

  • 总线两端并联120Ω终端电阻;

  • 用示波器测量CAN_H/CAN_L差分电压(显性电平≈3.5V,隐性电平≈2.5V)。

4.3 总线关闭(Bus Off)

  • 现象:CAN控制器进入总线关闭状态(错误计数器超限)。

  • 原因:总线短路、节点故障、波特率不匹配。

  • 解决

  • 检查硬件连接(CAN_H/CAN_L是否短路);

  • 调用HAL_CAN_ResetErrorCounter(&hcan1)重置错误计数器;

  • 配置hcan1.Init.AutoBusOff = ENABLE(自动退出总线关闭)。

五、测试与验证

5.1 单节点自发自收测试

  • 配置:CubeMX中CAN模式设为"Loopback(自发自收)";

  • 现象:发送报文后,接收回调触发,打印接收数据(ID和数据与发送一致)。

5.2 双节点通信测试

  • 硬件:两个STM32节点(Node A、Node B),均按2.2节连接,总线两端接120Ω电阻;

  • 步骤

  1. Node A发送ID=0x123的报文;

  2. Node B接收并打印数据;

  3. Node B回复ID=0x456的报文;

  4. Node A接收并打印数据。

5.3 工具辅助测试

  • CAN分析仪(如USB-CAN适配器):监控总线报文,验证ID、数据、波特率正确性;

  • 串口助手 :通过USART1打印调试信息(需重定向printf到USART1)。

六、总结

本例程基于STM32F103和HAL库实现了CAN通信,核心是CAN控制器初始化、波特率配置、报文收发、中断接收。通过CubeMX快速配置+HAL库函数调用,可快速实现双节点或多节点通信,适用于工业传感器网络、电机控制等场景。

相关推荐
lzj_pxxw1 小时前
W25Q64存储芯片 软件设计刚需常识
stm32·单片机·嵌入式硬件·mcu·学习
时空自由民.4 小时前
蓝牙协议栈介绍
linux·网络·单片机
蓝天居士5 小时前
M24C64芯片资料与程序代码(2)
嵌入式硬件·芯片资料
吃米饭7 小时前
HC32L021C8UB 移植 FreeRTOS
stm32·嵌入式·freertos·rtos
asjodnobfy7 小时前
开关电源尖峰电压计算
嵌入式硬件·硬件工程
振南的单片机世界7 小时前
开漏输出:只能拉低,不能拉高,高电平靠“外部”帮忙
stm32·单片机·嵌入式硬件
FFF团团员9098 小时前
CCS快速使用4(tim,pwm)
单片机·嵌入式硬件
某先森不吃鱼9 小时前
工程日志——离轴编码器矫正与磁场串扰解决
嵌入式硬件
黑白园9 小时前
STM32 通过I2C 读写EEPR0M AT24C02
stm32·单片机·嵌入式硬件