【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,可以自己去下载下来看看)
|