main函数中如何更好的定时执行任务

没有使用RTOS实时操作系统,一个祼奔的单片机如果要每隔20ms扫描一次按键,100ms让LED变化一次,我们应该怎么做?
第一种实现方法:

void main(void)
{
    
    
	u8 cnt;
	HardInit(void);
	while(1)
	{
    
    	
		if (cnt++ >=5) 
		{
    
    
			cnt = 0;
			ScanKey();//扫描按键
		}
		LED = ~LED; //LED灯取反一次
		delay_ms(20);
		DoSomething(); //做其它事情
	}
}

这是采用delay延时的方法,LED和按键扫描都能执行,但是DoSomething()也会被延时成20ms执行一次。

第二种实现方法:

bool FlagSysClk2ms;
bool FlagSysClk20ms;
bool FlagSysClk100ms;
bool FlagSysClk1s; //1秒

u8 CntSysClk20ms;
u8 CntSysClk100ms;
u8 CntSysClk1s;

void Timer_Init()
{
    
    
	//清零
	FlagSysClk2ms = 0;
	FlagSysClk20ms = 0;
	FlagSysClk100ms = 0;
	FlagSysClk1s = 0; 

	CntSysClk20ms = 0;
	CntSysClk100ms = 0;
	CntSysClk1s = 0;

	//定时器初始化
	//省略....
}

#pragma vector=0x19
__interrupt void TIM4_UPD_OVF_IRQHandler(void)
{
    
    
	//2ms 产生一次中断
	FlagSysClk2ms = 1;
	if(++CntSysClk20ms >= 10) //20ms
	{
    
    
		CntSysClk20ms = 0;
		FlagSysClk20ms = 1; 

		if (++CntSysClk100ms >= 5) //100ms
		{
    
    
			CntSysClk100ms = 0;
			FlagSysClk100ms = 1;
			
			if (++CntSysClk1s >= 100) //1s
			{
    
    
				CntSysClk1s = 0;
				FlagSysClk1s = 1;
			}	
		}
	}
}
void main(void)
{
    
    
	HardInit(void);
	Timer_Init();
	while(1)
	{
    
    
		if (FlagSysClk20ms)
		{
    
    
			FlagSysClk20ms = 0;
			ScanKey();//扫描按键
		}

		if (FlagSysClk100ms)
		{
    
    
			FlagSysClk100ms = 0;
			LED = ~LED; //LED灯取反一次
		}
		DoSomething();
	}
}

我们用一个定时器,每2ms中断一次,计数到20ms,100ms分别做个标记,在main函数中判断这个标记来确定时间是否到了,这样不会像delay_ms函数那样一直等待,DoSomething()可以无等待执行。

第一种方法可以大致满足要求,但是程序要执行任务多了,延时函数就可能会影响到响应速度。第二种方法能较好解决延时问题,但是有没有注意到,定时中定义了太多变量,移植这样的程序要修改的地方多,不够方便,又容易出错。例如,现在要把LED改成250ms执行一次,就需要修改中断函数,重新计算时间在这里插入图片描述

这样容易改错,程序跑不正常,我们把第二种方法优化一下。
第二种方法的演变

#define DELAY_NUM		5	//要计数的数量
uint16_t DelayTable[DELAY_NUM]={
    
    0};
uint8_t i;

void Timer_Init()
{
    
    
	for (i=0;i<DELAY_NUM;i++)DelayTable[i] = 0;//清零
	//定时器初始化
	//省略....
}

#pragma vector=0x19
__interrupt void TIM_IRQHandler(void)//1ms 产生一次中断
{
    
    
	for (i=0;i<DELAY_NUM;i++) DelayTable[i]++; //数组中每个元素都加1
	//清除中断
}

//nms:要定时的毫秒数
//index:定时器号(数组索引)
BitStatus SysGetSignal_1ms(uint16_t nms,uint8_t index)
{
    
    
	if (index <= DELAY_NUM)
	{
    
    
		if (DelayTable[index] >= nms) //计数大于定时值,说明时间到
		{
    
    
			DelayTable[index] = 0;
			return SET;
		}
	}
	return RESET;
}
//复位一个定时信号
//index 定时数组的索引
void SysResetSignal(uint8_t index)
{
    
    
	if (index < DELAY_NUM)DelayTable[index] = 0;
}	
void main(void)
{
    
    
	HardInit(void);
	Timer_Init();
	while(1)
	{
    
    
		if (SysGetSignal_1ms(20,0))
		{
    
    
			ScanKey();//扫描按键
		}

		if (SysGetSignal_1ms(100,1))
		{
    
    
			LED = ~LED; //LED灯取反一次
		}
		DoSomething();
	}
}

聪明的你有没有发现程序一下子变得如此简洁高效!
它的运行原理就是:定时器每隔1ms产生一次中断,定义一个数组,用来记录定时产生的次数,在中断里把所有数组元素加1,然后通过调用SysGetSignal_1ms()判断定时是否到达。
在这里插入图片描述

这样做的优点:
高效简洁:中断函数中只做记数,耗时短,时间确定
修改方便:1、如果需要很多个定时,只要修改DELAY_NUM把数组扩大。在这里插入图片描述
2、只要改变SysGetSignal_1ms的参数就能改变定时时间 在这里插入图片描述
3、只管调用一个函数就行,不需要清除标记。你有没有经常忘记清除标记而让自己瞎拆腾半天的经历?
在这里插入图片描述
4、在主函数中干的啥事如此的明了
在这里插入图片描述
做的项目多了,51、PIC、STM8、SMT32 不同平台切换来切换去,就会体会,怎么让代码能重复使用,方便移植,安全可靠,是一件多么重要的事,如果你也在追求写一些可靠优雅的代码,欢迎留言探讨

猜你喜欢

转载自blog.csdn.net/Lennon8_8/article/details/109259825