# 前言
------------
我们使用STM32CUBEMX软件生成的工程里,有一个官方提供的延时函数HAL_Delay(uint32_t Delay),这个函数提供一个1ms的延时,并且不占用系统,因为这是一个利用滴答时钟实现的。但是在我们实际使用中,1ms的延时真是太久了,有些外设还真的是需要使用到us级别的延时,那怎么办!官方早想到了,提供了让你重写这个函数的办法。但是,我说但是,小白刚学啥都不懂怎么办?重写完一堆error会不会奔溃?
这里笔者提供了一个思路和办法,用最简单的办法实现。
## 首先
__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)
{
}
}
这个函数的作用是提供了1ms的延迟,以SysTick timer作为time base时间源。其中uwTickFreq为设置的频率,频率封装在结构体中,分为10HZ\100HZ以及1KHZ,系统默认是1KHZ。
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT; /* 1KHz */
typedef enum
{
HAL_TICK_FREQ_10HZ = 100U,
HAL_TICK_FREQ_100HZ = 10U,
HAL_TICK_FREQ_1KHZ = 1U,
HAL_TICK_FREQ_DEFAULT = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;
我们通过查手册,可以知道,外部晶振为8MHZ,软件设置了72MHZ主时钟的条件下,滴答时钟SysTick只要计数9次就是1us,那么可以得到计数1次就是1/9us,理论上是可以实现伪纳秒级别的延时。现在系统默认设置了1KHZ的时钟频率,那我们怎么才能修改呢?
下面我们找到滴答时钟的初始化函数,用于初始化time base。
__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_Config(SystemCoreClock / (1000U / uwTickFreq)函数的作用是初始化滴答时钟和中断。
函数内的参数用到了主时钟和频率做运算,按照系统默认的设置,参数最后的值应为72000
uint32_t SystemCoreClock = 72000000U; /*!< System Clock Frequency (Core Clock) */
接下来我们再来看看配置函数拿这个72000做了什么
__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); /* set reload register */
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 */
}
从这里可以知道,滴答时钟的重载值和72000直接相关,作为两次相邻中断间隔中滴答时钟的滴答次数。
事情看到这里是不是已经很明朗了,主时钟为72MHZ,滴答时钟的重载值为72000(赋值为72000-1),得到1KHZ,那么两次滴答时钟的中断间隔为1ms了,这样就能得到1ms的延时。现在我们只需要增大ticks这个参数的值就行了,因为这个参数是由
SystemCoreClock / (1000U / uwTickFreq)
计算得到的,那么我们修改为
SystemCoreClock / (10000U / uwTickFreq)
就能暗改1KHZ为10KHZ了。
下载验证
验证一
HAL_Delay(1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
HAL_Delay(1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
示波器波形,这里因为是用了hal库,本来延时1ms的波,理论上一个周期应该为2ms,但是实际上一个周期为4ms,但是这个不打紧,精确延时是需要自己拿着示波器一点一点的调出来的,我们粗略延时用hal库就行了
然后我们暗改了公式后的示波器波形,很明显的,实际一个周期为400us了
试验成功,说明这个办法是可行的,但是对于一些外设来说,1ms和几百us的延时不够用啊,那么我们下面来试一下,能不能做到10us级别延时,修改公式SystemCoreClock / (100000U / uwTickFreq),得到下图,试验成功。
那么我们能通过修改hal库得到的最小延时是多少呢?这就要知道重载值最小可以是多少而系统不报错。
根据重载寄存器的赋值可知,理论上参数ticks最小值为1(赋值为0),此时公式为SystemCoreClock / (72000000U / uwTickFreq)
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
但是在赋值语句的上面有一个if判断,如果参数ticks大于了某个值就会系统出错
(ticks - 1UL) > SysTick_LOAD_RELOAD_Msk
其中,SysTick_LOAD_RELOAD_Msk的最大值为0XFFFFFF,在手册中,重载寄存器为一个24位的寄存器,等于16777215远远小于72MHZ,所以这里我就将公式改为得到的值接近0xffffff并且比它小
SystemCoreClock / (16777215U / uwTickFreq)
结果呢,没有波形,看来这个已经超过极限
接下来经过我的多次反复的测试,得到的最小的延时公式为SystemCoreClock / (167000U / uwTickFreq),约为41.6KHZ左右,经过实测,170000U延时反而增加了,具体什么原因,本人暂时不知道,在这里也就不解答这个问题了。
## 最后一句
------------
13us不到的延时相对于大部分情况来说已经够用了,本人也不建议继续探究极限值在哪里,浪费时间。另外需要提醒的地方是,因为我们是用STM32CUBEMX生成的工程,并且我们的修改不在软件的保护代码范围区,所以下次修改了STM32CUBEMX的内容并且重新生成了工程,还需要手动去修改一下那个公式