
简介这份STM32F103 CAN实例代码面向嵌入式开发初学者与需要快速上手CAN通信的工程师围绕STM32F103系列微控制器的CAN控制器展开帮助读者理解从硬件接口到软件编程的完整实现路径。资源包共81个文件以30个h头文件、28个c源文件为核心配合12个s启动文件及uv2、opt、dep等工程配置与备份文件整体约280KB目录按Source、App、CMSIS、STM32F10x_StdPeriph_Driver等模块组织结构清晰便于检索。内容覆盖CAN控制器初始化、波特率与滤波器配置、发送接收邮箱操作、中断回调及错误处理等关键环节并附带说明文档与工程文件可直接编译调试。目前已有2872人学习下载适合作为学习CAN总线协议与STM32外设驱动的实践起点帮助开发者快速搭建可靠的多主通信网络。1. 从一块“哑巴”板子说起STM32F103 CAN 实例代码到底能解决什么手头有一块 STM32F103 的开发板想让它跟另一块板子或者上位机通过 CAN 总线聊起来结果翻遍官方库发现只有一堆寄存器定义和结构体连个能直接编译下载的完整例子都找不到——这大概是很多嵌入式从业者第一次碰 CAN 时的真实处境。CAN 总线在汽车电子、工业控制、储能设备里用得极广但它的初始化流程比串口复杂得多光一个滤波器配置就能卡住半天。这份 STM32F103 CAN 实例代码要解决的就是把“能跑通”这件事从玄学变成可复现的流程它提供了一套基于标准外设库的完整工程包含 CAN 初始化、滤波器配置、收发中断处理以及一个可验证的回环测试逻辑。适合谁适合已经会点灯、会串口收发但第一次接触 CAN 或者被 STM32 的 CAN 滤波器搞晕的嵌入式开发者。你不需要先啃完 300 页的参考手册照着实例把参数改对就能先看到数据在总线上跑起来。2. 拆开实例看门道CAN 初始化的三个关键参数与代码落地拿到一份 CAN 实例代码最忌讳的就是直接编译下载然后祈祷。CAN 能不能通八成取决于初始化那几行参数对不对。这一章把实例里的初始化部分拆开讲清楚每个参数为什么这么设以及改错了会出什么现象。2.1 位时序与波特率不是随便填个数就能通CAN 是异步总线没有时钟线所以通信双方必须提前约定好位时序。STM32F103 的 CAN 外设挂在 APB1 总线上通常 APB1 时钟是 36MHz。实例代码里一般会定义一个结构体CAN_InitStructure其中CAN_Prescaler、CAN_BS1、CAN_BS2这三个参数共同决定波特率。计算公式是波特率 APB1时钟 / (Prescaler × (1 BS1 BS2))。假设要配 500kbpsAPB1 为 36MHz常见做法是 Prescaler 设为 4BS1 设为 15BS2 设为 2这样 36M / (4 × (1152)) 500k。实例代码里通常直接给出这组值但你要根据自己板子的晶振和 APB1 分频去核对。// CAN 初始化核心代码片段基于标准外设库 CAN_InitTypeDef CAN_InitStructure; CAN_FilterInitTypeDef CAN_FilterInitStructure; // 使能 CAN 和 GPIO 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置 CAN 引脚PA11 为 CAN_RXPA12 为 CAN_TX GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_Init(GPIOA, GPIO_InitStructure); // CAN 位时序配置500kbps APB136MHz CAN_InitStructure.CAN_TTCM DISABLE; // 关闭时间触发模式 CAN_InitStructure.CAN_ABOM ENABLE; // 自动离线恢复调试时建议开 CAN_InitStructure.CAN_AWUM DISABLE; // 自动唤醒关闭 CAN_InitStructure.CAN_NART DISABLE; // 允许自动重传 CAN_InitStructure.CAN_RFLM DISABLE; // 接收 FIFO 不锁定 CAN_InitStructure.CAN_TXFP DISABLE; // 发送优先级由标识符决定 CAN_InitStructure.CAN_Mode CAN_Mode_Normal; // 正常模式回环测试时改 Loopback CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_15tq; CAN_InitStructure.CAN_BS2 CAN_BS2_2tq; CAN_InitStructure.CAN_Prescaler 4; CAN_Init(CAN1, CAN_InitStructure);这段代码里CAN_ABOM建议在调试阶段使能这样总线一恢复芯片能自动回来不用手动复位。CAN_Mode是第一个要确认的地方如果你只有一块板子必须改成CAN_Mode_Loopback否则没有应答信号发送会一直失败。CAN_SJW是重新同步跳跃宽度一般设 1tq 就够总线质量差可以适当加大。2.2 滤波器配置不过滤就等着收一堆垃圾STM32F103 的 CAN 滤波器是出了名的绕。它有两个 FIFO14 个滤波器组互联型有 28 个每个组可以配成 32 位掩码模式或 16 位列表模式。实例代码里通常会配一个“接收所有”的滤波器方便测试但实际项目里必须精确过滤。// 滤波器配置接收所有标准帧存入 FIFO0 CAN_FilterInitStructure.CAN_FilterNumber 0; CAN_FilterInitStructure.CAN_FilterMode CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_Filter_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation ENABLE; CAN_FilterInit(CAN_FilterInitStructure);CAN_FilterIdHigh和CAN_FilterIdLow拼起来是 32 位标准帧的 ID 要左移 5 位再填进去这一点实例代码里一般会封装成宏或者注释说明。CAN_FilterMaskIdHigh/Low全 0 表示“不关心任何位”也就是全通过。如果你只想收 ID 为 0x123 的标准帧掩码要设成 0x7FF 左移后的值ID 也要对应移位。很多新手在这里翻车明明发了数据但收不到就是因为 ID 没移位滤波器把它当成了扩展帧或者直接过滤掉了。2.3 发送与接收中断方式比轮询靠谱在哪实例代码通常提供两种收发方式轮询和中断。轮询适合快速验证中断适合实际项目。发送部分核心是选一个空的发送邮箱填好标识符、数据长度和数据然后请求发送。// 中断方式发送一帧标准数据帧 CanTxMsg TxMessage; TxMessage.StdId 0x123; // 标准标识符 TxMessage.ExtId 0x00; TxMessage.IDE CAN_Id_Standard; // 标准帧 TxMessage.RTR CAN_RTR_Data; // 数据帧 TxMessage.DLC 8; // 数据长度 8 字节 TxMessage.Data[0] 0x11; TxMessage.Data[1] 0x22; // ... 填充其余数据 CAN_Transmit(CAN1, TxMessage);接收中断里要判断是 FIFO0 还是 FIFO1 有挂起然后调用CAN_Receive把数据读出来最后必须调用CAN_FIFORelease释放邮箱否则下一次中断不会再来。实例代码里一般会用一个全局结构体存接收到的数据然后在主循环里处理。这里有个血泪经验中断里不要做耗时操作把数据拷走就赶紧退出否则高负载时容易丢帧。3. 从编译到收发把实例跑起来的最小验证流程代码看懂是一回事真正下载到板子上跑通是另一回事。这一章给出一条最短验证路径用两块 STM32F103 板子加一个 CAN 收发器模块把实例代码跑成能互相收发的小系统。3.1 硬件接线与终端电阻最容易忽略的物理层CAN 总线是差分信号需要 CAN_H 和 CAN_L 两根线。两块板子之间除了这两根线还必须接 120 欧姆终端电阻通常接在总线两端。很多开发板自带了收发器比如 TJA1050 或 SN65HVD230但终端电阻不一定焊了。如果你只连了两根线发现通信不稳定或者干脆不通先拿万用表量一下 CAN_H 和 CAN_L 之间的电阻正常应该是 60 欧姆左右两个 120 欧并联。只有一块板子做回环测试时不需要外部收发器但要把模式改成 Loopback。接线步骤板 A 的 CAN_H 接板 B 的 CAN_HCAN_L 接 CAN_L。在板 A 和板 B 的 CAN_H 与 CAN_L 之间各并一个 120 欧电阻如果板子上已有跳线选择终端电阻直接短接。共地。两块板子的 GND 必须连在一起否则差分信号没有参考通信会随机出错。3.2 编译环境与下载配置实例代码一般提供的是 Keil MDK 工程或者 Makefile GCC 的版本。以 Keil 为例打开工程后先确认芯片型号选的是 STM32F103C8 还是其他尾缀然后检查外部晶振频率是否和你的板子一致。如果用的是 8MHz 晶振系统时钟一般倍频到 72MHzAPB1 分频后是 36MHz这跟上一章的波特率计算是对应的。下载器用 ST-Link 的话在 Debug 选项卡里选 ST-Link DebuggerFlash Download 里勾上 Reset and Run。# 如果用 GCC Makefile编译和下载命令大致如下 make clean make st-flash write build/firmware.bin 0x8000000编译时如果报错找不到stm32f10x_can.h检查标准外设库的路径有没有加到 Include Paths 里。标准库的 CAN 驱动文件是stm32f10x_can.c启动文件选startup_stm32f10x_md.s中容量还是hd.s大容量取决于你的芯片型号选错了会在中断向量表那里跑飞。3.3 用回环模式先验证软件逻辑只有一块板子的时候把CAN_InitStructure.CAN_Mode改成CAN_Mode_Loopback发送的数据会直接回到自己的接收 FIFO不经过外部总线。这样可以先确认滤波器、中断、数据打包这些软件逻辑没问题。回环模式下你发什么就应该收到什么如果收不到问题一定在软件配置上跟硬件无关。// 回环测试主循环片段 while (1) { if (CAN_TransmitStatus(CAN1, 0) CAN_TxStatus_Ok) { // 上一帧发送完成可以发下一帧 } CanTxMsg tx; tx.StdId 0x123; tx.IDE CAN_Id_Standard; tx.RTR CAN_RTR_Data; tx.DLC 2; tx.Data[0] 0xAA; tx.Data[1] 0x55; CAN_Transmit(CAN1, tx); Delay_ms(500); // 简单延时实际项目用定时器 }回环通了之后再把模式改回 Normal接上另一块板子或者 CAN 分析仪就能看到真实总线上的波形和数据。如果回环通但正常模式不通九成是硬件接线、终端电阻或者波特率不一致的问题。4. 避坑与排查CAN 调试中最容易翻车的五个地方CAN 调试的坑往往不是代码写错了而是一些默认配置和硬件细节在作怪。这一章列五个最常见的翻车现场每条按现象、原因、解决来说。4.1 发送一直失败CAN_TransmitStatus永远返回 Pending现象代码下载后发送函数调用了但状态一直不是 Ok也没有任何数据发出去。原因最常见的是没有其他节点应答。CAN 总线要求至少有两个节点发送方发出数据后接收方要拉低应答位。如果你只有一块板子且模式是 Normal没有节点应答发送会一直重试直到错误计数溢出。另一个原因是 CAN 收发器没有供电或者 standby 引脚没拉低。解决单板调试改 Loopback 模式双板检查收发器供电和使能引脚用示波器看 CAN_TX 引脚有没有波形输出如果没有说明 MCU 的 CAN 外设根本没工作回头查时钟使能和引脚复用配置。4.2 能发不能收中断进不去现象发送正常另一块板子也显示收到了但自己的接收中断死活不触发。原因滤波器配置错误是最常见的。标准帧的 ID 需要左移 5 位再填入CAN_FilterIdHigh如果直接填了原始 ID滤波器匹配的就不是你期望的帧。另一个原因是中断优先级没配置或者 NVIC 没使能CAN1_RX0_IRQn。解决先把滤波器设成全通过掩码全 0排除过滤问题检查NVIC_Init里有没有使能对应的中断通道在中断函数入口翻转一个 GPIO用示波器看有没有进中断。4.3 波特率算错总线错误计数飙升现象两块板子都配置了 500k但通信时好时坏错误计数器很快涨到 128 进入错误被动状态。原因APB1 时钟不是你以为的 36MHz。如果你的系统时钟配置成了 48MHz 或者外部晶振不是 8MHzAPB1 的分频系数会变波特率自然就偏了。另外CAN_BS1和CAN_BS2的宏定义值容易看错CAN_BS1_15tq代表的是 15 个时间单元不是 151。解决用RCC_GetClocksFreq打印实际 APB1 时钟重新按公式算 Prescaler。或者直接用 CAN 分析仪测实际波特率偏差超过 1% 就会不稳定。4.4 中断里读数据后没释放 FIFO第二次中断再也不来现象第一帧数据能收到之后接收中断就沉默了。原因CAN_Receive只是把数据从邮箱读到结构体邮箱本身还处于挂起状态。必须调用CAN_FIFORelease(CAN1, CAN_FIFO0)告诉外设这个邮箱已经处理完了否则中断标志不会清除。解决在接收中断处理完数据后立刻释放 FIFO。如果用了 FIFO 锁定模式CAN_RFLM ENABLE还要注意溢出处理。4.5 终端电阻漏接短距离能通长距离必挂现象两块板子放在桌上用杜邦线连着能通信线一延长到一米以上就开始丢帧。原因CAN 总线两端必须有 120 欧终端电阻来吸收信号反射。很多开发板为了省成本没有焊短距离时反射影响小线一长反射就严重了。解决在总线两端的 CAN_H 和 CAN_L 之间各并一个 120 欧电阻。如果不确定板子上有没有断电用万用表量 CAN_H 和 CAN_L 之间的电阻接近 60 欧说明两端都有接近 120 欧说明只有一端有无穷大就是都没有。5. 进阶用法用过滤器分组和标识符规划搭一个多节点小网络把单节点收发跑通之后下一步通常是多个节点挂在同一根总线上各取所需。STM32F103 的 14 个滤波器组如果只用一组全通过等于所有节点都在处理所有报文CPU 白白浪费。这一章讲怎么用过滤器分组和标识符规划让每个节点只收自己关心的帧。5.1 标识符分配与滤波器分组策略假设有三个节点节点 A 负责采集传感器节点 B 负责控制执行器节点 C 是网关。可以这样分配标准帧 ID节点功能标识符范围方向A传感器数据上报0x100 ~ 0x10FA 发B/C 收B执行器控制指令0x200 ~ 0x20FC 发B 收C网关转发0x300 ~ 0x30FC 发A/B 收节点 B 只需要收 0x200 段和 0x300 段那就可以配两个滤波器组一组匹配 0x200 段掩码屏蔽低 4 位一组匹配 0x300 段。STM32F103 的滤波器组可以分别激活每个组可以指向 FIFO0 或 FIFO1这样不同优先级的报文可以分到不同 FIFO中断里先处理高优先级 FIFO。// 滤波器组 0接收 0x200 ~ 0x20F存入 FIFO0 CAN_FilterInitStructure.CAN_FilterNumber 0; CAN_FilterInitStructure.CAN_FilterMode CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale CAN_FilterScale_32bit; // 标准帧 ID 左移 5 位0x200 5 0x4000 CAN_FilterInitStructure.CAN_FilterIdHigh 0x4000; CAN_FilterInitStructure.CAN_FilterIdLow 0x0000; // 掩码低 4 位不关心其余必须匹配 CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0x7FF0; CAN_FilterInitStructure.CAN_FilterMaskIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_Filter_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation ENABLE; CAN_FilterInit(CAN_FilterInitStructure); // 滤波器组 1接收 0x300 ~ 0x30F存入 FIFO1 CAN_FilterInitStructure.CAN_FilterNumber 1; CAN_FilterInitStructure.CAN_FilterIdHigh 0x6000; // 0x300 5 CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0x7FF0; CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_Filter_FIFO1; CAN_FilterInit(CAN_FilterInitStructure);这里掩码0x7FF0的意思是高 11 位标准帧 ID 部分必须完全匹配低 4 位也就是 ID 的最低 4 位不关心。这样 0x200 到 0x20F 都能通过。注意CAN_FilterIdHigh和掩码都是 16 位值但标准帧 ID 只有 11 位左移 5 位后占据高 11 位低 5 位是 RTR、IDE 等标志位掩码里对应位要设 1 才能保证只匹配数据帧。5.2 用双 FIFO 做优先级分流STM32F103 的 CAN 有两个接收 FIFO每个 FIFO 有 3 个邮箱深度。你可以把紧急报文比如急停指令分到 FIFO1普通传感器数据分到 FIFO0中断里先读 FIFO1 再读 FIFO0。这样即使 FIFO0 被大量数据塞满紧急指令也不会被阻塞。配置的时候注意每个滤波器组只能指向一个 FIFO但多个组可以指向同一个 FIFO。FIFO 溢出时如果CAN_RFLM设成 ENABLE新报文会被丢弃但旧报文保留设成 DISABLE 则新报文覆盖旧报文。实际项目里紧急报文建议用锁定模式普通数据用覆盖模式。5.3 验证多节点通信的实用技巧多节点调试最怕的是“不知道谁在发、谁没收”。我一般会拿一个 USB-CAN 分析仪挂在总线上开监听模式看所有报文。如果没有分析仪可以在每个节点的接收中断里翻转一个 GPIO用逻辑分析仪或者示波器看哪个节点在什么时候收到了什么。另外发送的时候尽量用不同的数据内容做标记比如节点 A 发 0xA1节点 B 发 0xB1这样在分析仪上一眼就能看出是谁发的。从那以后我每次配 CAN 滤波器都强制先拿全通过模式跑一遍确认收发链路没问题再逐步收紧掩码。这样一旦收不到数据就能立刻判断是滤波器的问题还是链路的问题省掉大量来回改代码的时间。希望帮到你。本文还有配套的精品资源点击获取