ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32 CAN通信例程精讲:从协议原理到波特率配置与调试

2026/9/9 10:06:32 拓冰建站 浏览量
STM32 CAN通信例程精讲:从协议原理到波特率配置与调试 简介面向嵌入式初学者的STM32 CAN通信经典例程包基于ARM Cortex-M内核与标准外设库实现适合学习CAN协议在单片机上的实际落地。压缩包共93个文件以h头文件与c源文件为主并含Keil/EWARM工程配置、链接脚本及readme说明整体303KB目录结构清晰便于直接打开调试。例程覆盖CAN外设初始化、GPIO与时钟配置、标准与扩展帧收发、滤波器设置及中断处理并附完整标准外设库驱动可快速理解CAN两线差分通信与优先级仲裁机制。已有978人学习适合自动化、汽车电子及工业控制方向的开发者作为设计参考帮助掌握多设备可靠通信的工程实现细节。 最近在整理资料的时候翻出一个老项目里面有一份很经典的STM32 CAN通信例程压缩包名字就叫“STM32的CAN通信例程很经典可供学习设计参考.zip”。说来惭愧当年我就是靠这份例程把CAN总线彻底跑通的后来做电池管理系统、工业设备通信甚至给毕业设计学生讲CAN底子都是从这里打下的。所以今天想把这个例程的要点拆开来讲一讲包括协议理解、代码结构、波特率计算、过滤器配置还有调试时最容易踩的坑。无论你是刚接触CAN的新手还是想快速上手STM32 CAN开发的工程师这篇内容应该都能给你省不少时间。1. 这个例程到底在讲什么CAN通信的核心价值与整体设计1.1 为什么CAN在嵌入式里经久不衰CANController Area Network总线是博世在1986年搞出来的串行通信协议最早是为汽车电子设计的。三十多年过去了它依然是工业控制、医疗设备、机器人、储能系统中主流的现场总线之一。原因很简单CAN是差分信号抗干扰能力极强多主结构任何节点都能主动发报文有完善的仲裁机制优先级高的报文先发不会冲突还有CRC校验和错误处理机制数据可靠性高。我一直觉得CAN的设计哲学很像一个文明的多方对话现场——每个人都有发言权但身份高的人优先讲话如果有人听错了大家会同步重讲直到信息一致。对比一下别的通信方式UART是点对点想要多点通信得自己搞组网逻辑RS485虽然能挂多节点但主机轮询式的半双工机制效率不高而且没有内置的硬件仲裁。CAN等于把这些常用痛点都在底层解决了所以不少MCU甚至直接把CAN外设叫bxCANBasic Extended CANSTM32就是这样。1.2 经典例程的模块划分与学习路线对于这份“很经典”的例程我印象中它的代码结构并不复杂但五脏俱全。一般就是main.c、can.c、can.h加上stm32f10x_can.c标准库文件有些版本还会有中断处理函数。代码流程基本是初始化CAN外设GPIO、波特率、过滤器→ 进入正常模式 → 主循环发送报文 → 中断接收报文。这个设计相比直接把收发逻辑全塞在main里的写法优势非常明显初始化与业务处理分离CAN底层配置一次搞定收发接口清晰方便套用到自己的项目里。我建议新手拿到例程后不要太急着改功能先按下面这条线路走一遍第一步烧录例程用环回模式LoopBack验证收发逻辑不用接外部设备也能跑通第二步把环回模式改成正常模式用两块开发板对接或者接到USB-CAN分析仪上实测报文收发第三步对照数据手册和代码注释把波特率计算、过滤器掩码这几段彻底弄明白第四步在此基础上增加中断接收、错误处理、多节点组网逐步变成自己的工程模板。2. 核心细节bxCAN配置中容易忽视的关键参数2.1 引脚与时钟CAN外设的“地基”很多人一上来就急着配置CAN的波特率结果通信死活不通回过头来才发现GPIO和时钟根本没配对。STM32F103的CAN外设复用引脚有两个映射组默认复用功能是PA11RX、PA12TX重映射之后是PB8RX、PB9TX。你要是用了重映射不开启AFIO重映射时钟RX/TX就不会连接到CAN外设上这是非常常见的低级而又隐蔽的问题。时钟方面CAN1外设挂载在APB1总线上时钟频率一般是36MHz如果系统时钟为72MHz。注意STM32的CAN外设具有“时钟魔术”机制——它内部的位时序单元要求输入时钟是APB1时钟但波特率分频器则以该时钟为基础进行分频。API函数里的CAN_InitStructure.CAN_Prescaler就是分频系数这个值直接决定波特率。我在代码里看到很多人喜欢把分频系数写成4或者5然后发现通信不上其实就是没算清楚APB1时钟频率和想要的目标波特率之间的关系。提示配置CAN前务必确认RCC是否开启了CAN外设时钟在标准库中就是RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE)。少开这行CAN寄存器全读出来都是0。2.2 波特率配置位时间与SJW的计算逻辑CAN总线的波特率不是像串口那样随便填一个数值就能行的它需要把一个位时间Bit Time拆成多个时间段然后由分频时钟来驱动。STM32 bxCAN的位时间由三部分组成同步段SYNC_SEG、时间段1BS1和时间段2BS2。协议规定同步段固定为1个时间量子tqBS1和BS2各占若干个tq可配置范围是1到16实际ST限制1到15。跳变宽度SJW影响的是重同步时的最大补偿范围通常取1除非总线时钟误差较大时才调大。最终波特率的计算公式为波特率 APB1时钟 / (CAN_Prescaler × (1 BS1 BS2))举个例子假设APB136MHz我想得到500kbps的波特率。常见的搭配是CAN_Prescaler4BS18BS27这样实际波特率 36MHz / (4 × (187)) 36MHz / 64 562.5kHz这不满足要求。正确的计算思路是先确定采样点。我习惯把采样点控制在75%到80%之间在500kbps时如果每秒有20个时间量子设置BS113、BS26采样点就是 (1 13) / 20 70%如果设置BS114、BS25采样点就是75%。然后再反推分频系数500kbps 36MHz / (Prescaler × 20) → Prescaler 3.6不满足整数所以 36MHz 下严谨来说做不到严格的500kbps需要调整分频值或略微调整位时间。很多工程师没意识到这一点会导致不同节点的等效波特率不完全一致总线偶发性丢帧。经典例程中如果注释写着500kbps通常用的分频是9、BS17、BS26实际波特率 36M / (9×14) 285.7kbps不对仔细算一下9×1412636M/126≈285.7k这显然不是500k。所以要按自己板子的时钟源核对不能照抄。注意两个CAN节点通信必须满足三个一致——CAN波特率一致、采样点尽量一致、位时间参数都落在协议允许范围内。否则短距离通信可能正常距离一长或总线负载一高就出错。2.3 过滤器与验收屏蔽报文选择性接收的核心过滤器的设计是CAN例程学习和实际项目开发中的分水岭。如果你不需要选择性地接收报文把过滤器设为掩码全0也就是全部报文都接收开发调试没问题。但如果是正经项目这个配置就是埋雷。STM32F103的bxCAN有28个过滤器组每个过滤器组可以工作在列表模式或掩码模式。列表模式适合匹配精确ID能收到指定ID的报文掩码模式适合匹配一段范围的ID掩码位为1的位必须精确匹配为0的位可以忽略。以标准帧为例如果我想只接收ID为0x123的标准帧把过滤器ID设为0x123屏蔽寄存器设为全0x7FF11位全必须匹配就可以。如果我想接收ID为0x120到0x12F的所有报文屏蔽寄存器就要把低4位设为忽略。我曾经遇到一个工程问题一台设备总线狂收垃圾帧查了半天发现是过滤器配成了全部接收模式MCU不得不在中断里处理上百个无用消息直接拖垮主循环。后来把过滤器掩码精确化CPU占用率立刻降了30%。所以例程里看起来不起眼的十几行过滤器代码其实是整个接收策略的核心值得反复研究。3. 实操过程从初始化到收发报文的完整走读3.1 初始化流程与GPIO配置把这个经典例程翻出来一步一步走一遍。首先看main函数里的初始化部分第一步配置的是GPIO时钟和CAN时钟。以最常用的STM32F103C8T6为例PA11和PA12作为CAN_RX和CAN_TX。推挽复用输出。很多人问为什么不用开漏模式因为CAN收发器比如TJA1050内部有电平转换MCU侧的TX脚作为输出端只负责给出高低电平信号推挽模式没有问题RX脚配置为上拉输入。接下来是CAN初始化结构体的配置。经典例程一般是这样写的CAN_InitStructure.CAN_TTCM DISABLE; // 关闭时间触发通信模式 CAN_InitStructure.CAN_ABOM ENABLE; // 自动离线管理总线关闭后自动恢复 CAN_InitStructure.CAN_AWUM DISABLE; // 自动唤醒模式 CAN_InitStructure.CAN_NART DISABLE; // 非自动重传为ENABLE时发送失败不重传 CAN_InitStructure.CAN_RFLM DISABLE; // 关闭接收FIFO锁定模式 CAN_InitStructure.CAN_TXFP DISABLE; // 关闭发送FIFO优先级由报文ID决定优先级 CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_8tq; CAN_InitStructure.CAN_BS2 CAN_BS2_7tq; CAN_InitStructure.CAN_Prescaler 4; CAN_Init(CAN1, CAN_InitStructure);这里有一个细节特别值得新人琢磨CAN_ABOM自动离线管理。当CAN控制器进入Bus Off状态发送错误计数器超过255后硬件需要等待128个11位隐性位才能恢复。如果不开启ABOM你用软件方式恢复会很麻烦开启了之后硬件会自动处理恢复流程。经典例程里把这位置ENABLE我认为非常正确值得推广到所有工业项目里。3.2 报文发送与中断接收的标准写法发送报文的核心是填充CanTxMsg结构体然后调用CAN_Transmit函数。一个标准的发送函数是这样uint8_t CAN_Send_Msg(uint32_t id, uint8_t *msg, uint8_t len) { CanTxMsg TxMessage; uint8_t i; TxMessage.StdId id; // 标准标识符 TxMessage.ExtId 0; // 扩展标识符不用 TxMessage.IDE CAN_Id_Standard; // 标准帧 TxMessage.RTR CAN_RTR_Data; // 数据帧 TxMessage.DLC len; // 数据长度 for (i 0; i len; i) TxMessage.Data[i] msg[i]; return CAN_Transmit(CAN1, TxMessage); }这里最容易出错的是DLC数据长度码CAN协议里数据段的长度上限是8字节很多新手会填超过8结果发送失败或数据被截断。虽然CAN FD扩展到了64字节但STM32F103的经典CAN外设并不支持CAN FD在例程里老老实实用8字节以内就好。接收部分经典例程采用中断接收。初始化时需要配置接收FIFO0的消息挂起中断CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);然后在中断服务函数中识别中断标志读取FIFOvoid USB_LP_CAN1_RX0_IRQHandler(void) { CanRxMsg RxMessage; if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) ! RESET) { CAN_Receive(CAN1, CAN_FIFO0, RxMessage); // 处理接收到的数据 // 根据 RxMessage.StdId 做分发 } }关于中断函数名需要特别说明不同型号的向量表不同比如STM32F103ZET6和STM32F103C8T6共用USB_LP_CAN1_RX0_IRQHandler这个名字而部分互联型产品可能是CAN1_RX0_IRQHandler。写代码前一定要对照启动文件里的中断向量表核对否则中断服务函数永远进不去。3.3 环回模式实测没有硬件也能验证代码我刚接触CAN时手里只有一块板子没有CAN分析仪也没有第二块开发板这时候环回模式救了大命。环回模式把CAN控制器内部的发送输出直接环回到接收输入内置收发器不参与外部总线通信。也就是说你发一帧报文自己就能在接收FIFO里收到这帧。在例程里把CAN_Mode从CAN_Mode_Normal改成CAN_Mode_LoopBack其他配置不变。主循环里发送一个ID为0x001的报文然后在中断或轮询中读取接收FIFO如果数据和自己发出去的一致说明bxCAN外设的发送/接收路径、过滤器配置、中断机制都没问题。等把环回模式跑通了再切回正常模式接到实际总线上可以少踩很多坑。实操提示用环回模式调试时建议把过滤器临时设为“全部接收”掩码全0先排除过滤器干扰。等收发通了再逐渐把过滤器收紧最后测试精确掩码场景。4. 常见问题与排查技巧实录4.1 连不上总线、收不到数据先测波形CAN通信不成功时我建议不要先怀疑代码先测量CAN_H和CAN_L之间的差分波形。用示波器探头夹在CAN_H和CAN_L上正常通信时你会看到一个约2V的共模电压水平上有明显的差分摆幅。如果波形是一条直线没有任何跳变大概率是硬件链路没打通如果波形乱跳但接收端收不到可能是波特率不一致。没有示波器的场合用逻辑分析仪也可以抓CAN报文现在不少逻辑分析仪软件都支持CAN协议解析。直接把CAN_H接到逻辑分析仪通道设置好波特率就能看到报文ID和数据排查效率非常高。我强烈建议每一个做CAN开发的人手里至少有一个USB-CAN分析仪其实不贵但能省下的调试时间远超它的价格。4.2 波特率不同步导致的Bus Off总线关闭是CAN节点最令人头疼的问题之一。现象是代码跑着跑着就发不出报文了错误计数器一路飙升最终节点脱离总线。大多数情况下这是一个典型的波特率配置错误引发的——位时间配置错误或分频值算错导致本节点的位时序和其他节点不一致。总线上只要有一个节点时序不对就会持续产生错误帧形成雪崩效应。排查步骤我总结成一张表方便对照现象可能原因排查手段发送超时TXERR计数持续增加APB1时钟频率与分频值不匹配核对RCC时钟树重新计算Prescaler总线上只有本节点无法与其他设备通信没有接终端电阻120Ω确认总线两端各接一个120Ω电阻能发出报文但立即收到错误帧采样点偏差过大调整BS1/BS2比例采样点设为75%左右发送一次后不再工作状态寄存器显示BusOffABOM未使能开启CAN_ABOM实现自动恢复接收到错误帧或CRC错误波特率不匹配用CAN分析仪对比双方参数4.3 过滤器配置错误导致丢帧有一个典型案例项目中节点需要接收ID范围0x200到0x2FF的所有报文我把掩码寄存器设成了0x700看起来低8位全部忽略理论上是对的。但实际运行中发现0x100到0x1FF的报文也混进来了因为这组报文的低8位满足被忽略的条件而高3位又没有挡住。后来我把掩码调整成0x7F8把高3位精确匹配为0x2低8位忽略报文过滤就精确了。过滤器这块的经验是先明确业务上必须接收的ID范围再反推掩码值掩码中为1的位是必匹配位为0的位是“不关心”位。如果条件允许优先使用列表模式配置多个精确ID比掩码模式更直观、更不容易误收。4.4 中断进不去或程序卡死中断进不去有一个被忽略的常见原因CAN1的接收中断在STM32F103上对应的是USB LP CAN1 RX0中断向量但如果你在其他地方对USB中断做了处理两者可能冲突。这种问题在同时使用USB和CAN的场景下非常考验向量表分配需要阅读数据手册中的中断向量表说明必要时采用查询方式接收CAN数据避开冲突。程序卡死则多发生在中断处理时间过长的情况下。CAN中断里塞了耗时操作比如printf串口打印、Flash读写、长延时会导致中断函数一直占用CPU主循环无法执行看起来就是程序卡死。我的建议是中断里只做报文拷贝和置标志位数据解析和业务逻辑放到主循环处理。5. 调试工具与扩展思路从例程到项目落地的最后一公里5.1 用好调试工具CAN分析仪与示波器的分工很多人在开发CAN项目时只依赖MCU代码里的串口打印这实在太慢了。CAN分析仪是必备利器它能让你看到总线上每一帧报文的时间戳、ID、数据、帧类型还能模拟发送报文是双机通信调试的照妖镜。示波器负责物理层验证特别是检查CAN_H和CAN_L的共模电压、波形上升下降沿、位时间宽度。波形是判断通信质量的重要依据正常情况下每位波形清晰差分幅值在1.5V到3V之间如果波形失真或边沿过缓要考虑总线长度、支线长度、终端电阻等因素。在经典例程的后续实践中我建议在初始化完成后主动读取CAN_GetFlagStatus(CAN_FLAG_TSR)等发送状态标志并输出CAN_GetErrorCode()的错误代码用串口打印出来这样调试时能快速定位是发送失败还是接收错误。5.2 后续扩展多节点组网与错误处理机制一旦把例程跑通下一步就是往工程化方向进化。比如多节点组网时要合理分配CAN ID优先级ID越小优先级越高重要报文如急停、故障报警分配小ID要设计心跳报文机制每个节点周期上报在线状态要考虑错误恢复机制把CAN_ABOM、错误中断、被动错误检测全部用上。我自己在项目里的通用模板是每个节点有一个唯一ID周期性发送状态帧事件触发发送数据帧接收方通过过滤器只接收与自己相关的报文。接收缓冲区做成FIFO环形队列避免中断频繁操作队列造成数据互相覆盖。这些能力都可以从经典例程的基础上逐层叠加而不需要推倒重来。5.3 学习建议怎么把这份例程吃透并变成自己的最后分享一点个人经验。拿到这份例程后不要满足于“能收到数据、能发数据”这个状态一定要亲手做三件事第一重算一遍波特率用不同APB1时钟值代入公式直到不查表也能随口算出结果第二改过滤器配置分别验证精确接收、范围接收、全部接收三种模式对中断频率和CPU占用率的影响第三故意制造错误比如把波特率调错、拔掉终端电阻观察错误计数器和总线的反应。这三件事做完你对CAN的理解会比光看十遍教程都要扎实。我当年踩过最深的坑就是盲目照搬例程结果换了一块主频不同、APB1分频不同的开发板通信死活就是不通最后被逼着翻数据手册自己推演了一遍波特率才解决问题。从那以后我拿到别人的CAN代码第一反应永远是先看时钟配置和位时序参数而不是急着往功能里钻。希望这篇内容也能帮你少走这段弯路。本文还有配套的精品资源点击获取