STM32系列(HAL库)—Delay函数重写

在MCU开发过程中,最常用的函数之一就是Delay函数(即延时函数)。在Firmware执行过程中,需要等待一定时间,确保执行动作顺利完成,如继电器控制,通常需要延时10~20ms。STM32系列的HAL库提供了默认的Delay函数,函数精度为ms级别。

HAL库中的Delay函数的思路是,ARM内核会提供一个32位的滴答定时器(即Systick),利用这个定时器设置成1ms产生一次中断,然后Firmware里面有一个32位的变量,通过查询该变量的值,确定时间到底过了多久。因为该函数的核心思想是中断,ARM的中断是支持中断优先级以及中断嵌套的,因此,该函数的中断抢占优先级和响应优先级都要设置为最高,这样,其他函数或者中断才有机会调用该函数。

不过,在我使用过程中,我感觉这个函数并不好用,首先,你需要特别关注在中断处理函数调用的情况,中断优先级是否设置正确?然后,就是不管我有没有调用HAL_Delay函数,实际这个中断每1ms都会产生一个中断,这是一个极大的资源浪费。

因此,我对HAL_Delay函数重写,并且增加了HAL_Delayus函数,这样ms级别和us级别的延时延时都有了。

首先,我的思路维持不变,还是用滴答定时器(Systick)。Systick的寄存器可以在ARM的官网下载Cortex-M3 Devices Generic User Guide,因为这个定时器是内核自带的。


HAL_Delay(ms级别的延时函数)思路比较简单,每次调用HAL_Delay函数时,启动Systick,将Systick->Ctrl最低位设置为1,即使能Systick,其他位默认,这样就不用启动中断。之后在Systick->LOAD设置为要计数的值,(Clock源为HCLK/8,我的MCU为72M主频,因此最大为9MHz),然后利用while或者for循环查询Systick->CTRL第16位即可。

HAL_DelayUs(us级别的延时函数)基本思路和上面一致,不过,1,2,3us的时候,由于数值比较小,因此,需要重新校准。

代码如下所示:

//延时函数,Delay为延时的ms数
void HAL_Delay(uint32_t Delay)
{
	uint32_t temp,ctrl_flag;
	for(temp=0;temp<((Delay>>9)+1);temp++)
	{
    //因此Systick定时器时钟为9M,因此计数器的值不能太大,需要特殊处理一下,以512ms为一个周期。如果多于512ms,那么先计数512ms,之后计数值减512ms
		if(temp ==( Delay>>9))
		{
			SysTick->LOAD = 9000*(Delay&(0x1FF))-20; //减去的20,为校准值,可以根据自己的不同主频,进行调整,我这边72M主频
		}
		else
		{
			SysTick->LOAD = 9000*512; //512ms,9MHz时钟
		}
		SysTick->VAL = 0x00;//清除计数器
		SysTick->CTRL = 0x01;//开始计数
    //查询是否计数结束
		do
		{
			ctrl_flag = SysTick->CTRL;
		}while((ctrl_flag&0x10000)!=0x10000);
		SysTick->CTRL = 0x00;//关闭滴答定时器
	}
}

//延时函数,DelayUs为延时的us数,小于512ms,如果大于512ms,请使用HAL_Delay函数
void HAL_DelayUs(uint32_t DelayUs)
{
	uint32_t temp;
	switch(DelayUs)
	{
	case 1:
      //1us,只需要几个NOP调整时钟即可,主频不同,这个需要调整
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		__NOP();
		break;
	case 2:
		SysTick->LOAD =0x04;//2us,计数值需要进行微调
		SysTick->VAL = 0x00;
		SysTick->CTRL=0x01;
		do
		{
			temp = SysTick->CTRL;
		}while(((temp&(0x10000))!=0x10000));
		SysTick->CTRL = 0x00;
		break;
	case 3:
		SysTick->VAL = 0x00;
		SysTick->LOAD =0x0D;//3us,计数值需要微调
		SysTick->CTRL=0x01;
		do
		{
			temp = SysTick->CTRL;
		}while(((temp&(0x10000))!=0x10000));
		SysTick->CTRL = 0x00;
		break;
	default:
		SysTick->LOAD = ((DelayUs-1)<<3)+DelayUs-6; //大于3us之后的值,使用通用调整,
		SysTick->VAL = 0x00;
		SysTick->CTRL=0x01;
		do
		{
			temp = SysTick->CTRL;
		}while(((temp&(0x10000))!=0x10000));
		SysTick->CTRL = 0x00;
		break;
	}
}

通过上述函数即可实现延时函数,另外提醒一点,HAL库中的HAL_Delay函数定义前面有“_weak”关键字,因此,只需要在main.c文件中,直接定义HAL_Delay即可,HAL库中用到的HAL_Delay函数都将调用我们自己的函数。

另外需要说明一点儿,就是STM32CubeIDE自动使能了Systick,需要在Main.c函数中将其Disable。如下代码在HAL_Init()之后调用该函数即可

void Systick_DeInit()
{
	SysTick->CTRL = 0x00;
}

当然,不管是HAL库自带的延时函数,还是我们这篇文章重写的延时函数,都是串行的方式,就是说,调用延时函数时,MCU是不执行其他指令,只在while循环或者for循环中一次又一次的查询。

那么,如果并行的执行呢,就是说当调用延时函数时,MCU执行其他任务呢?当然可以用定时器来做,不过MCU定时器资源是有限的,那么如何用一个定时器可以实现多个延时函数并行执行呢?我将会在下一篇文章进行叙述。

原文链接:,转发请注明来源!