引言
μC/OSII是一种公开源代码、结构小巧、具有可剥夺实时内核的实时操作系统[1]。绝大部分代码是用C 语言编写的,便于移植到各种内核上。μC/OSII要求用户提供定时中断来实现延时与超时控制等功能,这个定时中断叫做时钟节拍。时钟节拍率越高,系统的额外负荷就越重,而且会随着任务数的增加而增加。
本文提出一种基于硬件的延时机制,利用类似于计数器阵列PCA(Programmable Counter Array)的硬件机制[2],对任务延时进行管理。这样不但提高了计数精度,降低了系统负荷,而且系统的开销不会随着任务数的增加而增加。
1对μC/OSII系统中时间管理的介绍
μC/OSII系统中时间管理的主要功能是任务延时,在μC/OSII 2.83及以后的版本中,加入了对软件定时器的支持。任务延时与软件定时器都是由时钟节拍驱动的,其区别是:软件定时器是对函数执行的定时,它有专门的定时器控制块与操作函数,使用了一种类似于信号量集的处理方式;而任务延时主要用于当任务执行的某些条件不满足而被挂起或自身挂起时,对任务的定时。
μC/OSII系统的时钟节拍主要是用来实现时间的延时和确认超时的,节拍率一般在10 Hz到100 Hz之间,时钟节拍率越高,系统的额外负荷就越重。μC/OSII中的时钟节拍中断是通过在中断服务子程序中调用时钟节拍函数OSTimeTick()实现的。如果使用软件定时器,则时钟节拍中断函数中调用OSTimeTickHook ()函数驱动软件定时器。时钟节拍函数OSTimeTick()中的工作主要是扫描每一个任务控制块中的时间延时项OSTCBDly,完成任务的延时。由于OSTimeTick()要对每个任务都进行一遍同样的工作,因此它的运行时间和任务数成正比[3]。
μC/OSII 2.86中与任务延时有关的函数包括(不包括软件定时器相关函数)3类:任务延时设置函数、延时结束处理函数与任务恢复函数(延时没结束而被别的任务恢复)。与任务延时设置相关的函数有:OSTimeDly()、OSTimeDlyHMSM()、OSEventPendMulti()、 OS_FlagBlock()、 OSMboxPend()、OSMutexPend()、OSQPend()、OSSemPend()等;与任务恢复有关的函数:OS_EventTaskRdy()、OS_FlagTaskRdy()、OSTaskDel()、OSTaskResume()、OSTimeDlyResume()等;时钟节拍处理函数:OSTimeTick(),当然还包括时钟读取与设置函数OSTimeGet()和OSTimeSet()[4]。
2硬件延时
2.1硬件延时的介绍
如果处理器内核中集成一个PCA(如同C8051F120中的PCA0),PCA包含一个16/32位计数器,一定数量的16/32位比较模块,当计数器中的值与比较模块中的值相等时,置位该比较模块匹配标志,并产生比较匹配中断。PCA计数器具有以下功能:当使能PCA,计数器从0开始向上计数,而且是循环计数;当关闭PCA,计数器硬件清零。
PCA应用到μC/OSII时间延时中,把比较模块从0到N编号。根据比较模块的个数(决定最多可以有几个任务同时延时)、处理器的字长(即数据总线的宽度)与具体应用(系统中任务的总数),来定义类似于任务就绪表的硬件定时器比较模块使用情况表PCA_OSUsedGrp、PCA_OSUsedTbl[]。如果某一个模块被使用,则对应的情况表中的相应位清零(1表示比较模块空闲),而且使用比较模块时,优先使用编号小的空闲比较模块。定义一个数组PCA_OSPrio[N]存储占用此比较模块的任务优先级,在任务控制块中定义变量PCA_Num存储该任务使用的比较模块编号,并且PCA_Num的初始值为OS_LOWEST_PRIO,表示任务不处于延时中。
2.2计数器阵列PCA使用方法
2.2.1初始化PCA
设置PCA的计数频率,由于这种PCA不需要每个时钟节拍对各个任务扫描,因此可以设置PCA的频率高一些,比如1 kHz,以提高延时的精确度。
2.2.2修改与任务延时设置相关的函数
在以上提到的与任务延时设置相关函数中,在其代码OSTCBCur->OSTCBDly = ticks(或timeout)后面加入PCA设置函数PCASet (ticks),也就是延时的设置函数。例如OSTimeDly()函数修改成如下形式:
voidOSTimeDly (INT16U ticks) reentrant{
if (ticks > 0) {
……//参考μC/OSII 2.86版的源代码
OSTCBCur->OSTCBDly = ticks;
PCASet(ticks); //设置PCA0比较器中的值,即定时值
……
}
}
PCASet()函数的流程如图1所示。
图1函数PCASet()流程
图1中,获取一个空闲计数比较模块PCA_MinNum的方法与从就绪表中获取最高优先级任务的方法相同,形式如下:
PCA_Y = OSUnMapTbl[PCA_UsedGrp];
PCA_X = OSUnMapTbl[PCA_UsedTbl[PCA_Y]]);
PCA_MinNum = (INT8U)((PCA_Y<<3) + PCA_X;
标记占用此比较模块:
if ((PCA_UsedTbl[PCA_Y] &= ~(1<<PCA_X)) == 0)
PCA_UsedGrp &= ~(1<<PCA_Y);
计算比较模块中的设定值,假设PCA计数器是32位(如ARM系列),读取PCA计数器当前值为PCA_ValueCur,ticks为延时值,计算比较模块中设定值的方法如下:
if(ticks+PCA_ValueCur<0xFFFFFFFF )
PCA_SetValue=PCA_ValueCur+ticks;
else
PCA_SetValue=PCA_ValueCur-(0xFFFFFFFF-ticks);
2.2.3PCA中断函数
对任务延时的处理不需要在时钟节拍中断函数中处理,因此时钟节拍中断函数不用调用OSTimeTick()函数。当某一任务延时结束后,产生PCA中断并在中断函数中完成任务的恢复工作。PCA中断函数的流程如图2所示。
图2函数PCA中断函数流程
由图2可以看出,有可能几个比较模块同时产生中断,也就是有几个任务同时延时结束,因此,需要在PCA的中断函数中处理所有的比较模块中断。首先从比较模块中断标志寄存器中找出产生中断的比较模块编号,这个过程根据不同的硬件有所不同,在这里假设中断标志是如同就绪表那样利用硬件来实现的,那么就可以利用在就绪表中查找最高优先级任务的方式来找出产生中断的比较模块编号。否则,要对各个中断标志寄存器扫描查找。清除比较模块使用表中的方法也是类似,即相应位置1。
如果比较模块使用情况表也可以用硬件来实现,那么中断的处理就更快,系统的实时性更高。这样延时的处理时间不随任务的增加而增加,而仅仅与同时延时完成的任务数有关。
2.2.4修改与任务恢复有关的函数
修改与任务恢复有关的函数和修改与任务延时设置相关的函数类似,在代码ptcb->OSTCBDly=0后面加上PCAResume(ptcb)。函数的执行过程为:由任务控制块中的变量PCA_Num得到比较模块的编号,并对变量赋值OS_LOWEST_PRIO(表明任务没延时),清比较模块情况使用表中的相应位。最后关闭此比较模块的比较功能与中断功能,并判断是否有比较模块在用,如果没有则关闭PCA计数器。
最后,在μC/OSII中,任务优先级修改函数OSTaskChangePrio()虽然与延时无关,但数组PCA_OSPrio[N]中存储了任务的优先级,因此任务优先级改变时,数组中相应元素中的值也要改变。则在函数OSTaskChangePrio()中加入如下代码:
if (ptcb->PCA_Num!=OS_LOWEST_PRIO)
//如果任务处在延时中
PCA_OSPrio[ptcb->PCA_Num]=newprio;
//PCA_OSPrio[]重新赋值
从理论上来看,这种硬件延时机制可以大大降低系统的开销,如果不使用软件定时器,可以关掉时钟节拍,或者PCA中的比较模块比较多,也可以分配给软件定时器使用。
3硬件延时在C8051F120上的实现
3.1C8051F120简介
C8051F系列器件使用Silicon Labs的专利CIP51 微控制器内核,与MCS51指令集完全兼容,可以使用标准803x/805x 的汇编器和编译器进行软件开发。
C8051F120的主要特征包括:高速、流水线结构的8051兼容的CIP51 内核,时钟频率最大100 Hz;真正12位100 ksps 的ADC,带PGA 和8 通道模拟多路开关;两个12 位DAC,具有可编程数据更新方式;2周期的16×16 乘法和累加引擎等。特别是具有6 个捕捉/比较模块的可编程计数器/定时器阵列PCA,利用此计数阵列可以实现μC/OSII操作系统中的硬件延时,并且最大可以同时延时6个任务[2]。
3.2硬件延时在C8051F120上实现
① 设置PCA0定时器的时钟时基,在C8051F120单片机中(系统时钟使用外部晶振22.1184 MHz),选择定时器0溢出作为PCA0的计数脉冲。通过设置TIME0来确定计数阵列的计数频率。如果定时器0按16位方式计数,可以设定PCA0的计数频率为10 Hz;如果设置定时器0按8位自动重装载计数,PCA0的计数频率可设定为2 kHz(也就是计数周期为0.5 s)。最后PCA0中断使能。
② PCA0有6个比较模块,可以同时延时6个任务,那么比较模块使用表只用一个8位变量PCA_UsedGrp即可,并且PCA_UsedGrp初始值设为0x3F。
③ CIP51内核对寄存器与数据存储器高128位的访问是通过不同的访问方式来实现的。对寄存器直接访问,C语言中使用sfr或sfr16等关键字定义寄存器,因此对PCA0比较模块相应寄存器访问只能使用switch case的方式。而ARM各个存储器/寄存器的映射是不重叠的,可以使用如同STM32库[5]或LPC1700系列例程[6]中对寄存器的封装方式来访问各个寄存器。
4实验测试
对修改后的操作系统在C8051F120单片机上进行测试,本测试实验为:ADC0按一定的频率采样(采样完成产生中断),把采样得到的数据由任务1(优先级为1)显示到LCD屏上,而另一个任务0(优先级为0)用于扫描4×4的键盘。当ADC完成一个通道的采样后产生一次中断,并在中断中对任务0延时一段时间(中断级切换),切换执行任务1更新ADC的采样数据;另一方面如果有按键按下,任务0自己延时一段时间(任务级切换)去抖动,并切换执行任务1更新LCD上的按键值。
经实验测试,由于原先的μC/OSII系统的时钟节拍不能太大,当ADC采样结束后延时1个时间段,而如果ADC的采样频率太高的话,任务0一直处在挂起状态得不到执行,经过修改后的延时系统,由于提高了延时的精确度,可以延时一段比较精确的时间而不会加重系统的负荷,也不会出现任务0一直得不到执行的情况。 28
结语
随着技术的进步,处理器芯片的功能与综合性能不断提高,芯片的成本不断下降。如果处理器内部集成一个PCA(比较模块的数量可以根据实际情况而定),那么μC/OSII操作系统的延时处理就不会占用CPU太多的时间,系统的开销不会随着任务的增多与时钟节拍增大而增加,并且可以得到更高的延时精度。因此这种硬件延时机制具有很好应用价值。