但对于绝大多数人,我们在最最初让灯闪烁起来时大约都会用到阻塞延时实现,会像如下代码的样子:
这时我们就会慢慢意识到,第一种(阻塞延时)方法效率很低,让芯片在那儿空运行几百毫米,什么也不做,真是莫大的浪费,尤其在芯片频率较高,任务又很多时,这样做就像在平坦宽阔的高速公路上挖了一大坑,出现事故可想而知。
但一个单片机中的定时器毕竟有限,如果我需要几十个或者更多不同时间的定时中断,每一个时间到都完成不同的处理动作,如何去做呢。一般我们会想到在一个定时中断函数中再定义static 变量继续定时,到了所需时间,做不同的动作。而这样又会导致在一个中断里做了很多不同的事情,会抢占主轮询更多时间,有时甚至喧宾夺主,并也不是很如的思维逻辑。
那么有没有更好的方法来实现呢,答案是肯定的。下面介绍我在一个项目中偶遇,一个精妙设计的非阻塞定时延时软件的设计(此设计主要针对于无操作系统的裸机程序)。
在上篇文章中有对systick的介绍,比如我要设置其10ms中断一次,如何实现呢?
也很简单,只需调用 core_cm3.h文件中 SysTick_Config 函数 ,当系统时钟为72MHZ,则设置成如下即可 SysTick_Config(720000 ); (递减计数720000次后中断一次) 。此时SysTick_Handler中断函数就会10ms进入一次;
任务定时用软件是如何设计的呢 ?
且先看其数据结构,这也是精妙所在之处,在此作自顶向下的介绍:
其定义结构体类型如:
typedef struct{ uint8_t Tick10Msec; Char_Field Status; } Timer_Struct;
复制代码typedef union{ unsigned char byte; Timer_Bit field; } Char_Field
复制代码typedef struct{ unsigned char bit0: 1; unsigned char bit1: 1; unsigned char bit2: 1; unsigned char bit3: 1; unsigned char bit4: 1; unsigned char bit5: 1; unsigned char bit6: 1; unsigned char bit7: 1; } Timer_Bit
复制代码如此结构体的设计就完成了。
然后我们定义的一全局变量,Timer_Struct gTimer;
并在头文件中宏定义如下:
#define bSystem10Msec gTimer.Status.field.bit0#define bSystem50Msec gTimer.Status.field.bit1 #define bSystem100Msec gTimer.Status.field.bit2 #define bSystem1Sec gTimer.Status.field.bit3 #define bTemp10Msec gTimer.Status.field.bit4 #define bTemp50Msec gTimer.Status.field.bit5 #define bTemp100Msec gTimer.Status.field.bit6 #define bTemp1Sec gTimer.Status.field.bit
复制代码typedef enum{ TIMER_RESET = 0, TIMER_SET = 1, } TimerStatus;
复制代码首先,10ms定时中断处理函数如,可以看出,每到达10ms 将把bTemp10Msec置1,每50ms 将把bTemp50Msec 置1,每100ms 将把bTemp100Msec 置1,每1s 将把bTemp1Sec 置1,
void SysTick_Handler(void){ bTemp10Msec = TIMER_SET; ++gTimer.Tick10Msec; if (0 == (gTimer.Tick10Msec % 5)) { bTemp50Msec = TIMER_SET; } if (0 == (gTimer.Tick10Msec % 10)) { bTemp100Msec = TIMER_SET; } if (100 == gTimer.Tick10Msec) { gTimer.Tick10Msec = 0; bTemp1Sec = TIMER_SET; } }
复制代码这时,我们需在主轮询while(1)内最开始调用一个定时处理函数如下:
void SysTimer _Process(void){ gTimer.Status.byte &= 0xF0; if (bTemp10Msec) { bSystem10Msec = TIMER_SET; } if (bTemp50Msec) { bSystem50Msec = TIMER_SET; } if (bTemp100Msec) { bSystem100Msec = TIMER_SET; } if (bTemp1Sec) { bSystem1Sec = TIMER_SET; } gTimer.Status.byte &= 0x0F; }
复制代码gTimer.Status.byte &= 0xF0;gTimer.Status.byte &= 0x0F
复制代码但要保证while(1)轮询时间要远小于10ms,否则将导致定时延时不准确。这样,在每轮询一次,就先把bSystemXXX ,再根据bTempXXX判断是否时间到达,并把对应的bSystemXXX 置1,而后面所有的任务就都可以通过bSystemXXX 来进行定时延时,在最后函数退出时,又会把bTempXXX清零,为下一次时间到达后查询判断作好了准备。
说了这么多,举例说明一下如何应用:
void Task_A_Processing(void){ if(TIMER_SET == bSystem50Msec){ //do something } } void Task_B_Processing(void) { if(TIMER_SET == bSystem100Msec){ //do something } } void Task_C_Processing(void) { static uint8_t ticks = 0; if(TIMER_SET == bSystem100Msec){ ticks ++ ; } if(5 == ticks){ ticks = 0; //do something } } void Task_D_Processing(void) { if(TIMER_SET == bSystem1Sec){ //do something } } 以上示例四个任务进程, 在主轮询里可进行如下处理: int main(void) { while(1) { SysTimer _Process(); Task_A_Processing(); Task_B_Processing(); Task_C_Processing(); Task_D_Processing(); } }
复制代码本文转自网络。