常用单片机编程思想及例程3——延时篇

目录

 延时应用

阻塞型延时

非阻塞型延时


嵌入式编程中,很多地方都要用到延时程序,常用的单片机延时有很多种,大概分为两种类型:阻塞型延时非阻塞型延时,今天将就以下几种常见的延时函数使用进行说明。

 延时应用

阻塞型延时

顾名思义,这种延时是通过程序“死等”来完成延时操作的,一般在时效性要求不高的场合下使用,但不宜延时太长时间,过长的阻塞延时会极大的影响CPU的效率。实现这种延时的方法有很多,如利用空指令、循环空跑等封装出的延时函数,这类延时大都做粗延时(不太精确)使用,如果想要做到精确的延时,还可以配合定时器使用,以下以华大单片机为例说明这些延时的具体实现方法。

///*空指令实现延时(在stm32环境中,一般多少M的主频就放多少个空指令,就能实现1us的延时,但是华大单片机上好像不行,可以做粗延时使用)*///
void delay(void) 
{
    _NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();
    _NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();_NOP();
}

//循环空跑
void delay_nms(unsigned short time)
{
    unsigned short i = 0, j = 0;
    for(i = 0;i < time;i++)
    {
        for(j = 0;j < 925;j++) //mcu环境不同,j的阈值也不同,需要多次尝试
        {

        }
    }
}

//利用定时器实现延时
#include "timer4.h"
static uint16_t timer_timeout = 0;

void Timer4_Init(void)
{
    stc_adt_basecnt_cfg_t stcAdtBaseCntCfg;

    DDL_ZERO_STRUCT(stcAdtBaseCntCfg);

    Sysctrl_SetPeripheralGate(SysctrlPeripheralAdvTim, TRUE);	///< ADT外设时钟使能

    stcAdtBaseCntCfg.enCntMode = AdtSawtoothMode;	///< 锯齿波模式
    stcAdtBaseCntCfg.enCntDir = AdtCntUp;			///< 递加计数
    stcAdtBaseCntCfg.enCntClkDiv = AdtClkPClk0Div16;
    Adt_Init(M0P_ADTIM4, &stcAdtBaseCntCfg);

    /// 设置定时器周期 500us 主频16M 16分频
    Adt_SetPeriod(M0P_ADTIM4, 500); 

    Adt_ClearAllIrqFlag(M0P_ADTIM4);			///< 清标志位
    Adt_CfgIrq(M0P_ADTIM4, AdtOVFIrq, TRUE);	///< 上溢中断配置
    EnableNvic(ADTIM4_IRQn, IrqLevel3, TRUE);	///< 使能AdvTimer4 中断

    Adt_StartCount(M0P_ADTIM4);
}

void Tim4_IRQHandler(void)
{
    if (Adt_GetIrqFlag(M0P_ADTIM4,AdtOVFIrq))
    {
        Adt_ClearIrqFlag(M0P_ADTIM4,AdtOVFIrq);

        /* 500us timer */
        if (timer_timeout != 0) {
            timer_timeout--;
        }
    }
}

void delay_500us(uint16_t usec) //时基为500us的精确延时
{
    timer_timeout = usec;
    while(timer_timeout != 0);
}

以上几种延时方式换到其他MCU环境中大同小异,根据这些思想还可以衍生出很多种其他的延时,有兴趣的可以尝试一下。

非阻塞型延时

相比于阻塞型的延时,非阻塞型延时既可以实现计时功能,又可以最大化的释放MCU的性能,一个成熟的程序员一般是不会允许自己的代码中存在阻塞的。其实这种延时的实现原理也很简单:通过定时器计时,每隔一段时间判断一次(该过程一般在中断中进行),如果延时时间到了就执行用户要执行的语句,延时时间没有到也不影响其他语句的执行。(类似于:你明天早上八点钟要坐飞机出去玩,这个时候你会提前定一个闹钟提醒你明天要准时起床,只要闹钟还没有响,这段等待时间内你就还可以做其他的事情;如果是阻塞型延时的话,你就需要一直坐着,干瞪着眼,等到你起床的时间,中间什么也不能干。)

//配合系统定时器使用

SysTick_Config(SystemCoreClock/1000); //1ms 一次中断
 
void SysTick_IRQHandler(void)
{
	gSysTickCount++;
}

int main(void)
{
    while(1)
    {
        if(gSysTickCount > 1000) //每过1s执行一次
        {
            //user语句
            gSysTickCount  = 0;
        }
    }
}

//利用定时器实现非阻塞延时 timer.c
#include "adt.h"
#include "delay.h"
#include "Timer.h"

#define PERIOD	7500	///< PWM频率:100Hz = 48M/64/7500

uint8_t timer10msFlag,timer100msFlag;
uint8_t timer10msPlus;		///< 10ms脉冲,可供其他函数计时用
uint8_t timer100msPlus;	///< 100ms脉冲,可供其他函数计时用

/**
 * @brief 定时器初始化
 */
void TIMER_Init(void)
{
    stc_adt_basecnt_cfg_t stcAdtBaseCntCfg;

    DDL_ZERO_STRUCT(stcAdtBaseCntCfg);

    Sysctrl_SetPeripheralGate(SysctrlPeripheralAdvTim, TRUE);	///< ADT外设时钟使能

    stcAdtBaseCntCfg.enCntMode = AdtSawtoothMode;	///< 锯齿波模式
    stcAdtBaseCntCfg.enCntDir = AdtCntUp;			///< 递加计数
    stcAdtBaseCntCfg.enCntClkDiv = AdtClkPClk0Div64;
    Adt_Init(M0P_ADTIM4, &stcAdtBaseCntCfg);

    /// 设置定时器周期 10ms
    Adt_SetPeriod(M0P_ADTIM4, PERIOD);

    Adt_ClearAllIrqFlag(M0P_ADTIM4);			///< 清标志位
    Adt_CfgIrq(M0P_ADTIM4, AdtOVFIrq, TRUE);	///< 上溢中断配置
    EnableNvic(ADTIM4_IRQn, IrqLevel3, TRUE);	///< 使能AdvTimer4 中断

    Adt_StartCount(M0P_ADTIM4);
}
/**
 * @brief 定时器4中断服务程序,中断周期10ms
 */
void Tim4_IRQHandler(void)
{
    static uint8_t timer100msCnt = 0;

    if(TRUE == Adt_GetIrqFlag(M0P_ADTIM4, AdtOVFIrq))
    {
        timer10msFlag = 1;
        tick_10ms += 1;

        if(++timer100msCnt >= 10) //10ms中断一次,进来10次则为100ms
        {
            timer100msCnt = 0;
            timer100msFlag = 1;
            tick_100ms += 1;
        }

        Adt_ClearIrqFlag(M0P_ADTIM4, AdtOVFIrq);
    }
}

//delay.c
/* SystemFrequency / 1000    1ms中断一次
 * SystemFrequency / 100000  10us中断一次
 * SystemFrequency / 1000000 1us中断一次
 */
#include "delay.h"

unsigned int tick_10ms = 0;
unsigned int tick_100ms = 0;

//****************************************************************************
//函数名称:Delay10MS
//函数说明:10ms为单位的非阻塞延时
//入口参数:dly			延时结构体
//		start		启动延时标志,上升沿:开始延时    0:关闭延时
//		tout		延时时间,单位为10ms
//出口参数:无
//返回值:  0:定时时间未到    1:定时时间到
//****************************************************************************
unsigned char Delay10MS(tDelayType *dly, unsigned char start, unsigned int tout)
{
	if((dly->start_old == 0) && (start != 0))
	{
		dly->tick_old = tick_10ms;
	}
	if(start != 0)
	{
		if((tick_10ms - dly->tick_old) >= tout)
		{dly->timeout = 0xff;}
		else
		{dly->timeout = 0;}
	}
	else
	{dly->timeout = 0;}

	dly->start_old = start;
        
	return dly->timeout;
}

//****************************************************************************
//函数名称:Delay100MS
//函数说明:100ms为单位的非阻塞延时
//入口参数:    dly		延时结构体
//		start		启动延时标志,上升沿:开始延时    0:关闭延时
//		tout		延时时间,单位为100ms
//出口参数:无
//返回值:  0:定时时间未到    1:定时时间到
//****************************************************************************
unsigned char Delay100MS(tDelayType *dly, unsigned char start, unsigned int tout)
{
	if((dly->start_old == 0) && (start != 0))
	{
		dly->tick_old = tick_100ms;
	}
        
	if(start != 0)
	{
		if((tick_100ms - dly->tick_old) >= tout)
		{dly->timeout = 0xff;}
		else
		{dly->timeout = 0;}
	}
	else
	{dly->timeout = 0;}

	dly->start_old = start;
        
	return dly->timeout;
}

//delay.h
#ifndef _DELAY_H_
#define _DELAY_H_			   

typedef struct _delaytype
{
	unsigned char start_old;
	unsigned char timeout;
	unsigned int tick_old;
}tDelayType;

#define DELAY_DEFAULT	{0, 0, 0}

extern unsigned int tick_10ms;
extern unsigned int tick_100ms;

//****************************************************************************
//函数名称:DelayReset
//函数说明:复位延时结构体
//入口参数:dly			延时结构体的指针
//出口参数:无
//返回值:  无
//****************************************************************************
#define DelayReset(dly)	Delay10MS((dly), 0, 0)

#endif