在嵌入式应用中,使用RTOS的最主要原因是提高系统的可靠性,其次是提高开发效率、缩短开发周期。μC/OS-II 是一个基于优先级的抢占式实时内核,支持56 个用户任务,90%的代码使用标准的ANSI C语言书写,程序可读性强,移植性好,代码可固化,可裁剪,非常灵活。C8051F是美国Cygnal公司生产的与51系列兼容的微控制器,流水线指令结构70%的指令的执行时间为1个或2个系统时钟周期。当时钟频率为25MHz时,速度可达25MIPS,是一款不错的片上系统。
1 开发工具和运行环境
实现μC/OS-II的移植,要求所用的C编译器支持混合编程。KEIL C51可为众多的8051派生器件编程。我们选用的是KEIL7.02集成开发环境,仿真板基于C8051F015芯片。
2 移植中所需修改的文件
和CPU相关的文件主要有三个,分别是汇编文件OS_CPU_A.ASM、C语言文件 OS_CPU_C.C和头文件OS_CPU.H。
2.1 OS_CPU.H文件
OS_CPU.H文件中定义了数据类型及与硬件相关的基本信息。其中改动部分如下:
在C8051F中,堆栈都是按字节操作的,故数据类型OS_STK声明为8位。方向从低地址向高地址方向递增,所以OS_STK_GROWTH设置为 0。μC/OS-II在进入系统临界代码区之前要关中断,等到退出临界区后再打开,以保护核心数据不被多任务环境下的其它任务或中断破坏。开、关中断可通过设置SFR中的中断屏蔽位实现。在关中断时,先将IE的内容保存在全局变量IE_ SHADOW中,然后关中断;退出临界区时,还原IE_SHADOW的值。OS_TASK_SW()用来实现任务切换。就绪任务的堆栈初始化应该模拟一次中断发生后的样子,堆栈中应该按入栈次序设置好各个寄存器。OS_TASK_SW()函数模拟一次中断过程,在中断返回的时候进行任务切换。由于 C8051F015没有软中断,故直接定义宏OS_TASK_SW()为函数OSCtxSw()。
2.2 OS_CPU_A.ASM文件
编译器将每个文件作为一个模块,编译模块以主名命名,称为编译模块名,用NAME 来声明。因此,应在文件头部声明NAME OS_CPU_A。
函数有程序部分和局部变量部分,它们分别放在独立的段中。在大模式下,段名声明的固定格式为 ?PR?函数名?模块名 SEGMENT CODE。因此需要将OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()和OSTickISR()用上面的格式一一声明。如?PR?OSStartHighRdy?OS_CPU_A SEGMENT CODE,本模块实现的函数需要用PUBLIC声明,如PUBLIC OSStartHighRdy等。
C51将所有定义说明的数据标识符转换为大写字符,对函数则根据有无寄存器参数传送和函数是否可重入进行换名,如:void OSIntEnter(void) reentrant函数的名字OSIntEnter换成_?OSIntEnter。这些规则可从编译后的LST文件中看出。程序中声明引用的五个全局变量为OSTCBCur、OSTCBHighRdy、OSRunning、OSPrioCur、OSPrioHighRdy,声明格式是EXTRN IDATA (OSTCBCur)等。调用四个外部子程序OSTaskSwHook()、OSIntEnter()、OSIntExit()、 OSTimeTick(),固定格式为:EXTRN CODE (_?OSTaskSwHook)等。
由于C8051F的堆栈指针只有8位,只能指向内部数据区的256个字节,因此,当前运行的任务的堆栈在IDATA区,堆栈大小为40H(64字节),堆栈起点由KEIL决定。通过标号可以获得KEIL分配的SP起点,代码如下:
?STACK SEGMENT IDATA
RSEG ?STACK
OSStack:
DS 40H
OSStkStart IDATA OSStack-1
为简化子程序特定义压栈出栈宏。压栈的次序为PSW、ACC、B、DPL、DPH、R0~R7,出栈的次序与入栈相反。
PUSHALL MACRO
IRP REG, 《SW,ACC, B, DPL, DPH, 0, 1, 2, 3, 4, 5, 6, 7》
PUSH REG
ENDM
POPALL MACRO
IRP REG, 《7, 6, 5, 4, 3, 2, 1, 0, DPH, DPL, B, ACC, PSW》
POP REG
ENDM
具体函数的修改部分见本刊网络补充版(http://www.dpj.com.cn)。
2.3 OS_CPU_C.C文件
移植μC/OS-II 需要在OS_CPU_C.C中定义六个函数,而实际上需要定义的只有OSTaskStkInit()一个函数。该函数用来初始化任务的堆栈。初始状态的堆栈只须初始化?C_XBP (仿真堆栈指针)、任务地址及堆栈的长度。由于只有INC DPTR指令,故返回栈的最低地址,且最低地址处存放栈的长度,方便用汇编语言实现任务的切换。堆的大小可根据任务的实际情况自行确定,由参数 ppdata所指的值确定。
void *OSTaskStkInit (void (*task)(void *pd), void *ppdata,
void *ptos, INT16U opt) reentrant
{
OS_STK *stk;
INT8U HeapSize;
HeapSize=*(INT8U *)ppdata;
opt = opt;
stk = (OS_STK *)ptos+HeapSize+2;
*stk++ = 15;
*stk++ = (INT16U)task & 0xFF;
*stk++ = (INT16U)task 》》 8;
stk = (OS_STK *)ptos+HeapSize+2;
*--stk = (INT16U) (ptos+HeapSize-1) 》》 8;
*--stk = (INT16U) (ptos+HeapSize-1) & 0xFF;
return ((void *)stk);
}
3 可重入函数
因为51系列堆栈空间的限制, KEIL编译器没有像大系统那样使用调用堆栈。一般C语言调用过程中,会把过程的参数和使用的局部变量入栈。为了提高效率,编译器没有提供这种堆栈,而是提供一种压缩栈,每个过程被给定一个空间用于存放局部变量。过程中的每个变量都放在这个空间的固定位置,当递归调用这个过程时,会导致变量被覆盖。编译器允许将函数定义成可重入函数,由reentrant关键字指定,可重入函数可被单独保存。因为这些堆栈是模拟的,可重入函数一般都比较大,运行起来也比较慢。模拟栈不允许传递bit类型的变量,也不能定义局部位标量。移植中最好是将可能被多个任务使用的函数定义成可重入函数。
责任编辑:gt
全部0条评论
快来发表一下你的评论吧 !