
简介本资源是一套面向嵌入式初学者与物联网项目开发者的STM32F103单片机CAN总线通信实战例程聚焦工业现场常用的CAN收发功能实现适用于课程设计、毕业设计及小型工控节点开发等场景。压缩包共174个文件含90个头文件.h定义外设接口与结构体76个源文件.c涵盖HAL库底层驱动如CAN、UART、SPI、I2C、TIM等、主应用逻辑及初始化配置另有批处理脚本keilkilll.bat、工程配置文件uvprojx/uvoptx、烧录用hex文件及说明文档整体仅1.01MB轻量易导入。已有574人学习下载代码全程采用KEIL MDK基于HAL库编写关键路径均附中文注释CAN引脚定义、时钟配置、过滤器设置及中断收发流程清晰可查同时支持F103系列其他型号芯片迁移仅需在KEIL中调整目标芯片型号与Flash容量即可适配。1. 项目概述与核心价值最近在整理资料时翻出了一个老项目压缩包名字就叫“STM32F103单片机HAL库例程-CAN口收发通讯实验.rar”。相信很多刚开始接触STM32和汽车电子、工业控制的朋友都对CAN总线既向往又有点发怵。向往是因为它确实是工业级通信的基石发怵则是觉得协议复杂、配置繁琐。这个例程包可以说是我当年从标准库转向HAL库并啃下CAN通讯这块硬骨头时一步步调试、踩坑后留下来的“生存笔记”。它不仅仅是一段能跑通的代码更是一个包含了从环境搭建、模块配置、到收发调试、错误处理全流程的实战指南。无论你是正在做毕业设计的学生还是需要快速在工控项目上实现节点通信的工程师这个例程都能帮你绕过我当年走过的弯路直击核心快速建立起一个稳定可靠的双向CAN通信通道。接下来我就把这个“压箱底”的宝贝彻底拆解手把手带你复现并理解每一个环节。2. 工程框架与HAL库环境搭建2.1 工程创建与基础配置拿到一个现成的.rar包固然方便但理解如何从零搭建同样重要。我使用的是STM32CubeMX版本号建议在6.0以上以确保对HAL库的完善支持。首先在CubeMX中选择正确的芯片型号STM32F103C8Tx这里以常见的“蓝桥杯”核心板芯片为例然后开始配置。时钟树配置是第一步也是稳定性基石。STM32F103的最高主频是72MHzCAN总线的时钟源来自APB1总线而APB1的最大频率是36MHz。因此我们需要在时钟配置界面将HCLK设置为72MHz并确保PCLK1APB1的频率是36MHz。这一步千万不能错因为CAN模块的波特率计算依赖于APB1的时钟。如果APB1时钟超频CAN通信将根本无法正常工作或者出现极其难以排查的随机错误。接下来是CAN外设的使能。在Connectivity分类下找到CAN1将其工作模式设置为Normal。这里有一个关键选择是否使用中断。对于初学者我强烈建议同时开启接收中断和发送中断。虽然这增加了代码复杂度但它能让你清晰地掌握数据收发的完整生命周期便于调试。在NVIC Settings选项卡中勾选CAN1 RX0 interrupt和CAN1 TX interrupt。最后是生成工程代码。在Project Manager里选择好开发环境MDK-ARM V5或STM32CubeIDE为项目命名并将Toolchain/IDE选对。关键一步是在Code Generator里选择“为每个外设生成独立的.c/.h文件”。这样做的好处是代码结构清晰can.c和can.h里集中了所有CAN相关的函数和变量后期维护和移植都非常方便。2.2 HAL库驱动层解析HAL库相对于标准库最大的特点是提供了更高级的抽象和更强的硬件无关性但代价是代码体积稍大执行效率需要优化。对于CAN驱动HAL库封装了几个核心结构体和函数。首先是CAN_HandleTypeDef这是CAN操作的句柄包含了CAN实例如CAN1、初始化结构体、锁状态以及指向邮箱Mailbox的指针。我们的所有操作都围绕这个句柄展开。初始化结构体CAN_InitTypeDef包含了CAN工作的所有参数Prescaler预分频器用于决定CAN的时间单元Time Quantum长度。Mode模式如正常模式、回环模式、静默模式等。调试初期可以先用CAN_MODE_LOOPBACK回环模式自发自收验证软件逻辑。SyncJumpWidth同步跳转宽度用于重新同步时钟边沿通常设为CAN_SJW_1TQ。TimeSeg1和TimeSeg2定义了位时间段中采样点的位置。TimeSeg1包含传播段和相位缓冲段1TimeSeg2是相位缓冲段2。它们的设置直接关系到总线仲裁和采样的可靠性。HAL库提供了HAL_CAN_Init()、HAL_CAN_Start()、HAL_CAN_AddTxMessage()、HAL_CAN_GetRxMessage()等函数。我们需要重点关注的是回调函数机制。例如当一帧数据通过中断方式接收完成时会调用HAL_CAN_RxFifo0MsgPendingCallback()。我们需要在用户文件中重写这个弱定义函数在里面编写自己的数据处理逻辑。这是HAL库中断处理的核心思想。3. CAN协议核心与配置详解3.1 波特率计算从理论到实践CAN总线通信的稳定性十有八九出在波特率配置上。总线上所有节点的波特率必须严格一致误差需要控制在极小的范围内通常1%。CAN位时间Bit Time被划分为4个段同步段Sync Seg、传播段Prop Seg、相位缓冲段1Phase Seg1和相位缓冲段2Phase Seg2。在HAL库中我们主要通过配置Prescaler、TimeSeg1和TimeSeg2这三个参数来定义它们。计算公式是理解的关键位时间 (TimeSeg1 TimeSeg2 1) * (Prescaler) / APB1_Clock 波特率 1 / 位时间其中TimeSeg1和TimeSeg2的单位是时间份额Time Quantum, Tq。Sync Seg固定为1Tq所以位时间的总Tq数为1 TimeSeg1 TimeSeg2。举个实战例子目标波特率1Mbps即1us/bitAPB1时钟为36MHz。计算时间份额Tq的时长Tq Prescaler / 36MHz。我们需要位时间 1us 1000ns。假设我们选择Prescaler 3则Tq 3 / 36MHz ≈ 83.33ns。需要的总Tq数 1000ns / 83.33ns ≈ 12。分配TqSync Seg 1 剩下的11个Tq分配给TimeSeg1和TimeSeg2。一个常见的分配是采样点位于位时间的75%左右这样抗干扰能力较强。可以设置TimeSeg1 8TimeSeg2 3。验证总Tq 18312 位时间12 * 83.33ns ≈ 1000ns 波特率1/1000ns 1Mbps。在CubeMX的图形化界面中它会根据你输入的波特率自动计算并推荐一组参数。但你必须理解背后的原理当自动计算的参数无法满足通信时尤其是长距离布线时你需要手动调整TimeSeg1和TimeSeg2通常增加TimeSeg1即延长采样点之前的时间可以增强抗干扰能力。3.2 过滤器配置硬件级的消息筛选这是CAN控制器最强大的功能之一能极大减轻CPU负担。STM32的CAN控制器提供了最多14个取决于型号可配置的过滤器组每个组可以工作在标识符列表模式或掩码模式。标识符列表模式就像一个白名单。你设置一组具体的ID只有ID完全匹配的报文才会被接收。掩码模式更灵活。你设置一个ID和一个掩码。掩码位为1表示必须匹配ID的对应位为0表示不关心。例如ID0x123掩码0xFFF则只接收ID为0x123的帧。如果ID0x120掩码0xFF0则会接收ID从0x120到0x12F的所有帧低4位不关心。在例程中我通常将过滤器组0配置为掩码模式并设置一个较宽的过滤范围用于接收调试信息或广播数据。再将过滤器组1配置为列表模式用于接收目标明确的关键指令。配置过滤器的HAL库函数是HAL_CAN_ConfigFilter()。一个至关重要的细节是过滤器的配置必须在CAN启动HAL_CAN_Start()之前完成否则配置不会生效。很多同学卡在收不到数据就是因为初始化顺序错了。4. 收发功能实现与代码拆解4.1 发送流程阻塞、中断与FIFO管理CAN发送有三种邮箱FIFOHAL库帮助我们管理它们。发送一帧数据的核心函数是HAL_CAN_AddTxMessage()。阻塞式发送是最简单的但在高优先级任务中可能造成卡顿。其流程是调用AddTxMessage后循环查询发送邮箱是否空闲HAL_CAN_GetTxMailboxesFreeLevel()直到发送完成。这种方式代码直观但效率低。中断式发送是推荐的方式。配置好发送中断后调用AddTxMessage将数据放入发送邮箱即可立即返回。当硬件发送完成时会触发发送中断在HAL_CAN_TxMailbox0CompleteCallback()等回调函数中你可以释放资源或准备下一帧数据。这实现了非阻塞操作。这里有一个实战坑点STM32F103的CAN发送中断是在至少一个发送邮箱变为空时触发的。这意味着如果你快速连续发送多帧数据填满了三个邮箱那么只有在第一帧发送完成、空出一个邮箱时才会触发一次中断而不是每发送一帧就触发一次。在回调函数里你需要检查是哪个邮箱完成了发送通过传入的Mailbox参数并做相应处理。发送的代码结构通常如下CAN_TxHeaderTypeDef TxHeader; uint8_t TxData[8]; uint32_t TxMailbox; TxHeader.StdId 0x321; // 标准ID TxHeader.ExtId 0; // 扩展ID标准帧时设为0 TxHeader.IDE CAN_ID_STD; // 标识符类型标准帧 TxHeader.RTR CAN_RTR_DATA; // 帧类型数据帧 TxHeader.DLC 8; // 数据长度 0-8 TxHeader.TransmitGlobalTime DISABLE; // 填充TxData... if(HAL_CAN_AddTxMessage(hcan1, TxHeader, TxData, TxMailbox) ! HAL_OK) { // 发送请求失败处理可能是邮箱满了 } // 如果是中断方式此时即可返回等待回调4.2 接收流程FIFO管理与数据解析CAN控制器有两个接收FIFOFIFO0和FIFO1。例程中通常只使用FIFO0。接收也有轮询和中断两种方式。中断接收是主流选择。使能接收中断后当FIFO中有新的报文时会触发CAN1_RX0_IRQHandler中断HAL库在中断服务函数中会调用HAL_CAN_RxFifo0MsgPendingCallback()。我们需要重写这个回调函数。在回调函数中使用HAL_CAN_GetRxMessage()来读取数据。这个函数会从指定的FIFO中取出最早的一帧报文并将其从FIFO中移除。void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef RxHeader; uint8_t RxData[8]; if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, RxHeader, RxData) HAL_OK) { // 成功接收到一帧数据 uint32_t id RxHeader.StdId; // 或 ExtId 根据IDE判断 uint8_t length RxHeader.DLC; // 处理RxData... } }重要提示接收回调函数是在中断上下文执行的务必保持其简短高效只做最必要的拷贝或标记操作将复杂的数据处理放到主循环或任务中。避免在中断中使用printf、长时间循环等阻塞操作。关于FIFO溢出如果报文接收过快而CPU处理不及时会导致FIFO溢出。HAL库提供了HAL_CAN_GetRxFifoFillLevel()来查询FIFO填充等级。在初始化时也可以使能FIFO溢出中断HAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_OVERRUN)在溢出时及时处理防止数据丢失。5. 调试技巧与常见问题实录5.1 硬件连接与物理层检查软件调不通首先怀疑硬件。CAN总线需要120欧姆的终端电阻连接在总线的两端以消除信号反射。如果你的网络只有两个节点并且距离很近比如都在一块面包板上有时可以省略但为了稳定性最好加上。用万用表测量在断电情况下测量CAN_H和CAN_L之间的电阻理论上应该是60欧姆左右两个120欧姆终端电阻并联。如果电阻无穷大说明终端电阻没接或总线断路如果电阻远小于60欧姆可能有短路。上电测量电压一个健康的隐性电平逻辑1状态下CAN_H和CAN_L对地电压大约在2.5V左右。当发送显性电平逻辑0时CAN_H会升高至约3.5VCAN_L会降低至约1.5V。用示波器观察波形是最直观的可以看到清晰的差分信号。如果电压异常检查CAN收发器如TJA1050的电源、使能引脚和接线。5.2 软件调试与问题排查当硬件确认无误后问题就集中在软件配置上。我总结了一个排查清单时钟配置检查反复确认APB1的时钟频率是否与代码中波特率计算的基础频率一致。这是最高频的错误源。初始化顺序严格遵循CAN初始化HAL_CAN_Init-过滤器配置HAL_CAN_ConfigFilter-启动CANHAL_CAN_Start-使能中断HAL_CAN_ActivateNotification的顺序。中断未触发检查NVIC中断是否使能中断优先级配置是否合理CAN通信中断优先级通常应设为较高。在main函数初始化后是否调用了__HAL_CAN_ENABLE_IT(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING)来使能接收中断收不到数据回环模式正常这几乎肯定是波特率不匹配。请使用CAN分析仪如周立功、PCAN等或另一个已知正常的节点对比发送的波形位宽精确计算实际波特率。微调Prescaler、TimeSeg1和TimeSeg2。只能收不能发或反之检查收发器的模式控制引脚如TJA1050的STBY引脚是否被正确拉低工作模式。检查单片机CAN引脚PA11/PA12即CAN_RX/CAN_TX是否与收发器对应连接切记不要接反。数据错误或乱码检查发送和接收方的数据长度码DLC是否一致。检查数据字节序Endianness尤其在传输多字节数据如int32、float时发送和接收方需要对字节序有约定。5.3 进阶使用回环与静默模式辅助调试在项目初期强烈建议充分利用CAN控制器的工作模式回环模式Loopback在此模式下芯片内部将TX输出反馈到RX输入无需外部硬件即可测试发送和接收流程。这是验证你所有软件配置时钟、波特率、过滤器、中断、收发函数是否正确的最快方法。如果回环模式能自发自收说明软件层基本没问题。静默模式Silent在此模式下CAN控制器可以接收总线数据但不会发送任何报文包括ACK位因此不会影响总线。这可以用来“监听”总线上的流量检查其他节点是否在正常发送总线波特率是否正确而自己作为一个纯粹的观察者。你可以在CAN_InitTypeDef.Mode中设置这些模式或者在运行时通过HAL_CAN_Stop()停止CAN修改模式后再HAL_CAN_Start()来动态切换。这个“STM32F103 HAL库 CAN例程”包的价值远不止于几行能编译通过的代码。它是一套经过验证的方法论从时钟树这个“地基”开始到协议配置的“蓝图”再到收发中断的“施工”最后是软硬件结合的“验收调试”。希望这次深度的拆解能帮你把CAN总线从书本上的协议变成你手中切实可用的工具。在实际项目中当你稳定地收到第一帧来自另一个节点的数据时那种成就感就是对我们这些嵌入式开发者最好的奖励。本文还有配套的精品资源点击获取