注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

指南针的天空

你永远也看不到我最寂寞时候的样子,因为只有你不在我身边的时候,我才最寂寞。

 
 
 

日志

 
 

VC环境下ucos-ii移植  

2010-12-30 00:04:33|  分类: Visual C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
1   VC下时钟的获得
《嵌入式实时操作系统uC/OS-II》这本书已经安排了大量篇幅来专门讲解uC/OS-II的移植:第13章 移植uC/OS-II,第14章uC/OS-II在80x86上的移植,第15章uC/OS-II在带有硬件浮点运算单元的80x86上的移植。所以本文 只是重点讲解移植到VC下和其他处理器上的不同地方,更详细的介绍读者可以参考《嵌入式实时操作系统uC/OS-II》这本书。和所有其他的移植一样,本 文所做的移植也只需要修改uC/OS-II处理器相关代码,一共包括3个文件:OS_CPU.H,OS_CPU_A.ASM,OS_CPU_C.C。考虑 到VC可以嵌入汇编代码,并不需要专门的汇编代码文件,所以OS_CPU_A.ASM是多余的,最终只有OS_CPU.H和OS_CPU_C.C两个文 件。所以这两个文件成了移植的关键,首先要解决的问题就是时钟“滴答”的获得。
移植到BC下的uC/OS-II是通过修改DOS下的硬件时钟中断来得到时钟滴答的,VC下时钟滴答从哪里来 呢?这是移植uC/OS-II到VC下第一个要考虑的问题。在windows的保护模式下不能像DOS下面那么容易,直接通过一个函数调用就能够修改中 断。windows下要修改中断涉及到驱动程序,这样就加大了移植的困难度与复杂度,但好处是只有真正硬件时钟的“滴答”才能够保证uC/OS-II的实 时性。另外一种解决方法是采用windows下的软件定时器,通过定时器来产生模拟时钟“滴答”。考虑到本移植只是为了教学和学习,并没有应用到对实时性 要求高的产品,所以最终决定采用软件定时器来模拟时钟中断。Windows下软件定时器种类很多,下面分别简要介绍一下这些定时器:
1.SetTimer()函数
有windows下编程经验的最先想到的应该是SetTimer这个API函数,但本文采用的移植程序是基于控 制台的,也就是说最开始建立VC工程的时候选择的是创建win32 console application,控制台下的程序是没有消息循环的,所以要使用SetTimer函数则必须再创建一个线程来专门处理消息循环,这样一来事情就复杂 了,而且这个函数定时精度非常不高。所以这种方法不是特别合适。
2.timeSetEvent()函数
这个函数很简单,不需要消息循环,定时精度为ms级,主要应用在多媒体定时方面,能够在非常精确的时间间隔内完 成一个事件、函数或过程的调用。函数原型:MMRESULT timeSetEvent(UINT uDelay, UINT uResolution,LPTIMECALLBACK lpTimeProc,WORD dwUser,UINT fuEvent ),可以通过调用timeSetEvent()函数,将需要周期性执行的任务定义在LpTimeProc回调函数中,从而完成所需处理的事件。调用这个函 数后会增加一个线程,时间一到则在这个线程中调用回调函数,对于主线程来说,非常类似外部中断调用,我们需要的正是这样的效果,所以本文最终选择这个函数来产生时钟“滴答”。
3.QueryPerformanceFrequency()和 QueryPerformanceCounter()函数
这两个函数可以实现更高精度的定时,误差不超过1微秒,进行定时之前,先调 用QueryPerformanceFrequency()函数获得机器内部定时器的时钟频率, 然后在需要严格定时的事件发生之前和发生之后分别调用QueryPerformanceCounter()函数,利用两次获得的计数之差及时钟频率,计算 出事件经 历的精确时间。可见,这两个函数主要是应用在计算时间方面,并没有设置回调函数机制,如果我们要使用这个函数的话,则需要首先创建一个线程,然后在这个线 程中计算时间调用我们要定时处理的函数,等于需要手动实现定时函数回调机制,远比timeSetEvent()函数来得复杂。
当然还有更多的定时器函数,这里不一一介绍,读者可以自行参考相关书籍。本文选择的是timeSetEvent函数,调用这个函数后uC/OS-II就已经开始它的脉搏了。
 
2    模拟时钟中断的产生
中断指的是中止当前的事务,处理别的更要紧的事情。我们通过软件定时器来模拟产生uC/OS-II的时钟中断, 但timeSetEvent()函数调用定时回调函数是和主线程同时被windows操作系统调度的,并没有起到中断的作用。所以在调用定时回调函数的时 候必须停止主线程的运行,退出回调函数则恢复主线程的运行,自然这些事情可以都放在定时回调函数,也就是uC/OS-II的时钟中断处理函数中完成。 Windows下要挂起一个线程的运行,首先要得到这个线程的句柄,然后调用SuspendThread(hangdler)和 ResumeThread(handler)就可以挂起和继续执行线程。如图1所示。

主线程
定时器线程
主线程调用timesetEvent函数后定时器线程启动
时间到调用OSTickISR时钟中断处理函数,此函数挂起主线程
OSTickISR运行
OSTickISR退出
恢复主线程运行

1 模拟中断
在我们的程序中刚开始只有一个线程,调用timeSetEvent则产生了定时器线程,定时器线程会按我们预定的时间周期调用uC/OS-II的中断处理函数OSTickISR,如果uC/OS-II没有屏蔽中断,OSTickISR则挂起主线程,然后才做它改做的事情:调度任务执行。
main函数的的OSInit()前,我们加入了一个VCInit()函数,主要初始化VC环境,包括获得主线程的句柄,设置上下文环境标志位,特别要注意的是,句柄的获得是要通过伪句柄转换的,如程序清单 L1所示
程序清单 L1 初始化VC环境
void main (void)
{
VCInit();       //在此加入我们的VCInit函数
OSInit();       // 初始化uC/OS-II
// 安装uC/OS-II的任务切换向量
                      // 创建用户起始任务(为了方便讨论,这里以TaskStart()作为起始任务)
OSStart();      // 开始多任务调度                                              
}
HANDLE mainhandle;      //增加的全局变量:主线程句柄
CONTEXT Context;          //增加的全局变量:主线程切换上下文
void VCInit(void)
{
HANDLE cp,ct;
Context.ContextFlags = CONTEXT_CONTROL;
cp = GetCurrentProcess();                             //得到当前进程句柄
Ct = GetCurrentThread();                              //得到当前线程伪句柄
DuplicateHandle(cp, ct, cp, &mainhandle, 0, TRUE, 2); // 伪句柄转换,得到线程真句柄
 
}
时钟中断处理子程序OSTickISR将在调用timeSetEvent后,被定时器线程周期调用,其代码如程序清单 L2所示
程序清单 L2 时钟节拍ISR
Void CALLBACK OSTickISR(unsigned int a,unsigned int b,unsigned long c,unsigned long d,unsigned long e)
{
if(!FlagEn)                //通过一个全局变量表示是否屏蔽中断
     return;                     //如果当前中断被屏蔽则返回
     SuspendThread(mainhandle); //中止主线程的运行,模拟中断产生.但没有保存寄存器
     GetThreadContext(mainhandle, &Context);   //得到主线程上下文,为切换任务做准备
     OSIntNesting++;
     if (OSIntNesting == 1) {
         OSTCBCur->OSTCBStkPtr = (OS_STK *)Context.Esp; //保存当前esp
     }   
     OSTimeTick();         //ucos内部定时
     OSIntExit();          //由于不能使用中断返回指令,所以此函数是要返回的
     ResumeThread(mainhandle);  //模拟中断返回,主线程得以继续执行
}
开关中断我们通过设置一个全局变量来解决,代码如下:
#define  OS_ENTER_CRITICAL()  FlagEn=0             //禁止定时器 调度 
#define  OS_EXIT_CRITICAL()   FlagEn=1          //容许定时器调度
因此在中断处理程序中首先判断当前是否容许中断,如果容许才挂起主线程,模拟中断产生,完成进一步的工作。
 
3 任务切换
任务切换,其实做的是任务的上下文切换,在其他CPU上非常容易分辨出任务的上下文,一般就是 CPU上的相应寄存器,那么在VC下呢?从简单考虑,我们选择了不带浮点运算的上下文环境,因此任务的上下文和uC/OS-II在80x86上移植的上下 文很相近,不同点只是段寄存器不用保存,因为在VC下任务其实只是在同一个线程中切换,而且在保护模式下段寄存器的概念已变,其值在同一个线程中是不会变 的。任务切换压栈时如图2所示
低端地址

模拟pushad
模拟pushfd
保存任务切换地址
任务参数地址
子程序是从当前的esp+4处取得的传入参数,所以要空出4个字节
EDI

ESI
EBP
ESP
EBX
EDX
ECX
EAX
PSW
OFF task
0
OFF pdata
高端地址
 
图2 任务切换压栈后状态
手动任务切换代码如程序清单L3所示,主要涉及VC中嵌入汇编的编写,请读者自行分析
程序清单L3 手动任务切换
void OSCtxSw(void)
{
       _asm{
              lea   eax, nextstart       ;任务切换回来后从nextstart开始
              push eax
              pushfd                         ;标志寄存器的值
              pushad                         ;保存EAX -- EDI        
              mov ebx, [OSTCBCur]
              mov [ebx], esp              ;把堆栈入口的地址保存到当前TCB结构中
       }
       OSTaskSwHook();
       OSTCBCur = OSTCBHighRdy;    
       OSPrioCur = OSPrioHighRdy;
       _asm{
              mov ebx, [OSTCBCur]
              mov esp, [ebx]              ;得到OSTCBHighRdy的esp
              popad                           ;恢复所有通用寄存器,共8个
              popfd                           ;恢复标志寄存器
              ret                               ;跳转到指定任务运行
       }
nextstart:                   //任务切换回来的运行地址
       return;
}
至此,其他VC下的移植方法与普通的移植无异,不必再深究下去,请读者自行分析

摘自 :
http://www.51-arm.com/proArticle_51All.asp?sid=185
  评论这张
 
阅读(1221)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018