HAL库大雷预警!STM32 HAL库CAN启动超时解决办法

发布时间:2026/6/30 13:41:20

HAL库大雷预警!STM32 HAL库CAN启动超时解决办法 引言最近在一个AGV控制模块的驱动开发场景需要测试封装的协议帧是否符合CANopen协议格式。主控平台使用STM32F103ZET6驱动对象为同毅的IXL-II系列舵轮驱动器使用ST官方提供的HAL库以及bxCAN控制器作为底层抽象基础进行CAN报文发送测试。整个测试前置过程严格按照标准流程进行配置引脚 - 配置bxCAN及过滤器初始化参数 - 执行Init初始化 - 执行Start启动bxCAN然而同样的初始化流程这次却卡在了HAL_CAN_Start()里返回HAL_TIMEOUT。通过反复检查所有相关配置完全正确无误。如果您也有如下问题HAL_CAN_Init()初始化成功但是后续启动失败。经检查所有相关配置参数均无误。使用HAL库作为底层抽象。那么这篇文章很大概率能够帮助到你。现象复现先声明一下开发环境 STM32F103ZET6. 外部8MHz晶振走PLL倍频至72MHz主频. 使用CAN1实例.以上是相关GPIO引脚配置。bxCAN参数配置由于篇幅所限此处仅给出配置说明波特率500 kbpsCAN_BAUDRATE_500K模式环回模式MODE_LOOPBACKSJW1 TqCus_CAN_SJW_1Tq自动重发关闭is_AutoRestransmission false自动唤醒关闭is_AutoWakeUP false过滤器全通模式Cus_CAN_FILTERMODE_IDMASK掩码/ID 均设为 0x00通过调试可以看到HAL_CAN_Init()返回正确状态HAL_OK。此时寄存器: MCR 0x00010011. MSR 0x00000409. 记住这两个寄存器值后续将会作为我们排故的最佳凭证。流程来到HAL_CAN_Start()。执行CLEAR_BIT软件清除MCR寄存器的INRQ位使bxCAN设备进入正常工作模式。流程继续往下while ((hcan-Instance-MSR CAN_MSR_INAK) ! 0U)检测超时置TIMEOUT状态返回HAL_ERROR! bxCAN控制器启动失败。以上便是整个错误流程的复现。有趣的是该问题可稳定复现且在该芯片之前跑的CAN例程中从未出现过该情况。值得注意整个测试过程中 CAN 总线接口PA11/PA12处于完全悬空状态未连接任何收发器或终端电阻引脚仅靠GPIO内部上拉。这意味着所有观测到的现象完全来自芯片内部行为排除了外部总线电平干扰的可能性。为了排除是因为工程原因导致的潜在问题我使用 STM32CubeMX 重新生成了一个最小测试工程。这个工程仅包含 CAN 外设初始化和发送测试完全不使用任何自定义封装库全部代码由 CubeMX 生成。结果现象依然同上初始化超时。如下图所示HAL_CAN_Start()返回HAL_ERROR进入while(1)死循环。哪怕是 ST 官方 CubeMX 生成的标准工程HAL_CAN_Start()依然无法成功启动。问题剖析及解决方案回顾我们之前成功Init之后记录的MSR,MCR寄存器的值:MCR 0x00010011. MSR 0x00000409.结合参考手册的寄存器映射图根据参考手册对照成功Init之后的MCR/MSR寄存器的值可以看到上图中4个关键位均被置起。当流程来到HAL_CAN_Start()时该API内部通过CLEAR_BIT将INRQ位置0使CAN控制器从初始化模式进入正常模式当成功进入正常收发工作模式后硬件会自动清除MSR中的INAK位。HAL_CAN_Start()内部通过在规定的CAN_TIMEOUT_VALUE时间窗口内检测该位是否被硬件清0来判断CAN设备是否启动成功。我们之所以会启动失败是因为INAK位并没有被硬件清0!回顾硬件清空INAK的规则当CAN退出初始化模式时硬件对该位清’0’ (需要跟CAN总线同步)。这里跟CAN总线同步是指 硬件需要在CAN的RX引脚上检测到连续的11位隐性位。在CAN总线中当RX引脚上连续接收到11个高电平(隐性电平时)即可视为总线空闲状态。同时先前我们已经提到我们的环境并没有接入CAN总线而是使用回环模式并且PA11/PA12悬空通过内部上拉。也就是说我们的配置理应满足条件根据参考手册SAMP位Bit 10记录的是最近一次 RX 引脚的实际采样电平。当前该位为 1说明总线实际处于高电平隐性状态。但为何硬件依然拒绝清零INAKCAN_TIMEOUT_VALUE 超时过短这是一个可能的方向当超时窗口过短时INAK可能来不及改变HAL内部就已经超时了。HAL库中该宏被定义为 10(ms)我将其修改为了50ms的时间窗口但是依旧超时因此排除该情况。引脚电平不稳我将PA11(CAN_RX)接入示波器观察到PA11电平被稳定上拉至3.3v附近因此也排除该情况。至此常规排查手段全部失效在后续的深入排查中发现每次复位后、进入 main 函数时MSR 的值均为 0x0000 0C0A。但查阅手册可以看到 MSR 寄存器的复位值为 0x0000 0C02两者差异正好是 WKUI 位Bit 3也就是说WKUI在芯片复位后的某一时刻被硬件置位了而 HAL 库的HAL_CAN_Start()并未对该标志做任何处理该标志一旦置位硬件会认为当前总线正处于“唤醒处理”或“活动状态”从而跳过或忽略对 11 个连续隐性位的空闲检测流程导致INAK无法清零最终导致HAL的状态机超时。至于WKUI为何会在复位后被置位目前原因暂不明。我自己的推测是芯片复位后PA11在时钟使能瞬间产生了一次电平跳变如浮空状态下的上电抖动被处于睡眠模式的 bxCAN 误判为帧起始SOF从而锁存了WKUI。但该推测无法解释“为何每次复位都能稳定复现”参考手册中并未描述此类行为网络上也很难找到相关的文献记载。目前可以确定的事实是MSR复位值异常WKUI被硬件稳定置起。HAL_CAN_Start()因为INAK无法被清除而超时。至于WKUI被置位的深层原因本文不作定论。但问题的现象、定位过程和解决方案均已明确以下给出可直接应用的补丁代码。解决方案解决方法很简单绕过HAL的状态机自己实现一个HAL_CAN_Start()方法。voidCAN_forceStart(void){// 先清除 WKUIif(CAN1-MSRCAN_MSR_WKUI){CAN1-MSRCAN_MSR_WKUI;}// 确保不在睡眠模式CAN1-MCR~CAN_MCR_SLEEP;// 请求退出初始化清除 INRQCAN1-MCR~CAN_MCR_INRQ;// 等待 INAK 清零但给一个短超时5000 次循环uint32_ttimeout5000;while((CAN1-MSRCAN_MSR_INAK)timeout){timeout--;}// 如果超时了INAK 仍为 1发送一帧空数据来强制唤醒if(CAN1-MSRCAN_MSR_INAK){// 等待邮箱空闲while((CAN1-TSRCAN_TSR_TME0)0);// 发送一个“虚拟帧”DLC0ID0x7不影响总线CAN1-sTxMailBox[0].TIR(0x721)|CAN_TI0R_TXRQ;CAN1-sTxMailBox[0].TDTR0;// DLC0无数据CAN1-sTxMailBox[0].TIR|CAN_TI0R_TXRQ;// 再次等待 INAK 清零这次应该很快timeout1000;while((CAN1-MSRCAN_MSR_INAK)timeout){timeout--;}}}该补丁应在HAL_CAN_Init()成功初始化并返回之后在HAL_CAN_Start()之前被调用。补丁内部原理与HAL的实现是类似的但是HAL库在启动时是假设硬件是完全干净的没有考虑WKUI位被异常置起的情况。我们先手动清除WKUI位再将INRQ位清0请求进入正常收发模式。但是在实际测试中就算手动将WKUI位清0硬件依然不会清除MSR中的INAK位如下图所示可以看到短超时过后MSR最低位的INAK依然为置起状态。对于这种情况我们需要发送一帧dummy报文来强行启动设备。如下图所示提交报文请求后可以看到INAK被清0MSR寄存器也进入了正常工作状态。发一帧测试报文MSR 归零——本质是强制外设完成一次完整的收发状态机循环把残留的异常状态冲刷掉。这实际上利用了bxCAN 的一个特性发送请求TXRQ会强制控制器完成状态转移。当硬件处于初始化模式INAK1时写入TXRQ会迫使它立即退出初始化、进入发送流程同时自动将INAK清零。这也是为什么补丁能用一帧 dummy 报文把外设从卡死状态里拉出来。这里需要提到的是尽管补丁过后外设已经能够正常收发工作但是后续的HAL_CAN_Start()调用是必须的因为补丁只管把外设从卡死状态拉出来后续HAL抽象层的状态机同步仍然需要HAL的专用API驱动。打上补丁后逻辑分析仪成功捕获到了两帧报文强制启动时用于冲刷异常状态的dummy报文以及NMT命令测试报文。至此该问题算是初步得到解决。希望这篇文章能帮你省下一些排查时间。如果对内容有任何疑问或建议欢迎留言交流。

相关新闻