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 定时器被你重新加载了
LOAD和VAL。 -
它也被重新配置成 不产生中断,只轮询标志位。
-
所以
SysTick_Handler在delay_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 内部周期计数器实现微秒延时,保证系统时基的稳定性和功能的可靠性。
680

被折叠的 条评论
为什么被折叠?



