STM32F103VET6 CAN 双板通信实战从配置到代码实现在工业控制和汽车电子领域CANController Area Network总线凭借其高可靠性和多主特性成为最常用的通信协议之一。本文将带你一步步在 STM32F103VET6 上实现两块开发板之间的 CAN 通信包含硬件连接、HAL 库配置、过滤器设置及中断接收的完整代码。一、硬件准备与连接在编写代码之前物理连接是通信成功的基础。CAN 总线是差分信号必须严格遵循以下接线规则该图来自Embedd1000010出处1. 接线图你需要两块 STM32F103VET6 开发板或一块板子连接 USB-CAN 分析仪以及 CAN 收发器如 TJA1050 模块。板子 A (或分析仪)连接线板子 B (或分析仪)CAN_H------CAN_HCAN_L------CAN_LGND------GND(必须共地)2. 终端电阻CAN 总线要求在物理链路的两端并联120Ω电阻以消除信号反射。注意如果你的开发板板载了 120Ω 电阻无需额外添加如果没有请在两块板子的 CAN_H 和 CAN_L 之间各焊一个 120Ω 电阻。二、软件架构与流程根据你提供的流程图CAN 通信的初始化逻辑非常清晰。在main函数中我们需要严格按照以下顺序执行CAN 初始化配置波特率、模式正常/环回。过滤器初始化决定接收哪些 ID 的数据。启动 CAN进入正常工作模式。开启接收中断让 CPU 在收到数据时自动处理不阻塞主循环。发送数据在while(1)中循环发送。三、核心代码实现以下是基于 STM32 HAL 库的完整实现代码。1. 头文件与全局变量首先定义全局的句柄和报文结构体。TxHeader和RxHeader是 HAL 库收发函数的核心参数。#include main.h #include stdio.h #include string.h CAN_HandleTypeDef hcan; UART_HandleTypeDef huart1; // 用于 printf 打印调试 /* CAN 报文结构体 */ CAN_TxHeaderTypeDef TxHeader; CAN_RxHeaderTypeDef RxHeader; uint8_t RxData[8]; // 接收数据缓存 uint32_t TxMailbox; // 发送邮箱号 char MsgBuffer[50]; // 打印缓存 /* 函数声明 */ void CAN_Filter_Config(void); void CAN_Start_Init(void); void CAN_Send_Msg(uint8_t *data, uint8_t len);2. CAN 初始化波特率配置在MX_CAN_Init()中波特率是最关键的参数。双板通信必须保证波特率完全一致。static void MX_CAN_Init(void) { hcan.Instance CAN1; hcan.Init.Prescaler 9; // 波特率分频系数 hcan.Init.Mode CAN_MODE_NORMAL; // 正常模式调试时可用环回模式 CAN_MODE_LOOPBACK hcan.Init.SyncJumpWidth CAN_SJW_1TQ; hcan.Init.TimeSeg1 CAN_BS1_6TQ; // 时间段1 hcan.Init.TimeSeg2 CAN_BS2_1TQ; // 时间段2 // 波特率计算公式: 36MHz / (Prescaler * (1 BS1 BS2)) // 36000000 / (9 * (1 6 1)) 500Kbps hcan.Init.TimeTriggeredMode DISABLE; hcan.Init.AutoBusOff DISABLE; hcan.Init.AutoWakeUp DISABLE; hcan.Init.AutoRetransmission ENABLE; // 开启自动重传 hcan.Init.ReceiveFifoLocked DISABLE; hcan.Init.TransmitFifoPriority DISABLE; if (HAL_CAN_Init(hcan) ! HAL_OK) { Error_Handler(); } }3. 过滤器配置CAN 过滤器决定了单片机“愿意”接收哪些数据。以下代码配置为32位掩码模式接收所有标准 ID 数据。void CAN_Filter_Config(void) { CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank 0; // 过滤器组0 sFilterConfig.FilterMode CAN_FILTERMODE_IDMASK; // 掩码模式 sFilterConfig.FilterScale CAN_FILTERSCALE_32BIT; // 32位尺度 sFilterConfig.FilterIdHigh 0x0000; // ID高16位 sFilterConfig.FilterIdLow 0x0000; // ID低16位 sFilterConfig.FilterMaskIdHigh 0x0000; // 掩码高16位 (0表示不关心) sFilterConfig.FilterMaskIdLow 0x0000; // 掩码低16位 sFilterConfig.FilterFIFOAssignment CAN_RX_FIFO0;// 匹配后存入 FIFO0 sFilterConfig.FilterActivation ENABLE; // 激活 sFilterConfig.SlaveStartFilterBank 14; // 仅用于互联型产品F103可忽略 if (HAL_CAN_ConfigFilter(hcan, sFilterConfig) ! HAL_OK) { Error_Handler(); } }4. 启动与中断使能这一步将 CAN 外设置于工作状态并打开接收中断允许 CPU 响应收到的数据。void CAN_Start_Init(void) { // 1. 启动 CAN if (HAL_CAN_Start(hcan) ! HAL_OK) { Error_Handler(); } // 2. 开启接收中断 (FIFO 0 消息挂起中断) if (HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING) ! HAL_OK) { Error_Handler(); } // 3. 配置 NVIC 中断优先级 (通常在 stm32f1xx_it.c 中处理但需在此使能) HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0); HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn); }5. 数据发送函数封装一个发送函数方便在while(1)中调用。void CAN_Send_Msg(uint8_t *data, uint8_t len) { TxHeader.StdId 0x111; // 标准 ID TxHeader.ExtId 0x0; // 扩展 ID (不使用) TxHeader.IDE CAN_ID_STD; // 使用标准 ID TxHeader.RTR CAN_RTR_DATA; // 数据帧 TxHeader.DLC len; // 数据长度 TxHeader.TransmitGlobalTime DISABLE; // 发送数据 if (HAL_CAN_AddTxMessage(hcan, TxHeader, data, TxMailbox) ! HAL_OK) { printf(发送失败!\r\n); } }6. 接收中断回调当 CAN 总线收到数据且通过过滤器后硬件会自动触发中断并执行此回调函数。void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { if (hcan-Instance CAN1) { // 获取数据 HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, RxHeader, RxData); printf(--- 接收到数据!\r\n); printf(ID: 0x%X, DLC: %d\r\n, RxHeader.StdId, RxHeader.DLC); printf(Data: %02X %02X %02X %02X\r\n, RxData[0], RxData[1], RxData[2], RxData[3]); printf(---\r\n); } } // 中断服务程序 (需放在 stm32f1xx_it.c 或 main.c 中) void CAN1_RX0_IRQHandler(void) { HAL_CAN_IRQHandler(hcan); }四、主函数逻辑将上述模块在main函数中串联起来。int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN_Init(); MX_USART1_UART_Init(); // 初始化串口用于 printf /* CAN 配置流程 */ CAN_Filter_Config(); // 配置过滤器 CAN_Start_Init(); // 启动并开启中断 uint8_t tx_data[4] {0x01, 0x02, 0x03, 0x04}; uint32_t tick 0; printf(CAN 双板通信测试开始...\r\n); while (1) { // 每 500ms 发送一次数据 if (HAL_GetTick() - tick 500) { tick HAL_GetTick(); tx_data[0]; // 数据自增方便观察 CAN_Send_Msg(tx_data, 4); } } }五、常见问题排查收不到数据检查 GND 是否连接共地是必须的。检查波特率是否一致两块板子必须完全相同。检查过滤器配置建议先配置为全接收掩码全 0测试通断。printf 不打印确保重定向了_write或fputc函数到 USART1。报错HAL_CAN_ActivateNotification确保在stm32f1xx_it.c中添加了CAN1_RX0_IRQHandler中断入口函数。通过以上步骤你应该能成功实现 STM32F103 之间的双向数据通信。