首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

μC/OS-Ⅱ在MC9S12A64上的移植及应用

μC/OS-Ⅱ在MC9S12A64上的移植及应用

自从嵌入式系统开发以来,很长时间都采用前后台系统软件设计模式:主程序为一个无限循环,单任务顺序执行。通过设置一个或多个中断来处理异步事件。这种系统对于简单的应用是可以的,但对于实时性要求比较高的、处理任务较多的应用,就会暴露出实时性差、系统可靠性低、稳定性差等缺点。uCOS—II是一种给予优先级的抢占式多任务实时操作系统,包含了实时内核、任务管理、时间管理、任务间通信同步(信号量,邮箱,消息队列)和内存管理等功能。它可以使各个任务独立工作,互不干涉,很容易实现准时而且无误执行,使得实时应用程序的设计和扩展变得容易,使得应用程序的设计过程大为减化。而且它内核源代码公开,移植性强,为编程人员提供了很好的一个软件平台。
    1 uCOS—II内核工作原理
    1.1 基本工作原理
    多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通讯。内核提供的基本服务是任务切换。uCOS—II多任务操作系统工作原理:
    (1)首先调用 OSInit()函数初始化处理器,操作系统,以及完成任务控制块(TCB)初始化,TCB优先级表初始化,TCB链表初始化,事件控制块(ECB)链表初始化,空任务的创建;
    (2)然后创建新任务,每个任务都被赋予一定的优先级,有它自己的一套CPU寄存器和自己的栈空间。
    (3)最后调用OSSTART()函数开始多任务调度。
    1.2 任务调度实现方式
    系统任务调度有两种触发方式:中断级的和任务级的。
    (1)中断级的调度是系统多任务调度开始后,启动时钟节拍源开始计时,给系统提供周期性的时钟中断信号,实现延时和超时确认。当时钟节拍中断发生时,CPU由中断向量进入中断服务子程序,CPU会自动把当前内容的寄存器推入自己的堆栈,然后进行中断处理,中断处理完判断有无任务延时到期,若有则使该任务进入就绪态,并把所有进入就绪态的任务的优先级进行比较,通过中断级任务切换函数将最高优先级任务的状况从该任务的栈中装入CPU寄存器,执行该任务。若没有别的任务进入就绪态,则恢复现场继续执行原任务。
    (2)任务级的调度是通过任务发软中断命令或依靠处理器在任务执行中调度函数OSSCHEDULE(),当多任务内核决定运行另外的任务时,例如任务要等待信号量或正在执行的任务被挂起时,就需要在此任务中调度,找出目前处于就绪态的优先级最高的任务去执行。它保存正在运行任务的当前状态,即CPU 寄存器中的全部内容。这些内容保存在任务自己的栈区之中。入栈工作完成以后,就是把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU的寄存器,并开始下一个任务的运行。当没有任何任务进入就绪态时,就去执行空任务OSIDLE()。
    1.3 共享资源的处理
    可以被一个以上任务使用的资源叫做共享资源。
    (1)可重入型函数可以被一个以上的任务调用,而不必担心数据的破坏。可重入型函数一般只使用局部变量,即变量保存在CPU寄存器中或每个任务自己的堆栈中。如果使用全局变量,则要把它视为不可重入型函数。所以可重入型函数任何时候都可以被中断,一段时间以后又可以运行,而相应数据不会丢失。
    (2)因为uCOS—II为可剥夺型内核,它总是让就绪态的高优先级的任务先运行,中断服务程序可以抢占CPU,到中断服务完成时,内核让此时优先级最高的任务运行(不一定是当时被中断的任务),这时不可重入型函数中的数据有可能被破坏。所以应用程序不应直接使用不可重入型函数,在调用时,要满足互斥条件,这一点我们以前的处理方法是调用函数之前关中断,调用后再开中断,但是如果关中断的时间太长,会影响整个系统的中断响应时间。现在可以用互斥型信号量来实现。
    (3)任务要运行下去先要得到信号,想要得到信号量的任务执行等待操作。如果该信号量有效(即信号量值大于0),则信号量值减1,任务得以继续运行。反之,则代表信号已被别的任务占用,等待信号量的任务就被挂起并列入等待信号量任务表。直到信号被当前使用者释放。
    2 移植过程
    2.1需要移植的文件
    移植工作主要是对源文件的添加和改写,本文以在MC9S12A64上的移植为例,分析μC/OS-II移植的一般方法。
    (1)μC/OS-II与CPU类型无关的代码:μC/OS-II.H和μC/OS-II.C,其中μC/OS-II.C文件包含以下文件:OS_CORE.C  OS_TASK.C OS_TIME.C OS_SEM.C  OS_MBOX.C  OS_MUTEX.C  OS_FLAG.C也就是说这些文件可以直接添加不用修改。
    (2)μC/OS-II与CPU类型有关的代码OS_CPU.H  OS_CPU_A.ASM  OS_CPUC.C.也就是说用户需要根据单片机的类型将这些函数修改后才能添加入内核。OS_CPU.H文件定义与编译器相关的数据类型,堆栈的宽度和增长方式以及开关中断的宏定义。由于我在移植时使用的是C交叉编译器,可以使OS_CPU_A.ASM和OS_CPUC.C两个文件合成为一个 OS_CPUC.C文件。
    2.2 OS_CPU_C.C中主要函数的创建
    2.2.1 时钟节拍中断服务子程序
    void OSTickISR(void)
    {
    /*  asm{
    ldaa    ppage   //将每个任务的存储页面寄存器推入堆栈
    psha
    }*/
    OSIntEnter();       //通知μC/OS-II进入时钟节拍中断服务子程序
    OS_SAVE_SP();
    CRGFLG &=0xEF;      //清中断标志
    OSTimeTick();      //给每个用户任务控制块OS_TCB中的时间延时项OSTCBDly减1
    OSIntExit();        //通知μC/OS-II时钟节拍服务子程序结束。
    /*asm{
    pula
    staa    ppage       //恢复页面寄存器
    nop
    rti                 //中断返回
    }*/
    }
    当时钟节拍中断发生时,CPU12会自动把CPU寄存器推入堆栈,但并不包括存储页面寄存器PPAGE,如果单片机系统的寻址范围超过64KB,需要通过给PPAGE赋值来区分不同的16KB地址,则需要把PPAGE也推入堆栈。OSTimtick()当某任务的任务控制块中的时间延时项 OSTCBDly减到了零,这个任务就进入了就绪态。OSIntExit()会调用中断级的任务切换函数OSIntCtxSw()做任务切换,而不再执行后面的指令。如果没有更高优先级的任务进入就绪态,则CPU会返回中断前状态。
    2.2.2 任务级任务切换
    OSCtxSW()实际上就是软中断服务子程序,软中断服务子程序向量地址必须指向OSCtxSW(),在连接文件中设置。如果当前任务调用μC/OS-Ⅱ提供的系统服务,并使得更高优先级任务处于就绪状态,μC/OS-Ⅱ就会借助上面提到的向量地址找到OSCtxSw()。
    void OSCtxSw(void)
    {
    asm{
    ldaa    ppage               //将任务的存储页面寄存器推入堆栈
    psha
    ldx     OSTCBCur            // 保存被挂起任务的堆栈指针到任务控制块
    sts     0,x
    }
    OSTaskSwHook();               // 如果有时间要求苛刻的任务,可以通过用户接口函数调用
    OSTCBCur = OSTCBHighRdy;       // 将优先级最高的就绪态任务的任务控制块指针复制给当前运行的任务控制块中
    OSPrioCur = OSPrioHighRdy;
    asm{
    ldx     OSTCBCur            // 找出新任务的堆栈指针
    lds     0,x                 // 装入CPU的SP寄存器中
    pula
    staa    ppage               //恢复页面寄存器
    nop
    rti
    }
    }
    2.3 信号量的建立与使用
    (1)首先到μC/OS-II配置文件OS_CFG.C中将OS_SEM_EN设置为1这样μC/OS-II才能支持信号量。接着要调用OSSemCreate()函数建立该信号量,如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1.
    (2)等待信号量OSSemPend()函数,它首先检查指针pevent所指的任务控制块是否是由OSSemCreate()。如果信号量当前是可用的,将信号量的计数值减1,然后函数将“无错”错误代码返回给它的调用函数。如果信号量的计数值为0,则调用OSSemPend()函数的任务要进入睡眠状态,等待另一个任务发出该信号量。OSSemPend()允许用户定义一个最长等待时间作为它的参数,这样可以避免该任务无休止地等待下去。如果该参数值为0,则该任务将一直等待下去。
    3)发送信号量OSSemPost()函数。它也首先检查参数指针pevent指向的任务控制块是否是OSSemCreate()函数建立的,接着检查是否有任务在等待该信号量。如果有任务正在等待该信号量,就把其中的最高优先级任务从等待任务列表中删除并使它进入就绪状态。如果这时没有任务在等待该信号量,该信号量的计数值就简单地加1.
    Sem=OSSemCreate(1);  //创建一个信号量
    OSSemPend(Sem,0,&err);// 等待信号量
    不可重入型函数…
    OSSemPost(Sem);       //释放信号量
    3 基于μC/OS-II操作系统的项目创建
    源程序移植完,用户就可以试着制作自己的项目。编写任务代码与以前在前后台系统中基本相同,不同的是要把每个任务独立编写成一个文件,最后由主程序统一调度。我们可以先分别用三个文件编写三个小任务,每个任务都要设置成死循环。为了方便调度,任务的起始地址应被强制定位在固定地址上,我就规定它们起始地址分别为0x8000,0x9000和0xA000,如果任务都比较复杂,可以将每个任务的程序都定位在0x8000-0xBFFF地址空间,但分配的页面不同。其余的程序都要下到0x4000开始的地址中,这些工作都是在程序下载时解决的。所以我下载的Targets选择的是Flash Application它可以把程序下到0x4000开始的地址中,交叉编译软件Code Warrior给出的连接文件_MC9S12A64_FLAT.prm是程序最终链接与定位用的,他给出了程序的装载地址,用户可以根据使用单片机的内存分配情况和应用要求来修改这个文件。在定位时我先定义了三个地址块,
    PAGE_3C8000 = READ_ONLY  0x3C8000 TO 0x3C8FFF;
    PAGE_3C9000 = READ_ONLY  0x3C9000 TO 0x3C9FFF;
    PAGE_3CA000 = READ_ONLY  0x3CA000 TO 0x3CAFFF;
    然后将任务的程序代码和常数代码分别放入不同的地址块,
    TASK1CODESEG, TASK1STRINGSEG      INTO PAGE_3C8000;
    TASK2CODESEG, TASK2STRINGSEG      INTO  PAGE_3C9000;
    TASK3CODESEG, TASK3STRINGSEG      INTO  PAGE_3CA000;
    同时在每个任务的。C文件的开头加上
    #pragma CODE_SEG   TASK1(2.3)CODESEG
    #pragma STRING_SEG  TASK1(2.3)STRINGSEG
    在末尾加上
    #pragma CODE_SEG DEFAULT
    #pragma STRING_SEG DEFAULT   即可实现任务的地址分配。
    三个任务虽然分别用。C文件定义了,但并没有被任务程序调用过,它们的运行是靠μC/OS-II的任务调度实现的。没有被调用的函数不会自动链接到最终生成的目标代码中去,故要在链结文件中加上:
    ENTRIES
    Task1  Task2  Task3
    END
    在文件的最后,还要定义三个向量。第一个是应用程序的起始地址,第二个是软中断向量,第三个是定时中断的中断服务子程序的入口地址。
    VECTOR ADDRESS 0xFFFE  _Startup
    VECTOR ADDRESS 0xFFF6  OSCtxSw
    VECTOR ADDRESS 0xFFF0  OSTickISR
    程序的定位与链接完成以后就可以编写主程序对个任务进行调度。如果运行无误,操作系统的大体框架就建立起来,用户以后可以将复杂的任务加进来,也可以添加一些功能,这些做起来就相对比较简单。
    4 总结:
    本文作者创新点在于从uCOS—II内核工作原理入手,以MC9S12A64为例介绍uCOS—II内核的移植过程及项目的创建。通过原理对其主要函数进行剖析,最后根据自身处理器及编译器的情况移植文件。创建项目最重要的是程序的链接与定位,这要对内核的结构、CPU的地址分配、链接文件有较深入地了解。uCOS—II实时操作系统的移入,不但可以提高系统的实时性、可靠性和稳定性,还提高了应用软件的可移植性,降低了开发人员的工作量。
返回列表