如果你正在用STM32F103做项目尤其是汽车电子、工业控制或机器人领域那么CAN总线通讯几乎是一个绕不开的技术点。很多人第一次接触CAN时会觉得它比UART、I2C复杂得多——协议栈、过滤器、邮箱、仲裁、错误帧……一堆新概念扑面而来。网上的资料要么是纯理论要么是零散的代码片段真正能把STM32F103的CAN模块讲清楚、让你能快速上手调试的教程并不多。这篇文章要解决的就是这个问题。我们不只讲“CAN协议是什么”而是聚焦于如何在STM32F103这颗经典的MCU上快速、正确地配置和使用其内置的CAN控制器实现稳定可靠的双向通讯。你会发现只要理解了几个核心机制特别是过滤器配置和邮箱管理避开几个常见的坑比如波特率计算错误、过滤器掩码模式理解偏差STM32的CAN开发并没有想象中那么难。本文将带你从零开始完成一个完整的STM32F103 CAN通讯示例。你会学到CAN基础概念与STM32F103 CAN模块的独特之处。如何精准计算CAN波特率确保与总线上的其他节点匹配。过滤器Filter的配置精髓如何用掩码模式和列表模式高效接收目标报文。如何利用发送邮箱和接收FIFO实现数据的收发。通过一个自发自收的完整代码示例快速验证整个流程。开发中最高频的几个问题与排查思路比如“为什么收不到数据”、“为什么总线错误”。无论你是正在做课程设计、毕业设计还是实际产品开发这篇文章都能为你提供一个清晰、可落地的实践指南。建议收藏边看边操作。1. 为什么STM32F103的CAN值得你花时间掌握在开始技术细节之前我们先明确一点为什么是CAN为什么是STM32F103CANController Area Network的核心价值在于高可靠性和多主架构。它采用差分信号抗干扰能力远强于UART、I2C它没有严格的主从概念任何节点都可以在总线空闲时主动发送数据并通过“仲裁”机制优雅地解决冲突而不是像I2C那样靠时钟线控制。这使得它成为汽车如OBD-II、工业自动化如PLC网络、医疗器械等对稳定性要求极高场景的“标配”。而STM32F103尤其是C8T6、RCT6等型号作为一代“神片”其内置的bxCANBasic Extended CAN控制器是一个完整且功能强大的模块。它支持CAN 2.0A和2.0B协议最高速率1Mbps拥有3个发送邮箱和2个接收FIFO各带3级深度以及一组强大的标识符过滤器。这意味着你无需外接独立的CAN控制器芯片如MCP2515就能直接连接CAN收发器如TJA1050接入总线极大地简化了硬件设计和成本。然而它的强大也带来了配置的复杂性。很多开发者卡在第一步波特率算不对导致无法通讯或者卡在第二步过滤器配不对导致收不到任何报文或收到大量垃圾报文。本文将直击这些痛点把配置过程拆解成清晰的步骤。2. CAN基础概念与STM32F103 bxCAN模块速览在动手写代码前我们需要统一“语言”。如果你已经了解CAN可以快速浏览本节如果是新手这里是你理解后续操作的基础。2.1 CAN通讯的核心机制差分信号与总线结构CAN使用CAN_H和CAN_L两根线以差分电压形式传递信号。这种设计对共模噪声有极强的抑制能力。所有节点都并联在总线上需要终端电阻通常120Ω来消除信号反射。多主与仲裁任何节点都可以在总线空闲时发起传输。如果多个节点同时发送它们会通过对比标识符ID来仲裁。ID值越小优先级越高。优先级低的节点会自动退出发送转为接收整个过程没有数据损坏。这是CAN实时性的关键。报文格式主要分为数据帧携带数据和远程帧请求数据。我们最常用的是数据帧。一个标准数据帧包含仲裁场包含标识符11位标准ID或29位扩展ID和RTR位区分数据/远程帧。控制场包含IDE位标准/扩展帧标识、DLC数据长度码0-8字节。数据场实际要传输的数据最多8字节。CRC场、应答场、帧结束等。错误处理CAN有复杂的错误检测CRC、格式检查等和错误处理机制错误主动、错误被动、总线关闭状态保证了极高的数据可靠性。2.2 STM32F103 bxCAN模块架构要点STM32的bxCAN模块将上述协议硬件化你需要关注以下几个部分工作模式初始化模式配置波特率、过滤器等参数前必须进入的模式。正常模式常规的收发模式。静默模式只能接收不能发送用于监听总线。环回模式自发自收用于测试不对外输出信号。环回静默模式内部环回且不干扰总线。发送单元包含3个发送邮箱。当你需要发送报文时将数据填入一个空闲的邮箱硬件会自动处理发送和仲裁。你需要检查邮箱状态空、挂起、发送成功/失败。接收单元包含2个接收FIFOFIFO0和FIFO1每个FIFO有3个邮箱深度。所有通过过滤器的报文会被存入其中一个FIFO。你需要从中读取数据。过滤器组Filter这是bxCAN最核心也最容易出错的部分。STM32F103有14个过滤器组互联型产品有28个。每个过滤器组可以配置为掩码模式指定一个ID值和掩码。掩码位为1表示必须匹配ID对应位为0表示不关心。功能强大且常用。列表模式指定一个或两个ID列表只有完全匹配的ID才能通过。每个过滤器可以关联到FIFO0或FIFO1。关键理解过滤器不是“过滤掉”而是“允许通过”。只有通过任一过滤器组的报文才会被存入对应的FIFO。如果所有过滤器都不匹配报文会被丢弃。理解了这些我们就可以开始搭建环境了。3. 硬件连接与软件环境准备3.1 硬件清单与连接你需要准备STM32F103核心板/开发板如最小系统板。CAN收发器模块如TJA1050或SN65HVD230模块。这是必须的因为STM32的CAN控制器输出的是逻辑电平需要收发器转换为差分信号。USB转CAN适配器如PCAN、USBCAN、CANable等。用于连接电脑模拟另一个CAN节点或监控总线。如果没有可以用两块STM32板子互连测试。杜邦线若干。120Ω终端电阻至少两个挂在总线两端。连接示意图STM32F103 -- TJA1050模块 -- CAN总线 -- USB转CAN适配器 -- PC (MCU) (逻辑转差分) (CAN_H, CAN_L) (差分转USB) (上位机)STM32与TJA1050PA11/CAN_RX(STM32) -RX(TJA1050)PA12/CAN_TX(STM32) -TX(TJA1050)3.3V-VCCGND-GNDTJA1050与CAN总线CAN_H- 总线CAN_HCAN_L- 总线CAN_L总线两端在物理距离最远的两个节点处分别在CAN_H和CAN_L之间接入一个120Ω电阻。注意STM32F103的CAN引脚通常是PA11(RX)/PA12(TX)重映射后是PB8(RX)/PB9(TX)。请查阅你的芯片数据手册确认。3.2 软件环境与工程创建我们使用最通用的开发环境IDEKeil MDK-ARM (uVision) 或 STM32CubeIDE。库标准外设库StdPeriph Lib或HAL库。本文示例将基于标准外设库因为它更贴近寄存器有助于理解原理。HAL库的思路是类似的。调试工具串口用于打印调试信息或调试器J-Link/ST-Link。创建工程步骤以Keil StdPeriph Lib为例使用Keil新建一个基于STM32F103的工程。将标准外设库中的相关文件添加到工程stm32f10x_can.c,stm32f10x_gpio.c,stm32f10x_rcc.c,stm32f10x_usart.c如果要用串口打印。在工程选项中定义宏USE_STDPERIPH_DRIVER和STM32F10X_MD根据你的芯片型号可能是MD/HD/XL等。配置正确的时钟源通常使用外部8MHz晶振PLL到72MHz。4. CAN模块初始化与波特率计算详解初始化是第一步也是最容易出错的一步。核心流程是进入初始化模式 - 配置波特率 - 配置过滤器 - 退出初始化模式。4.1 精确计算波特率CAN波特率 APB1时钟 / (Prescaler * (TimeSegment1 TimeSegment2 1))APB1时钟STM32F103在72MHz系统时钟下APB1时钟通常是36MHz。Prescaler预分频器范围1-1024。TimeSegment1 (BS1)包含同步段(Sync_Seg)和传播时间段(Prop_Seg)。设置采样点位置。TimeSegment2 (BS2)相位缓冲段2。一个经典且稳定的配置500Kbps 假设APB1时钟为36MHz目标波特率500Kbps。选择Prescaler 4。TimeSegment1 13个时间单位。TimeSegment2 2个时间单位。计算36000000 / (4 * (13 2 1)) 36000000 / (4 * 16) 36000000 / 64 562500。这略高于500K但误差在允许范围内1%。更精确的配置需要调整BS1/BS2。实际配置代码// 文件can.c #include stm32f10x_can.h #include stm32f10x_gpio.h #include stm32f10x_rcc.h void CAN_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; CAN_FilterInitTypeDef CAN_FilterInitStructure; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 2. 配置CAN引脚 PA11-RX, PA12-TX GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; // RX GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; // TX GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. CAN单元复位并进入初始化模式 CAN_DeInit(CAN1); CAN_StructInit(CAN_InitStructure); // 4. 配置CAN工作模式与波特率 CAN_InitStructure.CAN_TTCM DISABLE; // 禁止时间触发通信模式 CAN_InitStructure.CAN_ABOM ENABLE; // 使能自动离线管理 CAN_InitStructure.CAN_AWUM ENABLE; // 使能自动唤醒模式 CAN_InitStructure.CAN_NART DISABLE; // 禁止非自动重传建议DISABLE保证送达 CAN_InitStructure.CAN_RFLM DISABLE; // FIFO不锁定新报文覆盖旧报文 CAN_InitStructure.CAN_TXFP DISABLE; // 发送优先级由标识符决定 CAN_InitStructure.CAN_Mode CAN_Mode_Normal; // 正常模式 // 波特率设置36MHz APB1, 500Kbps, BS113, BS22, Prescaler4 CAN_InitStructure.CAN_SJW CAN_SJW_1tq; // 重新同步跳跃宽度 CAN_InitStructure.CAN_BS1 CAN_BS1_13tq; // TimeSegment1 CAN_InitStructure.CAN_BS2 CAN_BS2_2tq; // TimeSegment2 CAN_InitStructure.CAN_Prescaler 4; if (CAN_Init(CAN1, CAN_InitStructure) ! CAN_InitStatus_Failed) { // 初始化成功 } else { // 初始化失败需检查硬件或配置 while(1); } // 5. 配置CAN过滤器下一节详细讲 // 先留空我们马上配置 }关键点CAN_NART建议设为DISABLE即启用自动重传。这样当发送失败如仲裁丢失时硬件会自动重试确保报文最终发出。设为ENABLE则只发一次适合特定实时性要求极高的场景。CAN_ABOM和CAN_AWUM建议使能让CAN模块能自动从错误状态恢复。波特率计算务必与总线上其他节点严格一致否则无法通信。5. 过滤器配置精准接收的关键过滤器是bxCAN的“守门员”。配置不当你会收不到任何数据或者收到大量无关报文导致FIFO溢出。5.1 过滤器组工作模式选择假设我们只想接收标准ID为0x123的报文。有两种配置方式方式一掩码模式更常用、更灵活过滤器ID0x123 5左移5位因为标准ID占高11位过滤器掩码0x7FF 50x7FF是11位全1表示所有11位ID位都必须匹配效果只有ID恰好等于0x123的报文才能通过。方式二列表模式在列表模式下直接设置过滤器寄存器为0x123 5。效果与上述掩码模式效果相同但列表模式一个过滤器组只能精确匹配1个或2个ID。掩码模式的优势在于可以使用“不关心”位。例如你想接收ID从0x120到0x12F的报文可以设置ID 0x120 5掩码 0x7F0 5(二进制11111110000)这表示高7位0x12必须匹配低4位不关心。这样0x120-0x12F都能通过。5.2 过滤器配置代码实现我们将过滤器组0关联到FIFO0采用32位掩码模式接收标准ID为0x123的报文。// 接上一段CAN_Config函数 // 5. 配置CAN过滤器 CAN_FilterInitStructure.CAN_FilterNumber 0; // 使用过滤器组0 CAN_FilterInitStructure.CAN_FilterMode CAN_FilterMode_IdMask; // 掩码模式 CAN_FilterInitStructure.CAN_FilterScale CAN_FilterScale_32bit; // 32位宽 // 设置要过滤的ID标准ID 0x123数据帧非远程帧 CAN_FilterInitStructure.CAN_FilterIdHigh ((0x123 5) 0xFFFF0000) 16; // ID高16位 CAN_FilterInitStructure.CAN_FilterIdLow ((0x123 5) 0x0000FFFF); // ID低16位 // 设置掩码所有位都必须匹配 CAN_FilterInitStructure.CAN_FilterMaskIdHigh ((0x7FF 5) 0xFFFF0000) 16; // 掩码高16位 CAN_FilterInitStructure.CAN_FilterMaskIdLow ((0x7FF 5) 0x0000FFFF); // 掩码低16位 CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_Filter_FIFO0; // 通过过滤器的报文存到FIFO0 CAN_FilterInitStructure.CAN_FilterActivation ENABLE; // 使能该过滤器 CAN_FilterInit(CAN_FilterInitStructure); // 6. 使能CAN接收中断可选但推荐使用中断方式接收 CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // 使能FIFO0消息挂起中断配置解析CAN_FilterNumber选择0-13中的一个过滤器组。CAN_FilterModeIdMask掩码模式或IdList列表模式。CAN_FilterScale32bit或16bit。32bit模式用一个过滤器组检查一个ID标准或扩展。16bit模式用一个过滤器组检查两个标准ID。CAN_FilterIdHigh/Low和CAN_FilterMaskIdHigh/Low需要根据模式仔细计算。对于标准帧ID左移5位后放入32位寄存器。STID[10:0]占据位[31:21]。CAN_FilterFIFOAssignment决定匹配的报文去FIFO0还是FIFO1。CAN_FilterActivation必须ENABLE否则过滤器无效。重要提醒过滤器配置必须在初始化模式下进行。我们的CAN_Init函数内部会进入初始化模式配置完成后自动退出。如果在之后需要修改过滤器必须再次请求进入初始化模式CAN_EnterInitMode配置完再退出CAN_LeaveInitMode。6. 发送与接收邮箱与FIFO实战配置好波特率和过滤器CAN模块就准备就绪了。接下来是核心的收发操作。6.1 发送数据使用发送邮箱发送需要填充一个CanTxMsg结构体然后将其放入一个空闲的发送邮箱。// 文件can.c uint8_t CAN_SendMessage(uint32_t id, uint8_t* data, uint8_t length) { CanTxMsg TxMessage; uint8_t mailbox 0; // 1. 填充报文结构 TxMessage.StdId id; // 标准ID TxMessage.ExtId 0; // 扩展ID标准帧时设为0 TxMessage.IDE CAN_Id_Standard; // 标准帧 TxMessage.RTR CAN_RTR_Data; // 数据帧 TxMessage.DLC length; // 数据长度 (0-8) for (int i 0; i length; i) { TxMessage.Data[i] data[i]; } // 2. 发送报文返回使用的邮箱号0,1,2或CAN_NO_MB0xFF表示无空闲邮箱 mailbox CAN_Transmit(CAN1, TxMessage); if (mailbox CAN_TxStatus_NoMailBox) { // 没有空闲邮箱发送失败 return 1; } // 3. 可选等待发送完成 // while(CAN_TransmitStatus(CAN1, mailbox) ! CAN_TxStatus_Ok); return 0; }发送流程说明CAN_Transmit函数会查找第一个空闲的发送邮箱共3个将报文填入并启动发送。发送是硬件自动完成的包括位定时、仲裁、错误重传等。你可以通过CAN_TransmitStatus查询指定邮箱的发送状态成功、失败、挂起。6.2 接收数据轮询与中断两种方式方式一轮询方式简单但效率低// 文件can.c uint8_t CAN_ReceiveMessage_Polling(CanRxMsg* RxMessage) { // 检查FIFO0是否有数据 if (CAN_MessagePending(CAN1, CAN_FIFO0) 0) { CAN_Receive(CAN1, CAN_FIFO0, RxMessage); return 1; // 收到数据 } return 0; // 无数据 }方式二中断方式推荐实时性高配置NVIC嵌套向量中断控制器// 文件can.c (初始化部分补充) void CAN_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置优先级分组 NVIC_InitStructure.NVIC_IRQChannel USB_LP_CAN1_RX0_IRQn; // CAN1 RX0中断FIFO0 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }编写中断服务函数// 文件stm32f10x_it.c extern CanRxMsg RxMessage; // 在can.c中定义 extern volatile uint8_t can_data_received_flag; // 接收标志位 void USB_LP_CAN1_RX0_IRQHandler(void) { if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) ! RESET) { // FIFO0有新的报文 CAN_Receive(CAN1, CAN_FIFO0, RxMessage); can_data_received_flag 1; // 设置标志位在主循环中处理 CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0); // 清除中断标志 } }在主循环中处理接收到的数据// 文件main.c CanRxMsg RxMessage; volatile uint8_t can_data_received_flag 0; int main(void) { // 系统初始化... CAN_Config(); CAN_NVIC_Config(); // ...其他初始化 while(1) { if (can_data_received_flag) { can_data_received_flag 0; // 处理RxMessage中的数据 // 例如打印ID和数据 // printf(ID:0x%03X, Len:%d, Data:, RxMessage.StdId, RxMessage.DLC); // for(int i0; iRxMessage.DLC; i) printf(%02X , RxMessage.Data[i]); // printf(\n); } // ...其他任务 } }中断方式优势无需不断轮询节省CPU资源响应及时。在复杂的多任务系统中这是首选。7. 完整示例STM32F103 CAN自发自收测试为了验证整个配置流程我们实现一个“环回模式”下的自发自收测试。这个测试不需要连接外部CAN总线是验证代码逻辑最安全快捷的方式。7.1 主程序代码// 文件main.c #include stm32f10x.h #include can.h // 假设我们将CAN相关函数声明在can.h中 #include stdio.h // 用于串口打印需要实现fputc重定向 CanTxMsg TxMsg; CanRxMsg RxMsg; volatile uint8_t can_rx_flag 0; int main(void) { // 初始化系统时钟、GPIO等 SystemInit(); // 初始化串口用于调试输出省略具体代码假设已实现 USART1_Config(); // 配置CAN为环回模式关键 CAN_Config_LoopBack(); // 这个函数与之前的CAN_Config类似只是将模式改为CAN_Mode_LoopBack // 配置CAN接收中断 CAN_NVIC_Config(); printf(STM32F103 CAN Loopback Test Start...\r\n); uint8_t send_data[8] {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; uint32_t send_id 0x123; while(1) { // 发送一帧数据 if (CAN_SendMessage(send_id, send_data, 8) 0) { printf(Message Sent. ID:0x%03X, Data:, send_id); for(int i0; i8; i) printf(%02X , send_data[i]); printf(\r\n); } else { printf(Send Failed: No free mailbox.\r\n); } // 等待接收中断方式 Delay_ms(1000); // 简单延时实际应用中用更精确的方式 if (can_rx_flag) { can_rx_flag 0; if (RxMsg.StdId send_id) { printf(Message Received. ID:0x%03X, Data:, RxMsg.StdId); for(int i0; iRxMsg.DLC; i) printf(%02X , RxMsg.Data[i]); printf(\r\n); // 简单校验数据 uint8_t match 1; for(int i0; i8; i) { if (RxMsg.Data[i] ! send_data[i]) match 0; } if (match) printf(Data Match OK!\r\n); else printf(Data Match FAIL!\r\n); } } // 修改数据准备下次发送 for(int i0; i8; i) { send_data[i]; } Delay_ms(1000); } }7.2 修改后的环回模式CAN配置函数// 文件can.c void CAN_Config_LoopBack(void) { // ... 前面GPIO、时钟初始化与CAN_Config相同 ... CAN_InitStructure.CAN_Mode CAN_Mode_LoopBack; // 关键改为环回模式 // ... 波特率、过滤器等配置保持不变 ... CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_13tq; CAN_InitStructure.CAN_BS2 CAN_BS2_2tq; CAN_InitStructure.CAN_Prescaler 4; // ... 其余初始化代码 ... }7.3 中断服务函数// 文件stm32f10x_it.c void USB_LP_CAN1_RX0_IRQHandler(void) { if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) ! RESET) { CAN_Receive(CAN1, CAN_FIFO0, RxMsg); can_rx_flag 1; CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0); } }运行结果 通过串口调试助手你应该能看到类似以下的输出表明CAN模块自发自收成功STM32F103 CAN Loopback Test Start... Message Sent. ID:0x123, Data:11 22 33 44 55 66 77 88 Message Received. ID:0x123, Data:11 22 33 44 55 66 77 88 Data Match OK! Message Sent. ID:0x123, Data:12 23 34 45 56 67 78 89 Message Received. ID:0x123, Data:12 23 34 45 56 67 78 89 Data Match OK! ...成功要点模式设为CAN_Mode_LoopBack。波特率计算正确。过滤器配置允许接收ID为0x123的帧。发送和接收代码逻辑正确。8. 切换到正常模式与真实总线测试环回模式测试通过后就可以进行真实的总线通讯了。修改模式将CAN_InitStructure.CAN_Mode从CAN_Mode_LoopBack改回CAN_Mode_Normal。连接硬件按照第3节的说明将STM32通过TJA1050连接到CAN总线并确保总线两端有120Ω终端电阻。连接另一个节点使用USB转CAN适配器连接到电脑并使用上位机软件如CANTest、CANPro、SavvyCAN等设置相同的波特率500Kbps。测试发送在STM32端发送ID为0x123的数据。在电脑端的上位机软件中应该能看到来自STM32的报文。测试接收在电脑端的上位机软件中向总线发送一个ID为0x123的报文。在STM32端通过串口打印应该能接收到并打印出该报文。如果这一步失败请进入下一节的故障排查。9. 常见问题与排查思路FAQ以下是STM32F103 CAN开发中最常遇到的几个问题及解决方法。问题现象可能原因排查步骤解决方案环回模式测试正常但正常模式无法收发1. 硬件连接错误RX/TX反接。2. CAN收发器未工作或损坏。3. 总线缺少终端电阻。4. 波特率与其他节点不一致。1. 用万用表检查TJA1050的VCC、GND。2. 测量CAN_H和CAN_L之间的静态电压正常应约2.5V。3. 检查是否有120Ω电阻接在总线两端。4. 确认所有节点的波特率、BS1、BS2、Prescaler完全一致。1. 纠正接线。2. 更换收发器模块。3. 补上终端电阻。4. 统一波特率配置。能发送但收不到任何报文1. 过滤器配置错误ID不匹配。2. 接收FIFO溢出新报文被丢弃。3. 接收中断未使能或未正确处理。4. 报文被其他过滤器屏蔽。1. 检查过滤器ID和掩码设置。2. 检查CAN_RFLM位如果为DISABLEFIFO满时新报文会覆盖旧报文如果为ENABLE则会丢弃新报文。检查FIFO状态寄存器。3. 检查NVIC和CAN中断使能位。4. 禁用所有过滤器CAN_FilterActivation DISABLE测试是否能收到所有报文。1. 重新计算过滤器参数或先禁用过滤器测试。2. 及时读取FIFO数据或调整CAN_RFLM。3. 正确配置中断并清除中断标志。4. 仔细规划过滤器组避免冲突。总线频繁进入错误状态Bus-Off1. 波特率偏差过大。2. 总线物理层问题短路、开路、干扰。3. 多个节点同时发送冲突严重。1. 用示波器测量实际波特率。2. 检查总线布线远离强干扰源使用双绞线。3. 检查CAN_ABOM是否使能自动恢复。1. 精确计算并统一波特率。2. 改善硬件连接和屏蔽。3. 确保CAN_ABOMENABLE。发送邮箱总是满发送失败1. 总线持续繁忙无机会发送。2. 发送优先级低在仲裁中总是失败。3. 错误状态导致无法发送。1. 监听总线看是否一直有数据。2. 尝试发送更高优先级更小ID的报文测试。3. 检查CAN错误状态寄存器。1. 优化网络负载或选择空闲时段发送。2. 合理规划报文ID优先级。3. 检查并清除错误或重启CAN初始化。收到的数据长度DLC不对1. 发送方DLC设置错误。2. 接收方处理DLC的逻辑错误。1. 确认发送方填充的DLC字段0-8。2. 接收方打印RxMessage.DLC进行核对。1. 修正发送代码确保DLC与实际数据字节数一致。2. 接收方根据DLC处理数据不要固定为8字节。通用排查顺序软件层面确认代码逻辑特别是波特率、过滤器、模式Normal/Loopback的配置。硬件层面测量CAN收发器电源、CAN_H/CAN_L差分电压静止时应约2.5V显性位时CAN_H~3.5VCAN_L~1.5V。工具辅助使用USB转CAN适配器监听总线看报文是否真的发出、ID和数据是否正确。这是最直接的诊断方法。10. 最佳实践与工程建议当你掌握了基础收发后以下建议能帮助你在实际项目中构建更健壮的CAN应用。波特率配置标准化在团队或项目中统一使用一个精确的波特率计算工具或表格。常见的如500Kbps、250Kbps、125Kbps、100Kbps、50Kbps。记录下对应的Prescaler、BS1、BS2值并写入代码注释。过滤器规划策略分组接收将不同功能的报文映射到不同的FIFO。例如高实时性控制报文到FIFO0并配高优先级中断普通状态报文到FIFO1。合理使用掩码利用掩码模式实现ID范围接收减少过滤器组占用。例如接收某个范围内的所有诊断报文。预留调试通道保留一个过滤器组用于接收特定的调试ID便于在线监测和诊断。错误处理与状态监控定期检查CAN错误状态寄存器CAN_ESR监控错误警告、被动错误、总线关闭等状态。使能错误中断CAN_IT_ERR在中断服务函数中记录错误类型便于快速定位物理层或协议层问题。发送管理实现一个简单的发送队列。当三个发送邮箱都满时将待发送报文放入软件队列等待邮箱空闲时再取出发送避免数据丢失。对于关键指令结合CAN_NART非自动重传和软件重传机制确保送达。代码封装与可移植性将CAN初始化、发送、接收、中断处理封装成独立的模块can.c/h。使用宏或配置表来管理波特率、过滤器等参数方便不同项目移植。如果使用RTOS如FreeRTOS可以在CAN接收中断中释放信号量或发送消息给处理任务避免在中断中处理复杂逻辑。测试与调试环回模式是验证驱动逻辑的第一步。静默模式可用于监听总线而不干扰网络适合学习或诊断。务必使用终端电阻尤其在高速率如1Mbps或长距离通讯时。投资一个USB转CAN分析仪它是开发、测试、逆向分析不可或缺的工具。通过本文的梳理你应该已经对STM32F103的CAN模块从原理到实践有了系统的认识。从精准的波特率计算到灵活的过滤器配置再到中断驱动的收发机制这些是构建稳定CAN通讯应用的基石。建议你根据示例代码在开发板上实际运行并尝试修改ID、数据、波特率观察现象加深理解。在实际项目中再结合具体的应用层协议如CANopen、J1939等就能游刃有余地应对各种工业控制和车载网络的开发需求了。