HAL_Delay函数死循环问题调试报告
这篇记的是 HAL_Delay 卡死在死循环里的排查过程。现象很典型:程序走到某处延时后就不再往下走,看门狗过一会儿就复位。一开始怀疑中断没开,查了一圈才发现根子在 Systick 定时器的配置——时钟没对上,延时自然走不完。下面是完整的定位过程和最终修法,记录于 2026-04-21。
1. 问题描述
现象:
- HAL_Delay函数进入死循环,无法正常退出
- 函数卡在
while((HAL_GetTick() - tickstart) < wait)循环中 - 系统可能因此无法正常运行
可能原因:
- Systick定时器配置错误
- 系统时钟频率与Systick配置不匹配
- 中断优先级问题
- 系统时钟配置后未重新初始化Systick
2. 问题分析
2.1 代码分析
HAL_Delay函数实现:
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while((HAL_GetTick() - tickstart) < wait)
{
}
}
HAL_IncTick函数实现:
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
SysTick_Handler函数实现:
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
系统初始化顺序:
HAL_Init()- 初始化Systick定时器(使用默认MSI时钟)SystemClock_Config()- 配置系统时钟为HSI 16MHz- 初始化其他外设
2.2 问题原因
主要原因:
- 系统时钟配置后未重新初始化Systick:
HAL_Init()在系统启动时使用默认的MSI时钟(约2MHz)初始化SystickSystemClock_Config()将系统时钟改为HSI 16MHz- 但Systick的配置未更新,仍然使用旧的时钟频率
- 导致Systick中断间隔不正确,
HAL_IncTick()调用频率错误 HALGetTick()返回值增长缓慢或停止,导致HALDelay()死循环
次要原因:
- 可能存在中断优先级问题
- 可能存在FLASH访问时序问题
3. 解决方案
3.1 重新初始化Systick定时器
修改前:
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
修改后:
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
// 重新初始化Systick定时器,确保使用正确的系统时钟频率
if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK)
{
Error_Handler();
}
说明:
- 在系统时钟配置完成后,重新调用
HAL_InitTick()函数 - 确保Systick定时器使用正确的系统时钟频率
- 保证
HALIncTick()的调用频率正确,HALGetTick()返回值正常增长
3.2 其他优化措施
- 确保FLASH_LATENCY设置正确:
- 系统时钟为16MHz时,FLASH_LATENCY应设置为1
- 避免因FLASH访问时序问题导致系统不稳定
- 检查中断优先级:
- 确保SysTick中断优先级高于其他可能阻塞它的中断
- 避免因中断优先级问题导致SysTick中断被阻塞
- 添加错误处理:
- 对
HAL_InitTick()的返回值进行检查 - 确保Systick初始化成功
4. 测试验证
4.1 测试步骤
- 硬件连接:
- 连接STM32L031开发板
- 连接调试器
- 软件设置:
- 编译并下载修改后的代码
- 打开调试器
- 测试执行:
- 运行程序
- 在
HAL_Delay()函数处设置断点 - 观察
HAL_GetTick()的返回值 - 验证
HAL_Delay()是否能正常退出
4.2 预期结果
| 测试项目 | 预期结果 |
|---|---|
| HAL_Delay(100) | 正常退出,耗时约100ms |
| HAL_GetTick() | 正常增长,每秒增加1000 |
| 系统运行 | 稳定,无死循环 |
5. 结论
通过分析和修改,解决了HAL_Delay函数进入死循环的问题。主要修改包括:
- 重新初始化Systick定时器:在系统时钟配置完成后,重新调用
HAL_InitTick()函数,确保Systick使用正确的系统时钟频率 - 确保FLASHLATENCY设置正确:系统时钟为16MHz时,FLASHLATENCY设置为1
- 添加错误处理:对
HAL_InitTick()的返回值进行检查
这些修改确保了Systick定时器的配置与系统时钟频率匹配,HALIncTick()能够按预期调用,HALGetTick()返回值正常增长,从而使HAL_Delay()函数能够正常退出。
到这里 HAL_Delay 死循环问题就算彻底解决了。这类问题往往不在延时本身,而在它背后的时钟和中断配置,排查时从"为什么走不动"反推,往往比追延时参数更快。
还想接着看
- HAL_Delay 死循环(中断优先级版)——同样的现象换个角度,值得对照着看。
- 蓝牙模块通讯故障调试——同批板子上的另一桩疑难杂症。
- PA15 拉低后无法恢复的排查——信号时序类的典型现场。