ucos的51单片机移植

【uCOS_51的移植概述】
uCOS_51是uCOS-II v2.52在MCS-51系列单片机上的移植实例,采用大模式,须外部扩展64KB的SRAM,内核的移植简单地归纳为如下几条:
(1)声明11个数据类型(OS_CPU.H);
(2)用#define声明4个宏(OS_CPU.H);
(3)用C语言编写10个简单的函数(OS_CPU_C.C);
(4)编写4个汇编语言函数(OS_CPU_A.ASM)。
上述为一般移植过程中所要进行的工作,除此之外,我还增设了其它措施,以便于应用。
 
【uCOS_51的技术支持】
uCOS_51由本人休闲在家编写,由于时间精力和能力的有限,难免有所疏乎,欢迎有志人士一起学习探讨。
作者:华兄
邮箱: 591881218@qq.com
 
“uCOS_51”是我给工程起的名称,“望文生义”,我还习惯Version的简写使用小写字母,uCOS-II v2.52,而不是uCOS-II V2.52,哈哈,习惯了就好。我原本想去移植从官网下的最新版本uCOS-II内核《Micrium-uCOS-II-V290》,我只移植作简单地测试,并不打算试用其它的功能,我又热忠uCOS-II v2.52这个版本,对其内核源码非常了解,于是打消了移植最新版本内核的念头,感兴趣的朋友可以去试试。其实这篇文章早就酝酿要写了,由于没有工作的压力,也就没有学习的动力,三天打鱼两天晒网,想到什么,写什么,没有什么逻辑性,不要见怪,哈哈!
 
我不想谈移植工作和具体的实现,这些随大流的东西,源码是最好的老师,里面有详尽的注释。我就随写自己的想法吧。
 
每个接触过uCOS_51的朋友,心里面总会有一些想法:它究竟是如何运作的?OS内核究竟有多奇妙?小小51单片机也能跑操作系统 ……?
 
想必不少朋友听说过或者使用过RTX-51实时系统,这是我使用过的最小操作系统内核,Keil自带的,内核源码完全使用汇编语言编写。谈起OS内核,核心任务之一就是调度:为任务分配资源和时间,决定任务运行的次序,从而使系统满足特定的性能要求。这些说起来过于笼统,一句话就是不停地切换CPU寄存器——保存寄存器:入栈;弹出寄存器:出栈;保存寄存器:入栈;恢复寄存器:出栈。讲来讲去,都跟栈有关系,那就谈谈uCOS_51的任务栈和硬件堆栈。
 
每个任务都会有自己的任务栈用于保存现场——CPU寄存器和其它信息,uCOS_51中通过OSTaskStkInit函数(源码见工程,建议下载uCOS_51修订版)来初始化所有的任务栈,栈底保存堆栈长度,然后就是任务代码起始地址,接下来CPU寄存器,最后仿真堆栈指针。接下来谈谈硬件堆栈,硬件堆栈在OS_CPU_A.ASM中通过保留一定数量的存储单元获得,它只关心存储单元数量,堆栈起始地址OSStack由Keil指定。所谓硬件堆栈,就是专门给占用CPU的任务使用,如何使用的呢?就是把任务栈内容弹出到硬件堆栈,然后用去恢复现场,还有保存运行过程中的中间数据。发生任务切换保存现场时,也是按初始化任务栈的次序,从硬件堆栈中弹出任务运行信息包括其它信息(如任务调用子函数产生的断点信息)、任务代码地址,Oh,no!此时叫断点,还有CPU寄存器。其实是由OS_CPU_A.ASM中的出、入栈次序决定了OSTaskStkInit函数中的出、入栈次序。你会发现没有从硬件堆栈中弹出堆栈长度以及仿真堆栈指针!堆栈长度在OS_CPU_A.ASM中是通过硬件堆栈栈顶SP减去OSStkStart(OSStkStart=OSStack-1)得来的,怎么不会是初始值15呢?当然不是啦,谁知道程序在哪里由于突然发生调度而中断运行,任务运行过程中调用子函数就会有出入栈行为,还有其它因素影响堆栈长度。在uCOS-II v2.52内核中,中断嵌套不允许发生任务的调度,只有当一层中断嵌套时才能进行中断级的任务切换,如若此时发生任务的切换,会调整硬件堆栈栈项SP,SP=SP-4,去掉在调用OSIntExit()、OSIntCtxSw()过程中压入堆栈的多余内容(就是两个断点)。接下来就是仿真堆栈指针,它是赋给了?C_XBP这个变量,保存现场时也是从这里获取,我们来专门谈谈?C_XBP这个仿真堆栈指针。
 
?C_XBP初始让人觉得异常神秘,我也是,移植时差些被它迷糊了。查阅资料发现,这个仿真堆栈非常特殊,它是向下生长,而普通堆栈是向上生长,所以我们要把仿真堆栈的高地址值赋给?C_XBP。在STARTUP.A51用户上电初始化程序中,我把?C_XBP设在了外部RAM的最高地址FFFFH+1处,这里较少被占用。哈哈,其实这些都是门面工作,uCOS_51中每个任务都会有自己的仿真堆栈指针?C_XBP,OSTaskStkInit函数中,它是设在任务栈项端,也就是任务栈栈底ptos+MAX_STK_SIZE地址处,那么,ptos+MAX_STK_SIZE除去任务运行正常使用,其余就用作仿真堆栈。哈哈,你不必担心,仿真堆栈我也不熟,uCOS_51不使用它。
 
我们再谈些什么好呢,真想出去走走了,外边下着雨,算了,辛苦一下 …,先指正uCOS_51修订版中的三个错误吧:(一)在OS_CPU_A.ASM中,?PR?_?SerialISR?OS_CPU_A    SEGMENT CODE,串口中断服务子程序不可重入,应该是?PR?SerialISR?OS_CPU_A         SEGMENT CODE;(二)在serial.c中,InitSerial函数里注释有误,SCON=0x50;PCON=0x00; // 模式2,SM2=0,SMOD=0,应该是模式1,这也是张义和老师书上的错误;(三)在uCOS_II.H中,OS_TaskIdle函数声明以及OS_TaskStat函数声明的参数少写了一个'd'变成ppata,应该是ppdata;(四)这个错误来自内核源码,在OS_TASK.C中,OSTaskDel函数定义self这个布尔变量却没有使用,编译器发出警告,该定义已在修订版中去除,这里只是引起朋友的关注。
 
我再谈谈测试uCOS_51过程中发现的一个非常有意思的问题,就是工作状态指示灯闪烁异常,起初我设置1秒闪烁一次,而实际发现几乎要2秒才会闪烁。系统只有两个任务——空闲任务和工作状态指示任务,负荷不重,折腾了我老半天,最后我添加另外一个低优先级任务,高于空闲任务,里面置死循环,完完全全的空任务,使得空闲任务无法得到执行,这下发现指示灯正常闪烁起来了,然后盘根究底,发现问题出在空闲计数器OSIdleCtr上,"OSIdleCtr" 变量务必设置为 "idata" 存储类型,否则任务运行节拍变慢。这就是答案,我特意注释在main.c中。
由上面的问题引发我们思考,为什么设置idata存储类型就正常了呢?由于uCOS_51采用大模式,全部变量默认为xdata存储类型,存储在外部RAM中,访问需要花费更多的CPU时间,而空闲任务是经常被调度的任务,自然空闲计数器OSIdleCtr也就成了经常被访问的变量。访问频率如此之高,如若设在外部RAM中,也就要耗费更多的CPU时间,增加了系统延时,闪烁也就变得慢了。因此需要把它放在内部RAM中,其它同理。
 
好了,我要出去了,哈哈 …
 
今天先谈谈#define REENTRANT  reentrant这个宏,也就是Keil里reentrant这个关键字。原则上我们不需要修改任何与处理器无关的代码,由于Keil编绎器的特殊性,这些代码仍需作改动。Keil缺省情况下编译出来的代码不可重入,考虑多任务并发执行,系统要求可重入代码,因此,需要在每个C函数及其声明后加上关键字reentrant。代码的可重入是指可以被多个任务并发使用,而数据不会遭到破坏;不可重入是指不能由多个任务所共享,除非能确保互斥访问。想要了解更多,还请朋友自发去查阅资料,我就不再赘述!
 
下面说说uCOS-II v2.52里pdata这个参数,pdata是Keil里保留的关键字,代表分页式存储类型。所以避免冲突,给内核源码所有pdata改名为ppdata,这是追随杨大侠的做法。
 
在uCOS-II v2.52里,有BOOLEAN这个数据类型,它是布尔型,取值0或1。虽然你很想用bit这个数据类型去typedef,但强烈建议最好不要这么去做,因为bit无法在结构体里使用,所以把BOOLEAN定义成uchar类型。

... ...
 
貌似很久没有更新过了 。。。
 
修正两个错误吧,OS_EXT  DF_IDATA  OS_TCB           *OSTCBCur; 以及 OS_EXT  DF_IDATA  OS_TCB           *OSTCBHighRdy; 改为 OS_EXT    OS_TCB           *DF_IDATA OSTCBCur; 和 OS_EXT    OS_TCB           *DF_IDATA OSTCBHighRdy;,原因很简单,OSTCBCur和OSTCBHighRdy存放于idata中,所指向的对象才存放在xdata。
 
讨论一下OSTCBCur和OSTCBHighRdy这两个指针,根据Keil C51用户手册,这两个指针分别占用三个字节的地址空间,+0表示指针所指向对象的数据类型,+1表示高8位数据,+2表示低8位数据,+1数据和+2数据共同组成对象的16位地址。
 
这里讲一下uCOS_51中的模块化编程,uCOS_II.C中,#define  OS_GLOBALS这个宏,使得uCOS_II.H中的普通变量、结构体变量等对于内核来说是全局变量(见uCOS_II.H之#define  OS_EXT);内核以外,由于没有这个宏,BSP、应用层将视为extern类型(见uCOS_II.H之#define  OS_EXT  extern)。#define  OS_MASTER_FILE防止内核重复包含INCLUDES.H这个头文件。uCOS_II.C包含头文件和内核文件,编译预处理阶段,就会加载这些头文件和内核文件信息,编译时,一块编译成uCOS_II.O目标文件。
 
#include "..\ucos_51\ucos-ii\inc\includes.h",这个东东,起初也许你会觉得它比较冗长,没有办法,#include "includes.h"编译器报错找不到文件,只能使用笨方法为编译器指定文件路径。
 
扯一下就绪表吧,uCOS-II v2.52支持64个优先级,OSRdyGrp、OSRdyTbl[]、OSUnMapTbl[]这几个变量功不可没。我们可以通过void  OS_Sched (void);这个函数(见OS_CORE.C)来研究uCOS-II v2.52查找当前就绪的最高优先级算法。特别谈一下OSUnMapTbl[]这个东西,它是由256个元素组成的数组,作用:查找一个8位二进数中,最低位为1的是哪个位。如0x80,最低位为1的是第7位;0x00和0xff,最低位为1的是第0位。我们来看看uCOS-II v2.52查找当前就绪的最高优先级算法,下面代码来自void  OS_Sched (void);(见OS_CORE.C)。
 
 y             = OSUnMapTbl[OSRdyGrp];          /* Get pointer to HPT ready to run              */
 OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
 
OSUnMapTbl[OSRdyGrp]获得OSRdyGrp中最低位为1的位置y,即最高优先级所在就绪表OSRdyTbl[]的分组,一共8组(0~7)。OSRdyTbl[y]可以获得最高优先级所在就绪表OSRdyTbl[]的分组信息,一串8位二进制数。OSUnMapTbl[OSRdyTbl[y]]可以获得这串二制数中最低位为1的位,假设为x。在uC/OS中,数值越小,优先级越大,y和x都是OSRdyGrp以及OSRdyTbl[y]位最低的,uCOS-II v2.52中优先级计算公式:Prio=y<<3+x。y和x都是最低最小,则Prio最小,优先级就越大。
 
uCOS-II v2.52只支持64个优先级,我记得应该在uCOS-II v2.80以后,就开始支持256个优先级了。我们拿uCOS-II v2.83举例说明,它没有改变OSUnMapTbl[]这个数组,但改变了OSRdyGrp和OSRdyTbl[],有8位和16位两种定义,也改变了OSPrioHighRdy的长度,由8位变为16位长度。我们来看看uCOS-II v2.83查找当前就绪的最高优先级算法。
 
下面代码来自uCOS_II.H。
 
 #if OS_LOWEST_PRIO <= 63 // 优先级数少于64时
 OS_EXT  INT8U             OSRdyGrp;                        /* Ready list group                         */
 OS_EXT  INT8U             OSRdyTbl[OS_RDY_TBL_SIZE];       /* Table of tasks which are ready to run    */
 #else // 优先级数多于63时,注意变量数据长度的变化
 OS_EXT  INT16U            OSRdyGrp;                        /* Ready list group                         */
 OS_EXT  INT16U            OSRdyTbl[OS_RDY_TBL_SIZE];       /* Table of tasks which are ready to run    */
 #endif
 
下面代码来自static  void  OS_SchedNew (void);(见OS_CORE.C),此函数会被OS_Sched ()调用,用于查找当前就绪的最高优先级。
 
 static  void  OS_SchedNew (void)
 {
 #if OS_LOWEST_PRIO <= 63                         /* See if we support up to 64 tasks                   */
     INT8U   y;
 
 
     y             = OSUnMapTbl[OSRdyGrp];
     OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
 #else                                            /* We support up to 256 tasks                         */
 // ---------------------------------------- 上半部分与uCOS-II v2.52一样 ----------------------------------------
     INT8U   y;
     INT16U *ptbl;
 
 
     if ((OSRdyGrp & 0xFF) != 0) { // OSRdyGrp低8位不为0,最高优先级信息必定分布在OSRdyGrp低8位
         y = OSUnMapTbl[OSRdyGrp & 0xFF]; // 查找OSRdyGrp低8位中最低位为1的位置y
     } else { // OSRdyGrp低8位为0,最高优先级信息必定分布在OSRdyGrp高8位
         y = OSUnMapTbl[(OSRdyGrp >> 8) & 0xFF] + 8; // OSRdyGrp右移8位,查找OSRdyGrp低8位中最低位为1的位置,再加8才是最高优先级信息分布在OSRdyGrp中的实际位置y,这是高明之处
     }
     ptbl = &OSRdyTbl[y]; // 注意,OSRdyTbl[y]长度为16位
     if ((*ptbl & 0xFF) != 0) { // OSRdyTbl[y]低8位不为0,最高优先级信息必定分布在OSRdyTbl[y]低8位
         OSPrioHighRdy = (INT8U)((y << 4) + OSUnMapTbl[(*ptbl & 0xFF)]); // OSUnMapTbl[(*ptbl & 0xFF)])类似计算y,不过,这里计算优先级的方法有所不同而已
     } else { // OSRdyTbl[y]低8位为0,最高优先级信息必定分布在OSRdyTbl[y]高8位
         OSPrioHighRdy = (INT8U)((y << 4) + OSUnMapTbl[(*ptbl >> 8) & 0xFF] + 8); // OSUnMapTbl[(*ptbl >> 8) & 0xFF] + 8类似计算y
     }
 #endif
 }
 
前半截优先级数少于64时,与uCOS-II v2.52一样,咱们看后半截,我就直接在代码里面注释说明。
(补充:代码地址在 http://ishare.iask.sina.com.cn/f/22488247.html?w,可以自己去下载下来看看)
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值