LV105-自定义延时函数
在20-基本外设篇/15-定时器/LV105-自定义延时函数.md中我们分析了HAL库中的HAL_Delay()函数,它是借助SysTick定时器的中断来实现的,就意味着不能在其他中断中使用这个延时函数,那么有没有其他办法实现延时?
一、时钟摘取法
我们来看一下正点原子SYSTEM目录下的delay.c中的delay_us函数。
1. delay_init()
在main函数中是这样调用的:
c
delay_init(72);这个函数的定义(删除了OS相关的内容)如下:
c
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck), 72MHz
* @retval 无
*/
void delay_init(uint16_t sysclk)
{
g_fac_us = sysclk;
}g_fac_us是一个全局变量:
c
static uint32_t g_fac_us = 0;所以这里初始化之后g_fac_us值为72。
2. delay_us()
delay_us函数定义如下:
c
/**
* @brief 延时nus
* @note 无论是否使用OS, 都是用时钟摘取法来做us延时
* @param nus: 要延时的us数
* @note nus取值范围: 0 ~ (2^32 / fac_us) (fac_us一般等于系统主频, 自行套入计算)
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = SysTick->LOAD; /* LOAD的值 */
ticks = nus * g_fac_us; /* 需要的节拍数 */
told = SysTick->VAL; /* 刚进入时的计数器值 */
while (1)
{
tnow = SysTick->VAL;
if (tnow != told)
{
if (tnow < told)
{
tcnt += told - tnow; /* 这里注意一下SYSTICK是一个递减的计数器就可以了 */
}
else
{
tcnt += reload - tnow + told;
}
told = tnow;
if (tcnt >= ticks)
{
break; /* 时间超过/等于要延迟的时间,则退出 */
}
}
}
}我们不需要os系统,先把os系统相关的删掉,前面我们分析过,SysTick时钟是AHB,未经分频,所以这里SysTick->LOAD 的值就是 72000000 / 1000 - 1,最终就是71999。从0~71999一共是72000个数,SysTick->VAL是当前值寄存器的值,这个值从71999递减到0。
我们再来看一下while循环中的逻辑:
- 读取当前计数器SysTick->VAL值并赋值给
tnow。 - 如果
tnow与told不同,说明计数器发生了变化,计算经过的节拍数:- 如果
tnow < told:计数器递减但没有重载,经过的节拍数为told - tnow。 - 如果
tnow > told:计数器已重载(从told递减到 0 后重载到reload,再递减到tnow),经过的节拍数为reload - tnow + told。
- 如果
- 更新
told为当前值tnow。 - 累计节拍数
tcnt,如果tcnt >= ticks,则 break 退出循环。
ticks计算方式是要延时的us数乘以g_fac_us,前面我们知道系统时钟频率为 72MHz(72,000,000 Hz),则每微秒有 72 个时钟周期(72,000,000 / 1,000,000 = 72)。因此,g_fac_us 应定义为 72。
这样只是读取SysTick的VAL寄存器的值,并不涉及中断,这样即便是在其他中断中作短暂的延时也是没有问题的。
3. delay_ms()
c
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= (2^32 / fac_us / 1000))(fac_us一般等于系统主频, 自行套入计算)
* @retval 无
*/
void delay_ms(uint16_t nms)
{
delay_us((uint32_t)(nms * 1000)); /* 普通方式延时 */
}nms: 要延时的ms数 (0< nms <= (2^32 / fac_us / 1000))(fac_us一般等于系统主频, 可以自行套入计算)
二、HAL_Delay()
HAL_Delay()这个函数在库中带有__weak符号,意味着我们可以重新定义,所以这里我们重新定义它:
c
/**
* @brief HAL库内部函数用到的延时
* @note HAL库的延时默认用Systick,如果我们没有开Systick的中断会导致调用这个延时后无法退出
* @param Delay : 要延时的毫秒数
* @retval None
*/
void HAL_Delay(uint32_t Delay)
{
delay_ms(Delay);
}