在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定时器资源是有限的,那么如何用一个定时器可以实现多个延时函数并行执行呢?我将会在下一篇文章进行叙述。