μC/OS-II的多任务系统实时性分析与优先级分配
从产品研发的角度,针对小资源系统中使用μC/OS-II的实时性和优先级关系进行了分析,提出了可删除任务的灵活应用和可变大小任务栈的实现方法,对于并行任务使用共享资源的几种情况给出了实用解决方案。这些措施获得了良好的任务并行性和实时响应,节约了代码存储空间。
关键词 μC/OS-II 实时性 可变任务堆栈 优先级
引言
μC/OS-II作为一种轻量级的嵌入式实时操作系统,正随着嵌入式微处理器性能的不断提高和外围资源(主要是存储器资源)的不断增加,得到越来越多的应用。例如,原来的51系列单片机,限于6~12 MHz的主频、12个Clock的机器周期以及有限的存储器资源,使用μC/OS-II会大大加重系统负担,使应用程序的运行受到影响,特别在快速A/D转换等实时性较强的场合,无法得到及时的响应,于是才有了更轻量级的Small RTOS等操作系统的出现。
但目前更强劲的51内核版本微处理器的大量出现,从根本上改变了这种情况。40 MHz以上的主频,单周期指令的微处理器,加上64 KB的程序空间和8 KB以上的数据空间,这样的系统已经可以流畅地运行μC/OS-II[1]。μC/OS-II的移植版本很多,选择一个适合系统CPU的版本,然后进行正确的配置和优化是非常重要的。
1 系统实时性分析
本系统工作原理是在恒定温度条件下,任意启动4个测试通道来进行多个项目的并行分析,每个测试通道的工作流程完全相同,如图1所示。
C8051F120集成了8路12位高速A/D转换器(ADC0)和8路8位高速A/D转换器(ADC2)。系统要求对4个光学传感器输出进行采样,ADC0可以构成4个差分测试通道以满足需求。系统还要求能对2路温度实现实时控制,用于监控2个外部温度传感器的输出电压: 一个保证测试部分的温度恒定在37±0.2 ℃,通过对加热组件的PWM控制来完成;另一个用于监测机箱温度,在32 ℃以上时启动风扇散热,30 ℃以下关闭风扇。
从图1中可以看出,完整的流程包括信息输入、样本预温、通道启动、试剂添加、微分测量、结果处理6个阶段。其中前4个属于操作阶段,操作者通过每个通道的进度条、转动条和状态标志块,在LCD界面上获得明确的操作指示,确保每个通道的正确操作。
要求在通道启动后,能精确地每隔0.1 s完成一次采样并处理数据,所以每个通道的最长处理时间不能超过25 ms,且软件微分法需要较高的A/D转换精度和稳定性。C8051F120中的A/D最高采样速率可达100 ksps,约10 μs/次,因此可采用过采样后的高平滑,同时提高精度和稳定性。简单地计算,如果每个通道采样1 024次后平均计算,则4个通道的A/D总速率为:ADMAX=1 024×4×10=40 ksps。
理论上来说,即使把采样次数再加倍,仍然略小于100 ksps,但考虑到每次A/D中断后,中断服务程序都要简单地处理采样数据,然后才能启动下一次A/D转换,所以实际转换速度是不能达到100 ksps的。通过调试,合理的选择为每个采样点取1 024次平滑滤波。实际处理中,把A/D转换精度从12位扩展到了16位,以满足0~2 000 mV范围内0.1 mV的精度和稳定性,这可以简单地通过改变求均值时的右移次数和扩大满量程基数来完成[2]。
除了实时性的要求必须得到严格满足外,有很多因素需要一并考虑。例如每个通道测试过程中的图形动态显示,通道测试过程中的键值交叉处理,独立于测试之外的实时温度控制,耗时较多的测试结果传送和打印等,这些人为操作和功能处理均不能中断,或影响正在测试的通道每0.1 s一次的数据采样。另外,还必须提供一项强制结束功能:在任何测试阶段按Exit键,确认后可退出所有测试。
总体上看,系统在各个通道共用1个键盘、1个串口、1个打印机、1个LCD显示器、1个A/D转换器的条件下,能进行独立控制,互不干扰地进行各自的测试、计算、显示、传送和输出,满足实时性和并行性要求。
2 内存分配
μC/OS-II最多有64个任务优先级,根据版本的不同可供用户使用的优先级有56~62个。每个任务优先级不能相同,最大优先级数即用户的最大任务数[1]。
每个任务必须有自己的任务栈,任务栈由系统物理栈拷贝区和vwin 栈两部分组成,是μC/OS-II移植的核心内容[3-4],因此任务的多少直接影响数据RAM的需求。在本系统中,物理栈拷贝区为64字节,模拟栈可以安排16~64字节,这种可变大小任务栈稍后分析。
C8051F120微处理器带有8 KB的RAM,系统没有扩展外部数据存储器。首先分析一下系统数据内存的需求情况:
① 系统显示部分使用了240×128点阵的LCD模块和图形GUI,如果建立完整的显示缓冲区,需要240/8×128=3 840字节,约4 KB的RAM空间;
② Flash除了用作程序存储器外,剩下的部分作为非易失性数据存储器。每个Flash块为1 KB,故需要1 KB的RAM作为Flash的读写缓冲区;
③ GUI自身约占去了1 KB内存;
④ 传感器响应曲线需打印,其点坐标数据(对应打印机384个点行)约占1 KB。
以上共占用7 KB,最终仅剩下1 KB的RAM留给操作系统以及用户函数,这显然不够。可通过优化GUI的驱动,去掉LCD的显示缓冲区,增加4 KB的RAM,以满足系统需要。
3 可变任务栈和任务划分
3.1 可变大小任务栈
一般来说,一个嵌入式系统中不会建立60个以上的任务。从简单的角度考虑,应用μC/OS-II时,建立的所有任务均驻留不删除,使用足够大并且同样大小的堆栈。这种处理任务的方法会对内存数量提出较高要求。例如50个任务,每个任务栈均为128字节,则需要6 KB的RAM。
本着这样一个原则来改进任务划分:不需要驻留的任务运行完毕即删除,可以自身删除,也可以在别的任务里删除。这种任务处理不仅可以重用任务优先级,还可以重用任务堆栈,并且减少同时运行的任务数量。
删除任务的办法不仅可获得共享的任务栈资源,而且也是一个方便中止某些应用程序的手段,前面提及的任何测试阶段按Exit键退出所有测试的动作,就可以依靠删除任务来实现。
把任务看作有大小的,这里的“大小”指需要“重入”到任务堆栈的变量的数量。如果每一个任务都使用相同大小的任务栈,对小任务而言显然是在浪费宝贵的RAM,大小可变的任务栈才是经济合理的。
首先,在os_cfg.h头文件中增加大、小两个宏参数,如果需要更多不同的栈大小,还可以继续增加参数。
#define MaxStkSize128//大任务栈
#define MinStkSize80//小任务栈
然后定义任务栈时使用它们:
OS_STK TaskDelStk1[MinStkSize];
OS_STK TaskDelStk2[MaxStkSize];
TaskDelStk1和TaskDelStk2是两个可删除任务所使用的任务栈,一个80字节,另一个128字节。
接下来是关键,要在任务堆栈初始化函数OSTaskStkInit中进行处理:定义一个全局整型变量STK_SIZE,创建任务前给它赋值,任务创建时执行的OSTaskStkInit函数用它来初始化模拟栈的长度。
修改前,OSTaskStkInit中的?C_XBP仿真堆栈指针(即模拟栈指针)的初始化语句如下:
*stk++ = (INT16U) (ptos + MaxStkSize) >> 8;//MaxStkSize是固定的栈长度
*stk++ = (INT16U) (ptos + MaxStkSize) & 0xFF;
注意:ptos是任务栈栈底,模拟栈从任务堆栈的另一头开始。
修改后变为:
*stk++ = (INT16U) (ptos + STK_SIZE) >> 8;//指针高8位
*stk++ = (INT16U) (ptos + STK_SIZE) & 0xFF;//指针低8位
创建任务前要给STK_SIZE赋予堆栈定义匹配的栈长度值,否则,会因为堆栈不能正确初始化导致任务崩溃。不同大小的任务栈结构如图2所示,可以看出区别在于模拟栈不同。
利用可变大小任务栈创建一个小堆栈任务,示例如下:
STK_SIZE=MinStkSize;//提供堆栈大小值
/*创建带TimeLimit参数的,分配有任务栈TaskDelStk1,优先级为10的任务OverTime*/
OSTaskCreate(OverTime,(void*)&TimeLimit, &TaskDelStk1[0],10);
3.2 系统任务划分
用户任务分为两类: 一类任务是常驻任务,即创建后不用删除的任务;另一类是可删除任务,运行时建立,运行完删除,该任务资源又可用来创建新的任务。
常驻任务有以下几个:
① 主任务。用于控制程序流程,使用大任务栈,相当于主函数。
② 温度检测任务。使用小任务栈,系统初始化后创建,并一直运行;每秒检测一次温度,根据两个温度传感器的状况调整加热时间占空比和启停散热风扇,维持37 ℃恒温。
③ 键盘扫描任务:使用小任务栈,自检完毕后创建。等待接受键盘中断程序发出的信号量,进行处理后发出键值到消息邮箱,供其他任务使用。需要这个键盘任务处理键值,而不是直接在键盘中断服务程序中处理的原因是: 要尽可能缩短中断服务时间。
④ 旋转光标指示器任务: 使用小任务栈。该任务在自检和通道测试时起过程指示作用,当过程进行时,光标旋转,过程结束则光标停止。因为在主任务的自检和通道测试程序中均使用它,故采取常驻任务的形式,通过定时判断4个标志位来决定是否旋转1~4个光标。
常驻任务的堆栈共使用了128+80×3=368字节RAM。
可删除任务根据任务函数的复杂性,也使用了大堆栈和小堆栈两种结构,共8个任务。
① 超时检测任务:使用小任务栈,用于检验某一过程是否超时。
② 日期时间显示:使用小任务栈,在主菜单中定时显示日期时间。离开主菜单后即可删除,回到主菜单再重新建立。
③ ADC0任务:使用小任务栈,每隔100 ms扫描4个测试通道的A/D值,并发送至相应通道的消息邮箱,通道接收消息指针,获得此A/D值。 这3个小堆栈任务不会同时运行,因此共享同一个任务堆栈。
④~⑦ 通道1~4的测试任务:使用大任务栈,4个任务对应4个通道的并行测试。
⑧ 数据打印和传送任务:使用大任务栈,因为涉及较多的局部变量。如果直接在通道测试任务中进行打印和传送,那么数据打印和串口传送的时间之和可能会超过100 ms;特别是图形点阵打印,有较多的数据发往打印机。这种情况违背了每次100 ms的精确采样时间要求,丢失了数据,所以单独安排为一个任务在后台运行。
可删除任务栈共使用80+128×5=720字节RAM。
总的任务共有12个,使用的任务栈空间只有大约1 KB。
4 任务优先级分配
优先级的合理分配围绕实时性要求进行,因为可用优先级较多,所以任务优先级之间可取较大间隔。这样当有更多的任务加入时,能安排在这些间隔中,不用改变已有任务的优先级。本系统中优先级关系可以按由高到低顺序依次排列: 键盘扫描任务,超时检测任务, A/D扫描转换任务,主任务,各通道测试任务,数据打印和传送任务,温度检测任务,日期时间显示任务,如图3所示。
常驻任务一般是固定优先级,而可删除任务每一次创建时却能够对应不同的任务函数和优先级,且使用同一个任务栈。
一个任务函数可用于多个任务,这种“重用”的特性在获得任务并行性的同时,最大限度地节约了代码。本系统只有一个测试函数,却构成了4个并行的通道任务。这些任务都是进入测试菜单后随着各自通道键被按下后在主任务中创建的,复用的4个测试函数接受任务创建时主任务传递过来的参数,为本通道的计算、显示、数据存储等一系列工作服务。
5 资源共享的处理
ADC0、LCD、打印机和串口等资源被4个测试任务所共享。资源共享有一些经典的方法(如信号量、全局变量等),需要根据实际需求灵活运用。本系统的处理如下:
① 对于ADC0,在A/D扫描转换任务中每0.1 s轮流扫描4个通道一次,产生4个邮箱消息发往各自通道任务,以读取对应的转换结果。0.1 s的精确定时由定时器中断获得,中断服务发出信号量启动AD任务。一次扫描完成后A/D扫描转换任务挂起,直至下一次信号量到来。
② 对于LCD,情况要复杂一些,每个测试任务都要在LCD的各自区域显示,但是LCD不能实现2个以上位置同时进行显示处理,所以当涉及显示过程时需要禁止任务切换,否则会造成显示混乱。例如,当一个低优先级任务的显示工作还没有完成时,若发生了任务切换,则高优先级任务去进行另一个显示。当重新切换回低优先级任务后就没有办法继续未完的显示,因为显示位置已经被高优先级任务所改变。
③ 对于打印机和串口,也有类似的问题,不允许一个结果尚未打印完成又去打印另一个结果。禁止任务切换的办法不适合这里,因为打印和传送耗时比LCD显示长很多,禁止任务切换会影响到测试的实时性,它们单独建立后台任务的原因也在于此。
数据打印和传送任务循环扫描各个通道的请求服务标志,当标志有效时即为该通道服务,服务完毕清除该标志,因数据打印和传送任务的优先级较低而不会对其他测试任务造成任何影响。被服务的通道测试任务循环检测请求服务标志,检测到标志被清除后才可以继续下次测试,表示此次数据已经输出。其他高优先级测试任务不会影响打印过程,因为先检测到标志的通道独占资源,直至传送和打印完成后,打印任务才去检测下一个标志;高优先级任务的输出请求要等低优先级输出完毕才能得到响应,优先级反转在这里是正确的。
6 结论
本文从嵌入式系统的并行程序出发,结合实时性的要求,讨论了μC/OS-II操作系统环境下的任务划分和优先级确定的相关问题,提出了一些在μC/OS-II中用于减少资源耗用和同时运行的任务数量的改进措施。实践证明,这些措施可以充分发挥μC/OS-II小巧精悍的特点,提高程序代码的重用性,节约宝贵的RAM资源。本系统与采用双通道测试、没有RTOS、采用前后台方式解决并行问题的方法比较[5],后者占用了54 KB代码,在人机界面和并行度上均不及前者。前者在加入了μC/OS-II和GUI,并能进行四通道测试的情况下,仅有59 KB代码;排除更大的汉字库容量后,实际代码大小几乎与原来相同,性能却有了很大的提高。
评论
查看更多