stm32 HAL_Delay 卡死原因解析(非中断优先级问题)

该文章已生成可运行项目,

STM32 HAL库是STM32系列单片机开发中非常流行的硬件抽象层库,其中的延时函数 HAL_Delay 被广泛使用。然而,在实际开发中,我们会遇到 HAL_Delay 卡死的问题,尤其是在自定义微秒延时函数的情况下,本文将详细解析 HAL_Delay 的原理,介绍常见的微秒延时实现方法,并重点分析两者冲突的根本原因。当我们用keil进行debug的时候,会进入到这个界面:

debug 调试图

 一、HAL_Delay 的工作原理

HAL_Delay 函数是基于系统时基 uwTick 实现的阻塞延时函数。其核心代码大致如下:

__weak void HAL_Delay(uint32_t Delay)//hal_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)
  {
  }
}

__weak uint32_t HAL_GetTick(void)//返回uwtick值
{
  return uwTick;
}

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 */
}

__weak void HAL_IncTick(void)//uwtick自增函数
{
  uwTick += uwTickFreq;
}

1.1 系统时基 uwTick

  • uwTick 是 HAL 库中的全局变量,表示系统运行的“毫秒计数器”。

  • 这个计数器通过 SysTick 定时器的 1ms 中断周期自动递增。

  • SysTick_Handler 中断服务程序调用 HAL_IncTick() 实现 uwTick++

 

1.2 SysTick 定时器

  • SysTick 是 Cortex-M内核自带的一个24位递减计数器,专门用来产生系统节拍。

  • HAL库在初始化时(HAL_Init函数内部)配置 SysTick 产生1ms周期中断。

  • 这保证了 uwTick 可以稳定、自动递增。

  • HAL_StatusTypeDef HAL_Init(void)//部分代码
    {
    
      HAL_InitTick(TICK_INT_PRIORITY);
    }

     我们在使用cubemx的时候会自动进行hal库的初始化,就会将滴答定时器初始化为1ms中断一次进行uwtick的自增。

二、常见的微秒延时实现方式

STM32单片机HAL库不自带精度很高的微秒延时函数,通常我们会自行实现,常见方式有:

2.1 使用 SysTick 重载寄存器轮询(卡死原因)

void delay_us(uint16_t uS){ //uS微秒级延时程序(参考值即是延时数,72MHz时最大值233015)	
	
	SysTick->LOAD= AHB_INPUT *uS;      //重装计数初值(当主频是72MHz,72次为1微秒)
	SysTick->VAL=0x00;        //清空定时器的计数器
	SysTick->CTRL=0x00000005;//时钟源HCLK,打开定时器
	while(!(SysTick->CTRL&0x00010000)); //等待计数到0
	SysTick->CTRL=0x00000004;//关闭定时器
}

SysTick->CTRL = 0x5;
这个配置里:

  • Bit0:计数器使能(Enable)

  • Bit1:SysTick 中断使能(TickInt)

  • Bit2:时钟源(CLKSOURCE)

当我们调用delay_us的时候,会将systick的模式进行改变,并且关闭了中断,把 SysTick 改成了“单次倒计数,轮询等待,没有中断”模式。 

2.2 使用通用定时器(TIM)(示例没有测试)

  • 配置定时器为1MHz计数频率(1us计数)。

  • 通过读取计数器寄存器或中断实现微秒延时。

  • 优点:不会干扰 SysTick 计时。

void delay_us(uint16_t us)
{
    __HAL_TIM_SET_COUNTER(&htim2, 0);
    HAL_TIM_Base_Start(&htim2);
    while(__HAL_TIM_GET_COUNTER(&htim2) < us);
    HAL_TIM_Base_Stop(&htim2);
}

 

2.3 使用 Cortex-M 的 DWT 计数器(适用于 M3/M4/M7)

  • 利用 DWT 的周期计数器读取 CPU 时钟周期数。

  • 精度高,且不影响 SysTick。

三、产生卡死的原因

  • 3.1当执行 delay_us 时:

    • 原本 HAL 配置的 SysTick 定时器被你重新加载了 LOADVAL

    • 它也被重新配置成 不产生中断,只轮询标志位。

    • 所以 SysTick_Handlerdelay_us 期间根本不会触发。

  • 3.2当 delay_us 结束:

    • SysTick 被关闭(CTRL=0x4),也没恢复 HAL 的配置。

    • 所以系统 Tick 中断没了,uwTick 不再增加,HAL_Delay 就会卡死。陷入循环

while ((HAL_GetTick() - tickstart) < wait)
  {
  }

 

 四、解决办法

4.1 推荐做法

  • 不要使用 SysTick 做微秒延时,保留 SysTick 用于系统时基。

  • 使用定时器(TIM)或者 DWT 来实现微秒延时。

  • 保持 SysTick 中断和 uwTick 正常运行,HAL_Delay 正常工作。

4.2 如果必须使用 SysTick 做微秒延时

 

  • delay_us 使用完成后,必须重新配置 SysTick,恢复 1ms 周期中断。但这种做法复杂且易出错,不推荐。

void delay_us(uint16_t uS){ //uS微秒级延时程序(参考值即是延时数,72MHz时最大值233015)	
	
	SysTick->LOAD= AHB_INPUT *uS;      //重装计数初值(当主频是72MHz,72次为1微秒)
	SysTick->VAL=0x00;        //清空定时器的计数器
	SysTick->CTRL=0x00000005;//时钟源HCLK,打开定时器
	while(!(SysTick->CTRL&0x00010000)); //等待计数到0
	SysTick->CTRL=0x00000004;//关闭定时器
SysTick_Config(SystemCoreClock/1000);//加入时钟配置,将滴答定时器配置为1ms中断一次
}

 五、结论

STM32 HAL库中 HAL_Delay 的正确使用依赖于 SysTick 定时器的正常工作。自定义微秒延时时务必避免干扰 SysTick,否则极易导致程序卡死。推荐使用专用硬件定时器或 CPU 内部周期计数器实现微秒延时,保证系统时基的稳定性和功能的可靠性。 

 

 

本文章已经生成可运行项目
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芯世界电子坊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值