旧文备份:单片机上实现实时多任务的一种方法
虽然单片机的处理能力低下,但是我们还是要尽量榨干它,以最少的资源干更多的事情,所以在单片机上进行多任务处理还是很常见的事情,任务多了,资源还是那些,每个任务得到执行的周期必定拉长,势必会影响任务的实时性。
遇到这种情况,为了保证实时性,都会引入任务调度机制,对于ARM7或更高级的16位或32处理器,我们可以加入一个RTOS来处理,但RTOS的任务调度和系统开销会占用很大一部分处理器资源的,对于一般的低档8位机显然难以承受。怎么办?想想,引入RTOS不就是为了实时任务调度么,我们自己调度一下不就完了嘛,费那事。不过自己调度是挺费事的,不过8位机也不会安排太多任务,下面就介绍一种保证实时性的任务调度方法。
一般无OS的单片机程序都是在初始化后直接进入死循环,几个要执行的任务都放在死循环中去周期循环调用。因为这个循环周期无法保证,所以死循环中每个任务被调用执行的周期就无法保证,当然具有实时性要求的任务就无法保证在规定时间内得到处理。现在以一个范例来说明这种任务调度方法,假定为某单片机安排了5个任务用Task1~Task5表示,其各任务的实时性要求和实测得最长执行时间如下表:
Task1
|
Task2
|
Task3
|
Task4
|
Task5
|
|
实时性要求(ms)
|
1
|
2
|
5
|
10
|
100
|
最长执行时间(ms)
|
0.1
|
0.2
|
0.5
|
0.3
|
1.2
|
由上表可以看出,五个任务放在一个死循环中顺序执行,最长的执行周期达到了2.3ms,这对于Task1和Task2来说是无法忍受的,而且响应系统中断也要花费时间,虽然一般系统中断处理函数都很短。现假设有3个中断处理函数Int1~Int3,其最小中断周期和最长执行时间见下表:
Int1
|
Int2
|
Int3
|
|
最小中断周期(ms)
|
0.2
|
2
|
10
|
最长执行时间(ms)
|
0.02
|
0.05
|
0.05
|
现在我要保证最短的Task1的1ms响应时间,必须使得程序初始化后进入的死循环执行周期在1ms内,于是乎我对某些任务进行以下加工:
Task3拆分成5个最长执行时间为0.1ms的任务Task3.1~Task3.5。
Task5拆分成4个最长执行时间为0.3ms的任务Task5.1~Task5.4。
做完拆分后将这些子任务安排到5个组(Class1~Class5)里,分配如下:
|
Class1
|
Class2
|
Class3
|
Class4
|
Class5
|
任务
|
Task1,Task2
Task3.1,Task4
|
Task1,Task3.2
Task5.1
|
Task1,Task2
Task3.3,Task5.2
|
Task1,Task3.4
Task5.3
|
Task1,Task2
Task3.5,Task5.4
|
最长执行时间(ms)
|
0.7
|
0.5
|
0.7
|
0.5
|
0.7
|
如上表,再算上期间各中断的执行时间,每组的执行时间也不可能超过1ms吧。
好了,将这五个组按顺序放在死循环里,并且保证被拆分的每个子任务在执行之前都去判断排在它前头的那个子任务是否被执行到,如果被执行过了则本段子任务执行,否则不执行(例Task3.3在执行前要去判断Task3.2是否被执行,而且本段子程序在执行后要给出标志,告知下段子程序Task3.4我执行过了)。这么做看看能不能满足每个任务要求的实时响应时间?都在允许范围内吧。
如果希望Task1能够实现精准的1ms定时执行,可以设置一个1ms的定时器来定时顺序切换这5个组。
上面的这种做法也算是实现一种简单的实时任务调度,其优点是:任务调度都在编程时做好了,在程序实际运行过程中几乎不会占用额外的硬件资源和处理器时间。缺点是:编程麻烦,必须预先算准各任务和拆分的子任务的最长执行时间,不能有偏差,而且编写出来的程序可读性差。
总的来说,这种调度方法对于低档8位单片机来说还是一种不错的实时多任务的解决方案,行之有效,就是费点事。
(2007.11.7)