欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

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

程序员文章站 2022-07-05 21:34:57
没有使用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(); //做其它事情...

没有使用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执行一次,就需要修改中断函数,重新计算时间main函数中如何更好的定时执行任务

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

#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()判断定时是否到达。
main函数中如何更好的定时执行任务

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

本文地址:https://blog.csdn.net/Lennon8_8/article/details/109259825