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,由于这个定时器是内核自带的。
Systick Ctrl寄存器
SysTick Reload寄存器
SysTick Cur寄存器
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,之后计数值减512msif(temp ==( Delay>>9)){SysTick->LOAD = 9000(Delay&(0x1FF))-20; //减去的20,为校准值,可以根据自己的不同主频,进行调度,我这边72M主频}else{SysTick->LOAD = 9000512; //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定时器资源是有限的,那么如何用一个定时器可以实现多个延时函数并行实行呢?我将会不才一篇文章进行阐述。