数据载入中...

 
uCOS51 移植心得
[ 2005-1-5 12:58:53 | By: MCUBLOG ]
 
讨论1----uCOS51 高效内核
前一段时间我参与了一个SNMP 网管板的项目我负责硬件设计和单板软件开发该板的硬件由MCS51+RTL8019AS 组成有64K FLASH 和64K SRAM 软件部分有操作系统和TCPIP 协议栈硬件比较简单用了一个月就搞定了协议栈我参考了老古开发板的部分程序又上网找了SNMP 源代码也很快完成了但是测试时发现当使用较低时钟频率的CPU 时为了降低成本由于ASN.1 编解码部分过于庞大而我的程序又是一个大循环AGENT 的响应速度受到严重影响用户界面也反应迟钝更坏的消息是公司为了适应市场需求还要在上面跑PPP 和HTTP 那样的话我就得用40MHz 的AT89C51RD2 或者人为的把程序断成几部分然后用状态机的方法在运行时再把它们连接起来不过我不想增加成本也不想把程序搞乱迫不得已只好使用操作系统说实在的一开始我也不是很有把握一来我不清楚51 的FLASH 是否装得下这么多代码二来我只做过OS 应用开发对于它的移植想都不敢想不过我在BBS 上搜索了一阵儿后还是有了一些头绪我找到了几个OS 的源代码我喜欢用现成的按照代码大小实时性使用人数众人口碑等标准最后选定了uCOS2 我感觉它的实时性有保障延时可预测代码据说可小到2K 网上讨论这个话题的人也比较多而且它的网站上有针对KEIL C51 的移植实例经过一番查找我得到了5 个版本其中3 个是用KEIL 编译的本来我想直接把OS代码嵌到应用程序中但后来发现没有一个可以直接使用有的无法用KEIL 直接编译有的需要修改DLL 在软件仿真下使用而我需要的是能在串口输入输出不需要修改任何无关软件能在软件仿真和硬件上运行的实时多任务操作系统没有办法我只好硬着头皮去改编
我分析了自己的劣势1 KEIL 刚开始使用不太熟悉2 混合编程以前从没有作过3 时间紧迫要在1 个月内搞定而我的优势就是有5 个移植实例可供参考可以上网查资料一开始我用堆栈混合编程汇编ucos 等关键字在C51BBS 和老古论坛上检索相关信息并逐条阅读读过之后头脑中的思路逐渐清晰了我了解到在KEIL 的HLP 目录下有A51.PDF 和C51.PDF 非常全面的介绍了汇编和C51 是KEIL 的权威用户手册SP 初始化内存清0 等操作在STARTUP.A51 文件中实现用户可以改写它KEIL 的变量子程序等的分配信息可以在.M51 文件里查到KEIL 自己的论坛里有很多疑难问题的解答通过阅读并经过思考解决了堆栈起点堆栈空间大小的设定等关键问题论坛里
的问题有些是我没有想到的这使我发现了自己的疏漏
在网上获得大量信息后我开始阅读uCOSII 中文版一共读了3 遍第一遍是浏
览了解到uCOSII 包括任务调度时间管理内存管理资源管理信号量邮箱消息
队列四大部分没有文件系统网络接口输入输出界面它的移植只与4 个文件相关汇编文件OS_CPU_A.ASM 处理器相关C 文件OS_CPU.H OS_CPU_C.C 和配置文件OS_CFG.H 有64 个优先级系统占用8 个用户可创建56 个任务不支持时间片轮转第二遍主要是把整个工作过程在头脑里过了一下不懂的地方有针对性地查书重点是思考工作原理和流程我发现其实它的思路挺简单的就是近似地每时每刻总是让优先级最高的就绪任务处于运行状态为了保证这一点它在调用系统API 函数中断结束定时中断结束时总是执行调度算法原作者通过事先计算好数据简化了运算量通过精心设计就绪表结构使得延时可预知任务的切换是通过模拟一次中断实现的第三遍重点看了移植部分的内容对照实例研究了代码的具体实现方法
前期准备用了20 几天真正编写代码只用了1.5 天调试用了2 天具体过程如下
(1)拷贝书后附赠光盘sourcecode 目录下的内容到C:\YY 下删除不必要的文件和
EX1L.C 只剩下p187( uCOSII )上列出的文件
(2)改写最简单的OS_CPU.H
数据类型的设定见C51.PDF 第176 页注意BOOLEAN 要定义成unsigned char 类型因为bit 类型为C51 特有不能用在结构体里EA=0 关中断EA=1 开中断这样定义即减少了程序行数又避免了退出临界区后关中断造成的死机
MCS-51 堆栈从下往上增长(1=向下0=向上) OS_STK_GROWTH 定义为0
#define OS_TASK_SW() OSCtxSw() 因为MCS-51 没有软中断指令所以用程序调用代替两者的堆栈格式相同RETI 指令复位中断系统RET 则没有实践表明对于MCS-51 用子程序调用入栈用中断返回指令RETI 出栈是没有问题的反之中断入栈RET出栈则不行总之对于入栈子程序调用与中断调用效果是一样的可以混用在没有中断发生的情况下复位中断系统也不会影响系统正常运行详见uC/OS-II 第八章193 页第12 行
(3)改写OS_CPU_C.C
TCB 结构体中OSTCBStkPtr 总是指向用户堆栈最低地址该地址空间内存放用户堆栈长度其上空间存放系统堆栈映像即用户堆栈空间大小=系统堆栈空间大小+1SP 总是先加1 再存数据因此SP 初始时指向系统堆栈起始地址(OSStack)减1 处(OSStkStart) 很明显系统堆栈存储空间大小=SP-OSStkStart任务切换时先保存当前任务堆栈内容方法是用SP-OSStkStart 得出保存字节数将其写入用户堆栈最低地址内以用户堆栈最低地址为起址以OSStkStart 为系统堆栈起址由系统栈向用户栈拷贝数据循环SP-OSStkStart 次每次拷贝前先将各自栈指针增1其次恢复最高优先级任务系统堆栈方法是获得最高优先级任务用户堆栈最低地址从中取出长度以最高优先级任务用户堆栈最低地址为起址以OSStkStart 为系统堆栈起址由用户栈向系统栈拷贝数据循环长度数值指示的次数每次拷贝前先将各自栈指针增1
用户堆栈初始化时从下向上依次保存用户堆栈长度15 PCL PCH PSW
ACC B DPL DPH R0 R1 R2 R3 R4 R5 R6 R7 不保存SP 任务切换时根
据用户堆栈长度计算得出OSTaskStkInit 函数总是返回用户栈最低地址
操作系统tick 时钟我使用了51 单片机的T0 定时器它的初始化代码用C 写在了本文件中
最后还有几点必须注意的事项本来原则上我们不用修改与处理器无关的代码但是由于KEIL 编译器的特殊性这些代码仍要多处改动因为KEIL 缺省情况下编译的代码不可重入而多任务系统要求并发操作导致重入所以要在每个C 函数及其声明后标注reentrant 关键字另外pdata data 在uCOS 中用做一些函数的形参但它同时又是KEIL 的关键字会导致编译错误我通过把pdata 改成ppdata data 改成ddata解决了此问题OSTCBCur OSTCBHighRdy OSRunning OSPrioCur OSPrioHighRdy 这几个变量在汇编程序中用到了为了使用Ri 访问而不用DPTR 应该用KEIL 扩展关键字IDATA 将它们定义在内部RAM 中
(4)重写OS_CPU_A.ASM
A51 宏汇编的大致结构如下
NAME 模块名;与文件名无关
;定义重定位段必须按照C51 格式定义汇编遵守C51 规范段名格式为?PR?
函数名?模块名
;声明引用全局变量和外部子程序注意关键字为EXTRN 没有E
全局变量名直接引用
无参数/无寄存器参数函数FUNC
带寄存器参数函数_FUNC
重入函数_?FUNC
;分配堆栈空间
只关心大小堆栈起点由keil 决定通过标号可以获得keil 分配的SP 起点
切莫自己分配堆栈起点只要用DS 通知KEIL 预留堆栈空间即可
?STACK段名与STARTUP.A51 中的段名相同这意味着KEIL 在LINK 时将把
两个同名段拼在一起我预留了40H 个字节STARTUP.A51 预留了1 个字节LINK 完成
后堆栈段总长为41H 查看yy.m51 知KEIL 将堆栈起点定在21H 长度41H 处于内部RAM

;定义宏
宏名MACRO 实体ENDM
;子程序
OSStartHighRdy
OSCtxSw
OSIntCtxSw
OSTickISR
SerialISR
END ;声明汇编源文件结束
一般指针占3 字节+0 类型+1 高8 位数据+2 低8 位数据详见C51.PDF 第178 页
低位地址存高8 位值高位地址存低8 位值例如0x1234 基址+0:0x12 基址+1:0x34
(5)移植串口驱动程序
在此之前我写过基于中断的串口驱动程序包括打印字节/字/长字/字符串读串口
初始化串口/缓冲区把它改成重入函数即可直接使用
系统提供的显示函数是并发的它不是直接显示到串口而是先输出到显存用户
不必担心IO 慢速操作影响程序运行串口输入也采用了同样的技术他使得用户在CPU 忙
于处理其他任务时照样可以盲打输入命令
(6)编写测试程序Demo(YY.C)
Demo 程序创建了3 个任务A B C 优先级分别为2 3 4 A 每秒显示一次B
每3 秒显示一次C 每6 秒显示一次从显示结果看显示3 个A 后显示1 个B 显示6
个A 和2 个B 后显示1 个C 结果显然正确
显示结果如下
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
CCCCCC666666 is active
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
CCCCCC666666 is active
Demo 程序经Keil701 编译后代码量为7-8K 可直接在KeilC51 上仿真运行
编译时要将OS_CPU_C.C UCOS_II.C OS_CPU_A.ASM YY.C 加入项目
以上是我这次移植uCOS51 的一些心得写出来只是让准备在51 上运行操作系统的同
行们少走弯路并增强使用信心我强烈推荐大家在自己的51 系统中使用uCOS 这个简单实
用的自己的操作系统它的大小应该不是问题性能上的提高却是显著的但愿此文能对朋
友们有所帮助错误在所难免希望各位大虾指正诸位高手们见笑了
注全部源码可来信索要(asdjf@163.com) 以下仅为关键代码部分
文件名: OS_CPU_A.ASM
$NOMOD51
EA BIT 0A8H.7
SP DATA 081H
B DATA 0F0H
ACC DATA 0E0H
DPH DATA 083H
DPL DATA 082H
PSW DATA 0D0H
TR0 BIT 088H.4
TH0 DATA 08CH
TL0 DATA 08AH
NAME OS_CPU_A ;模块名
;定义重定位段
?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
?PR?_?serial?OS_CPU_A SEGMENT CODE
;声明引用全局变量和外部子程序
EXTRN IDATA (OSTCBCur)
EXTRN IDATA (OSTCBHighRdy)
EXTRN IDATA (OSRunning)
EXTRN IDATA (OSPrioCur)
EXTRN IDATA (OSPrioHighRdy)
EXTRN CODE (_?OSTaskSwHook)
EXTRN CODE (_?serial)
EXTRN CODE (_?OSIntEnter)
EXTRN CODE (_?OSIntExit)
EXTRN CODE (_?OSTimeTick)
;对外声明4 个不可重入函数
PUBLIC OSStartHighRdy
PUBLIC OSCtxSw
PUBLIC OSIntCtxSw
PUBLIC OSTickISR
;PUBLIC SerialISR
;分配堆栈空间只关心大小堆栈起点由keil 决定通过标号可以获得keil 分配的SP 起点
?STACK SEGMENT IDATA
RSEG ?STACK
OSStack:
DS 40H
OSStkStart IDATA OSStack-1
;定义压栈出栈宏
PUSHALL MACRO
PUSH PSW
PUSH ACC
PUSH B
PUSH DPL
PUSH DPH
MOV A,R0 ;R0-R7 入栈
PUSH ACC
MOV A,R1
PUSH ACC
MOV A,R2
PUSH ACC
MOV A,R3
PUSH ACC
MOV A,R4
PUSH ACC
MOV A,R5
PUSH ACC
MOV A,R6
PUSH ACC
MOV A,R7
PUSH ACC
;PUSH SP ;不必保存SP 任务切换时由相应程序调整
ENDM
POPALL MACRO
;POP ACC ;不必保存SP 任务切换时由相应程序调整
POP ACC ;R0-R7 出栈
MOV R7,A
POP ACC
MOV R6,A
POP ACC
MOV R5,A
POP ACC
MOV R4,A
POP ACC
MOV R3,A
POP ACC
MOV R2,A
POP ACC
MOV R1,A
POP ACC
MOV R0,A
POP DPH
POP DPL
POP B
POP ACC
POP PSW
ENDM
;子程序
;-------------------------------------------------------------------------
RSEG ?PR?OSStartHighRdy?OS_CPU_A
OSStartHighRdy:
USING 0 ;上电后51 自动关中断此处不必用CLR EA 指令因为到此处还未
开中断本程序退出后开中断
LCALL _?OSTaskSwHook
OSCtxSw_in:
;OSTCBCur ===> DPTR 获得当前TCB 指针详见C51.PDF 第178 页
MOV R0,#LOW (OSTCBCur) ;获得OSTCBCur 指针低地址指针占3 字节+0
类型+1 高8 位数据+2 低8 位数据
INC R0
MOV DPH,@R0 ;全局变量OSTCBCur 在IDATA 中
INC R0
MOV DPL,@R0
;OSTCBCur->OSTCBStkPtr ===> DPTR 获得用户堆栈指针
INC DPTR ;指针占3 字节+0 类型+1 高8 位数据+2 低8 位数据
MOVX A,@DPTR ;.OSTCBStkPtr 是void 指针
MOV R0,A
INC DPTR
MOVX A,@DPTR
MOV R1,A
MOV DPH,R0
MOV DPL,R1
;*UserStkPtr ===> R5 用户堆栈起始地址内容(即用户堆栈长度放在此处) 详见
文档说明指针用法详见C51.PDF 第178 页
MOVX A,@DPTR ;用户堆栈中是unsigned char 类型数据
MOV R5,A ;R5=用户堆栈长度
;恢复现场堆栈内容
MOV R0,#OSStkStart
restore_stack:
INC DPTR
INC R0
MOVX A,@DPTR
MOV @R0,A
DJNZ R5,restore_stack
;恢复堆栈指针SP
MOV SP,R0
;OSRunning=TRUE
MOV R0,#LOW (OSRunning)
MOV @R0,#01
POPALL
SETB EA ;开中断
RETI
;-------------------------------------------------------------------------
RSEG ?PR?OSCtxSw?OS_CPU_A
OSCtxSw:
PUSHALL
OSIntCtxSw_in:
;获得堆栈长度和起址
MOV A,SP
CLR C
SUBB A,#OSStkStart
MOV R5,A ;获得堆栈长度
;OSTCBCur ===> DPTR 获得当前TCB 指针详见C51.PDF 第178 页
MOV R0,#LOW (OSTCBCur) ;获得OSTCBCur 指针低地址指针占3 字节+0
类型+1 高8 位数据+2 低8 位数据
INC R0
MOV DPH,@R0 ;全局变量OSTCBCur 在IDATA 中
INC R0
MOV DPL,@R0
;OSTCBCur->OSTCBStkPtr ===> DPTR 获得用户堆栈指针
INC DPTR ;指针占3 字节+0 类型+1 高8 位数据+2 低8 位数据
MOVX A,@DPTR ;.OSTCBStkPtr 是void 指针
MOV R0,A
INC DPTR
MOVX A,@DPTR
MOV R1,A
MOV DPH,R0
MOV DPL,R1
;保存堆栈长度
MOV A,R5
MOVX @DPTR,A
MOV R0,#OSStkStart ;获得堆栈起址
save_stack:
INC DPTR
INC R0
MOV A,@R0
MOVX @DPTR,A
DJNZ R5,save_stack
;调用用户程序
LCALL _?OSTaskSwHook
;OSTCBCur = OSTCBHighRdy
MOV R0,#OSTCBCur
MOV R1,#OSTCBHighRdy
MOV A,@R1
MOV @R0,A
INC R0
INC R1
MOV A,@R1
MOV @R0,A
INC R0
INC R1
MOV A,@R1
MOV @R0,A
;OSPrioCur = OSPrioHighRdy 使用这两个变量主要目的是为了使指针比较变为字
节比较以便节省时间
MOV R0,#OSPrioCur
MOV R1,#OSPrioHighRdy
MOV A,@R1
MOV @R0,A
LJMP OSCtxSw_in
;-------------------------------------------------------------------------
RSEG ?PR?OSIntCtxSw?OS_CPU_A
OSIntCtxSw:
;调整SP 指针去掉在调用OSIntExit(),OSIntCtxSw()过程中压入堆栈的多余内容
;SP=SP-4
MOV A,SP
CLR C
SUBB A,#4
MOV SP,A
LJMP OSIntCtxSw_in
;-------------------------------------------------------------------------
CSEG AT 000BH ;OSTickISR
LJMP OSTickISR ;使用定时器0
RSEG ?PR?OSTickISR?OS_CPU_A
OSTickISR:
USING 0
PUSHALL
CLR TR0
MOV TH0,#70H ;定义Tick=50 次/秒(即0.02 秒/次)
MOV TL0,#00H ;OS_CPU_C.C 和OS_TICKS_PER_SEC
SETB TR0
LCALL _?OSIntEnter
LCALL _?OSTimeTick
LCALL _?OSIntExit
POPALL
RETI
;-------------------------------------------------------------------------
CSEG AT 0023H ;串口中断
LJMP SerialISR ;工作于系统态无任务切换
RSEG ?PR?_?serial?OS_CPU_A
SerialISR:
USING 0
PUSHALL
CLR EA
LCALL _?serial
SETB EA
POPALL
RETI
;-------------------------------------------------------------------------
END
;-------------------------------------------------------------------------
文件名: OS_CPU_C.C
void *OSTaskStkInit (void (*task)(void *pd), void *ppdata, void *ptos, INT16U opt) reentrant
{
OS_STK *stk;
ppdata = ppdata;
opt = opt; //opt 没被用到保留此语句防止告警产生
stk = (OS_STK *)ptos; //用户堆栈最低有效地址
*stk++ = 15; //用户堆栈长度
*stk++ = (INT16U)task & 0xFF; //任务地址低8 位
*stk++ = (INT16U)task >> 8; //任务地址高8 位
*stk++ = 0x00; //PSW
*stk++ = 0x0A; //ACC
*stk++ = 0x0B; //B
*stk++ = 0x00; //DPL
*stk++ = 0x00; //DPH
*stk++ = 0x00; //R0
*stk++ = 0x01; //R1
*stk++ = 0x02; //R2
*stk++ = 0x03; //R3
*stk++ = 0x04; //R4
*stk++ = 0x05; //R5
*stk++ = 0x06; //R6
*stk++ = 0x07; //R7
//不用保存SP 任务切换时根据用户堆栈
长度计算得出
return ((void *)ptos);
}
#if OS_CPU_HOOKS_EN
void OSTaskCreateHook (OS_TCB *ptcb) reentrant
{
ptcb = ptcb; /* Prevent compiler warning
*/
}
void OSTaskDelHook (OS_TCB *ptcb) reentrant
{
ptcb = ptcb; /* Prevent compiler warning
*/
}
void OSTimeTickHook (void) reentrant
{ }
#endif
//初始化定时器0
void InitTimer0(void) reentrant
{
TMOD=TMOD&0xF0;
TMOD=TMOD|0x01; //模式1(16 位定时器) 仅受TR0 控制
TH0=0x70; //定义Tick=50 次/秒(即0.02 秒/次)
TL0=0x00; //OS_CPU_A.ASM 和OS_TICKS_PER_SEC
ET0=1; //允许T0 中断
TR0=1;
}
文件名: YY.C
#i nclude <includes.h>
#define MAX_STK_SIZE 64
void TaskStartyya(void *yydata) reentrant;
void TaskStartyyb(void *yydata) reentrant;
void TaskStartyyc(void *yydata) reentrant;
OS_STK TaskStartStkyya[MAX_STK_SIZE+1];//注意我在ASM 文件中设置?STACK 空间为
40H 即64 不要超出范围
OS_STK TaskStartStkyyb[MAX_STK_SIZE+1];//用户栈多一个字节存长度
OS_STK TaskStartStkyyc[MAX_STK_SIZE+1];
void main(void)
{
OSInit();
InitTimer0();
InitSerial();
InitSerialBuffer();
OSTaskCreate(TaskStartyya, (void *)0, &TaskStartStkyya[0],2);
OSTaskCreate(TaskStartyyb, (void *)0, &TaskStartStkyyb[0],3);
OSTaskCreate(TaskStartyyc, (void *)0, &TaskStartStkyyc[0],4);
OSStart();
}
void TaskStartyya(void *yydata) reentrant
{
yydata=yydata;
clrscr();
PrintStr("\n\t\t*******************************\n");
PrintStr("\t\t* Hello! The world. *\n");
PrintStr("\t\t*******************************\n\n\n");
for(;;){
PrintStr("\tAAAAAA111111 is active.\n");
OSTimeDly(OS_TICKS_PER_SEC);
}
}
void TaskStartyyb(void *yydata) reentrant
{
yydata=yydata;
for(;;){
PrintStr("\tBBBBBB333333 is active.\n");
OSTimeDly(3*OS_TICKS_PER_SEC);
}
}
void TaskStartyyc(void *yydata) reentrant
{
yydata=yydata;
for(;;){
PrintStr("\tCCCCCC666666 is active.\n");
OSTimeDly(6*OS_TICKS_PER_SEC);
}
}
 
 
Re:uCOS51 移植心得
[ 2007-4-23 20:33:54 | By: weller(游客) ]
 
weller(游客)能否指点一下怎样计算运行ucos所需的ram资源??
我的mail:weller.huang@tpvaoc.com谢谢,麻烦发给我
 
个人主页 | 引用 | 返回 | 删除 | 回复
 
回复:uCOS51 移植心得
[ 2006-4-12 11:36:04 | By: cj-005 ]
 
cj-005这些代码一定要用汇编写么?是不是一定要对arm的汇编指令熟悉?
 
 
回复:uCOS51 移植心得
[ 2005-5-25 16:54:59 | By: zhangxm217 ]
 
zhangxm217呵呵,杨大侠的文章被到处转
 
个人主页 | 引用 | 返回 | 删除 | 回复
发表评论:
数据载入中...

数据载入中...
时 间 记 忆
数据载入中...
最 新 评 论
数据载入中...

专 题 分 类
数据载入中...
最 新 日 志
数据载入中...
最 新 留 言
数据载入中...
搜 索
用 户 登 录
数据载入中...
友 情 连 接
博 客 信 息
数据载入中...


Powered by McuBLog.