常规阻塞式延时:
void delay_1ms(uint32_t count)
{
delay = count;
delay--;
while(0U != delay){
}
}
非常规阻塞式延时(可用于中断):
//处理延时在中断中卡死的情况
static u8 delaying_times = 0;//叠加执行延时的次数
static u16 delaying_finish = 0;//记录最多16个的递归溢出事件中,每一个是否都已经记数溢出
void delay_ms(u16 nms)
{
u32 last_systick_val;
if(delaying_times != 0)//如果主程序在跑delay函数的过程中,发生中断并在中断中又进入了delay函数
{
last_systick_val = SysTick->VAL;//将上次的计数器的值保存下来以便退出中断后回去时可以从该值继续递减
//如果上次记数已经溢出,代表着上次的delay已经记数完成,将该次溢出事件记录下来,以便出了中断回到原delay函数时,可以直接跳出while
//delaying_finish是16位的,最多可以记录16次溢出事件,即16层的递归
if(SysTick->CTRL & (1 << 16))delaying_finish |= (1 << (delaying_times - 1));
}
delaying_times ++;
SysTick->LOAD = (u32)fac_ms * nms;//自动重装载值
SysTick->VAL = 0x00;//清除计时器的值
SysTick->CTRL |= (1 << 0);//SysTick使能,使能后定时器开始倒数
while(!(SysTick->CTRL & (1 << 16)))//判断是否减到0,减到0时CTRL的第16位会置1,读取后会自动置0
{
//如果在中断中计数器已经溢出,就退出while,并且对应中断位清零
if(delaying_finish & (1 << (delaying_times- 1)))
{
delaying_finish &= ~(1 << (delaying_times- 1));
break;
}
}
delaying_times --;
if(delaying_times == 0)
{
SysTick->CTRL &= ~(1 << 0);//关闭SysTick,关闭后记数器将不再倒数
SysTick->VAL = 0x00;//清除计时器的值(执行关闭SysTick程序时,记数器又开始了新一轮的倒数,所以关闭后记数器的值不为0)
}
else
{
/* 读取CTRL寄存器的同时,CTRL的第16位会变为0,关闭SysTick后给VAL寄存器赋值再使能的原因
* 1.若未关闭SysTick,且先将CTRL的第16位清零后再给VAL寄存器赋值,则在赋值的过程中计数器可能会记数到0,从而导致CTRL的第16位又被置1
* 2.若未关闭SysTick,且先给VAL寄存器赋值后再将CTRL的第16位清零,则在清零的过程中计数器会继续递减并且可能在CTRL的第16位完成清零前就溢出
* 所以必须关闭SysTick,且赋值完需要再使能使得递归回原函数的while中计数器会继续递减
*/
SysTick->CTRL &= ~(1 << 0);//关闭SysTick,关闭后记数器将不再倒数
SysTick->LOAD = last_systick_val;
SysTick->VAL = 0x00;//清除计时器的值
SysTick->CTRL |= (1 << 0);//SysTick使能,使能后定时器开始倒数
}
}
为何用非阻塞式延时:
1.非阻塞式延时可以在等待的时间进行其他函数处理,可节省单片机效率
2.在中断中使用延时函数(如,按键的消抖),在中断中使用阻塞式延时,可能导致处理中断函数的时间大于中断处理时间,即要跳出中断时,还未处理完执行函数,会导致程序卡停在延时函数的计时处。故在中断函数中最好不要使用延时函数。
非阻塞式延时:
void SysTick_Handler(void) //滴答定时器中断
{
delay_decrement();
time_ms ++;
time_ms %=1000;
}
uint8_t Time_ms(uint8_t ms)
{
uint8_t turn = 0;
if(time_ms == ms)
{
time_ms = 0;
turn = ms;
}
return turn;
}
//调用
void time_ues()
{
if(Time_ms(1) == 1) //delay_ms(1);
{
;
}
}