延时函数(阻塞式,非阻塞式)

南北湖创客5个月前代码基础11

常规阻塞式延时:

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);
    {
        ;
    }
}


////////////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////

///////////////////

1. 阻塞延时

阻塞延迟会影响其他任务的执行,导致它们暂时停止,等待延迟结束后才能继续执行。这种延迟方式会浪费CPU,因为在延迟期间,CPU无法执行其他任务。

#include "reg52.h"
sbit D1 = P2^0;
sbit D2 = P2^1;
//预想目的:让D1的闪烁时间为D2的2倍(D1的闪烁频率为D2的1/2)
void delay_ms(unsigned int xms)
{
    unsigned int i, j;
    for (i = xms; i > 0; i --)
    for (j = 110; j > 0; j --);
}

void main(void)
{
    while(1)
    {
        D1 = 0;
        delay_ms(400);
        D1 = 1;
        delay_ms(400);
        
        D2 = 0;
        delay_ms(200);
        D2 = 1;
        delay_ms(200);
    }
}


2. 非阻塞延时

非阻塞延时是指在延时的过程中CPU可以执行其他的任务


#include "reg52.h"
#define D1_DELAY_TIME 8000   //D1定时的计数值
#define D2_DELAY_TIME 4000     //D2定时的计数值
sbit D1 = P2^0;
sbit D2 = P2^1;
unsigned int D1_Delay_Count;   //D1的延时初始值
unsigned char D1_Delay_Flag;   //D1延时时间到标志位

unsigned int D2_Delay_Count;    //D2的延时初始值
unsigned char D2_Delay_Flag;    //D2延时时间到标志位
void main(void)
{
    D1_Delay_Count = D1_DELAY_TIME;  //给延时计数赋值
    D2_Delay_Count = D2_DELAY_TIME;  //给延时计数赋值
    
    while(1)
    {
        if(D1_Delay_Count)   //每次大循环只执行一次    
        {
            D1_Delay_Count --;    //只对计数值减一次1,就跳出if
            if(!D1_Delay_Count) D1_Delay_Flag = 1;   //定时时间到
        }
        
        if(D2_Delay_Count)   //每次大循环只执行一次    
        {
            D2_Delay_Count --;    //只对计数值减一次1,就跳出if
            if(!D2_Delay_Count) D2_Delay_Flag = 1;   //定时时间到
        }
        
        if(D1_Delay_Flag)   //定时时间到就执行一定的操作
        {
            D1_Delay_Flag = 0;  //定时到标志位清零
            D1 = ~D1;
            D1_Delay_Count = D1_DELAY_TIME;   //计数值重装,为下一次非阻塞延时做准备
        }
        
        if(D2_Delay_Flag)   //定时时间到就执行一定的操作
        {
            D2_Delay_Flag = 0;  //定时到标志位清零
            D2 = ~D2;
            D2_Delay_Count = D2_DELAY_TIME;   //计数值重装,为下一次非阻塞延时做准备
        }
    }
}


相关文章

保存现场

以下是一个简单的示例,演示了如何在函数调用过程中保存和恢复寄存器的值:#include <stdio.h>// 定义一个全局变量,用于存储寄存器的值int global_var = 0;/...

二人接力队列

二人接力队列

一般循环队列的属性如下:    elements\textit{elements}elements:一个固定大小的数组,用于保存循环队列的元素。  &...

stm32的代码和内存管理(SRAM空间)

stm32的代码和内存管理(SRAM空间)

SRAM空间1、文件中声明和定义(全局变量、静态数据和常量)从0x20000000开始,到堆的起始地址(堆是向上的)。2、HEAP区;(堆是向上的)3、STACK区。(栈是向下的)堆区(heap):一...

动态规划

是一种算法设计技巧,用于解决具有重叠子问题和最优子结构的问题。#include <stdio.h>// 动态规划解决斐波那契数列int fibonacci(int n) {&nb...

make及makefile,编译原理

1、出现*** No rule to make target ` ‘, needed by xxx. Stop.时,基本都是属于找不到所依赖的文件所导致的,查看.pro文件是否有问题。使用Eclips...

DSP的三角函数(\Embedded Workbench 9.0\arm\CMSIS\DSP)

1、主要代码在BasicMathFunctions文件夹2、主要例子在arm_sin_cos_example文件夹3、arm_sin_cos_example_f32文件4、先总结网上的DSP说法FPU...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。