ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103RBT6 CAN总线开发调通:HAL库配置、过滤器与中断实战

STM32F103RBT6 CAN总线开发调通:HAL库配置、过滤器与中断实战 简介一套已调通的 STM32F103RBT6 CAN 总线开发代码基于 HAL 库与 STM32CubeMX 配置面向嵌入式初学者及需要快速落地 CAN 通信的开发者解决从 CubeMX 初始化、Keil 工程移植到消息收发调试的全流程问题。压缩包共 586 个文件约 6.01MB其中包含 337 个 C 源文件与 104 个头文件覆盖 HAL 库驱动与用户应用代码另有汇编启动文件、链接脚本、Keil 工程文件uvproj/uvopt、CubeMX 配置ioc以及编译生成的 hex/axf 文件便于直接下载验证或二次开发。目前已有 4420 人学习下载。代码内提供 CAN 初始化、滤波器配置、发送/接收回调等关键实现并留有调试与验证思路可用于工业控制、汽车电子等场景能帮助快速理解 F103RBT6 的 CAN 外设使用要点减少踩坑时间。 最近在把 stm32f103rbt6 的 CAN 开发代码完整调通HAL 库环境下从 CubeMX 配置、过滤器、中断接收到稳定性验证都跑了一遍。CAN 这个外设说难不难但坑点非常集中引脚重映射、波特率、过滤器、FIFO 中断任何一处设置不对现象都极具迷惑性。这篇文章不打算按手册复述 API而是把这次“已调通”的工程配置、代码结构和检查顺序完整写下来给准备用 F103RBT6 接 CAN 总线的朋友做一个可以直接参考的记录。1. F103RBT6的CAN值得认真调一次的原因1.1 这块芯片在CAN通信中的定位STM32F103RBT6 在工控板、机器人控制器、车载诊断设备里出现频率极高64 引脚、128KB Flash、20KB RAM主频 72MHz资源不算豪华但够用。CAN 外设是 bxCAN支持 CAN 2.0A/B 协议标准帧、扩展帧都能收发3 个发送邮箱、2 个接收 FIFO、14 个过滤组硬件能力对大多数工业现场总线应用来说是足够的。现在不少项目选择 F103 HAL 库是因为 CubeMX 能快速生成初始化代码少写很多底层寄存器操作。但生成并不等于调通HAL 库把寄存器细节包装得很整齐也把 CAN 的门槛藏了起来过滤器怎么配、FIFO 中断从哪里进、总线错误怎么恢复文档里都有但真正调的时候全是“发送超时”“回调没触发”这种折磨人的现象。我这次把工程完整跑通最大的体会是CAN 能不能通取决于你对位时间、滤波器、中断通知这三件事的理解而不是会不会调用 HAL 函数。1.2 调通CAN需要先建立三个认知第一条CAN 是双线差分通信不是 UART 那种点对点收发。总线上所有节点共享同一对 CAN_H/CAN_L靠 ID 仲裁和 ACK 机制决定谁发送、谁接收。加上错误帧、填充位这些机制你看到的波形和时间参数跟串口、I2C 完全不一样不能用过去的直觉去猜。第二条HAL 库的 API 只是外设的“驱动外壳”真正决定能不能通信的是 CAN_BTR 寄存器里的位时间参数以及两端是否约定同样的波特率和采样点。很多人把配置生成后就直接用结果两边的 500kbps 根本不是同一个 500kbps。第三条过滤器和 FIFO 是接收链路上的两层“闸门”任何一个设错即使总线上跑着大量报文软件里也读不到。这三条认知建立起来后面遇到的现象基本都能对号入座。2. CubeMX初始化位时间参数藏着通信的真相2.1 引脚、时钟与使能顺序CubeMX 里把 CAN1 选上之后默认会把 PA11RX、PA12TX点亮。如果你的板子实际走的是重映射后的 PB8/PB9 引脚需要手动改配置同时确保 AFIO 时钟已使能。很多朋友第一次在这里翻车CubeMX 明明生成了初始化代码硬件上却收不到数据折腾一整天后发现板子接的是 PB8/PB9代码里还在用 PA11/PA12。时钟方面CAN1 挂在 APB1 总线上。F103 在 72MHz 主频下APB1 通常是 36MHzHAL 库初始化波特率时就是拿 APB1 频率来算的。所以建议先看一眼 CubeMX 的 RCC 时钟树确定 APB1 实际分频后的频率再动 CAN 参数。我以前偷懒跳过这步后面算出的波特率差得离谱排查非常痛苦。2.2 500kbps到底是怎么算出来的CAN 一个位周期由同步段、传播段、相位缓冲段 1、相位缓冲段 2 组成。STM32 把传播段和相位缓冲段 1 合并成 TimeSeg1BS1相位缓冲段 2 是 TimeSeg2BS2同步段固定为 1TQ。波特率公式就是波特率 CAN时钟频率 / (Prescaler * (1 BS1 BS2))我这次目标 500kbpsCAN 时钟 36MHz需要总分频倍数为 72。最终参数取 Prescaler6、BS18、BS23、SJW1那么每个位就是 12TQ采样点约等于 (18)/12 75%这个采样点位置对多数收发器都比较友好。注意SJW 不是随便给的补偿参数它表示重同步时一个位最多能吸收多少个 TQ 的相位误差。总线越长、波特率越低SJW 可以适当调大到 2TQ短距离开发板上 1TQ 通常够用。2.3 Normal、LoopBack、Silent三种模式选哪个CubeMX 的 Mode 下拉里有正常、回环、静默等选项。我的习惯是首版先在 LoopBack 模式下调让 CAN 外设内部自发自收确认软件链路通再切到 Normal 模式接外部收发器和终端电阻。LoopBack 模式下报文不会真正发到总线上很适合没有任何第二节点时排查程序问题。Silent 模式只接收不发送适合做总线监听。真正和外部设备联调时记得切回 Normal否则你这边发得再欢别人也收不到。3. 过滤器设置从“来者不拒”到“精确定位”3.1 过滤器到底过滤掉了什么CAN 总线上所有节点共享同一对差分线理论上每个节点都会收到总线上所有报文。如果没有过滤器CPU 会被无关帧频繁打断实时性高的任务很容易被淹没。RBT6 的 CAN1 有 14 个过滤组每个过滤组可配置成 32 位或 16 位两种位宽也能配置成掩码模式或列表模式。标准帧 ID 只有 11 位扩展帧 ID 是 29 位。掩码模式里Mask 对应位是 1 时必须匹配是 0 时忽略该位适合接收一段连续的 ID 区间列表模式则是精确匹配只在接收几个固定 ID 时非常高效。项目早期别急着把筛选收太窄先用“来者不拒”验证链路后面再根据协议收窄。3.2 HAL库的滤波器配置代码HAL_CAN_ConfigFilter 的初始化结构体字段很多我最推荐的打底配置是“全通过”即不关心任何 ID 位所有报文都进 FIFO0CAN_FilterTypeDef filter; filter.FilterBank 0; filter.FilterMode CAN_FILTERMODE_IDMASK; filter.FilterScale CAN_FILTERSCALE_32BIT; filter.FilterIdHigh 0x0000; filter.FilterIdLow 0x0000; filter.FilterMaskIdHigh 0x0000; filter.FilterMaskIdLow 0x0000; filter.FilterFIFOAssignment CAN_RX_FIFO0; filter.FilterActivation ENABLE; HAL_CAN_ConfigFilter(hcan, filter);这段配置能让所有报文都进入 FIFO0。如果做双节点点对点测试这会大大降低排查难度先确保能收到东西再去研究怎么收得精准。3.3 从“全通过”到“只收0x123”如果你想只接收标准帧 ID 为 0x123 的报文HAL 代码里通常这样写filter.FilterIdHigh (uint16_t)((0x123U 5) 0xFFFF); filter.FilterIdLow 0x0000; filter.FilterMaskIdHigh (uint16_t)((0x7FFU 5) 0xFFFF); filter.FilterMaskIdLow 0x0000;这里把标准 ID 左移了 5 位是因为标准 ID 在 32 位过滤器寄存器中位于高位字的特定位置HAL 库封装后这么写是常见用法。如果你不想深究寄存器位段建议直接用 CubeMX 的 CAN Filter 图形页面生成或者继续用全通过配置确认业务确实需要过滤再有针对性地写。3.4 FIFO0和FIFO1怎么分配过滤组可以指定匹配帧进 FIFO0 还是 FIFO1。接收中断里FIFO0 对应 HAL_CAN_RxFifo0MsgPendingCallbackFIFO1 对应 HAL_CAN_RxFifo1MsgPendingCallback。简单应用全部映射到 FIFO0 就够了如果协议里同时存在实时控制帧和周期状态帧可以把控制帧安排进 FIFO0状态帧进 FIFO1利用不同中断优先级做分流。FIFO0 的优先级默认比 FIFO1 高这个特性在满载测试时很好用。4. 收发代码组织让发送不占CPU、接收不丢帧4.1 发送Mailbox的用法bxCAN 有 3 个发送邮箱调用 HAL_CAN_AddTxMessage 会按优先级分配一个邮箱。发送前最好看一眼邮箱空闲数邮箱全满时再塞帧会直接失败。我封装的一个发送函数长这样uint8_t can_send_std(uint16_t id, uint8_t *data, uint8_t len) { CAN_TxHeaderTypeDef tx; uint32_t mailbox; if (len 8) len 8; tx.StdId id; tx.ExtId 0; tx.IDE CAN_ID_STD; tx.RTR CAN_RTR_DATA; tx.DLC len; if (HAL_CAN_GetTxMailboxesFreeLevel(hcan) 0) return 1; if (HAL_CAN_AddTxMessage(hcan, tx, data, mailbox) ! HAL_OK) return 2; return 0; }这个函数里没有等数据发送完成的死循环因为 CAN 发送是否成功还取决于总线上有没有其他节点、有没有 ACK 应答。如果对方不在线或者波特率不对死等发送完成会把整个主循环卡住。调用端可以根据返回值做超时重处理而不是盲目重试。4.2 接收走中断回调接收有两种方式主循环轮询和中断回调。我的工程选中断 环形缓冲区初始化为HAL_CAN_Start(hcan); HAL_CAN_ActivateNotification(hcan, HAL_CAN_RX_FIFO0_MSG_PENDING | HAL_CAN_RX_FIFO1_MSG_PENDING);然后在回调里尽快取走数据不要做复杂处理void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rx; uint8_t d[8]; if (hcan-Instance CAN1) { HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rx, d); /* 把 rx.StdId、d 拷贝到应用层环形缓冲区 */ } }回调里不要放 printf、不要放 HAL_Delay这些操作耗时太长CAN 波特率一高就容易溢出 FIFO。中断回调只负责把数据搬进应用层缓冲区解析、处理、分类全部放主循环。4.3 错误中断与总线恢复CAN 的错误管理不只是“发不出来就报错”还涉及主动错误、被动错误、Bus Off 几个状态。初始化时我通常把 AutoBusOff 设为 DISABLE把 AutoRetransmission 设为 DISABLE然后注册错误回调在 HALE_ErrorCallback 里读取 hcan-ErrorCode 和错误计数器方便定位问题。如果总线故障严重到进入 Bus Off外设会停止参与总线通信。恢复方式可以是等总线安静后重新调用 HAL_CAN_Start也可以先 HAL_CAN_Stop 再 Start。自动重发这个功能看起来方便但总线故障时它会让发送邮箱一直占着不放反而掩盖了真实问题。我建议应用层自己控制超时重传保留对发送失败场景的完整判断。5. 调通过程中踩过的坑按排查顺序5.1 引脚重映射与AFIO时钟这次一开始就栽在引脚映射上。板子上的 CAN 收发器接的是 PB8/PB9我却在 CubeMX 里默认选了 PA11/PA12。现象特别迷惑LoopBack 模式正常切到 Normal 模式后外接设备完全收不到。查了两天才意识到板子根本没把 PA11/PA12 引到收发器。改成 PB8/PB9 并打开 AFIO 时钟后通信立刻恢复。以后拿到板子第一件事就是看原理图确认 CAN 收发器的 TX/RX 接到了哪个引脚。5.2 波特率没对上所有发送都“假成功”对方节点用的是 250kbps我这边配置成 500kbps现象是 HAL_CAN_AddTxMessage 返回成功但总线分析仪上全是错误帧我的发送邮箱也经常满。后来用示波器看 CAN_H/CAN_L 的位宽度才发现500kbps 的单 bit 理论宽度是 2us250kbps 是 4us一量就知道谁对谁错。调 CAN 时手头没有分析仪至少要有示波器否则这种问题全靠猜。5.3 忘记激活中断通知HAL_CAN_Start 只是启动外设并不会自动开启报文接收中断。你在 CubeMX 里配好了 NVIC代码里没有调用 HAL_CAN_ActivateNotificationFIFO 里照样有数据但回调就是进不去。很多人第一次用 HAL 库都会漏这一步。启动外设、开启通知、实现回调、注册中断向量函数四步缺一不可。中断向量函数也要检查一下CAN1_RX0_IRQHandler 里必须调用 HAL_CAN_IRQHandler(hcan)。5.4 在回调里用了延时导致丢帧刚开始我在接收回调里加了一个 HAL_Delay想等数据稳定再处理结果高波特率下帧率一高FIFO 溢出丢帧。后来把回调里的所有耗时操作清空只做数据搬移问题消失。接收回调的黄金法则是越快返回越好。示波器观察到的数据错乱很多时候不是协议问题是回调里代码写得太重。5.5 终端电阻CAN 总线两端必须各接一个 120 欧终端电阻。很多开发板默认没有接需要外接两个电阻。缺少终端电阻时短距离调试可能勉强能通但波形会有振铃距离稍微拉长就丢帧或完全不通。我自己踩过这个坑后已经把终端电阻做进了自制的 CAN 测试小板上反而比每次都翻箱倒柜找电阻省事。6. 调通后的验证波形与长期稳定性如何测6.1 示波器看CAN波形可靠的 CAN 差分波形应该满足显性时 CAN_H 约 3.5V、CAN_L 约 1.5V差分约 2V隐性时两条线都约 2.5V差分接近 0V。如果差分电压明显偏低多半是收发器供电不足或者终端电阻接错。波形上升沿和下降沿有明显锯齿则要考虑线缆过长、终端电阻缺失、总线分支过多这几个因素。通过位宽度可以快速验证波特率500kbps 的位宽是 2us250kbps 是 4us。量三次取平均基本能判断两端配置是否一致。采样点设置是否合理在长时间跑数据时会有更明显的体现采样点太靠后总线稍微有点干扰就出错误帧。6.2 双节点跑压力测试我只信“连续跑一晚上不丢帧”这种结论。测试方法是两个节点各发一个 ID主节点 100Hz 发送心跳帧给从节点从节点收到后回发一帧主节点统计回帧数量。通过之后把频率加到 500Hz、1000Hz跑 12 小时记录丢帧数和 CAN 错误计数器变化。人为拔掉一根 CAN 线再插回去观察程序能否自动恢复这个测试很能反映工程代码健壮性。只有这些测试都过了我才会把工程标记为“已调通”。6.3 给后续复用的小建议我把 can_init、can_send_frame、can_get_received_frame、can_error_handler 这些功能封装到了独立的 can_app.c 里主循环只丢数据进来、取数据出去。后面如果换到 F407 或者其他 F103 型号改一下时钟配置和引脚映射就能快速复用。另一个值得做的事在调试阶段加一个诊断模式上位机通过指定 ID 的报文去修改波特率或过滤器参数这样不用反复烧 Flash 就能测试不同场景上线后排查问题也方便不少。如果按“引脚 → 波特率 → 模式 → 过滤器 → 中断”这个顺序去排查绝大多数 CAN 通信问题都能在半小时内定位。我个人在这块踩得最多的还是“波特率明明算对了但实际不对”根源就是对 APB1 时钟树结构不够重视。现在每到一个新板子第一件事就是确认 RCC 时钟树和 CubeMX 里看到的时钟一致。希望这篇记录能帮你少走这段弯路。本文还有配套的精品资源点击获取
返回列表