systick HAL_Delay实现原理 微秒级延时(非中断)以及一些重写延时的小坑 关于HAL_Delay的使用问题
HAL_Delay实现原理
HAL_Delay 底层是配置systick进行1ms进行一次中断,每进入一次,全局变量 uwTick 就加1
上面这个函数就是systick的中断差处理函数
下面就是 计数的函数
void SysTick_Handler(void)
{
HAL_IncTick();
}
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;
}
上面的函数返回当前计数值
下面的函数获取初始计数值,并在之后不断的获取计数值与初始计数值的差值来与需要延时时间比较。
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
__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)
{
}
}
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
HAL库下的systick 底层配置
HAL_InitTick(uint32_t TickPriority)
这个函数在在下面两个函数中都有调用。
由HAL_Init()重置,
也可以HAL_RCC_ClockConfig()重新配置时钟,可以随时重置。
因为我没有修改systick的优先级,所以两次的优先级配置都是一样的,为第4组,最高优先级 0。(不懂得可以去了解一下stm32的中断机制)
HAL_Init();
SystemClock_Config();
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
return HAL_OK;
}
其中
HAL_SYSTICK_Config();就是配置定时器计数值
可以通过不断的查找定义发现
最终是配置SysTick->LOAD 也就是最底层的 systick的计数器的重载值 的寄存器
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL);
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
实现微秒级的延时
方法一:软件延时,while循环递减。个人不推荐,不够准确
方法二:在上面HAL_SYSTICK_Config()这个函数中修改,将1ms中断一次改成 1us计数一次。缺点就是1us进入一次中断,浪费单片机资源
方法三:通过查询寄存器的值,从而实现不通过中断的方法计数。(在一般情况下,这是最好用的方法)
以下是介绍
从寄存器入手,通过上面了解到
SysTick->LOAD 就是systick计数器的重载值,
再介绍两个寄存器
SysTick->VAL 当前的计数值
SysTick->CTRL=1 使能,减到零是无动作,采用外部时钟源
void Delay_us( uint32_t cnt)
{
uint32_t temp;
SysTick->LOAD = 9* cnt;
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void Delay_ms( uint32_t cnt)
{ uint32_t i=0;
for(i=0;i<cnt;i++)
Delay_us(1000);
}
重写延时的小坑
重写完基于systick的延时之后,hal库自带的HAL_Delay就没用了,如果有程序中还有HAL_Delay()函数,程序不会报错,但在单片机运行的时候会卡在这里。
请一定记住不要同时使用两者
关于HAL_Delay的使用问题
如果使用STM32cubemx默认的配置
所有的中断优先级都为第四组 ,抢占优先级0,即都为最高。
如果在其他中断中使用HAL_Delay()函数根据中断优先级机制,并不会执行,反而会卡在这一步。
要么提高systick的优先级(此时应为降低其他中断优先级),要么使用非中断方法(即上面的方法三或一(不推荐))。
码字不易,点赞,收藏支持一下吧。
哭泣。
2021.4.8