深圳中慧电子公司 CyberTeam 彭伟林
(1)实时系统和前/后台系统;
前/后台系统:一个大循环,循环查询各种标志位。如果标志位置位,就执行相应的服务程序。标志位就是标志事件的发生,事件响应延时处于不可预测状态。最坏的情况是循环中所有其他的事件服务程序执行完,才响应当前事件。中断服务虽然能即时/优先响应,但是它们和主循环的通讯,也是通过置主循环中相应的标志位来完成的。
实时系统(uCOS):整个程序分成一个个看起来好象是并行的任务,每个任务都在等待事件的发生。因为uCOS不支持时间片*转,除了最低优先级任务(在uCOS中是IDLE任务)是死循环以外,其他的任务都不能死循环,只能在驱动事件驱动下工作。任何驱动事件的产生,都使优先级最高的就绪任务运行。任务和任务/任务和中断的通讯,是通过相应事件驱动来完成的。
驱动事件:
不论是什么系统,CPU不可能一直在工作。CPU的工作是在各种驱动事件的驱动下工作的。CPU在完成一次驱动事件事件服务程序以后,进入IDLE模式等待新的驱动事件的发生。包括实时系统和前/后台系统都是在驱动事件的驱动下运行的。
按照uCOS中的观点,驱动事件分为三类:
1、事件 (Event)。包括信号量(Semaphores)、事件标志组(Flag)、邮箱(Message Box)、邮箱队列(Message Queue)。
2、时间(Time Tick)。包括时间延时和事件超时。
3、中断(Interrupt)。可以发出各种event。
由于第1种事件,通常都是在第2、3种状态下发出的,所以其实事件的驱动只有两种:时间(定时)和中断(各种异步中断)。
时间实际上也是中断的一种,可以说程序的驱动事件只有一种,就是:中断。
前/后台系统中还有一种驱动事件的产生,在主循环中不断的查询。有别与一般的定时查询,这种查询是为了将事件的响应时间降到最低,也可以将其归纳于定时(时间)事件。
(2)uCOS C51移植的准备工作;
2004年8月份,我在书城买了一本《uCOS-Ⅱ 第2版》,准备学习RTOS。因为以前没有玩过RTOS,在工作之余断断续续的看了3、4章。一直到12月初的时候,公司要重新设计一个项目,恰好要把uCOS移植到c51上。我的RTOS学习才正式开始。
因为对OS向往以久,我并不想在网上Down一个现成的移植OS程序,做一个OS的应用者。揭开OS的神秘面纱,了解OS的内部运行机制,这才是我想要做的。本文的主要目的是讨论uCOS的移植,希望对即将进行uCOS c51移植的兄弟有些帮助。对于OS的内部运行机制,由于东西比较多,在这里不想太展开。如果以后有时间,也想写一篇文章来讨论讨论。
最开始,我的计划就是看书,看《uCOS-Ⅱ 第2版》。看完这本几百页的大本本,花了我2个半星期。因为是工作需要,我才可以这样心安理得的在那里看呀看书^_^,辛苦呀9。在这期间,为了自己的思想不受别人的影响,我坚决没有从网上下任何uCOS的资料,我手头的资料就是uCOS-Ⅱ的书和附带光盘,这些就是最权威的资料了。在看书的时候,我都坚持做笔记,把每天的重点,明白的东西和心中的疑问都随时记录下来。对付这种大本本,前后的知识又相互关联,光靠我们的大脑是搞不定啊。
弄懂了uCOS的内核,下一本书应该是《单片机高级语言C51Windows环境编程与应用》。对于Keil C我还是很熟的,还是花了2、3天来复习。这里的重点是C51对汇编的转换结构,例于数据/系统堆栈的使用,C&Asm混合编程。我想对于任何CPU的uCOS移植,C语言的实现机制,你都是要了解的。这里也是要花大把时间的。
《uCOS-Ⅱ 第2版》和《单片机高级语言C51Windows环境编程与应用》这两本书网上都可以下电子档的,我这里也有(大家需要可以来信索取)。
uCOS和C51的书都看完了。我就下载了一堆uCOS的C51移植资料。其中的源程序有很多个版本的,不过详细的移植文档只有一个版本:巨龙一位大虾的"uCOS C51移植心得",相信很多人都看过。这些资料的作者都是我移植过程中的老师,有了这些资料,我才能把心中的朦胧想*变成源程序。但是我也发现这些资料中大多都有一些错误和遗漏,当然这是难免的。这也正是驱使我写这篇文章的原因,希望在前辈的基础上有所进步。欢迎大家来批评!真正的源代码移植,我花了大概一个星期时间。
(3)uCOS C51的移植概况;
1、工具:
uCOS 2.52版;
Keil C V6.23a。
2、uCOS V2.52的文件结构与移植所需要的修改:
A、与处理器无关的文件:
OS_CORE.C
OS_FLAG.C
OS_MBOX.C
OS_MEM.C
OS_MUTEX.C
OS_Q.C
OS_SEM.C
OS_TASK.C
OS_TIME.C
uCOS_II.C
uCOS_II.H
这些文件在c51的移植过程中,只需要给函数加上重入属性即可。
B、与应用相关的文件:
INCLUDES.H: 包含C51的标准库头文件;对"pdata"等c51关键字的重定义
OS_CFG.H: "OS_TICKS_PER_SEC"、" OS_FLAGS"注意可能需要修改。
C、与处理器相关的文件:
OS_CPU.H: 数据类型、关中断方*、任务堆栈方向、任务切换的宏定义都需要修改。
OS_CPU_A.ASM: OSTickISR()、OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()这几个函数的编写,是整个移植的关键。
OS_CPU_C.C:OSTaskStkInit()函数的编写。
(4)uCOS C51具体的移植过程;
1、C51的堆栈结构;
这是整个移植过程中的重中之重,所以特别详细介绍。
A、 系统堆栈;
c51中,系统堆栈的栈底地址是"?STACK",栈顶指针就是"SP"拉,栈的生长方向是向上的,栈空间分配在51的内部RAM(idata)中。"?STACK"分配在所有内部RAM
数据段的最后面,所以系统堆栈的范围是从?STACK到内部RAM的最高位(0x80或者0xFF)。
B、 数据堆栈;
c51中,由于我们使用OS,采用的LARGE编译模式,所以数据堆栈的指针是"?C_XBP", 栈的生长方向是向下的,栈空间分配在51的外部RAM(xdata)中。
C、 C51中断中堆栈的保护;
研究中断中堆栈的保护的意义在于,因为uCOS中的任务切换,本身就是模拟一次中断的发生:保护Task1的CPU寄存器,SP切换到Task2的堆栈,弹出Task2的CPU寄存器。用C51写中断函数的时候,编译器会自动保护CPU的寄存器,所以中断返回时任务调度OSIntCtxSw(),就不用重新保护寄存器。
C51中断中调用函数可以分为四种情况(中断函数本身不设为reentrant):
一、 没有函数调用;
二、 调用非reentrant函数,函数中没有嵌套调用其他函数;
三、 调用非reentrant函数,函数中嵌套调用其他函数;
四、 调用reentrant函数。
t0_isr:
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
MOV PSW,#00H
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3
PUSH AR4
PUSH AR5
PUSH AR6
PUSH AR7
用户代码
POP AR7
POP AR6
POP AR5
POP AR4
POP AR3
POP AR2
POP AR1
POP AR0
POP PSW
POP DPL
POP DPH
POP B
POP ACC
RETI
因为uCOS中所有的函数都必须是重入函数,因此我们只需要研究第四种情况下的堆栈保护,对于其他情况有兴趣可以在c51中看看。(注意:可能因为c51编译器的版本不同,上述压栈的顺序可能不同。)
2、uCOS C51任务切换时的堆栈操作;
每个任务都有一个独立的数据堆栈,系统堆栈是公用空间。
保护Task1的CPU寄存器: 首先将CPU寄存器按上例压进Task1系统堆栈,再将整个Task1系统堆栈压进Task1数据堆栈;
SP切换:?C_XBP = Task2 的数据堆栈栈顶地址。
弹出Task2的CPU寄存器:从Task2的数据堆栈重新恢复整个系统堆栈,然后再从Task2系统堆栈中恢复CPU寄存器值。
实现的方法有很多种,只要遵循uCOS任务切换的原理就可以了。
3、INCLUDES.H的移植;
重点:"pdata"是C51关键字,所以要重新定义
源代码:
#include
#include
#include
#include
#include
#include
#define TASK_REENTRANT large reentrant
#define KCREENTRANT large reentrant
#include "os_cpu.h"
#include "os_cfg.h"
#define data ucos51_data
#define idata ucos51_idata
#define pdata ucos51_pdata
#define xdata ucos51_xdata
#include "ucos_ii.h"
4、OS_CPU.H的移植;
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned int INT16U;
typedef signed int INT16S;
typedef unsigned long INT32U;
typedef signed long INT32S;
typedef float FP32;
typedef unsigned char OS_STK;
#define BYTE INT8S
#define UBYTE INT8U
#define WORD INT16S
#define UWORD INT16U
#define LONG INT32S
#define ULONG INT32U
5、OS_CPU_A.ASM的移植;
重点:
1.C51中断时积存器的入栈顺序
2.Asm中对" ?C_XBP"、" ?STACK'的引用
3.堆栈的压栈方向。堆栈是先加指针后存数还是先存数后加指针。
源代码:
NAME OS_CPU_A
;******************************************************************
; PUBLIC and EXTERNAL REFERENCES
;******************************************************************
?PR?_?OSStartHighRdy?OS_CPU_A SEGMENT CODE
?PR?_?OSCtxSw?OS_CPU_A SEGMENT CODE
?PR?_?OSIntCtxSw?OS_CPU_A SEGMENT CODE
?PR?_?OSTickISR?OS_CPU_A SEGMENT CODE
?STACK SEGMENT IDATA
DT?OS_CPU_A SEGMENT DATA
PUBLIC _?OSTickISR
PUBLIC _?OSStartHighRdy
PUBLIC _?OSCtxSw
PUBLIC _?OSIntCtxSw
PUBLIC OSTickRate
PUBLIC OSTickCtr
EXTRN DATA (?C_XBP)
EXTRN XDATA (OSTCBCur)
EXTRN XDATA (OSTCBHighRdy)
EXTRN XDATA (OSPrioCur)
EXTRN XDATA (OSPrioHighRdy)
EXTRN XDATA (OSRunning)
EXTRN CODE (_?OSIntEnter)
EXTRN CODE (_?OSIntExit)
EXTRN CODE (_?OSTimeTick)
EXTRN CODE (_?OSTaskSwHook)
;******************************************************************
; MACRO DEFINE
;******************************************************************
PUSHALL MACRO
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
IRP REG,
MOV A, REG
PUSH ACC
ENDM
ENDM
POPALL MACRO
IRP REG,
POP ACC
MOV REG, A
ENDM
POP PSW
POP DPL
POP DPH
POP B
POP ACC
ENDM
LoadXBP MACRO
MOV DPH, ?C_XBP
MOV DPL, ?C_XBP+1
ENDM
S**eXBP MACRO
;PUSH IE
;CLR EA
MOV ?C_XBP, DPH
MOV ?C_XBP+1,DPL
;POP IE
ENDM
LoadReg MACRO REG
MOVX A, @DPTR
MOV REG, A
ENDM
S**eReg MACRO REG
MOV A, REG
MOVX @DPTR, A
ENDM
;******************************************************************
; VARIABLE DEFINE
;******************************************************************
RSEG ?STACK
DS 1 ;
RSEG DT?OS_CPU_A
OSTickRate:
DS 1
OSTickCtr:
DS 1
;******************************************************************
; void _?OSStartHighRdy(void);
;******************************************************************
RSEG ?PR?_?OSStartHighRdy?OS_CPU_A
_?OSStartHighRdy:
LCALL _?OSTaskSwHook
; OSRunning = TRUE;
MOV DPL, #LOW(OSRunning)
MOV DPH, #HIGH(OSRunning)
S**eReg #01
LJMP ?C_XBP_Load
;******************************************************************
; void OSCtxSw(void);
;******************************************************************
RSEG ?PR?_?OSCtxSw?OS_CPU_A
_?OSCtxSw:
PUSHALL
SP_Offset:
LoadXBP
MOV A, SP
CLR C
SUBB A, #?STACK-1
MOV R5, A
INC A
CLR C
XCH A, DPL
SUBB A, DPL
JNC SP_S**e
DEC DPH
SP_S**e:
MOV DPL, A
S**eXBP
S**eREG R5
MOV R0, #?STACK-1
Stack_S**e_Loop:
INC DPTR
INC R0
S**eREG @R0
DJNZ R5, Stack_S**e_Loop
;POPALL
; OSTCBCur->OSTCBStkPtr = ?C_XBP;
MOV DPL, #LOW(OSTCBCur)
MOV DPH, #HIGH(OSTCBCur)
INC DPTR
LoadReg R0
INC DPTR
LoadReg DPL
MOV DPH, R0
INC DPTR
S**eReg ?C_XBP
INC DPTR
S**eReg ?C_XBP+1
LCALL _?OSTaskSwHook
; OSTCBCur = OSTCBHighRdy;
MOV DPL, #LOW(OSTCBHighRdy)
MOV DPH, #HIGH(OSTCBHighRdy)
LoadReg R0
INC DPTR
LoadReg R1
INC DPTR
LoadReg R2
MOV DPL, #LOW(OSTCBCur)
MOV DPH, #HIGH(OSTCBCur)
S**eReg R0
INC DPTR
S**eReg R1
INC DPTR
S**eReg R2
; OSPrioCur = OSPrioHighRdy;
MOV DPL, #LOW(OSPrioHighRdy)
MOV DPH, #HIGH(OSPrioHighRdy)
LoadReg R0
MOV DPL, #LOW(OSPrioCur)
MOV DPH, #HIGH(OSPrioCur)
S**eReg R0
?C_XBP_Load:
; ?C_XBP = OSTCBCur->OSTCBStkPtr;
MOV DPL, #LOW(OSTCBCur)
MOV DPH, #HIGH(OSTCBCur)
INC DPTR
LoadReg R0
INC DPTR
LoadReg DPL
MOV DPH, R0
INC DPTR
LoadReg ?C_XBP
INC DPTR
LoadReg ?C_XBP+1
SP_Load:
LoadXBP
LoadREG R5
MOV R0, #?STACK-1
Stack_Load_Loop:
INC DPTR
INC R0
LoadREG @R0
DJNZ R5, Stack_Load_Loop
MOV SP, R0
INC DPTR
S**eXBP
; OSTCBCur->OSTCBStkPtr = ?C_XBP;
POPALL
RET
;******************************************************************
; void OSIntCtxSw(void);
;******************************************************************
RSEG ?PR?_?OSIntCtxSw?OS_CPU_A
_?OSIntCtxSw:
DEC SP
DEC SP
DEC SP
DEC SP
LJMP SP_Offset
;******************************************************************
; void OSTickISR(void);
;******************************************************************
CSEG AT 000BH
LJMP _?OSTickISR
RSEG ?PR?_?OSTickISR?OS_CPU_A
_?OSTickISR:
LCALL ResetHwIntr
PUSHALL
;DEC OSTickCtr
;MOV A, OSTickCtr
;JNZ OSTick_End
;MOV OSTickCtr, OSTickRate
LCALL _?OSIntEnter
LCALL _?OSTimeTick
LCALL _?OSIntExit
OSTick_End:
POPALL
RET
ResetHwIntr:
RETI
END
6、OS_CPU_C.C的移植;
重点:
1.OSTaskStkInit函数的pdata参数在堆栈中怎么存放。
源代码:
OS_STK *OSTaskStkInit (void (*task)(void *pd) KCREENTRANT, void *pdata, OS_STK *ptos, INT16U opt) KCREENTRANT
{
INT8U * stk;
opt = opt; /* 'opt' is not used, prevent warning */
stk = (INT8U *) ptos; /* Load stack pointer */
/*
stk -= sizeof(void *);
*(void**)stk = pdata;
*/
/*
*--stk = 0x01;
*--stk = ((INT16U)pdata >> 8);
*--stk = ((INT16U)pdata & 0xFF);
*/
*--stk = ((INT32U)pdata & 0xFF0000) >> 8;
*--stk = ((INT32U)pdata & 0x00FF00) >> 4;
*--stk = (INT32U)pdata & 0xFF;
*--stk = 7;
*--stk = 6;
*--stk = 5;
*--stk = 4;
/*
*--stk = 3;
*--stk = 2;
*--stk = 1;
*/
/*
stk -= sizeof(void *);
*(void**)stk = pdata;
*/
*--stk = 0x01;
*--stk = ((INT16U)pdata >> 8);
*--stk = ((INT16U)pdata & 0xFF);
*--stk = 0;
*--stk = PSW;
*--stk = 'L';
*--stk = 'H';
*--stk = 'B';
*--stk = 'A';
*--stk = ((INT16U)task >> 8);
*--stk = ((INT16U)task & 0x00FF);
*--stk = 15;
return ((void *)stk);
}