ucos51移植心得.htm
来自「ucos的资料以及单片机dsp的经典移植程序」· HTM 代码 · 共 770 行 · 第 1/5 页
HTM
770 行
<P>
前期准备用了20几天,真正编写代码只用了1.5天,调试用了2天。具体过程如下:</P>
<P>
(1)拷贝书后附赠光盘sourcecode目录下的内容到C:\YY下,删除不必要的文件和EX1L.C,只剩下p187(《uCOSII》)上列出的文件。</P>
<P>
(2)改写最简单的OS_CPU.H<BR>
数据类型的设定见C51.PDF第176页。注意BOOLEAN要定义成unsigned char
类型,因为bit类型为C51特有,不能用在结构体里。</P>
<P>
EA=0关中断;EA=1开中断。这样定义即减少了程序行数,又避免了退出临界区后关中断造成的死机。</P>
<P>
MCU-51堆栈从下往上增长(1=向下,0=向上),OS_STK_GROWTH定义为0<BR>
#define OS_TASK_SW() OSCtxSw()
因为MCU-51没有软中断指令,所以用程序调用代替。两者的堆栈格式相同,RETI指令复位中断系统,RET则没有。实践表明,对于MCU-51,用子程序调用入栈,用中断返回指令RETI出栈是没有问题的,反之中断入栈RET出栈则不行。总之,对于入栈,子程序调用与中断调用效果是一样的,可以混用。在没有中断发生的情况下复位中断系统也不会影响系统正常运行。详见《uC/OS-II》第八章193页第12行</P>
<P> (3)改写OS_CPU_C.C</P>
<P>
我设计的堆栈结构如下图所示:<BR>*********************************************************************************<BR>*<BR>*
----------<BR>* |OSTCBCur|<BR>*
----------<BR>*
|<BR>* |
-----------------------
----------<BR>*
\---->|OSTCBCur->OSTCBStkPtr|
SP---->|
|<BR>*
-----------------------
----------<BR>*
|
|
|<BR>*
| ----------
-
----------<BR>*
|
| |
|
| .
|<BR>*
| ----------
|
| .
|<BR>*
|
| |
|
| .
|<BR>*
| ----------
|
----------<BR>*
|
| .
|长度
| |
+1<BR>*
|
| . |
|
----------<BR>*
|
| . |
|
OSStack---->| |
0<BR>*
| ----------
|
----------<BR>*
|
| |
|
OSStkStart---->| 不关心 | -1
低地址<BR>*
| ----------
-
----------<BR>*
\-------->| 长度 |
低地址
系统堆栈<BR>*
----------<BR>*
用户堆栈
长度=SP-OSStkStart<BR>*********************************************************************************</P>
<P>
TCB结构体中OSTCBStkPtr总是指向用户堆栈最低地址,该地址空间内存放用户堆栈长度,其上空间存放系统堆栈映像,即:用户堆栈空间大小=系统堆栈空间大小+1。</P>
<P>
SP总是先加1再存数据,因此,SP初始时指向系统堆栈起始地址(OSStack)减1处(OSStkStart)。很明显系统堆栈存储空间大小=SP-OSStkStart。</P>
<P>
任务切换时,先保存当前任务堆栈内容。方法是:用SP-OSStkStart得出保存字节数,将其写入用户堆栈最低地址内,以用户堆栈最低地址为起址,以OSStkStart为系统堆栈起址,由系统栈向用户栈拷贝数据,循环SP-OSStkStart次,每次拷贝前先将各自栈指针增1。</P>
<P>
其次,恢复最高优先级任务系统堆栈。方法是:获得最高优先级任务用户堆栈最低地址,从中取出“长度”,以最高优先级任务用户堆栈最低地址为起址,以OSStkStart为系统堆栈起址,由用户栈向系统栈拷贝数据,循环“长度”数值指示的次数,每次拷贝前先将各自栈指针增1。</P>
<P>
用户堆栈初始化时从下向上依次保存:用户堆栈长度(15),PCL,PCH,PSW,ACC,B,DPL,DPH,R0,R1,R2,R3,R4,R5,R6,R7。不保存SP,任务切换时根据用户堆栈长度计算得出。</P>
<P>
OSTaskStkInit函数总是返回用户栈最低地址。</P>
<P>
操作系统tick时钟我使用了51单片机的T0定时器,它的初始化代码用C写在了本文件中。</P>
<P>
最后还有几点必须注意的事项。本来原则上我们不用修改与处理器无关的代码,但是由于KEIL编译器的特殊性,这些代码仍要多处改动。因为KEIL缺省情况下编译的代码不可重入,而多任务系统要求并发操作导致重入,所以要在每个C函数及其声明后标注reentrant关键字。另外,“pdata”、“data”在uCOS中用做一些函数的形参,但它同时又是KEIL的关键字,会导致编译错误,我通过把“pdata”改成“ppdata”,“data”改成“ddata”解决了此问题。OSTCBCur、OSTCBHighRdy、OSRunning、OSPrioCur、OSPrioHighRdy这几个变量在汇编程序中用到了,为了使用Ri访问而不用DPTR,应该用KEIL扩展关键字IDATA将它们定义在内部RAM中。</P>
<P>
(4)重写OS_CPU_A.ASM<BR>
A51宏汇编的大致结构如下:<BR> NAME
模块名
;与文件名无关<BR>
;定义重定位段
必须按照C51格式定义,汇编遵守C51规范。段名格式为:?PR?函数名?模块名<BR>
;声明引用全局变量和外部子程序
注意关键字为“EXTRN”没有‘E’<BR>
全局变量名直接引用<BR>
无参数/无寄存器参数函数
FUNC<BR>
带寄存器参数函数
_FUNC<BR>
重入函数
_?FUNC<BR>
;分配堆栈空间<BR>
只关心大小,堆栈起点由keil决定,通过标号可以获得keil分配的SP起点。切莫自己分配堆栈起点,只要用DS通知KEIL预留堆栈空间即可。<BR>
?STACK段名与STARTUP.A51中的段名相同,这意味着KEIL在LINK时将把两个同名段拼在一起,我预留了40H个字节,STARTUP.A51预留了1个字节,LINK完成后堆栈段总长为41H。查看yy.m51知KEIL将堆栈起点定在21H,长度41H,处于内部RAM中。<BR>
;定义宏<BR>
宏名 MACRO 实体
ENDM<BR>
;子程序<BR>
OSStartHighRdy<BR>
OSCtxSw<BR>
OSIntCtxSw<BR>
OSTickISR<BR>
SerialISR<BR>
END
;声明汇编源文件结束<BR>
<BR>
一般指针占3字节。+0类型+1高8位数据+2低8位数据
详见C51.PDF第178页<BR>
低位地址存高8位值,高位地址存低8位值。例如0x1234,基址+0:0x12
基址+1:0x34<BR>
<BR> (5)移植串口驱动程序</P>
<P>
在此之前我写过基于中断的串口驱动程序,包括打印字节/字/长字/字符串,读串口,初始化串口/缓冲区。把它改成重入函数即可直接使用。</P>
<P>
系统提供的显示函数是并发的,它不是直接显示到串口,而是先输出到显存,用户不必担心IO慢速操作影响程序运行。串口输入也采用了同样的技术,他使得用户在CPU忙于处理其他任务时照样可以盲打输入命令。</P>
<P> (6)编写测试程序Demo(YY.C)</P>
<P>
Demo程序创建了3个任务A、B、C优先级分别为2、3、4,A每秒显示一次,B每3秒显示一次,C每6秒显示一次。从显示结果看,显示3个A后显示1个B,显示6个A和2个B后显示1个C,结果显然正确。<BR>
显示结果如下:<BR> AAAAAA111111
is active<BR> AAAAAA111111
is active<BR> AAAAAA111111
is active<BR> BBBBBB333333
is active<BR> AAAAAA111111
is active<BR> AAAAAA111111
is active<BR> AAAAAA111111
is active<BR> BBBBBB333333
is active<BR> CCCCCC666666
is active<BR> AAAAAA111111
is active<BR> AAAAAA111111
is active<BR> AAAAAA111111
is active<BR> BBBBBB333333
is active<BR> AAAAAA111111
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?