STM32F103 CAN通信实战:协议、硬件配置与调试要点全解析 📅 发布时间:2026/9/3 20:06:42 👁 浏览次数: 简介面向嵌入式开发者和STM32初学者的STM32F103 CAN通信完整Keil工程覆盖汽车电子、工业自动化等场景下的控制器局域网络实现。资源以标准库工程形式提供包含CAN控制器初始化、报文发送接收、波特率与滤波器配置、错误状态处理等核心代码可帮助理解差分传输、多主站仲裁、标准ID与扩展ID分配等关键概念。压缩包内共206个文件约4.73MB以.c/.h源码、.o/.d编译中间文件为主。同时提供.uvproj工程、.hex/.axf可执行文件及.map映射文件可直接编译烧录验证.lst、.crf等辅助文件也便于查看编译细节。已有1900人学习下载适合对照学习CAN总线协议、排查通信异常并进一步扩展多节点组网。 做嵌入式开发的人对CAN总线肯定不陌生但真正把STM32F103的CAN通信从头到尾跑通还是有不少细节值得记录。这篇文章就围绕STM32F103的CAN通信从协议基础、硬件设计、软件配置到常见坑点把我实际调试中的经验和教训完整梳理一遍适合正在用F103做CAN节点开发、或者刚接触CAN总线想快速上手的工程师参考。1. 项目整体思路为什么是CAN为什么是F1031.1 CAN总线解决了什么问题一个项目里一旦出现多个控制单元比如电机的控制器、温度采集模块、IO扩展板、显示面板它们之间怎么通信就成了大问题。如果用串口点对点连接还可以多节点就得靠一主多从轮询实时性和可靠性都比较差。如果用以太网协议栈复杂度又太高对MCU的资源要求也大。CAN总线的定位恰好填补了这个空白它是一根双绞线上的多主通信节点挂上去就能收发硬件自动处理仲裁和错误检测实时性有保障抗干扰能力也强特别适合工业控制和车载环境。STM32F103系列内置的bxCAN控制器支持CAN 2.0A和CAN 2.0B协议也就是标准帧和扩展帧都支持而且有3个发送邮箱、2个接收FIFO和28个过滤器。对大多数中小型项目来说这套硬件资源非常够用不需要外挂独立的CAN控制器芯片。而且F103的价格、供货、资料生态都非常成熟拿它做CAN通信的入门和落地平台几乎是最优解。1.2 方案选型的关键考量我见过不少人在选型时纠结要不要用带CAN FD的芯片这里先泼一盆冷水。F103的bxCAN不支持CAN FD只能跑经典CAN 2.0最大8字节数据场、最高1Mbps波特率。如果你的项目确实需要更高的带宽和更大的数据段直接换G4系列或者其他支持CAN FD的芯片更合适。但反过来如果是常规的传感器数据上报、控制指令下发、设备状态同步经典CAN完全够用没必要为了一个用不上的功能增加复杂度。还有一个容易被忽略的点F103的CAN控制器时钟来自APB1APB1的默认频率是36MHz而CAN外设需要的是APB1时钟分频后的结果。很多人一上来就抄网上的例程时钟树没配对CAN波特率算出来是歪的通信时断时续。后面我会专门讲波特率计算这块这是整个CAN通信项目能不能稳定的核心。2. CAN协议基础与bxCAN硬件结构2.1 CAN帧结构先把报文翻译成人话CAN的报文帧分好几种平时最常用的就两种数据帧和远程帧远程帧实际开发中用得很少。数据帧里又分标准帧和扩展帧区别主要在ID长度上。标准帧的ID是11位扩展帧是29位扩展帧是在标准帧基础上多了18位扩展ID。我在实际项目中如果只是板间通信一般用标准帧就够了ID范围0x000到0x7FF能定义128个不同ID的报文绰绰有余。数据帧的结构大概是这样的帧起始SOF、仲裁段IDRTR、控制段IDE、DLC、数据段0到8字节、CRC段、ACK段、EOF。要特别注意DLC这个字段它表示数据场实际有几个字节。发送端如果DLC填的是4那你后面就算塞了8个字节的数据对端也只会按4个字节去解析。这个坑我踩过一次调试时一直觉得丢数据最后发现是DLC没匹配。2.2 bxCAN的邮箱、过滤器和FIFO机制bxCAN的发送侧有3个邮箱你调用发送函数的时候可以把报文丢到任意一个空闲邮箱里硬件会按优先级自动发送。如果3个邮箱都满了再调用发送函数就会返回失败。所以代码里不能只调用一个发送接口就完事了一定要检查返回值必要时做重发或者缓存处理。接收侧有2个FIFO每个FIFO能存3个完整报文。报文进来之后先经过过滤器匹配的才进FIFO不匹配的直接丢弃。这个机制挺重要的尤其在多节点总线上如果不对报文做过滤CPU会频繁地被无关中断打扰浪费大量时间在处理垃圾报文上。过滤器可以配置成列表模式、掩码模式标准帧和扩展帧还可以分别过滤用得好的话对系统性能提升非常明显。2.3 波特率、位时间与时钟误差CAN是异步串行通信没有单独的时钟线接收方要从数据流里恢复时钟。每个位的传输时间被分成四段同步段、传播段、相位缓冲段1、相位缓冲段2。采样点就落在相位缓冲段1和2之间的边界上。波特率的计算公式是波特率 外设时钟 / (预分频值 × (1 BS1 BS2))这里的BS1和BS2都以时间为单位比如配置成了13个TQ和2个TQ再加上同步段的1个TQ一个位就是16个TQ。如果APB1是36MHz预分频设4那CAN时钟就是9MHz每个TQ的时间是1/9MHz 111ns一个位16个TQ就是1.777us算下来波特率约等于562.5kbps。想做到500kbps可以设预分频4、BS113、BS22或者预分频6、BS18、BS26不同组合的采样点位置不一样要根据总线长度和节点数量选择合适的采样点。一般的经验是采样点放在75%到85%左右既保证余量又能容忍一定程度的信号畸变。这里就是CAN时钟误差问题的根源。CAN协议允许的位时间误差很小在1Mbps下一般要求时钟误差不超过0.5%所以强烈建议使用外部8MHz晶振而不是内部RC振荡器。内部RC在温度变化下的漂移可能达到1%到2%直接导致高频通信时大量错误帧。如果项目对成本敏感、想用内部晶振那波特率就不要超过125kbps或者干脆换支持CAN FD容错更强的芯片。3. 硬件设计与最小系统搭建3.1 STM32F103最小系统的几个关键点F103最小系统很成熟电源3.3V、复位电路、8MHz晶振、BOOT引脚配置这几个部分基本是标配。电源部分最容易被忽略CAN收发器在工作时会向总线输出显性电平瞬时电流比较大如果3.3V稳压器余量不足板子在CAN通信时会出现电压跌落表现为偶发的总线错误。我习惯在CAN收发器的VCC脚旁边放一个100uF的电解电容再加一个0.1uF的瓷片电容效果比只放一个0.1uF稳定得多。晶振这块要认真对待CAN控制器对时钟精度敏感8MHz晶振的负载电容要按芯片手册推荐值来配一般在20pF左右。F103的OSC_IN和OSC_OUT引脚之间如果走线太长可能引入干扰Layout时晶振尽量靠近MCU地线包一圈别偷懒。3.2 CAN收发器选型与终端电阻STM32F103的CAN控制器只有一个CAN_TX和CAN_RX引脚输出的是TTL电平的逻辑信号不能直接挂到总线上中间必须加一个CAN收发器。最经典的收发器是TJA1050还有PCA82C250、SN65HVD230等功能类似。收发器的TXD引脚接MCU的CAN_TXRXD引脚接MCU的CAN_RXVCC接3.3V或者5V具体看型号。SN65HVD230是3.3V供电的和F103电源域完全一致比较省心。TJA1050是5V供电输出RXD的高电平大概是5V而F103的GPIO是5V容忍的也可以直接接但最好串个1k电阻限流。总线两端必须接120Ω的终端电阻。这个电阻的作用是吸收总线末端的信号反射如果漏接了末端信号会产生振铃通信距离稍远或者波特率稍高就会出错。调试阶段可以在CAN_H和CAN_L之间直接焊一个120Ω贴片电阻量产板子要考虑上拉/下拉偏置电阻和共模电感做好EMC防护。3.3 电平匹配、5V容忍和ESD防护F103的GPIO手册上写着5V容忍意思是FT引脚可以承受5V电平输入不用电平转换。但要注意不是所有引脚都支持PA11和PA12默认就是CAN_RX和CAN_TX这两个引脚是USB相关引脚恰好是5V容忍的所以收发器的5V输出可以直接接到PA11上。当然为了保险起见我会在CAN_RX线上加一个钳位电路防止收发器在上电瞬间的毛刺电压损坏MCU引脚。总线侧的保护也不能省CAN收发器面对的是工业现场的长距离线缆静电放电和浪涌随时可能打进来。我在实际产品外壳上都会在CAN_H、CAN_L对地各加一个TVS管选型一般是SMBJ15CA之类的双向TVS响应速度快能钳位过压。如果环境特别恶劣再串一个共模电感或者加个小电容滤波可以有效提高EMC测试的通过率。4. 软件实现从固件库配置到收发代码4.1 CAN外设初始化时钟、引脚和波特率这是整个软件部分的基础一步错步步错。以HAL库为例初始化顺序是先把CAN1的时钟打开配置PA11和PA12为复用推挽输出然后根据APB1时钟和期望波特率设置预分频和位时间参数最后使能CAN外设。下面是我实测可用的一段配置代码CAN_HandleTypeDef hcan1; void MX_CAN1_Init(void) { hcan1.Instance CAN1; hcan1.Init.Prescaler 4; // APB136MHz 9MHz hcan1.Init.Mode CAN_MODE_NORMAL; // 普通模式 hcan1.Init.SyncJumpWidth CAN_SJW_1TQ; // 同步跳转宽度 hcan1.Init.TimeSeg1 CAN_BS1_13TQ; // 采样点在87.5% hcan1.Init.TimeSeg2 CAN_BS2_2TQ; hcan1.Init.TimeTriggeredMode DISABLE; hcan1.Init.AutoBusOff DISABLE; hcan1.Init.AutoWakeUp DISABLE; hcan1.Init.AutoRetransmission ENABLE; // 出错自动重发 hcan1.Init.ReceiveFifoLocked DISABLE; hcan1.Init.TransmitFifoPriority DISABLE; if (HAL_CAN_Init(hcan1) ! HAL_OK) { Error_Handler(); } }这段代码配置出来的波特率大约在562.5kbps想精确到500kbps可以把Prescaler改成5BS19BS26或者Prescaler4BS113BS22两者采样点略有差异。需要注意HAL_CAN_Init必须在时钟树配置完成之后调用否则分频值是以默认时钟算的出来的波特率完全不对。4.2 发送报文别忽略返回值发送一个CAN报文用HAL_CAN_AddTxMessage函数参数包括句柄、发送头结构体、数据数组和邮箱编号。这里的发送头结构体一定要认真填IDE字段决定是标准帧还是扩展帧RTR决定是不是远程帧DLC是数据长度。我封装了一个发送函数项目里所有报文都走这个口便于统一管理优先级和重发机制uint8_t CAN_SendData(uint32_t id, uint8_t *data, uint8_t len) { CAN_TxHeaderTypeDef txHeader; uint32_t mailbox 0; uint8_t buf[8] {0}; if (len 8) len 8; memcpy(buf, data, len); txHeader.StdId id; txHeader.IDE CAN_ID_STD; txHeader.RTR CAN_RTR_DATA; txHeader.DLC len; if (HAL_CAN_AddTxMessage(hcan1, txHeader, buf, mailbox) ! HAL_OK) { return 1; // 发送失败邮箱已满或总线忙 } return 0; }实际测试下来轮询调用这个函数再配合HAL库内部的发送完成中断可以做到大概每1ms发送一帧标准报文速度完全够用。要注意的是如果总线一直处于忙状态AutoRetransmission使能的情况下HAL_CAN_AddTxMessage有可能一直占用邮箱程序里要有超时保护不然会出现看似发送成功、实际卡死的情况。4.3 接收报文中断 FIFO回调接收比发送简单因为硬件有FIFO自动缓存我们要做的就是把FIFO里的数据及时读走防止溢出。推荐使用接收中断在NVIC里使能CAN1_RX0_IRQn然后在中断处理函数里读取报文。HAL库的做法是重写这个回调函数void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8] {0}; uint32_t fifo CAN_RX_FIFO0; HAL_CAN_GetRxMessage(hcan, fifo, rxHeader, rxData); // 这里根据rxHeader.StdId分发到不同处理逻辑 ProcessCanMessage(rxHeader.StdId, rxData); }回调函数在中断上下文里执行不要做耗时操作比如打印日志或者处理复杂的业务逻辑只把收到的数据拷贝到全局环形缓冲区再到主循环里消费。这个结构处理多路报文非常方便PID控制、状态上报、参数读写都可以通过不同ID区分。4.4 过滤器配置让CPU只处理关心的报文如果总线上有多帧不同ID的数据但某节点只关心其中某几个ID就用过滤器把无关报文挡在门外。bxCAN的过滤器组可以配置成掩码模式下面这段代码配置过滤器0只接收ID为0x111和0x222的标准帧CAN_FilterTypeDef filter; filter.FilterBank 0; filter.FilterMode CAN_FILTERMODE_IDMASK; filter.FilterScale CAN_FILTERSCALE_32BIT; filter.FilterIdHigh (0x111 5) 0xFFFF; // 第一个ID filter.FilterIdLow 0x0000; filter.FilterMaskIdHigh 0x7FF0 | 0x0020; // 掩码 filter.FilterMaskIdLow 0x0000; filter.FilterFIFOAssignment CAN_RX_FIFO0; filter.FilterActivation ENABLE; HAL_CAN_ConfigFilter(hcan1, filter);掩码模式的规则是掩码位为1表示这一位必须匹配0表示任意。如果只想接收0x111和0x222两个ID把掩码设成0x7FF那么所有扩展位都会被过滤掉只保留11位标准ID中完全等于0x111或0x222的帧。过滤器配置不复杂但经常有人因为掩码计算错误导致收不到报文调试时用CAN分析仪抓一下总线上实际跑的帧再反推过滤器参数效率高很多。5. 实测与调试从回环模式到双机通信5.1 回环模式自测不接总线也能验证代码在写正式的双机通信之前先用回环模式自测一把很有必要。把初始化里的Mode改成CAN_MODE_LOOPBACK然后调用发送函数自己发的报文会直接回灌到自己的接收FIFO不需要接任何外部设备。这个模式用来验证时钟配置、过滤器、接收中断是否正常非常方便。回环模式跑通之后我一般会在接收回调里加一个计数器每秒钟通过串口打印一次接收到的帧数。如果发送100帧接收计数也是100那基本可以确定软件链路是通的可以放心进入下一步联调。5.2 双节点通信与CAN分析仪双节点联调时最好有一台CAN分析仪不管是周立功的CAN盒还是创芯科技的USB-CAN适配器都能把总线上的报文实时抓出来看。我第一次调双节点时程序写得明明没问题但两个板子就是不通用分析仪一抓发现其中一个节点一直在发送错误帧拿错误帧的波形去对才发现这个板的时钟配置错了波特率实际只有460kbps另一个是500kbps两边不同步自然收不到。还有一次遇到的问题是报文内容对不上。发送端明明发的0x01接收端显示0x08后来发现是CAN矩阵的字节序问题。上位机和MCU的字节序不一致导致多字节数据在解析时高低字节颠倒了。这里我要重点强调CAN报文是串行发送的一个字节内的位序是从高到低跨多个字节时没有统一规定具体要看协议矩阵的定义比如Motorola格式和Intel格式解析出来的结果就完全不同。调试多字节参数时先拿一帧固定的数据去验证你的解析函数别一上来就解析完整协议。5.3 帧间隔与总线利用率观察用CAN分析仪观察双节点通信时还可以留意帧间隔和总线利用率。总线上如果同时只有两个节点各发一帧总线利用率通常不到1%完全没压力。但如果有多个节点频繁发送比如说10ms发一帧200ms发一帧总线利用率依然很低CAN总线的带宽余量非常充足。真正需要注意的是一帧CAN报文的最小时间间隔。以500kbps为例一帧标准帧大概需要130us左右满负荷时1秒能发送大约7000帧实际项目中这个数字远达不到因为应用层还需要处理逻辑。6. 常见问题与排查技巧实录6.1 波特率误差和重同步导致的偶发失败CAN通信偶发失败的原因里最少见但最难查的就是波特率误差。两个节点的波特率如果差得不大CAN协议还能靠重同步机制勉强维持通信但误差一大就会开始出现错误帧。我测试过一个板子4个节点里3个正常1个丢包严重最后测了它的APB1实际时钟发现因为晶振旁边的负载电容虚焊频率偏了0.7%CAN就受不了了。排查建议用逻辑分析仪抓CAN_TX引脚的波形数一下一个位的时间反推实际波特率几秒钟就能发现偏差。重同步还有个特性是同步跳转宽度SJW它表示接收方一次最多能调整多少个TQ来补偿相位误差。一般来说SJW设1个TQ就够了但如果总线上节点数多、线缆长可以试着加大到2个TQ增加容错能力。不过SJW加大也会降低采样点的抗噪声能力别盲目调大。6.2 内部晶振模式下能不能跑CAN搜资料时经常有人问F103能不能用内部晶振跑CAN我的结论是能跑但只建议在低速下跑。HSI的内部RC振荡器精度在25℃时大约是1%但温度漂移以后可能到2%以上。CAN协议规定位时间的最大误差在0.5%以内用内部晶振很难满足。如果非要省掉外部晶振可以把CAN波特率降到125kbps以下然后用分析仪实测一段时间看看错误帧计数能不能接受。很多量产项目就是这么干的前提是波特率确实不高、节点数少。6.3 低功耗Stop模式下的CAN唤醒问题F103的停机模式STOP mode我也踩过坑。停机模式下外设时钟全部关闭CAN模块不工作外部CAN总线上的活动无法直接唤醒MCU除非开启了CAN的自动唤醒功能并且选用外部中断方式。在没有硬件唤醒信号的设计里我一般用CAN收发器的RXD脚接一个外部中断输入当总线有数据变化时RXD的电平翻转触发EXTI中断把MCU从STOP模式唤醒再重新初始化CAN外设。这个思路简单可靠但要记住唤醒之后必须重新配置CAN的邮箱和过滤器否则收发功能恢复不了。6.4 工具链兼容性和调试辅助调试CAN项目时上位机分析软件的选择也很影响效率。周立功的CANTest配合他家的CAN盒非常好用但偶尔会遇到在Windows 11下驱动不兼容的问题官方往往要更新驱动版本。创芯科技的USB-CAN设备在多数系统下免驱上手更快。如果是在Linux环境下做自动化测试python-can库值得试试我经常写个几十行的Python脚本通过USB-CAN设备回放和校验报文比手动用分析仪点来点去高效得多。另外提醒一句进行总线联调时测试代码里尽量加上错误状态寄存器CAN_ESR的读取出问题第一时间看错误计数器和上次错误码能直接定位到是位填充错误、CRC错误还是格式错误省去大量盲猜的时间。小结与个人体会我在STM32F103上做CAN通信已经有好几个项目了从最初照着网上例程改成自己的协议到后来整个通信层完全自己设计最大的体会是CAN看着简单真正稳定运行还是要把每个底层细节吃透。波特率算对了、硬件连接规范了、过滤器和FIFO用好了协议栈反而成了最简单的部分。如果刚开始接触建议从标准帧、500kbps、两个节点加一个CAN分析仪的组合开始一步步把代码和工具链跑熟再挑战高波特率、多节点、和低功耗场景。CAN总线作为一个老而弥坚的通信方案在嵌入式领域依然值得投入精力掌握。本文还有配套的精品资源点击获取