实时多任务系统µC/OS-Ⅱ在DSP上的移植
扫描二维码
随时随地手机看文章
0. 引言
µC/OS-Ⅱ内核是一个强占式优先级调度的系统,能管理63个任务,支持旗语、信号量、互斥信号量、队列和消息邮箱,是一个是典型的嵌入式实时操作系统。它最早由Jean J. Labrosse创作,源码完全公开,已有众多应用范例,可靠性能得到保证,内核小,移植性好。TI的C2000系列DSP处理器 TMS320LF2407A片内集成16路10位AD,4个通用定时器,8个16位PWM通道,4个CAP捕获单元,41路I/O通道,以及SPI、 RS232、CAN等通信接口,丰富的片内资源,使得控制器不需任何扩展就能满足所有的功能要求,且冗余很少。
DSP处理器在数学运算方面的优势也为智能化过程所需的数据处理提供了支持。TMS320LF2407A指令采用4级流水线操作,最高能以40M的系统时钟工作,再加上合适的RTOS的调度,完全能保证系统的实时性。其开发系统TMS320C2XX Code Composer Studio满足µC/OS-Ⅱ的移植条件,因此,它是嵌入式计算机控制系统主控制芯片的一个较好选择。笔者在设计基于CAN的工程机械嵌入式智能显示仪时,选用2407A做主控制芯片,软件方面,将实时内核µC/OS-Ⅱ移植到该DSP 控制器TMS320LF2407A上,而应用程序是在µC/OS-Ⅱ内核基础上的一系列任务。
1. µC/OS-Ⅱ的移植
由于µC/OS-Ⅱ在设计时就已经充分考虑了可移植性,所以µC/OS-Ⅱ的移植相对来说比较容易。移植工作包括以下几个内容:(1)用#define设置一个常量的值(OS_CPU.H)(2)声明10个数据类型(OS_CPU.H)(3)用#define声明三个宏 (OS_CPU.H)(4)用C语言编写六个简单的函数(OS_CPU_C.C)(5)编写四个汇编语言函数(OS_CPU_A.ASM);即µC/OS-Ⅱ的移植要修改3个文件OS_CPU.H、OS_CPU_C.C和OS_CPU_A.ASM。其中汇编语言文件OS_CPU_A.ASM是可选择的,因为某些C编译器允许用户在C语言中插入汇编语言,所以用户可以将所需的汇编语言代码直接放到OS_CPU_C.C中。CCS的C编译器允许在 C语言中嵌入汇编语言,但是由于这种方式破坏了C语言的完整性,因此只提倡在程序开始系统初始化部分少量采用。而在C语言中嵌入实现某一完整功能的多句汇编语言时,就不提倡采用这种方法。所以,移植中还是对OS_CPU_A.ASM做了修改。
2. 编写移植代码
移植µC/OS-Ⅱ的主要工作是声明与硬件相关的数据类型,定义与中断有关的宏定义,定义堆栈增长方向宏定义,编写堆栈初始化函数,HOOK接口函数,任务级上下文切换函数,中断级上下文切换函数以及系统时钟定时服务函数等。
2.1 移植OS_CPU.H文件
(1)一个常量值。OSInit需要知道当OS_TaskIdle() 和OS_TaskStat( ) 函数建立任务时,堆栈的顶端地址在哪里;其次调用OSTaskStkChk( )时,µC/OS-Ⅱ需要知道堆栈的底端地址在哪里。所以需要指明堆栈的增长方向。绝大多数微处理器和微控制器的堆栈是从上往下递减的,但是也有某些处理器使用的是相反的方式。TMS320LF2407A的堆栈方向是从下往上增长的,所以: #define OS_STK_GROWTH 0;// 堆栈方向是从下往上增长
(2) 声明数据类型。µC/OS-Ⅱ考虑到通用性,在内核中使用了自定义数据类型,与编译器无关,这就要求移植时必须定义微处理器的数据类型与µC/OS-Ⅱ的数据类型相一致,保证移植后的µC/OS-Ⅱ在微处理器平台上运行,在移植中应将其声明为CCS编译器可识别的类型。这可以由OS_CPU.h头文件实现,程序如下所示。
typedef unsigned char BOOLEAN;/*定义ucos里的boolean为unsigned char*/
typedef unsigned char INT8U; /*定义ucos里的INT8U为unsigned char*/
typedef signed char INT8S; /*定义ucos里的INT8S为signed char*/
typedef unsigned int INT16U; /*定义ucos里的INT16U为unsigned int*/
typedef signed int INT16S; /*定义ucos里的INT16S为signed int*/
typedef unsigned long INT32U; /*定义ucos里的INT32U为unsigned long*/
typedef signed long INT32S; /*定义ucos里的INT32S为signed long */
typedef float FP; /*定义ucos里的FP为float*/
#define OS_STK INT16U /*堆栈入口宽度为16位*/
由于系统没有用到OS_CPU_SR类型数据,所以没有定义此数据类型。
(3)3个宏定义。µC/OS-Ⅱ在内核中通过禁止中断来保护临界区,因此,需要在C语言中插入禁止和允许中断的汇编代码,DSP里用SETC INTM来屏蔽中断,用CLRC INTM来使能中断。所以移植代码定义了下面两条宏定义:
#define OS_ENTER_CRITICAL() asm(" SETC INTM")
#define OS_EXIT_CRITICAL() asm(" CLRC INTM")
µC/OS-Ⅱ定义了三种保护临界区的方式,此移植版本采用的是最简单的第一种方法。此种方法就要求中断关闭的情况下不能调用µC/OS-Ⅱ的功能函数。这对于应用来说是可以接受的,所以就选择了此种模式。TMS320LF2407A支持多种中断方式,包括可屏蔽硬中断 INT1~INT6,不可屏蔽硬中断RESET和NMI_VECT,不可屏蔽软中断INT8~INT16和INT20~INT31以及中断陷阱TRAP。因此使用INT31软中断来调用OSCtxSw()来从任务堆栈中恢复处理器所用的寄存器。用INT2的定时器1周期中断来调用OSTickISR()。定义模仿INT31中断的宏,来跳转到INT31
#define OS_TASK_SW() asm(" INTR 31")
在中断向量表里的定义
.include f2407regs.h
.global _c_int0, _OSTickISR, RESET, _OSCtxSw,_GRIS5,_adint,_nothing
.asect "vectors",0
……
INT2 B _OSTickISR ; B _c_int2
……
INT31 B _OSCtxSw ; task switching service vector.
2.2 移植OS_CPU_C.H文件
µC/OS-Ⅱ的移植范例要求编写10个简单的C函数,但是真正必要的函数是OSTaskStkInit(),其他9个函数必须申明,但并不一定要包含任何代码。OSTaskStkInit()主要是对任务堆栈的初始化。TMS320LF2407A的堆栈与一般微处理器的堆栈不同,一般微处理器的堆栈由编程定义一块内存作为堆栈比较灵活,而TMS320LF2407A的堆栈,是在CPU内有8级深度的硬件堆栈,因此任务堆栈的初始化与一般微处理器的堆栈初始化不同。芯片本身的堆栈(以下简称US)只有8级,无法作为系统的堆栈使用,所以CCS编译器将CPU内部的两个寄存器AR0和AR1保留,AR1作为堆栈指针,AR0用作堆栈中临时变量指针FP。编译器将函数或中断压进US的返回地址,弹出放在SP(AR1)指向的堆栈中,并保存CPU 的工作环境,不同的是函数只保存程序要用到的寄存器,中断要调用I$$SAVE子程序,保存CPU所有的寄存器,返回时调用I$$REST子程序,恢复 I$$SAVE和I$$REST两个函数是µC/OS-Ⅱ操作系统移植到TMS320LF2407A上的基础,一定要很清楚后才能够成功移植 OSTaskStkInit()函数。[!--empirenews.page--]
2.3 移植OS_CPU_C.H文件
需要在该文件中编写4个汇编语言函数:(1)OSStartHighRdy():这是系统完成初始化后启动多任务运行时要调用的函数,主要功能是:将OSRunning标志置为TRUE,然后获取已建立的优先级最高任务的堆栈指针,并从其堆栈中恢复处理器寄存器,最后执行返回指令返回上述任务中运行该任务。(2)OSCtxSw():在本移植中,任务级任务切换用软中断intr31实现,OSCtxSw()即为该中断的中断服务程序。它先要将当前处理器寄存器压入当前任务的堆栈中,将当前堆栈指针保存到当前任务的任务控制块中;然后用与OSStartHighRdy()相类似的方法运行当前处于就绪态中优先级最高的任务。(3)OSIntCtxSw():该函数被OSIntExit()函数调用,用于在ISR中进行任务切换。它与OSCtxSw() 的区别在于无需对当前任务的工作现场进行保存,因为这一工作在进入ISR之时已经做了。(4)OSTickISR():用定时器产生一个周期为恒定值的时钟源提供给µC/OS-Ⅱ,这是µC/OS-Ⅱ时间延迟和超时功能的时间基准。OSTickISR()是该定时器周期中断的中断服务程序。它主要有两个功能:一个是调用OSTimeTick()函数,计算自系统上电以来所经历的时钟节拍数,并将每个处于延时等待状态任务的OSTCBDIy项减1;另一个是调用OSIntExit()函数查看是否有更高优先级的任务因时钟节拍到来而延迟时间到并进入就绪态,如果有,则进行中断级的任务切换。另外,在该函数的入口处要将OSIntNesting加1;在出口处将OSIntNesting减1。其中堆栈的构造,采用了系统库函数I$$SAVE和I$$RSET函数来保护/恢复现场、保护/恢复任务栈。时钟节拍TICK中断由实时时钟完成,但是2407A中没有此定时器,移植是采用T1的周期中断来实现的,时钟频率为10M,4倍频后CPU时钟为40M。系统初始化代码如下。
ldp #00e0h ;指向第224页(0x7000~0x707F)
splk #00e8h,WDCR ;不使能看门狗
splk #080feh,SCSR1 ;时钟4倍频
ldp #DP_EVA ;指向第232页(0x7400~0x747F)
splk #080h,EVAIMRA ;通用定时器1 周期中断使能
splk #0ffffh,EVAIFRA ;清中断标志 ;
splk #0,GPTCONA ;无控制操作
splk #4E20h,T1PER ; 定时器计数使能 ,周期为20000
splk #0,T1CNT ; 计数初值清0
splk #080Ch,T1CON ;TMODE=01 连续增/减计数模式,计数时钟不分频
必须在最高优先级任务中开启定时器,系统在优先级为0的任务里开启T1。为提高系统的实时性,设置T1每500us(20000/40M)产生一次T1周期中断,即TICK为500us,µC/OS-Ⅱ每500us做一次任务调度。
3. 移植代码测试
CCS是一个强大的集成开发环境,带有源码级的调试工具,按照Jean J.Labrosse推荐的移植代码测试方法和步骤很快完成了函数OSTaskStkInit()、函数OSStartHighRdy()、函数 OSCtxSw()、OSIntCtxSw()和OSTickISR()函数的验证工作。证实了移植代码是能正常工作的。为进一步测试其可靠性,又另外创建了15个任务,并用上了µC/OS-Ⅱ的信号量和邮箱同步机制,在每个任务里加上不同的发光二极管,经测试其实时性和稳定性都很好优异。
4. 任务调度过程中优先级翻转问题及解决
优先级翻转是即当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,而这个低优先级任务在访问共享资源时可能又被其它一些中等优先级的任务抢先,因此造成高优先级任务被许多具有较低优先级的任务阻塞,实时性难以得到保证。解决优先级翻转问题有优先级天花板和优先级继承两种办法。优先级天花板是当任务申请某资源时,把该任务的优先级提升到可访问这个资源的所有任务中的最高优先级,这个优先级称为该资源的优先级天花板。这种方法简单易行,不必进行复杂的判断,不管任务是否阻塞了高优先级任务的运行,只要任务访问共享资源都会提升任务的优先级。在µC/OS-Ⅱ中,可以通过 OSTaskChangePrio()改变任务的优先级,但是改变任务的优先级是很花时间的。如果不发生优先级翻转而提升了任务的优先级,释放资源后又改回原优先级,则无形中浪费了许多CPU时间,也影响了系统的实时性。优先级继承是当任务A申请共享资源S时,如果S正在被任务C使用,通过比较任务C与自身的优先级,如发现任务C的优先级小于自身的优先级,则将任务C的优先级提升到自身的优先级,任务C释放资源S后,再恢复任务C的原优先级。这种方法只在占有资源的低优先级任务阻塞了高优先级任务时才动态的改变任务的优先级,如果过程较复杂,则需要进行判断。µC/OS-Ⅱ不支持优先级继承,而且其以任务的优先级作为任务标识,每个优先级只能有一个任务,因此,不适宜在应用程序中使用优先级继承。
在µC/OS-Ⅱ中,为解决优先级翻转影响任务实时性的问题,可以借鉴优先级继承的方法对优先级天花板方法进行改进。对µC/OS-Ⅱ的使用,共享资源任务的优先级不是全部提升,而是先判断再决定是否提升。即当有任务A申请共享资源S时,首先判断是否有别的的任务正在占用资源S,若无,则任务A继续执行,若有,假设为任务B正在使用该资源,
则判断任务B的优先级是否低于任务A,若高于任务A,则任务A挂起,等待任务B释放该资源,如果任务B的优先级低于任务 A,则提升任务B的优先级到该资源的优先级天花板,当任务B释放资源后,再恢复到原优先级。在µC/OS-Ⅱ中,每个共享资源都可看作一个事件,每个事件都有相应的事件控制块 ECB。在ECB中包含一个等待本事件的等待任务列表,该列表包括OSEventTbl[]和OSEventGrp两个域,通过对等待任务列表的判断可以很容易地确定是否有多个任务在等待该资源,同时也可判断任务的优先级与当前任务优先级的高低,从而决定是否需要用 OSTaskChangePio()来改变任务的优先级。这样,仅在优先级有可能发生翻转的情况下才改变任务的优先级,而且利用事件的等待任务列表进行判断,比用OSTaskChangePio()来改变任务的优先级速度快,并占用较少的CPU时间,有利于系统实时性的提高。
参考文献:
[1]邬可军,朱铭锆等.DSP实时多任务操作系统设计与实现[M]. 北京:电子工业出版社, 2005.
[2]钟坚文,蔡旭.基于μC/OS-II的CAN总线驱动程序设计[J].微计算机信息,2005,7-2:35-37.[!--empirenews.page--]
[3]Jean J.Labrosse著.邵贝贝等译.嵌入式实时操作系统µC/OS-Ⅱ[M]第2版.北京,北京航空航天大学出版社,2003.5
[4]刘和平,王维俊等. TMS320LF240X DSP C语言开发应用[M]. 北京:北京航空航天大学出版社, 2003.
[5]TI.Code Composer User\'s Guide.SPRU509C.pdf