时间触发 合作式调度器 编程模式
目前,RTOS特别是抢先式RTOS在嵌入式系统中的应用越来越广泛,但是还有很大一部分产品使用是小型单片机。这些系统由于成本的限制,通常资源非常有限,比如ROM往往小丁32 KB,RAM小于2 KB,由于RTOS对每个任务都要开辟单独内存区域,存放任务的上下文和各任务独立的堆栈,所以在这种系统中使用RTOS非常勉强。对于这些低成本资源受限系统通常采用“前后台”(或者叫“超级循环”)结构进行编程,这实际上是一种事件触发的编程模式,当中断数目较多且系统完成的功能相对复杂时,就会使系统的程序编写变得非常复杂并使系统运行的可预测性迅速下降。
针对这个问题,Michael J.Pont提出了一种“基于时间触发的编程模式”,这种方法有助于降低CPU的负荷并减少存储器的使用量,提高系统行为的可预测性,并使程序的结构变得简洁。但是在实际使用中,当系统中不同的任务对时间要求差异较大时,“基于时间触发的编程模式”难以给出简单有效的解决方案。为此,对“基于时间触发的编程模式”进行了改进,使之适应性更强,可以为成本和资源受限的小型嵌入式系统提供统一且有效的编程模式。
1 传统编程结构的局限性
当不使用RTOS时,嵌入式软件通常采用两种传统的编程结构进行编程,一种叫“前后台厅式”或者叫“超级循环结构”,本质上是事件触发的编程方式;另一种叫时间触发编程模式,Michael J.Pont的“基于时间触发的编程模式”即属于此。
在实际工作中,当系统稍微复杂时,会发现这两种方式都有一定局限性,下面以一个实际产品设计中遇到的问题为例来说明。在设计一个用于配电柜的壁装式智能配电仪表时,CPU的程序设计需完成以下任务:
①每半秒对前显示屏的显示数据进行一次刷新。
②每0.1 s对DI/DO进行一次刷新。
③每0.2 s对键盘进行一次扫描。
④每半秒对测量数据进行一次重新采集和计算。
⑤异步串行口与上位机使用Modhus通信,速率最高1 9 200 bps。
⑥CPU通过I2C总线与时钟芯片和EEPROM通信。
⑦CPU通过SPI总线与LED数码管及采集芯片通信。
⑧CPU要对所采集的6路信号进行FFT变换。
⑨当系统掉电时,CPU要能快速响应,把当前的电度底数写入EEPROM中。
上述任务中,任务⑤和任务⑨是强实时性的,如果对串口的收发事件得不到及时响应,接收时会导致字节丢失,发送时会导致字节间时间间隔太大,造成接收方的Modbus帧定界错误,对系统掉电事件如果不能及时响应会造成EEPROM的写入失败。其他任务只要在指定的周期内能得到执行就行,但是任务⑧比较特殊,使用通常的8位CPU进行6种信号的FFT变换,哪怕每种信号只做128点的FFT,运算一次也要好几秒。下面来看用传统编程结构实现上述设计时遇到的困扰。
1.1 使用“前后台方式”进行编程
使用“前后台方式”进行编程时,为保证任务⑤的及时性,使用了UART中断,当UART完成一个字节的收发后产生中断,在中断程序中将接收到的字符保存在接收缓冲区或从发送缓冲区取下一个待发字符装入UART进行发送,对Modbus协议的处理可以单独用一个任务在中断外处理,这保证了巾断程序的简短。为保证任务⑨响应的及时性,也必须为它安排一个中断。因为当系统掉电时,系统只有不到10 ms的过渡时间,系统如果不能在这个时间内完成相关的操作,系统电压将跌落至有效电压以下而丧失工作能力。
安排好了后台的中断任务后再来看看前台的任务如何完成。这里遇到的最大的挑战是对任务⑧的处理,因为任务⑧需要的执行时间太长了,简单的把它当成一个任务处理将影响系统对其他任务的响应,在超级循环中的代码结构如下:
while(1){
任务①;
任务②;
……
任务⑧;
}
由于任务⑧执行一次要几秒钟的时间,整个超级循环执行一次至少大于任务⑧需要的时间,也就是说这个超级循环循环一次要几秒钟时间,将满足不了各任务响应时间的要求。
要解决这个问题,只有把任务⑧拆分成很多个子任务,将每个子任务的耗时压缩到10 ms左右,并定义好各个子任务完成后的状态,在超级大循环中每次根据状态只执行一个子任务,程序结构如下:
while(1){
任务①;
任务②;
……
switch(子任务状态){
case 子任务状态①:
子任务①;
break;
……
case 子任务状态②:
子任务②;
break;
……
case 子任务状态:
子任务;
break;
}
}
这样,就需要把一个耗时几秒的FFT运算任务拆分成几百个耗时10 ms左有的子任务,这显然是不可接受的。除此之外,超级大循环结构隐含的一个缺点就是随着任务的增加,循环体的执行时间是线性增加的,在实际设计中即使没有像任务⑧那样的高耗时任务,当系统功能增加时要保证系统响应的及时性也是一个不小的挑战。
1.2 使用“时间触发编程模式”进行编程
“时间触发编程模式”的核心是建立一个基丁时间触发的合作式的任务调度器,在系统中尽量减少事件触发(减少中断的使用),系统通过任务调度器完成各任务的调度执行,下面是“时间触发编程模式”的典型程序结构:
系统中每个任务都定义了优先级、任务循环周期和任务延迟时间,系统定时器中断程序SCH Updatc()按设定的节拍对任务队列进行刷新,在超级大循环中只执行任务调度器SCH_Dispatch_Tasks(),根据任务队列的状念安排任务的执行。
这种编程结构避免了超级大循环结构循环时间随代码量的增加而线性增加的问题,但是由于任务是不可剥夺的,一旦任务启动执行,任务调度器只有在当前任务完成后才有机会执行,这就要求每个任务占用CPU的时间不能太长,否则将影响整个系统的响应速度。所以,FFT运算在这种编程模式下还是必须进行有效的拆分,否则就必须提高CPU的档次或使用可剥夺型的抢先式RTOS,这势必造成系统成本的增加。那么有没有更好的解决办法呢?
下面的编程结构埘“时间触发编程模式”进行了改进,使之在不提高硬件成本的情况下,使编程人员更直观地定义任务,减少任务特性对系统程序结构的冲击,使程序结构简单、明了并提高系统的实时响应速度。
- 一种基于时间触发模式的汽车防盗系统的设计(08-16)
- 看高手谈谈对单片机编程的理解(01-09)
- AVR单片机无法进入编程模式(01-09)
- Windows CE 进程、线程和内存管理(11-09)
- RedHatLinux新手入门教程(5)(11-12)
- uClinux介绍(11-09)