Skip to content

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
  • 如果 tnowtold 不同,说明计数器发生了变化,计算经过的节拍数:
    • 如果 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);
}