Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1347349
  • 博文数量: 206
  • 博客积分: 10571
  • 博客等级: 上将
  • 技术积分: 2610
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-30 11:50
文章分类
文章存档

2014年(1)

2013年(4)

2012年(18)

2010年(14)

2009年(31)

2008年(3)

2007年(135)

分类: LINUX

2007-05-18 22:50:46

          uC/OS-II在C8051F020单片机上的移植

1 uC/OS-II的版本和C8051F020单片机的集成开发环境
µC/OS-II内核使用了V2.52版本。虽然Silicon Lab公司免费提供C8051F系列单片机的集成开发环境,由于使用习惯,笔者还是选择了µVision2 V2.38A版本,C编译器版本是C51.exe V7.06,汇编器的版本是A51.exe V7.07。适配器使用Silicon Lab公司的EC2,需要说明的是,要在Keil uVision2 IDE中调试C8051F系列单片机,必须安装动态链接库。
2 uC/OS-II在C8051F020单片机上的移植
移植工作就是更改OS_CPU.H、OS_CPU_C.C、OS_CPU_A.ASM这几个文件。在说明这几个文件之前,先说明两点注意事项。(1)可重入函数。单片机内部堆栈空间有限,C51提供一种压缩栈的方式,当递归调用这个函数时,会导致变量被覆盖,所以在实时应用中,要用关键字reentrant将函数声明成可重入函数,把每次函数调用时的局部变量单独保存起来。但函数中不可以使用BOOL变量,因为在LARGE编译模式下,KEIL默认将所有变量定位到外部RAM的最高处,而MCS-51系列的MCU中只有内部20H-2FH的地址可以位寻址。(2)C51的关键字和uC/OS-II定义变量的矛盾。“pdata”、“data”在uC/OS-II中用做一些函数的形参,但它同时又是C51的关键字,在编译时导致语法错误,通过把“pdata”改成“ppdata”,“data”改成“ddata”解决了此问题。
2.1 OS_CPU.H文件
这个文件主要是与处理器相关的宏定义和数据类型声明。如前面提到的,不能使用bit型变量,把BOOLEAN型定义成unsigned char型。另外8位MCU数据宽度和堆栈宽度都是8位,分别将OS_STK和OS_CPU_SR定义成unsigned char型。uC/OS-II提供了3种处理临界代码的方法,这里使用第一种,即通过对寄存器EA开关中断。MCS-51系列MCU的堆栈是从下向上递增的,定义OS_STK_GROWTH = 0。
2.2 OS_CPU_C.C文件
这个文件主要是完成OSTaskStkInit()。在uC/OS-II中,任务是一个无限循环,任务之间也不会互相调用,但是uC/OS-II总是执行优先级最高的任务,假定当前有一个更高优先级的任务进入就绪状态,为了保证原来低优先级任务的完整性,uC/OS-II为每个任务建立了任务堆栈,就相当于函数调用时保存返回地址和参数一样,用来保存当前任务的状态,保证任务切换能和函数调用一样正确。只不过函数调用时函数堆栈的操作过程是编译器自动完成的,而任务切换时需要模拟一个和编译器类似的任务堆栈的操作过程。实际上,uC/OS-II的移植工作主要就是解决这个问题,OSTaskStkInit()完成任务栈初始化,后面介绍的OS_CPU_A.ASM文件完成3种不同条件下的任务切换操作。
图1列出了应用任务堆栈和MCS-51单片机的系统堆栈结构,左边是MCS-51单片机的系统堆栈,右边是应用任务的堆栈。MCS-51单片机系统堆栈是满递增栈,定义MCS51Sp为系统堆栈指针,定义MCS51SpTop为系统堆栈在内部RAM块的起始地址。初始化时把堆栈指针指向0x60H地址,即MCS51Sp = MCS51SpTop = 0x60H。需要保存的寄存器共有13个,应用任务入栈操作具体过程如下:(1)、把所有的寄存器压入系统堆栈;(2)、计算系统堆栈的深度(SP- MCS51SpTop),并且保存到任务堆栈的栈顶;(3)把系统堆栈所有的内容保存到任务堆栈,这里既包括在(1)中入栈的13个寄存器也包括程序运行时需要保存的参数(斜线填充部分)。应用任务出栈操作具体过程如下:(1)、从任务堆栈的栈顶得到要进行出栈的深度;把任务堆栈所有内容恢复到系统堆栈;(2)、把13个寄存器出栈(还剩下程序指针PC);(3)、执行RETI命令,把系统堆栈中的地址恢复到程序指针PC。
错误!链接无效。           图1任务堆栈和系统堆栈
OSTaskStkInit()的函数原型为 OS_STK *OSTaskStkInit(void (*task)(void *pd),void *pdata, OS_STK *ptos, INT16U opt);其中task指向应用任务的起始地址,pdata是应用任务传递的参数指针,ptos是任务堆栈的栈顶指针,需要说明的是,MCS-51单片机的堆栈是递增的,所以这个指针应该指向任务堆栈的低地址,在OSTaskCreat()中调用这个函数时opt没有意义,将opt设置为0。OSTaskStkInit()中,主要保存新建任务栈的起始地址(2个字节),也就是在图1右半部分的虚线部分,由于任务还没有被调度,MCU的状态字并不具有实际意义。
2.3 OS_CPU_A.ASM文件
这个文件包括4个汇编语言函数。OSStartHighRdy();OSCtxSw();OSIntCtxSw();OSTickISR()。Keil编译器不支持插入行汇编代码,需要写这4个汇编函数,如果编译器支持插入行汇编代码,就可以将所有与处理器相关的代码放到OS_CPU_C.C文件中(ARM和DSP的移植就是这样实现的)。
2.3.1 OSStartHighRdy()
OSStart()函数调用OSStartHighRdy(),使就绪态任务中优先级最高的任务开始执行,OSStartHighRdy()移植代码如下:
  RSEG ?PR?OSStartHighRdy?OS_CPU_A   ; RSEG 选择已经在SEGMENT定义的段          
OSStartHighRdy:                  
   USING    0                   ; 选择工作寄存器组
  LCALL _?OSTaskSwHook
   MOV R0, #LOW(OSRunning)  
   MOV @R0,#01                 ; OSRunning = TRUE
OSStartHighRdyRpt:      
MOV   R0,#LOW (OSTCBCur)         ; 取TCB指针OSTCBCur。在单片机C语言中,
INC   R0                   ; 指针占用3个字节,低字节是指针存储类
  MOV   DPH,@R0                 ; 型编码,第二、第三字节分别存放指针高    
  INC   R0                   ; 位、低位地址偏移量。  
  MOV   DPL,@R0
  INC   DPTR                   ; 装载OSTCBStkPtr地址到DPTR                        
  MOVX A, @DPTR            
  MOV   R1,A
  INC   DPTR
  MOVX A, @DPTR
  MOV   R0,A
  MOV   DPH, R1
  MOV   DPL, R0
  MOVX A, @DPTR                 ; 出栈第一个字节,即系统堆栈深度
  MOV   R4,A
  MOV R0,#OSStkStart
COPY_STK_TO_SYS:                 ; 把任务堆栈的数据恢复到系统堆栈
  INC   DPTR
  INC   R0
  MOVX A, @DPTR
  MOV   @R0, A
  DJNZ R4, COPY_STK_TO_SYS
  MOV   SP, R0                 ; 调整系统堆栈指针
  POPAll
RETI
2.3.2 OSCtxSw()
这个函数完成任务切换。包括3个步骤:(1)把系统堆栈保存到当前任务堆栈;(2) 找出优先级最高的任务;(3)把高优先级任务的堆栈恢复到系统堆栈。在写这部分代码时,只需写前两部分,第3部分的内容和OSStartHighRdy()几乎完全一样,跳转到OSStartHighRdyRpt开始的部分就可以了。
2.3.3 OSIntCtxSw()
OSIntExit()通过调用OSIntCtxSw(),在ISR中执行任务切换功能。因为OSIntCtxSw()是在ISR中被调用的,这时处理器寄存器已经被保存到任务堆栈。所以只要一条LJMP   指令跳转到OSCtxSw()函数的步骤(2)的入口地址就可以了。
2.3.4 OSTickISR()
  时钟节拍中断服务子程序OSTickISR()完成的操作和OSCtxSw()类似,只不过OSTickISR()是由硬件定时器溢出中断触发的,定时器使用了C8051F020单片机的T0,时钟节拍设置为每秒20次,外部的22.1184MHZ晶体经过2分频 作为T0基准,只要在退出这个函数之前加上如下2条指令即可。
  MOV   TH0, #0x2C
  MOV   TL0, #0x12
3 测试移植代码
创建的2个测试任务及源码如下:
OSTaskCreate(TestTransplantA, (void *)0, & TestTransplantAStk[0],2);
OSTaskCreate(TestTransplantB, (void *)0, & TestTransplantBStk[0],3);
void TestTransplantA(void *ddata) reentrant
{   ddata=ddata;
for(;;)
{   Uart0Send(0xAA);
    OSTimeDly(60*OS_TICKS_PER_SEC);  
  }
}
void TestTransplantB(void *ddata) reentrant
{   ddata=ddata;
for(;;)
{   Uart0Send(0xBB);
    OSTimeDly(30*OS_TICKS_PER_SEC);  
}
}
多任务调度开始后,通过超级终端接收的UART0的数据为:AA BB BB AA BB BB AA BB BB AA BB BB AA BB BB AA BB BB AA ……。
  高优先级的任务TestTransplantA()能首先被调度运行,说明OSTaskStkInit()和OSStartHighRdy()函数是正确的。任务TestTransplantA()和任务TestTransplantB()由时钟节拍驱动而周期地被调度,说明OSCtxSw()、OSIntCtxSw()、OSTickISR()也是正确的。通过以上两点可以认为移植结果是正确的。
4 结束语
在µC/OS-II平台下开发程序,首先要掌握内核。通过上述移植过程,能够对任务堆栈、任务调度有深刻理解。即使有已经移植成功的版本,还是建议自己动手移植,这样有助于更深刻的理解任务的调度过程,而且可以积累一些嵌入式软件调试的经验。

阅读(1200) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~