多任务机制在单片机系统中的应用———多任务机制在单片机系统中的应用
中山智达自动化科技有限公司

摘 要:提出了一种崭新的基于任务机制的单片机系统程序结构,讨论了具体实现方法,并给出应用实例。
关键词:多任务机制 单片机系统 中断机制
  传统的单片机系统监控程序通常是基于单任务机制的。这种机制具有简单直观,易于控制的优点。然而由于程序只能按单一的线索顺序执行,缺乏灵活性,在复杂系统中难以胜任。为了在更广泛的领域应用单片机系统,必须对传统的单任务机制进行改进。
  多任务机制是现代操作系统才具有的突出优点。在这种机制下,CPU的运行时间被划分为许多小的时间片,由某种调度算法按不同优先级别分配给不同的应用程序。多个应用程序分别在自已的时间片内访问CPU,从而造成微观上轮流运行,宏观上并发运行的多任务效果。
  在单片机系统中引入多任务机制,可以有效改善程序结构,满足复杂系统的要求。任何多任务本质上都借助于中断机制。通用单片机中都允许使用中断,从而保证了在单片机系统中实现多任务的切实可行。而单片机运算能力的有限要求多任务调度算法必须简洁。
  

1 两种机制的比较
  一个典型的单片机监控系统通常包括输入、输出控制,数据处理,显示以及键盘管理。在传统的单任务机制下,程序采用循环方式,其流程图如图1所示。
  由流程可知,在单任务机制下,各功能模块按固定顺序构成一个整体,作为一个任务得到执行。而在实际应用中,各个模块要求的执行频率往往不一致,如输入采样频率可能要求很高,而单位时间内键盘扫描的次数则相对较少,系统这种复杂的定时要求在单任务机制下难以满足。另外在这种结构下程序一旦建立,各模块的执行顺序即已固定,对于需在运行时动态改变执行结构的系统,程序需用许多条件判断和分枝转移语句进行控制,增加了程序的复杂性。程序作为一个整体而存在可读性和可维护性很差,调试不便,对已有程序进行扩充,也需先了解整个程序结构,增加了扩充难度。
  考虑到单片机完成的系统功能往往可以分解为若干相对独立的模块,我们可以将这些模块理解为子任务,并引进多任务机制进行管理,从而形成一种崭新的程序结构。其示意图如图2所示。
  在这种结构下,各功能模块由系统调度程序启动执行完毕后返回系统调度程序,彼此处于等同地位,其执行顺序也比较灵活,且可在程序运行过程中动态地改变执行结构。各子任务的执行频率要求表现为它们的优先级,优先级越高的子任务单位时间内执行次数越多,从而其执行频率越高。各子任务在自已的时间片内运行,通过合理设计时间片大小和各任务的优先级,可以自然地满足系统内各种复杂的定时要求。
  在多任务结构下程序的调试与维护也变得容易。程序以分立模块形式存在,各模块间功能独立,相互影响小,可以方便地单独调试,并寻找问题所在。对程序的扩充更为方便,不用更改原有结构,只需增加要扩充的部分即可。
  

2 多任务机制的实现
  利用单片机具有的内部时钟中断,可以方便地实现多任务机制。下面以常用的MCS—51系列单片机为例,说明其实现方法。
  由多任务机制的特点,必须提供一个系统时钟,根据系统时钟将CPU运行时间划分为若干时间片,由系统调度程序分给各个任务模块。我们可由MCS—51系统单片机内带的定时器完成这一工作。定时参数的选择应考虑到系统中各种定时要求,定时频率一般应设计为各模块要求执行频率的整数倍频。
  为了实现多任务,还需构造一个任务调度表并维护一个系统任务指针。任务调度表每一表项占4个字节,保存各任务模块的入口地址和访问计数器,表项数等于需调度的任务模块数。任务调度表指示了各任务的执行顺序和优先级别。在程序建立时可在程序存储器中建立一份缺省的任务调试表并在程序运行初始化时调入系统RAM中,在程序运行期间可根据实际需要修改RAM中的任务调度表,使各任务的执行顺序和优先级别得到动态调整。系统任务指针为一个1字节整数,指向系统当前正执行的任务。由任务指针和任务调度表表头可以找到相应的任务程序入口地址。
  多任务机制的具体实现由调度程序完成。调度程序在每个时钟片的开始取得控制,它将系统任务指针加4,使之指向下一个任务模块,并根据新的任务指针查阅任务调度表,将任务的访问计数器减一,看是否为零。不为零则直接返回;为零则将当前任务入口地址读入DPTR寄存器,用JMP @A+DPTR指令转而执行相应的任务模块。各任务模块执行完毕将相应访问计数器置成初值,供调度程序下一次使用。任务指针到达调试表未尾时,由任务调度程序将其复位,使之指向任务调度表开头,重新开始程序周期。
  由上述实现方案可知,各任务模块在时钟中断程序中执行,这可很好地满足各任务模块对执行频率的要求。但考虑到单片机调度能力有限的特点,为各个任务模块分配的时间片比实际所需可能尚有富余,而各任务模块作为时钟中断例程执行完毕都返回到主程序。因此可将某些对执行频率无要求的任务模块放入主程序运行,从而充分利用各时间片内的富余时间,提高程序运行效率。
  实现多任务的关键在于合理地将系统功能分解为各个任务模块。一般可将程序分为输入、输出、数据处理、显示、键盘扫描等任务模块。分解系统功能应注意以下几个问题:
  (1)各个任务模块需尽量短小精悍,在系统时间片内应能充分行到执行。在设计任务模块时应尽量将其中的延时要求转化为对模块执行频率的要求。如在动态显示模块设计中每位显示间要求有1-2ms左右的延时来保证显示稳定,若采用一次将所有位全部显示一遍的方案,显示模块将变得冗长,不适宜在中断程序中执行。我们可以采用一次只显示一位的方法,这样的显示模块由于没有延时而得到精简,位显之间的延时时间可由模块执行频率来决定。  (2)在设计中若不可避免出现某一任务模块过长而不能在系统时间片内执行完毕的情况,可由下列两种方法解决:
  第一种方法是若此模块无执行频率的要求和时间上的严格限制,可将其放入主程序,充分利用各时间片的富余时间执行。
  第二种方法是在进入该任务模块时将系统时钟停止,这样相当于临时给此模块分配了附加运行时间。在模块执行完毕时应重新打开系统时钟,使多任务调度程序能正常工作。
  (3)各任务模块在功能上应尽量相互独立,以保证程序调试和维护的方便。
  (4)若某些模块必须相互配合工作,可以采用公共单元进行通信,保持同步。
  综上所述,在单片机系统中实现多任务机制,要借助于时钟中断。由定时器产生系统时间片,由系统调度程序将时间片分配给各任务模块。合理设计各任务模块可充分发挥多任务机制的优越性。
  

3 应用实例
  利用单片机AT89C51设计一个应用系统。要求系统同时接受两个通道的数据输入,一个通道每10ms采集一次,用于显示和打印,另一通道每2ms采集一次,用于监控;显示采用LED数码管动态显示方式;系统要求配有键盘接受用户命令;系统要求每隔10s自动打印一次结果,打印程序应支持两种不同型号的打印机(由用户通过键盘选择)。
  由前述实现方案,可如下设计各任务模块:
  首先应提供两个输入模块,其执行频率分别为1次/10ms和1次/2ms,可分别记为I1,I2模块。
  由于采用动态显示方案,设计显示模块为每次显示一位。为保持显示稳定,每两位显示之间需间隔一定时间,这里定为2ms。即要求显示模块的执行频率为1次/2ms。显示模块记为D模块。
  键盘扫描的去抖延时时间定为50ms,因此键盘管理模块的执行频率为1次/50ms。键盘管理模块记为K模块。
  系统要求提供打印功能,因此还需设计打印模块,其执行频率为1次/10s。由于要支持两种不同的打印机,应设计两个打印模块提供服务,分别记为P1,P2模块。
  此外系统还应有数据转换程序及其它相关功能模块。由于这些功能模块并没有严格的时间限制,可以放入主程序中执行,不参与多任务调度。
  由上述分析可知,系统要求同时调度5个任务模块(P1与P2并不同时使用),因此任务调度表设计为5个表项。系统要求的最高执行频率为1次/2ms,可将系统时间片长度设计为400μs。由此可计算出各任务模块访问计数器的值,如表1。
  表1   模块名 十进制计数值 16进制计数值
       I1     5     0005
       I2     1     0001
        D     1     0001
       K     25    0019
     P1,P2     5000   1388
  若采用P1作为缺省打印管理程序,则可建立缺省的任务调度表如表2所示。
  表2   I1入口 I2入口 D入口 K入口 P1入口
       0005  0001  0001 0019 1388
  任务调度程序可如下编写:
  …… mov  a,pointer  ;pointer为当前任务指针
     add  a,#tasktbl;tasktbl为任务调度表首址
     add  a,#02h;指向访问计数器
     mov  r0,a
     mov  a,@r0;访问计数器值减一
     clr  c
     subb a,#01h
     mov  b,a
     inc  r 0
     mov  a,@r 0
     subb a,#00h
     jnz exit      ;不为零则返回