打印
[国产单片机]

单片机非阻塞延时程序设计

[复制链接]
995|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Abby118|  楼主 | 2016-7-9 15:22 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
对于每个单片机爱好者及工程开发设计人员,在刚接触单片机的那最初的青葱岁月里,都有过点亮跑马灯的经历。从看到那一排排小灯按着我们的想法在跳动时激动心情。到随着经验越多,越来又会感觉到这个小灯是个好东西,尤其是在调试资源有限的环境中,有时会帮上大忙。

但对于绝大多数人,我们在最最初让灯闪烁起来时大约都会用到阻塞延时实现,会像如下代码的样子:


1.        while(1)

2.        {
3.            LED =OFF;
4.            Delay_ms(500);
5.            LED = ON;
6.            Delay_ms(500);
7.        }
8.       
然后,在我们接触到定时器,我们会发现,原来用定时中断来处理会更好。比如我们可以500ms中断一次,让灯亮或灭,其余的时间系统还可以做非常之多的事情,效率一下提升了很多。

这时我们就会慢慢意识到,第一种(阻塞延时)方法效率很低,让芯片在那儿空运行几百毫米,什么也不做,真是莫大的浪费,尤其在芯片频率较高,任务又很多时,这样做就像在平坦宽阔的高速公路上挖了一大坑,出现事故可想而知。

但一个单片机中的定时器毕竟有限,如果我需要几十个或者更多不同时间的定时中断,每一个时间到都完成不同的处理动作,如何去做呢。一般我们会想到在一个定时中断函数中再定义static 变量继续定时,到了所需时间,做不同的动作。而这样又会导致在一个中断里做了很多不同的事情,会抢占主轮询更多时间,有时甚至喧宾夺主,并也不是很如的思维逻辑。

那么有没有更好的方法来实现呢,答案是肯定的。下面介绍我在一个项目中偶遇,一个精妙设计的非阻塞定时延时软件的设计(此设计主要针对于无操作系统的裸机程序)。

在上篇**中有对systick的介绍,比如我要设置其10ms中断一次,如何实现呢?
也很简单,只需调用 core_cm3.h文件中 SysTick_Config 函数 ,当系统时钟为72MHZ,则设置成如下即可 SysTick_Config(720000 ); (递减计数720000次后中断一次) 。此时SysTick_Handler中断函数就会10ms进入一次;

任务定时用软件是如何设计的呢 ?
且先看其数据结构,这也是精妙所在之处,在此作自顶向下的介绍:

其定义结构体类型如:

1.        typedef struct

2.        {
3.            uint8_t Tick10Msec;
4.            Char_Field Status;
5.        } Timer_Struct;
其中Char_Field 为一联合体,设计如下:
1.        typedef union

2.        {
3.            unsigned char byte;
4.            Timer_Bit field;
5.        } Char_Field
而它内部的Timer_Bit是一个可按位访问的结构体:
1.        typedef struct

2.        {
3.            unsigned char bit0: 1;
4.            unsigned char bit1: 1;
5.            unsigned char bit2: 1;
6.            unsigned char bit3: 1;
7.            unsigned char bit4: 1;
8.            unsigned char bit5: 1;
9.            unsigned char bit6: 1;
10.            unsigned char bit7: 1;
11.        } Timer_Bit
此联合体的这样设计的目的将在后面的代码中体现出来。
如此结构体的设计就完成了。

然后我们定义的一全局变量,Timer_Struct  gTimer;

并在头文件中宏定义如下:
1.        #define bSystem10Msec        gTimer.Status.field.bit0

2.        #define bSystem50Msec        gTimer.Status.field.bit1
3.        #define bSystem100Msec       gTimer.Status.field.bit2
4.        #define bSystem1Sec          gTimer.Status.field.bit3
5.        #define bTemp10Msec          gTimer.Status.field.bit4
6.        #define bTemp50Msec          gTimer.Status.field.bit5
7.        #define bTemp100Msec         gTimer.Status.field.bit6
8.        #define bTemp1Sec            gTimer.Status.field.bit
另外为了后面程序清晰,再定义一状态指示:
1.        typedef enum

2.        {
3.            TIMER_RESET = 0,
4.            TIMER_SET = 1,
5.        } TimerStatus;
至此,准备工作就完成了。下面我们就开始大显神通了!

首先,10ms定时中断处理函数如,可以看出,每到达10ms 将把bTemp10Msec置1,每50ms 将把bTemp50Msec 置1,每100ms 将把bTemp100Msec 置1,每1s 将把bTemp1Sec 置1,
1.        void SysTick_Handler(void)

2.        {
3.                
4.                bTemp10Msec = TIMER_SET;
5.                
6.                ++gTimer.Tick10Msec;
7.                if (0 == (gTimer.Tick10Msec % 5))
8.                {
9.                    bTemp50Msec = TIMER_SET;
10.                }
11.                
12.                if (0 == (gTimer.Tick10Msec % 10))
13.                {
14.                    bTemp100Msec = TIMER_SET;
15.                }
16.                
17.                if (100 == gTimer.Tick10Msec)
18.                {
19.                    gTimer.Tick10Msec = 0;
20.                    bTemp1Sec = TIMER_SET;
21.                }
22.        }
而这又有什么用呢 ?

这时,我们需在主轮询while(1)内最开始调用一个定时处理函数如下:
1.        void SysTimer _Process(void)

2.        {
3.            gTimer.Status.byte &= 0xF0;
4.            
5.            if (bTemp10Msec)
6.            {
7.                bSystem10Msec = TIMER_SET;
8.            }
9.            
10.            if (bTemp50Msec)
11.            {
12.                bSystem50Msec = TIMER_SET;
13.            }
14.            
15.            if (bTemp100Msec)
16.            {
17.                bSystem100Msec = TIMER_SET;
18.            }
19.            
20.            if (bTemp1Sec)
21.            {
22.                bSystem1Sec = TIMER_SET;
23.            }
24.            
25.            gTimer.Status.byte &= 0x0F;
26.        }
此函数开头与结尾两句
1.        gTimer.Status.byte &= 0xF0;

2.        gTimer.Status.byte &= 0x0F
就分别巧妙的实现了bSystemXXX (低4位) 和 bTempXXX(高4位)的清零工作,不用再等定时到达后还需手动把计数值清零。此处清零工作用到了联合体中的变量共用一个起始存储空间的特性。

但要保证while(1)轮询时间要远小于10ms,否则将导致定时延时不准确。这样,在每轮询一次,就先把bSystemXXX ,再根据bTempXXX判断是否时间到达,并把对应的bSystemXXX 置1,而后面所有的任务就都可以通过bSystemXXX 来进行定时延时,在最后函数退出时,又会把bTempXXX清零,为下一次时间到达后查询判断作好了准备。


说了这么多,举例说明一下如何应用:

1.        void Task_A_Processing(void)

2.        {
3.            if(TIMER_SET == bSystem50Msec){
4.                //do something
5.            }
6.        }
7.       
8.        void Task_B_Processing(void)
9.        {
10.            if(TIMER_SET == bSystem100Msec){
11.                //do something
12.            }
13.        }
14.       
15.        void Task_C_Processing(void)
16.        {
17.            static uint8_t ticks = 0;
18.            if(TIMER_SET == bSystem100Msec){
19.               ticks ++ ;
20.            }
21.       
22.            if(5 == ticks){
23.                ticks = 0;
24.                 //do something
25.            }
26.       
27.        }
28.       
29.        void Task_D_Processing(void)
30.        {
31.            if(TIMER_SET == bSystem1Sec){
32.                //do something
33.            }
34.        }
以上示例四个任务进程,

在主轮询里可进行如下处理:
1.        int main(void)

2.        {
3.            while(1)
4.            {
5.                SysTimer _Process();
6.       
7.                Task_A_Processing();
8.                Task_B_Processing();
9.                Task_C_Processing();
10.                Task_D_Processing();
11.       
12.            }
13.        }
这样,就可以轻松且清晰实现了多个任务,不同时间内处理不同事件。(但注意,每个任务处理中不要有阻塞延时,也不要处理过多的事情,以致处理时间较长。可设计成状态机来处理不同任务。)

相关帖子

沙发
McuPlayer| | 2016-7-10 13:07 | 只看该作者
不错,非阻塞的方式,有很多好处
也可以用状态机的方式来实现

使用特权

评论回复
板凳
coody| | 2016-7-10 16:14 | 只看该作者
说白了就是软件定时器,比如在一个定时器做1ms定时,则你可以做几十、几百个分辨率(最小定时单位)为1ms的定时器。

使用特权

评论回复
评论
shuiqinghan2012 2016-8-10 11:58 回复TA
简单直接,赞 
地板
jackhwang| | 2016-7-10 21:19 | 只看该作者
好东西,最近正在学习STM32

使用特权

评论回复
5
wlzeagle| | 2016-7-10 21:26 | 只看该作者
我就是用的这样的方法,但是有时并不是那么好用

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

30

主题

31

帖子

2

粉丝