STM32F103 CAN通信实战:从硬件配置到调试排错全解析
简介面向STM32F103嵌入式开发者的CAN通信工程资源覆盖CAN总线初始化、数据发送/接收、滤波器配置及错误处理等核心环节适合学习CAN协议与STM32 CAN外设应用的中初级开发者。压缩包共206个文件、大小4.73MB包含C/H源码、Keil工程文件uvproj/uvopt、编译生成的o/d/crf/lst/axf/hex等中间与目标文件以及说明文档和批量清理脚本可完整查看工程结构或直接编译烧录。工程提供标准帧/扩展帧处理、中断接收、回环测试等常见应用写法并集成LCD显示等模块便于直观验证通信状态还可结合定时器、Flash等外设代码学习多模块协同开发。已有1900人学习下载对计划在汽车电子、工业控制等场景中使用CAN通信的开发者有实际参考价值。 做 STM32F103 的 CAN 通信说难不难说简单也真不简单。很多人第一次接触不是死在波特率上就是死在过滤器上再不然就是总线上只有一个节点数据死活发不出去。这篇文章我把自己做 F103 CAN 通信的完整思路、硬件选型、代码配置和排查经验全部整理出来从“为什么这么配”讲到“出问题怎么办”适合刚接触 STM32F103 CAN 开发、或者已经在调但卡住的工程师直接参考。1. 项目概述与整体设计思路1.1 为什么选 STM32F103 做 CAN 节点STM32F103 内置的是 bxCAN 控制器Basic Extended CAN也就是说芯片内部已经集成了完整的 CAN 协议控制器我们只需要在外面挂一个 CAN 收发器比如 TJA1050、SN65HVD230把控制器输出的 TX/RX 电平转换成总线上的差分信号就能组成一个标准 CAN 节点。对比外挂 SJA1000 这种独立控制器的方案F103 的成本、体积、软件复杂度都低很多这也是它至今仍是工业控制、车载电子、机器人项目里最常见 MCU 的原因之一。CAN 总线本身的核心优势是多主通信、可靠的仲裁机制、极强的抗干扰能力特别适合节点数量多、数据量不大但实时性要求高的场景比如电机控制、电池管理系统、传感器网络。如果是跑普通串口主从结构、一主多从主机挂了整个链路就瘫了CAN 不一样任何一个节点都可以主动发数据优先级靠帧 ID 仲裁不用主机轮询组网灵活性高得多。1.2 整体实现方案选型做 F103 CAN 通信最小系统 收发器 两个终端电阻就是一个可以跑的节点。我这边方案固定为主控STM32F103C8T6 或 RBT6最小系统板即可CAN 收发器TJA10505V 供电逻辑电平兼容 3.3V晶振8MHz 外部晶振这是关键后面会专门讲时钟误差的问题通信速率500kbps这是工业上非常通用的速率软件库标准外设库StdPeriph_LibF103 生态成熟网上资料最多有人会纠结用标准库还是 HAL 库。我的建议是如果做 CAN 通信标准库更直接CAN 相关的结构体和寄存器逻辑清晰调试起来心里有数HAL 库封装得比较厚出问题反而不好定位。当然如果是新项目且团队统一用 HAL那也行但下面这些原理性的东西是一样的。1.3 bxCAN 资源梳理F103 的 bxCAN 有 3 个发送邮箱、2 个接收 FIFO、28 个过滤器组。这意味着硬件上可以缓存 3 条待发送报文接收侧由 FIFO 暂存过滤功能由硬件完成CPU 不需要参与每条报文的筛选这是 CAN 通信高实时性的硬件基础。使用上要注意区分发送邮箱是“同时最多排队 3 条”不是“1 个邮箱对应 1 个 ID”发送完成后邮箱会自动释放。过滤器组则是按编号从 0 到 27每个 FIFO 可以绑定多个过滤器报文通过任意一个过滤器就会进入对应 FIFO。2. 硬件准备与连接要点2.1 最小系统的关键点STM32F103 最小系统本身不复杂复位电路、BOOT0 下拉、电源滤波、8MHz 晶振但做 CAN 通信时晶振极其重要。你可以用内部 HSI 8MHz 跑流水灯跑串口也没大问题但跑 CAN 就得慎重。CAN 协议对时钟精度有硬性要求HSI 的出厂误差一般在 ±1% 左右温度漂移后可能更大在 500kbps 这种速率下时钟误差会让采样点偏移轻则偶发误码重则完全不通。所以我每次做 CAN 节点第一件事就是确认外部晶振已经焊接并且起振别在内部时钟上省事。晶振的参数要注意负载电容8MHz 晶振配两个 20pF 左右的负载电容是常见做法。另外 CAN 控制器时钟来自 APB1 总线最高 36MHz做波特率计算时要先确认 RCC 配置里 APB1 分频系数很多例程默认系统时钟 72MHz、APB1 36MHz如果你的工程改了分频那波特率就要重算。2.2 CAN 收发器电路连接收发器是把 CAN 控制器和物理总线连接起来的桥梁以 TJA1050 为例TXD 接 STM32 的 CAN_TX 引脚默认 PA12RXD 接 STM32 的 CAN_RX 引脚默认 PA11VCC 接 5VGND 共地CANH、CANL 接总线TJA1050 是 5V 供电但它的 TXD/RXD 引脚逻辑电平可以兼容 3.3V 的 STM32实测直接连接没问题。如果追求更稳或者你的板子没有 5V 电源可以用 3.3V 供电的 SN65HVD230逻辑电平完全匹配不过它的驱动能力稍弱长距离传输不如 TJA1050。还有一种做法是选 TJA1051T/33.3V 供电版本兼容性和驱动能力都兼顾只是市场上不如 TJA1050 好买。接线时特别容易犯的错是 CANH 和 CANL 接反或者收发器和 MCU 没有共地。CANH/CANL 接反节点收不到任何数据但示波器还能看到波形容易误判为软件问题共地问题则表现为时通时不通用手摸一下总线可能就好了这种问题排查起来最痛苦。2.3 终端电阻与布线经验CAN 总线两端必须各接一个 120 欧姆终端电阻作用是匹配阻抗、消除信号反射。我见过很多新手在面包板上飞线测试两个节点怎么都不稳定用示波器一看波形振铃非常严重就是没接终端电阻。如果只有两个节点就在每个节点的 CANH 和 CANL 之间各接一个 120 欧姆电阻。如果超过两个节点只在物理最远的两端接电阻中间节点不要接。接了多个电阻会导致总线负载过重信号幅值被拉低同样会通信失败。布线方面CANH 和 CANL 要双绞不是必须但双绞能显著降低共模干扰。工业现场线束比较长的话还要注意总线供电问题避免地电位差过大。3. 核心配置与代码实现3.1 GPIO 初始化与 CAN 外设时钟先使能 GPIOA、AFIO 和 CAN1 的时钟然后配置 PA11 为输入上拉PA12 为复用推挽输出。PA11 是 RX配置成上拉输入是为了保证总线空闲状态下 RX 引脚电平稳定避免误触发PA12 是 TX必须配置成复用推挽。GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); 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_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure);如果你的板子把 CAN 引脚重映射到了 PB8/PB9需要先调用 GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE) 开启重映射。这个很容易漏漏掉之后程序怎么调都不通。3.2 波特率与采样点计算CAN 波特率是很多人第一个卡住的地方。F103 的 CAN 外设挂在 APB1 总线上我默认系统时钟 72MHz、APB1 分频为 36MHz。CAN 的位时间由三部分组成同步段固定 1 个时间量子tq、传播段和相位缓冲段 1 合起来对应 BS1、相位缓冲段 2 对应 BS2。波特率公式波特率 APB1 时钟 / (CAN_Prescaler × (1 BS1 BS2))目标 500kbpsAPB1 为 36MHz我常用的参数是CAN_Prescaler 4BS1 14 tqBS2 3 tqSJW 1 tq代入公式36MHz / (4 × (1 14 3)) 36MHz / 72 500kHz采样点位置 (1 BS1) / (1 BS1 BS2) 15 / 18 83.3%采样点放在这个位置有什么讲究CAN 总线上每一位数据要在位时间靠后的位置采样才最稳定因为信号经过传输延迟和边沿抖动之后中后段才趋于稳定。一般建议采样点落在 75% 到 85% 之间83.3% 是我比较喜欢的位置。很多官方例程直接给 BS1 9、BS2 8波特率也是 500k但采样点只有 55.6%低速短距离可能没问题速率一高或者线一长就出奇奇怪怪的偶发错误。所以说波特率对不等于配置对采样点也要关注。SJW 是重同步跳转宽度它决定了控制器能容忍多大的时钟偏差。SJW 设 1tq 是保守做法如果时钟源精度一般可以适当加大到 2tq但不要超过 BS2 的值否则会引起错误重同步。CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM ENABLE; CAN_InitStructure.CAN_AWUM ENABLE; CAN_InitStructure.CAN_NART DISABLE; CAN_InitStructure.CAN_RFLM DISABLE; CAN_InitStructure.CAN_TXFP DISABLE; CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_14tq; CAN_InitStructure.CAN_BS2 CAN_BS2_3tq; CAN_InitStructure.CAN_Prescaler 4; CAN_Init(CAN1, CAN_InitStructure);这几个参数里值得说的是 ABOM自动离线恢复。总线上如果出现严重错误CAN 控制器会进入 Bus Off 状态不再参与通信。ABOM 使能后控制器会等待 128 个总线空闲位后自动恢复这对系统容错很重要。我见过有人没开 ABOM总线上一个节点掉线之后整个系统就永远沉默了重启才能恢复。NART 是禁止自动重传一般保持 DISABLE让硬件自动重发提高可靠性。3.3 过滤器配置过滤器是 CAN 接收的第一道关卡。如果配置不对即使总线上有数据FIFO 里也是空的。最简单的方式是配置成掩码模式、掩码全部为 0也就是不过滤任何报文全部接收然后软件里自己判断 ID。调试初期建议先这么干排除过滤器的干扰因素。CAN_FilterInitTypeDef CAN_FilterInitStructure; 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_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation ENABLE; CAN_FilterInit(CAN_FilterInitStructure);如果要做精确过滤比如只接收 ID 为 0x123 的标准帧就需要理解寄存器位的映射关系。32 位过滤器模式下标准帧 ID 是 11 位存放在 FilterIdHigh 寄存器的 [15:5] 位所以代码里要把 ID 左移 5 位CAN_FilterInitStructure.CAN_FilterIdHigh (uint16_t)(0x123 5); CAN_FilterInitStructure.CAN_FilterIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0xFFE0; CAN_FilterInitStructure.CAN_FilterMaskIdLow 0x0000;掩码 0xFFE0 表示高 11 位必须匹配低 5 位不关心。这段代码我在不同工程里见过无数种写法有直接赋 0x123 的有纠结要不要加 IDE 位的。我的建议是调试阶段全部接收确认通路没问题后再加过滤条件这样能快速定位问题是不是出在过滤器。3.4 数据发送实现发送报文用 CAN_Transmit 函数先填充 TxMessage 结构体选择空的发送邮箱然后请求发送。标准帧和扩展帧的区别在 IDE 字段数据帧和遥控帧的区别在 RTR 字段DLC 是数据长度最多 8 字节。CanTxMsg TxMessage; uint8_t mailbox; TxMessage.StdId 0x123; TxMessage.IDE CAN_Id_Standard; TxMessage.RTR CAN_RTR_Data; TxMessage.DLC 8; TxMessage.Data[0] 0x01; TxMessage.Data[1] 0x02; // ... Data[2] ~ Data[7] mailbox CAN_Transmit(CAN1, TxMessage);CAN_Transmit 返回的是邮箱编号如果三个邮箱都处于挂起状态返回 CAN_TxStatus_NoMailBox。发送之后可以轮询 CAN_TransmitStatus 判断是否发送完成。这里有个容易忽略的点在只有一个节点的总线上数据是发不出去的。因为 CAN 协议要求发送方必须收到至少一个其他节点的 ACK 应答如果总线上只有自己发送会一直失败最后进入 Bus Off。调试初期至少要有两个节点或者先用环回模式。3.5 中断接收实现接收数据我强烈建议用中断不要在主循环里轮询 CAN_MessagePending。CAN 报文到达时间不确定轮询会占用 CPU 且可能丢帧。使用 FIFO0 消息挂起中断CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);中断服务函数的名字比较特殊F103 的 CAN1 RX0 中断向量和 USB LP 中断共用所以函数名是void USB_LP_CAN1_RX0_IRQHandler(void) { CanRxMsg RxMessage; if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) ! RESET) { CAN_Receive(CAN1, CAN_FIFO0, RxMessage); // 在这里处理接收到的报文 // RxMessage.StdId 是帧ID // RxMessage.Data[0] ~ Data[7] 是数据 CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0); } }关于清除中断标志FMP0 是 FIFO 0 消息挂起标志读取 FIFO 后硬件会自动清掉这个消息挂起状态但代码里还是建议调用 CAN_ClearITPendingBit避免后续版本或库实现差异导致中断反复触发。中断服务函数里不要做耗时操作把数据拷贝到全局变量或者丢进环形缓冲区处理逻辑放在主循环。3.6 环回模式与总线自测如果没有第二块板子可以用环回模式验证代码。环回模式下CAN 控制器发送的报文会自动进入接收 FIFO不需要总线上有其他节点也不依赖外部收发器适合验证初始化、过滤器、接收中断这一整条软件链路。CAN_InitStructure.CAN_Mode CAN_Mode_LoopBack;环回模式跑通了再把模式改成 Normal接上第二块板子测试。很多人一上来就直接 Normal 模式接总线结果硬件问题、软件问题混在一起根本没法定位。先环回、再双机、再上总线这个顺序能省很多时间。4. 常见问题与排查技巧4.1 问题速查表先贴一张我在调试中经常对照的速查表遇到问题可以按图索骥现象可能原因排查方法一个节点单独发送失败总线上无其他节点应答接第二个节点或改用环回模式测试两个节点都收不到数据终端电阻缺失或接反检查 CANH/CANL两端各接 120 欧姆电阻时通时不通共地不良 / 晶振误差大检查收发器与 MCU 共地改用外部晶振总线上有波形但收不到波特率不匹配 / 滤波器配置错误对比两端的波特率参数先关闭过滤上电一段时间后通信中断Bus Off 未自动恢复确认 CAN_ABOM 已使能高速率时偶发错误帧采样点位置不合适调整 BS1/BS2使采样点落在 75%~85%4.2 收不到数据时先查过滤器我调试时收不到数据的排查顺序是先用示波器或逻辑分析仪看 CANH/CANL 波形确认总线上确实有数据在跑然后看 CAN 控制器的 ESR 寄存器检查是否有错误状态最后才查过滤器和 FIFO 配置。很多人一上来就怀疑过滤器结果发现物理层根本没通。如果是软件问题过滤器是最常见的坑。我建议调试期先用全接收模式掩码全 0把过滤器整个绕过去。确认能收到数据后再逐步加过滤条件。另外要注意CAN1 和 CAN2 的过滤器是独立配置的如果你用了重映射或者其他复用功能先确认过滤器号没有被其他功能占用。4.3 发送失败与 ACK 错误CAN 发送失败最典型的原因就是总线上没有第二个节点应答。CAN 协议规定发送节点发出帧结束位后需要等待接收节点的 ACK 应答。如果总线上只有发送节点ACK 槽位是隐性电平发送节点会判定发送错误重试一定次数后进入 Bus Off。这里有个判断技巧如果 CAN_Transmit 返回值是邮箱号但 CAN_TransmitStatus 一直处于 CAN_TxStatus_Pending或者状态寄存器一直报 ACK Error基本可以断定总线上没有应答节点。别急着改代码先数一数总线上有几个节点终端电阻接没接。用 USB-CAN 分析仪接上去做第二个节点是排查这类问题最省力的办法。我手头常用的调试工具是周立功的 USBCAN 系列在 Win11 上装驱动偶尔会遇到不兼容通常用兼容模式运行安装程序或者装老版本驱动能解决。如果手头没有专用工具一块带 CAN 的板子和逻辑分析仪也能凑合排查物理层波形。4.4 时钟误差导致波特率漂移这个坑很隐蔽表现是明明两个节点配置的波特率一样但通信不稳定或者低温、高温环境下出现偶发错误帧。原因就是时钟源精度不够。STM32F103 内部 HSI 的误差在温度变化时可能达到 ±2% 以上而 CAN 协议对时钟公差的要求跟采样点位置有关采样点越靠后允许的误差越小。在 500kbps 下如果两端都用了 HSI很可能实际波特率一个偏高一个偏低采样点刚好踩到信号边沿上就会出现时好时坏的现象。解决方法是必须用外部晶振8MHz 无源晶振配合 PLL 倍频到 72MHzAPB1 分频到 36MHz这个链路产生的时钟误差可以控制在 0.1% 以内。我见过有人把 HSI 校准寄存器调一调就上 CAN短时间能用温度一变化就露馅所以这里直接劝退别在内部时钟上做 CAN。4.5 字节序与信号解析问题最后说一个应用层的问题CAN 报文本身只有字节流是设备和设备之间约定好的协议。实际项目中引入 CAN 矩阵DBC 文件后信号定义通常分 Intel小端和 Motorola大端两种字节序。同一个信号在 Intel 格式下低字节在前在 Motorola 格式下高字节在前。新手最容易在这里栽跟头明明收到的一帧数据是对的一解析结果就不对。如果项目里要解析这类信号我的建议是先用 CAN 分析仪抓帧把原始字节 dump 出来对照 DBC 里的 start bit 和 length 逐位解析不要凭感觉猜端序。4.6 关于 CAN FD 和协议扩展如果项目文档里出现了 CAN FD 的字样要特别注意STM32F103 的 bxCAN 只支持经典 CAN 协议不支持 CAN FD。CAN FD 的帧格式、波特率和经典 CAN 不一样需要选型支持 CAN FD 的芯片比如 STM32G4 系列或者外置控制器。我在选型阶段就见过不止一次项目需求写了 CAN FD硬件工程师直接选了个 F103后面才发现跑不了只能重新设计。如果你对 1Mbps 以上的速率有需求也建议直接考虑 CAN FD 方案经典 CAN 在 F103 上跑到 1Mbps 已经接近极限了。最后再分享几个实际经验做 CAN 通信调试我的习惯是先把阻碍通信的“硬性条件”全部落实外部晶振、收发器接线、终端电阻、共地、双节点。这五项任何一个有问题后面软件怎么调都是白费。代码层面初始化流程固定为先配置 GPIO 和时钟再初始化 CAN 时序参数然后配置过滤器最后开中断。每次改动只动一个变量不要一次性把波特率、过滤器、中断全改了否则出了问题根本不知道是哪个环节引起的。调试顺序上先用环回模式验证单板软件链路再用两块板子做最基本的收发测试最后再挂到实际总线上联调。每一步都确认无误后再进入下一步看似多花了几分钟实际上能帮你省下大量排查时间。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →