最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 正文

ucos介绍

来源:动视网 责编:小OO 时间:2025-10-02 00:05:47
文档

ucos介绍

uC/OS-II简介uC/OS是一种免费公开源代码、结构小巧、具有可剥夺实时内核的实时操作系统。μC/OS-II的前身是μC/OS,最早出自于1992年美国嵌入式系统专家JeanJ.Labrosse在《嵌入式系统编程》杂志的5月和6月刊上刊登的文章连载,并把μC/OS的源码发布在该杂志的BBS上。μC/OS和μC/OS-II是专门为计算机的嵌入式应用设计的,绝大部分代码是用C语言编写的。CPU硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种
推荐度:
导读uC/OS-II简介uC/OS是一种免费公开源代码、结构小巧、具有可剥夺实时内核的实时操作系统。μC/OS-II的前身是μC/OS,最早出自于1992年美国嵌入式系统专家JeanJ.Labrosse在《嵌入式系统编程》杂志的5月和6月刊上刊登的文章连载,并把μC/OS的源码发布在该杂志的BBS上。μC/OS和μC/OS-II是专门为计算机的嵌入式应用设计的,绝大部分代码是用C语言编写的。CPU硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种
uC/OS-II简介

    u C / O S 是一种免费公开源代码、结构小巧、具有可剥夺实时内核的实时操作系统。

    μC/OS-II 的前身是μC/OS,最早出自于1992 年美国嵌入式系统专家Jean J.Labrosse 在《嵌入式系统编程》杂志的5 月和6 月刊上刊登的文章连载,并把μC/OS 的源码发布在该杂志的B B S 上。

    μC/OS 和μC/OS-II 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。用户只要有标准的ANSI 的C交叉编译器,有汇编器、连接器等软件工具,就可以将μC/OS-II嵌人到开发的产品中。μC/OS-II 具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB 。μC/OS-II 已经移植到了几乎所有知名的CPU 上。

    严格地说uC/OS-II只是一个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。但由于uC/OS-II良好的可扩展性和源码开放,这些非必须的功能完全可以由用户自己根据需要分别实现。

    uC/OS-II目标是实现一个基于优先级调度的抢占式的实时内核,并在这个内核之上提供最基本的系统服务,如信号量,邮箱,消息队列,内存管理,中断管理等。

任务管理

    uC/OS-II 中最多可以支持 个任务,分别对应优先级0~63,其中0 为最高优先级。63为最低级,系统保留了4个最高优先级的任务和4个最低优先级的任务,所有用户可以使用的任务数有56个。

    uC/OS-II提供了任务管理的各种函数调用,包括创建任务,删除任务,改变任务的优先级,任务挂起和恢复等。

    系统初始化时会自动产生两个任务:一个是空闲任务,它的优先级最低,改任务仅给一个整形变量做累加运算;另一个是系统任务,它的优先级为次低,改任务负责统计当前cpu的利用率。

时间管理

    uC/OS-II的时间管理是通过定时中断来实现的,该定时中断一般为10毫秒或100毫秒发生一次,时间频率取决于用户对硬件系统的定时器编程来实现。中断发生的时间间隔是固定不变的,该中断也成为一个时钟节拍。

    uC/OS-II要求用户在定时中断的服务程序中,调用系统提供的与时钟节拍相关的系统函数,例如中断级的任务切换函数,系统时间函数。

内存管理

    在ANSI C中是使用malloc和free两个函数来动态分配和释放内存。但在嵌入式实时系统中,多次这样的错作会导致内存碎片,且由于内存管理算法的原因,malloc和free的执行时间也是不确定。

    uC/OS-II中把连续的大快内存按分区管理。每个分区中包含整数个大小相同的内存块,但不同分区之间的内存快大小可以不同。用户需要动态分配内存时,系统选择一个适当的分区,按块来分配内存。释放内存时将该块放回它以前所属的分区,这样能有效解决碎片问题,同时执行时间也是固定的。

任务间通信与同步

    对一个多任务的操作系统来说,任务间的通信和同步是必不可少的。uC/OS-II中提供了4中同步对象,分别是信号量,邮箱,消息队列和事件。所有这些同步对象都有创建,等待,发送,查询的接口用于实现进程间的通信和同步。

任务调度

    uC/OS-II 采用的是可剥夺型实时多任务内核。可剥夺型的实时内核在任何时候都运行就绪了的最高优先级的任务。

    uC/os-II的任务调度是完全基于任务优先级的抢占式调度,也就是最高优先级的任务一旦处于就绪状态,则立即抢占正在运行的低优先级任务的处理器资源。为了简化系统设计,uC/OS-II规定所有任务的优先级不同,因为任务的优先级也同时唯一标志了该任务本身。

    任务调度将在以下情况下发生:

1)高优先级的任务因为需要某种临界资源,主动请求挂起,让出处理器,此时将调度就绪状态的低优先级任务获得执行,这种调度也称为任务级的上下文切换。

2)高优先级的任务因为时钟节拍到来,在时钟中断的处理程序中,内核发现高优先级任务获得了执行条件(如休眠的时钟到时),则在中断态直接切换到高优先级任务执行。这种调度也称为中断级的上下文切换。

    这两种调度方式在uC/OS-II的执行过程中非常普遍,一般来说前者发生在系统服务中,后者发生在时钟中断的服务程序中。

    调度工作的内容可以分为两部分:最高优先级任务的寻找和任务切换。其最高优先级任务的寻找是通过建立就绪任务表来实现的。u C / O S 中的每一个任务都有的堆栈空间,并有一个称为任务控制块TCB(Task Control Block)的数据结构,其中第一个成员变量就是保存的任务堆栈指针。任务调度模块首先用变量OSTCBHighRdy 记录当前最高级就绪任务的TCB 地址,然后调用OS_TASK_SW()函数来进行任务切换。

μC/OS-II的组成部分

    μC/OS-II可以大致分成核心、任务处理、时间处理、任务同步与通信,CPU的移植等5个部分。

1) 核心部分(OSCore.c)

    是操作系统的处理核心,包括操作系统初始化、操作系统运行、中断进出的前导、时钟节拍、任务调度、事件处理等多部分。能够维持系统基本工作的部分都在这里。

2) 任务处理部分(OSTask.c)

    任务处理部分中的内容都是与任务的操作密切相关的。包括任务的建立、删除、挂起、恢复等等。因为μC/OS-II是以任务为基本单位调度的,所以这部分内容也相当重要。

3) 时钟部分(OSTime.c)

    μC/OS-II中的最小时钟单位是timetick(时钟节拍)。任务延时等操作是在这里完成的。

4) 任务同步和通信部分

    为事件处理部分,包括信号量、邮箱、邮箱队列、事件标志等部分;主要用于任务间的互相联系和对临界资源的访问。

5) 与CPU的接口部分

    是指μC/OS-II针对所使用的CPU的移植部分。由于μC/OS-II是一个通用性的操作系统,所以对于关键问题上的实现,还是需要根据具体CPU的具体内容和要求作相应的移植。这部分内容由于牵涉到SP等系统指针,所以通常用汇编语言编写。主要包括中断级任务切换的底层实现、任务级任务切换的底层实现、时钟节拍的产生和处理、中断的相关处理部分等内容。

转载请注明出处,文章来源:http://www.threeway.cc/sitecn/InformationInfo.aspx?pid=1675&tid=1380

典教

1 所谓嵌入式系统是以应用为中心,以计算机技术为基础,软硬件可裁减,从而能够适应实际应用中对功能、可靠性、成本、体积、功耗等严格要求的专用计算机系统。 

     它一般由嵌入式微处理器、外围硬件设备、嵌入式操作系统以及用户的应用软件等四个部分组成,用于实现对其他设备的控制、监视或管理等功能。在大型嵌入式应 用系统中,为了使嵌入式开发更方便、快捷,需要具备一种稳定、安全的软件模块集合,用以管理存储器分配、中断处理、任务间通信和定时器响应,以及提供多任 务处理等,即嵌入式操作系统。嵌入式操作系统的引入大大提高了嵌入式系统的功能,方便了应用软件的设计,但同时也占用了宝贵的嵌入式系统资源。嵌入式操作 系统常常有实时要求,所以嵌入式操作系统往往又是“实时操作系统”。早期的嵌入式系统几乎都用于控制目的,从而或多或少都有些实时要求,所以从前“嵌入式 操作系统”实际上是“实时操作系统”的代名词。近年来,由于手持式计算机和掌上电脑等设备的出现,也有了许多不带实时要求的嵌入式操作系统。另一方面,由 于CPU速度的提 高,一些原先以为是“实时”的反应速度现在已经很普遍了。这样,一些原先需要在“实时”操作系统上才能实现的应用,现在已不难在常规的操作系统上实现。在 这样的背景下,“嵌入式操作系统”和“实时操作系统”就成了不同的概念和名词。而实时操作系统能及时响应外部事件的请求,在规定的时间内完成对该事件的处 理,并控制所有实时任务协调一致地运行,具有性、及时性、可靠性的特点。顾名思义,嵌入式实时操作系统则是在综合了以上的两种操作系统的特点之后形成 的,嵌入式实时操作系统没有一般的计算机操作系统的文件管理等庞大内容,一般也没有内存管理,它所拥有的是实时操作系统中最重要的内容,即多任务实时调度 和任务的定时、同步操作。其二进制代码的大小通常为几KB到几十KB,是纯粹为嵌入式应用而设计的,具有很短的任务切换时间和很高的实时响应速度。而嵌入式实时操作系统的核心是实时多任务内核。

 

2  嵌入式实时操作系统µC/OS-II简介

    µC/OS-II是著名的源代码公开的实时内核,是一个完整的,可移植、固化、裁剪的占先式实时多任务内核。µC/OS-II是用ANSI C编写的,包含一小部分与微处理器类型相关的汇编语言代码,使之可供不同架构的微处理器使用。虽然µC/OS-II是在PC机上开发和测试的,但µC/OS-II的实际对象是嵌入式系统,并且很容易移植到不同架构的微处理器上。至今,从8位到位,µC/OS-II已在超过40中不同架构的微处理器上运行。

 

3  嵌入式实时操作系统µC/OS-II内核结构

3.1 临界区(Critical Sections),OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()

    同其他内核一样,µC/OS-II为了处理临界区代码,必须关中断,处理完毕后再开中断。关中断使得µC/OS-II能够避免同时有其他任务或中断服务进入临界区代码。关中断的时间是实时内核开发商应提供的最重要的指标之一,因为这个指标影响用户系统对实时事件的响应特性。µC/OS-II努力使关中断时间降至最短,但就使用µC/OS-II而言,关中断的时间在很大程度上取决于微处理器的结构以及C编译器所生成的代码质量。

    微处理器一般都有关中断和开中断指令,用户使用的C编译器必须具有某种机制,能够在C源代码中直接实现关中断/开中断操作。有些C编译器允许在用户的C源代码中嵌入汇编语言的语句,使得关中断/开中断很容易实现;而有些C编译器把从C语言中关中断/开中断的操作放在语言的扩展部分,从而直接从C语言中可以关中断/开中断。

    µC/OS-II定义了2个宏来关中断和开中断,以便避免不同C编译器厂商使用不同的方法来处理关中断和开中断。µC/OS-II中的这2个宏分别是OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。因为这2个宏的定义取决于使用的微处理器,故在文件OS_CPU.H中可以找到相应的宏定义。每种微处理器都有自己的OS_CPU.H文件。

    OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()总是成对使用的,把临界区代码封装起来,如以下代码所示:

{

    ……

    ……

    OS_ENTER_CRITICAL();

    /* µC/OS-II临界区代码 */

    OS_EXIT_CRITICAL();

    ……

    ……

}

    OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()也可以用来保护应用程序中的临界区代码。

3.2 任务(Tasks)

    任务通常是一个无限循环,但是当任务完成后,任务可以自我删除。µC/OS-II可以管理多达个任务,但是µC/OS-II的作者建议用户不要使用优先级为0,1,2,3的任务,以及优先级为OS_LOWEST_PRIO-3,OS_LOWEST_PRIO-2, OS_LOWEST_PRIO-1和 OS_LOWEST_PRIO的任务,因为在未来的µC/OS-II版本中可能会用到这些任务。因此,如果遵循作者的建议,不使用以上优先级最高的4个任务和优先级最低的4个任务,则用户最多可以有56个自己的任务。

3.3 任务状态(Task States)

    下图是µC/OS-II控制下的任务状态转换图[3]。在任一给定的时刻,任务的状态应为以下5种状态之一。

 

    睡眠态(DORMANT)——指任务驻留在程序空间,还没有交给µC/OS-II来管理。把任务交给µC/OS-II,是通过调用下述2个函数之一:OSTaskCreate()或OSTaskCreateExt()来实现的。这些调用只是用于告诉µC/OS-II,任务的起始地址在哪里;任务建立时,用户给任务赋予的优先级是多少;任务要使用多少栈空间等。

    就绪态(READY)——任务一旦建立,这个任务就进入了就绪态,准备运行。任务的建立可以是在多任务运行开始之前,也可以动态地由一个运行着地任务建立。如果多任务已经启动,且一个任务是被另一个任务建立的,而新建立的任务优先级高于建立它的任务的优先级,则这个刚刚建立的任务将立即得到CPU的使用权。一个任务可以通过调用OSTaskDel()返回到睡眠态,或通过调用该函数让另一个任务进入睡眠态。

    运行态(RUNNING)——调用OSStart()可以启动多任务。OSStart()函数只能在启动时调用一次,该函数运行用户初始化代码中已经建立的、进入就绪态的优先级最高的任务。优先级最高的任务就这样进入了运行态。任何时刻只能有一个任务处于运行态。就绪的任务只有当所有优先级高于这个任务的任务都转为等待状态,或者是被删除了,才能进入运行态。

    等待状态(WAITING)——正在运行的任务可以通过调用以下2个函数之一:OSTimeDly()或OSTimeDlyHMSM(),将自身延迟一段时间。这个任务于是进入等待状态,一直到函数中定义的延迟时间到。这2个函数会立即强制执行任务切换,让下一个优先级最高的、并进入了就绪态的任务运行。等待的时间过去以后,系统服务函数OSTimeTick()使延迟了的任务进入就绪态。而正在运行的任务可能需要等待某一事件的发生,可以通过调用以下函数之一实现:OSFlagPend()、OSSemPend()、OSMutexPend()、OSMboxPend()或OSQPend()。如果该事件并未发生,调用上述函数的任务就进入了等待状态,直到等待的事件发生了。当任务因等待事件而被挂起时,下一个优先级最高的任务立即得到了CPU的使用权。当事件发生了或等待超时使,被挂起的任务就进入就绪态。事件发生的报告可能来自另一个任务,也可能来自中断服务子程序。

    中断服务态(ISR)——正在运行的任务是可以被中断的,除非该任务将中断关闭,或者µC/OS-II将中断关闭。被中断了的任务于是进入了中断服务态。相应中断时,正在执行的任务被挂起,中断服务子程序得到了CPU的使用权。中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就绪态。在这种情况下,从中断服务子程序返回之前,µC/OS-II要判定被中断的任务是否还是就绪态任务中优先级最高的。如果中断服务子程序使另一个优先级更高的任务进入就绪态,则新进入就绪态的这个优先级更高的任务将得以运行;否则,原来被众多拉的任务将继续运行。

    当所有的任务都在等待事件的发生或等待延迟时间的结束时,µC/OS-II执行被称为空闲任务(idle task)的内部任务,即OSTaskIdle()。

3.4 任务控制块(Task Control Blocks)

    一旦任务建立,一个任务控制块OS_TCB就被赋值。任务控制块是一个数据结构,当任务的CPU使用权被剥夺是,µC/OS-II用它保存该任务的状态。当任务重新得到CPU使用权时,任务控制块能确保任务从当时被中断的那一点丝毫不差地继续执行。OS_TCB全部驻留在RAM中。

3.5 就绪表(Ready List)

    每个任务被赋予不同的优先级等级,从0到最低优先级OS_LOWEST_PRIO,包括0和OS_LOWEST_PRIO在内。当µC/OS-II初始化时,最低优先级OS_LOWEST_PRIO总是被赋给空闲任务。

    每个就绪的任务都放在就绪表中,就绪表中有2个变量,OSRdyGrp和OSRdyTbl[]。在OSRdyGrp中,任务按优先级分组,8个任务为一组。OSRdyGrp中的每一位表示8组任务中每一组是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中相应元素的相应位也置为1。OSRdyGrp和OSRdyTbl[]之间的关系见下图[3]:

 

    就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PRIO。当应用程序中任务数目比较少时,这种安排可以减小OS_LOWEST_PRIO的值,可以降低µC/OS-II对RAM的需求量。

为确定下一次该哪个优先级的任务运行了,µC/OS-II中的调度器总是将最低优先级的任务在就绪表中相应字节的相应位置1。

    从上图可以看出,任务优先级的低3位用于确定任务在总就绪表OSRdyTbl[]中的所在位。接下去的3位用于确定是在OSRdyTbl[]数组的第几个元素。

3.5 任务调度(Task Scheduling)

    µC/OS-II总是运行进入就绪态任务中优先级最高的任务。确定哪个任务优先级最高,下面该哪个任务运行了,这一工作是由调度器(scheduler)完成的。任务级的调度是由函数OS_Sched()完成的。中断级的调度是由另一个函数OSIntExt()完成的。µC/OS-II任务调度的执行时间是常数,与应用程序建立了多少个任务没有关系。

    任务切换很简单,由以下2步完成:将被挂起任务的处理器寄存器压入堆栈;然后将较高优先级任务的寄存器值从堆栈中恢复到寄存器中。在µC/OS-II中,就绪任务的堆栈结构总是看起来跟刚刚发生过中断一样,所有处理器的寄存器都保存在堆栈中。换句话说,µC/OS-II运行就绪态的任务所要做的一切,只是恢复所有的CPU寄存器并运行中断返回指令。为了做任务切换,运行OS_TASK_SW(),人为模仿了一次中断。多数微处理器由软中断指令或者指令陷阱来实现上述操作。中断服务子程序或陷阱处理,也称做异常处理,必须给汇编语言函数OSCtxSw()提供中断向量。OSCtxSw()除了需要OS_TCBHighRdy指向即将被挂起的任务,还需要让当前任务控制块OSTCBCur指向即将被挂起的任务。

    OS_Sched()的所有代码都属于临界区代码。在寻找进入就绪态的优先级最高的任务过程中,为防止中断服务子程序把一个或几个任务的就绪位置位,中断是关闭的。为缩短切换时间,OS_Sched()全部代码都可以用汇编语言写。为增加可读性、可移植性及将汇编语言代码最少化,OS_Sched()是用C语言编写的。

3.6 给调度器上锁和开锁(Locking and Unlocking the Scheduler)

    给调度器上锁函数OSSchedLock()用于禁止任务调度,直到任务完成后,调用给调度器开锁函数OSSchedUnlock()为止。调用OSSchedLock()的任务将保持对CPU的使用权,即使有个优先级更高的任务进入了就绪态。此时,中断仍然是可以识别的,中断服务也能得到(假设此时中断是开着的)。OSSchedLock()和OSSchedUnlock()必须成对的使用。变量OSLockNesting跟踪OSSchedLock()函数被调用的次数,以允许嵌套的函数包含临界区代码,这段代码其他任务不得干预。µC/OS-II允许嵌套深度达255层。当OSLockNesting=0时,任务调度重新得到允许。函数OSSchedLock()和OSSchedUnlock()的使用要非常谨慎,因为它们影响到了µC/OS-II对任务的正常管理。调用OSSchedLock()之后,用户应用程序不得调用可能会使当前任务挂起的系统功能函数。也就是说,用户应用程序不得调用OSFlagPend(),OSMboxPend(),OSMutexPend(),OSQPend(),OSSemPend(),OSTaskSuspend(OS_PRIO_SELF),OSTimeDly()或者OSTimeDlyHMSM(),直到OSLockNesting回0为止。因为OSSchedLock()给调度器上了锁,不让其他任务运行,用户锁住了系统。

3.7 空闲任务(Idle Task)

    µC/OS-II总要建立一个空闲任务(idle task),这个任务在没有其他任务进入就绪态使投入运行。这个空闲任务(OSTaskIdle())永远被设置为最低优先级,即OS_LOWEST_PRIO。空闲任务不可能被应用软件删除。

3.8 统计任务(Statistics Task)

    µC/OS-II有一个统计运行时间的任务,叫做OSTaskStat()。如果将系统配置常数OS_TASK_STAT_EN设置为1,这个任务就会建立。一旦得到了允许,OSTaskStat()每秒运行1次,计算当前的CPU利用率。换句话说,OSTaskStat()告诉用户应用程序使用了多少CPU时间,用百分比表示。这个值放在一个有符号8位整数OSCPUUsage中,精确度是1%。如果应用程序打算使用统计任务,那么必须在初始化时建立唯一的任务中调用统计任务初始化函数OSStatInit()。换句话说,在调用系统启动函数OSStart()之前,用户初始代码中必须建立一个任务,在这个任务中调用系统统计任务初始化函数OSStatInit(),然后再建立应用程序中的其他任务。

3.9 µC/OS-II中的中断(Interrupts under µC/OS-II)

    µC/OS-II中,中断服务子程序要用汇编语言来编写。然而,如果用户使用的C语言编译器支持在线(in-line)汇编语言,则可以直接将中断服务子程序代码放在C语言的源文件中。µC/OS-II的中断服务过程大致如下:

1) 中断到来,但还不能被CPU识别。也许是因为中断被µC/OS-II或用户应用程序关了,或者是因为CPU还没执行完当前指令。

2) 一旦CPU响应了这个中断,CPU的中断向量被装载,跳转到中断服务子程序。

3) 中断服务子程序保存CPU的全部寄存器。

4) 保存完CPU寄存器之后,中断服务子程序通知µC/OS-II进入中断服务子程序。做法是调用OSIntEnter(),或者给OSIntNesting家1。还应该将堆栈指针保存到当前任务控制块OS_TCB中。

5) 用户中断服务代码开始执行。中断服务所做的事应尽可能的少,应把大部分工作留给任务去做。

6) 中断服务完成后,必须调用OSIntExit(),通知µC/OS-II退出中断服务。

7) 恢复CPU的寄存器,中断返回。

3.10 时钟节拍(Clock Tick)

    µC/OS-II需要提供周期性的信号源,用于实现时间延时和确认超时。节拍率应为10~20次/秒,或者说10~100Hz。时钟节拍率越高,系统的额外负荷就越重。时钟节拍的实际频率取决于应用程序的精度。时钟节拍源可以是专门的硬件定时器,也可以是来自50/60Hz交流电源的信号。必须在多任务系统启动之后,也就是在调用OSStart()之后,再开启时钟节拍器。换句话说,调用OSStart()之后应做的第一件事是初始化定时器中断。通常容易犯的错误是,将允许时钟节拍器中断放在系统初始化函数OSInit()之后,在启动多任务系统启动函数OSStart()之前。µC/OS-II中的时钟节拍服务是通过在中断服务子程序中调用OSTimeTick()实现的。OSTimeTick()跟踪所有任务的定时器以及超时时限,时钟节拍中断服务子程序的代码必须用汇编语言编写,因为在C语言中不能直接处理CPU的寄存器。

3.11 µC/OS-II初始化(µC/OS-II Initialization)

    在调用µC/OS-II的任何其他服务之前,µC/OS-II要求首先调用系统初始化函数OSInit()。OSInit()初始化µC/OS-II所有的变量和数据结构。OSInit()建立空闲任务OSTaskIdle(),这个任务总是处于就绪态。空闲任务OSTaskIdle()的优先级总是设成最低,即OS_LOWEST_PRIO。如果统计任务允许OS_TASK_STAT_EN和任务建立扩展都设为1,则OSInit()还须建立统计任务OS_TaskStat(),并且使其进入就绪态。OS_TaskStat的优先级总是设为OS_LOWEST_PRIO-1。

3.12 µC/OS-II的启动(Starting µC/OS-II)

    多任务的启动是通过调用OSStart()实现的。然而,在启动µC/OS-II之前,必须建立至少一个应用任务。当调用OSStart()时,OSStart()从任务就绪表中找出用户建立的优先级最高的任务的任务控制块。接着,OSStart()调用高优先级就绪任务启动函数OSStartHighRdy()函数,后者将任务栈中保存的值弹回到CPU寄存器中,然后执行一条中断返回指令,中断返回指令强制执行该任务代码。

 

4  结束语

    以上简要介绍了嵌入式实时操作系统µC/OS-II的内核结构,可以看出µC/OS-II具备一个嵌入式实时操作系统内核所必须的基本功能和良好的可移植性,目前也已经在嵌入式系统领域得到了广泛的应用。

转载请注明出处,文章来源:http://www.threeway.cc/sitecn/informationInfo.aspx?tid=1380&pid=1678

指南

本文面向首次接触uC/OS-II的程序员,为他们介绍一下这个系统的一些基本特征和编程上的注意事项,并介绍几个值得了解的API。本文作者已经成功的将uC/OS-II移植到几种不同CPU之上。包括EPSON S1C33和Sunplus unSP?等,积累了丰富的经验,现在愿意和朋友们分享这些经历。希望本文的资料对于希望使用这个系统来开发的朋友有所帮助,作者乐意与您分享任何您成功的喜悦。

(一) uC/OS-II 简介

    uC/OS-II是一种基于优先级的可抢先的硬实时内核。自从92年发布以来,在世界各地都获得了广泛的应用,它是一种专门为嵌入式设备设计的内核,目前已经被移植到40多种不同结构的CPU上,运行在从8位到64位的各种系统之上。尤其值得一提的是,该系统自从2.51版本之后,就通过了美国FAA认证,可以运行在诸如航天器等对安全要求极为苛刻的系统之上。鉴于uC/OS-II可以免费获得代码,对于嵌入式RTOS而言,选择uC/OS无疑是最经济的选择。

(二) uC/OS-II 应用程序基本结构

    应用uC/OS-II,自然要为它开发应用程序,下面论述基于uC/OS-II的应用程序的基本结构以及注意事项。

    每一个uC/OS-II应用至少要有一个任务。而每一个任务必须被写成无限循环的形式。以下是推荐的结构:

void task ( void* pdata )

{

  INT8U err;

  InitTimer(); // 可选

  For( ;; )

  {

    // 你的应用程序代码

    …….

    ……..

    OSTimeDly(1); // 可选

  }

}

    以上就是基本结构,至于为什么要写成无限循环的形式呢?那是因为系统会为每一个任务保留一个堆栈空间,由系统在任务切换的时候换恢复上下文,并执行一条reti 指令返回。如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定。换句话说,就是“跑飞”了。所以,每一个任务必须被写成无限循环的形式。程序员一定要相信,自己的任务是会放弃CPU使用权的,而不管是系统强制(通过ISR)还是主动放弃(通过调用OS API)。

    现在来谈论上面程序中的InitTimer()函数,这个函数应该由系统提供,程序员有义务在优先级最高的任务内调用它而且不能在for循环内调用。注意,这个函数是和所使用的CPU相关的,每种系统都有自己的Timer初始化程序。在uC/OS-II的帮助手册内,作者特地强调绝对不能在OSInit()或者OSStart()内调用Timer初始化程序,那会破坏系统的可移植性同时带来性能上的损失。所以,一个折中的办法就是象上面这样,在优先级最高的程序内调用,这样可以保证当OSStart()调用系统内部函数OSStartHighRdy()开始多任务后,首先执行的就是Timer初始化程序。或者专门开一个优先级最高的任务,只做一件事情,那就是执行Timer初始化,之后通过调用OSTaskSuspend()将自己挂起来,永远不再执行。不过这样会浪费一个TCB空间。对于那些RAM吃紧的系统来说,还是不用为好。

(三)一些重要的uC/OS-II API介绍

    任何一个操作系统都会提供大量的API供程序员使用,uC/OS-II也不例外。由于uC/OS-II面向的是嵌入式开发,并不要求大而全,所以内核提供的API也就大多和多任务息息相关。主要的有以下几类:

1)任务类

2)消息类

3)同步类

4)时间类

5)临界区与事件类

    我个人认为对于初级程序员而言,任务类和时间类是必须要首先掌握的两种类型的API。下面我就来介绍比较重要的:

1) OSTaskCreate函数

    这个函数应该至少再main函数内调用一次,在OSInit函数调用之后调用。作用就是创建一个任务。目前有四个参数,分别是任务的入口地址,任务的参数,任务堆栈的首地址和任务的优先级。调用本函数后,系统会首先从TCB空闲列表内申请一个空的TCB指针,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。

2) OSTaskSuspend函数

    这个函数很简单,一看名字就该明白它的作用,它可以将指定的任务挂起。如果挂起的是当前任务的话,那么还会引发系统执行任务切换先导函数OSShed来进行一次任务切换。这个函数只有一个参数,那就是指定任务的优先级。那为什么是优先级呢?事实上在系统内部,优先级除了表示一个任务执行的先后次序外,还起着分别每一个任务的作用,换句话说,优先级也就是任务的ID。所以uC/OS-II不允许出现相同优先级的任务。

3) OSTaskResume函数

    这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成就绪状态。如果恢复任务的优先级高于当前任务,那么还为引发一次任务切换。其参数类似OSTaskSuspend函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用。

4) OS_ENTER_CRITICAL宏

    很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的OS_EXIT_CRITICAL都是宏。他们都是涉及特定CPU的实现。一般都被替换为一条或者几条嵌入式汇编代码。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指令后系统进入临界区。其实,它就是关个中断而已。这样,只要任务不主动放弃CPU使用权,别的任务就没有占用CPU的机会了,相对这个任务而言,它就是独占了。所以说进入临界区了。这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。

5) OS_EXIT_CRITICAL宏

    这个是和上面介绍的宏配套使用另一个宏,它在系统手册里的说明是退出临界区。其实它就是重新开中断。需要注意的是,它必须和上面的宏成对出现,否则会带来意想不到的后果。最坏的情况下,系统会崩溃。我们推荐程序员们尽量少使用这两个宏调用,因为他们的确会破坏系统的多任务性能。

6) OSTimeDly函数

    这应该程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高就绪任务的话,那么运行之。简单点说,就是可以任务延时一定时间后再次执行它,或者说,暂时放弃CPU的使用权。一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权,好东西要大家分享嘛!

(四) uC/OS-II 多任务实现机制分析

    前面已经说过,uC/OS-II是一种基于优先级的可抢先的多任务内核。那么,它的多任务机制到底如何实现的呢?了解这些原理,可以帮助我们写出更加健壮的代码来。由于我们面向的初级程序员,本文不打算写成又一篇uC/OS-II的源码分析,那样的文章太多了,本文打算从实现原理的角度探讨这个问题。

    首先我们来看看为什么多任务机制可以实现?其实在单一CPU的情况下,是不存在真正的多任务机制的,存在的只有不同的任务轮流使用CPU,所以本质上还是单任务的。但由于CPU执行速度非常快,加上任务切换十分频繁并且切换的很快,所以我们感觉好像有很多任务同时在运行一样。这就是所谓的多任务机制。

    由上面的描述,不难发现,要实现多任务机制,那么目标CPU必须具备一种在运行期更改PC的途径,否则无法做到切换。不幸的使,直接设置PC指针,目前还没有哪个CPU支持这样的指令。但是一般CPU都允许通过类似JMP,CALL这样的指令来间接的修改PC。我们的多任务机制的实现也正是基于这个出发点。事实上,我们使用CALL指令或者软中断指令来修改PC,主要是软中断。但在一些CPU上,并不存在软中断这样的概念,所以,我们在那些CPU上,使用几条PUSH指令加上一条CALL指令来模拟一次软中断的发生。

    回想一下你在微机原理课程上学过的知识,当发生中断的时候,CPU保存当前的PC和状态寄存器的值到堆栈里,然后将PC设置为中断服务程序的入口地址,再下来一个机器周期,就可以去执行中断服务程序了。执行完毕之后,一般都是执行一条RETI指令,这条指令会把当前堆栈里的值弹出恢复到状态寄存器和PC里。这样,系统就会回到中断以前的地方继续执行了。那么设想一下?如果再中断的时候,人为的更改了堆栈里的值,那会发生什么?或者通过更改当前堆栈指针的值,又会发生什么呢?如果更改是随意的,那么结果是无法预料的错误。因为我们无法确定机器下一条会执行些什么指令,但是如果更改是计划好的,按照一定规则的话,那么我们就可以实现多任务机制。事实上,这就是目前几乎所有的OS的核心部分。不过他们的实现不像这样简单罢了。

    下面,我们来看看uC/OS-II再这方面是怎么处理的。再uC/OS-II里,每个任务都有一个任务控制块(Task Control Block),这是一个比较复杂的数据结构。在任务控制快的偏移为0的地方,存储着一个指针,它记录了所属任务的专用堆栈地址。事实上,再uC/OS-II内,每个任务都有自己的专用堆栈,彼此之间不能侵犯。这点要求程序员再他们的程序中保证。一般的做法是把他们申明成静态数组。而且要申明成OS_STK类型。当任务有了自己的堆栈,那么就可以将每一个任务堆栈再那里记录到前面谈到的任务控制快偏移为0的地方。以后每当发生任务切换,系统必然会先进入一个中断,这一般是通过软中断或者时钟中断实现。然后系统会先把当前任务的堆栈地址保存起来,仅接着恢复要切换的任务的堆栈地址。由于哪个任务的堆栈里一定也存的是地址(还记得我们前面说过的,每当发生任务切换,系统必然会先进入一个中断,而一旦中断CPU就会把地址压入堆栈),这样,就达到了修改PC为下一个任务的地址的目的。

    以上就是uC/OS-II的多任务实现机制,我们在这里大费笔墨谈论这个问题,是希望我们的程序员们可以善加利用这个机制,写出更健壮,更富有效率的代码来。 

转载请注明出处,文章来源:http://www.threeway.cc/sitecn/informationInfo.aspx?tid=1380&pid=1676

移植

今天突然有个想法,是否在其他结构比较简单的平台上移植比较容易一点,正好同学有一个凌阳的精简板,反正今天是星期天,就当是休息了。

    首先肯定是去熟悉SPCE061A的结构和IDE了。主要是存储器结构、指令系统和中断这几个部分。本来不是做这个的,没有必要深究,总体看看,知道在哪些地方查就行,所以看到很快。于是摆好uCOS系统的资料,按照移植步骤,一个个文件、函数地写好,其他没有什么,就是时间节拍比较难一点,用了不少时间写,主要是去熟悉凌阳的中断系统,了解几个寄存器的用法。按照标准移植函数步骤写下来,代码也就10来行。

    在这里我想说的不是如何移植,而是编译。凌阳的IDE说实话肯定是不太完善的。因为我同学本科的时候做过,那个时候似乎听他提到过这个问题。不过我今天算是感受到了。

    写好文件,编译——我的错,有一个函数写错了,编译没有通过。然后我改了。编译,???怎么回事,还是这个错误?大体是这样的,我写了一个OSTaskSw函数(原本想写OSCtxSw的),结果,这个IDE居然还真的认出来一个OSTaskSw,我当时就晕了,我好像在内核里没有看到过这个函数嘛。我赶紧去内核查找一下,没有嘛。我把OSTaskSw函数改成OSCtxSw(OS_CPU.H里),再编译,还是有。更晕了~

    这个错误是这样报的:

Error L0080: The external symbol _memset has not a public definition.

Error L0080: The external symbol _OSTaskSw has not a public definition.

memset嘛好说,这应该是我没有包含某个库文件,我只是知道这是个字符串处理函数,应该在string.h里面,但是包含了它,还是有这个错误(现在还没有解决,惭愧~),但是OSTaskSw都没有了还给我报什么?

    后来我想,这个IDE是GCC的,是不是因为增量编译,链接的时候用了以前的文件?干脆把所有以前编译生成的文件删除了(用clear没有用),再编译,嘿嘿,还真的没有这个错误了。I 服了HIM。确实没有用过,问题都不好找。

    今天比较晚了,明天来解决另一个问题吧!我怀疑最大的可能是在工程文件的组织上有问题,应该好好梳理一下。因为我发现,编译应用程序文件是没有错的,Build的时候才出现。

 

    心头憋得慌,一大早就跑到实验室来调试。弄了半天,把include文件夹中的memset搜索了一遍,就只有一个string.h里面有嘛。哪里还有其他的哩?难道我包含的地方不对?“SPCE061A.h”包含在includes.h中,能够使用,按说所有.c文件都包含includes.h,放在这里是没有问题的嘛。不过上天要它说不行,我也没辙啊~没办法,上网查查吧。

    一说是版本问题!我意识到,好像以前看过一个版本,在OSTask.c中好像是没有用到memset函数。那下载一个老版本的来实验一下,总不能让我去改内核吧,改出来更多问题,得不偿失。我下的是2.00的。

    经过一番挣扎一般的调试(很久都没有怎么用过凌阳的IDE,很多功能不会用了,又不知道其Bug,出了问题就只好老老实实重新编译等等,确实很累),总算调通了。

    经过记录下来——为了系统化,把uCOS标准移植伪码加上对比。

 

移植准备:

    ucOS-II的移植主要涉及以下三个文件:

        OS_CPU.H

        OS_CPU_A.ASM

        OS_CPU_C.C

    移植的工作包括以下几个内容:

1. 在 OS_CPU.H 文件中

    声明10个数据类型

        BOOLEAN

        INT8U

        INT8S

        INT16U

        INT16S

        INT32U

        INT32S

        FP32

        FP

        OS_STK

    声明三个宏

        OS_ENTER_CRITICAL()

        OS_EXIT_CRITICAL()

        OS_TASK_SW()

    设置一个常量的值

        OS_STK_GROWTH

2. 编写四个汇编语言函数 (OS_CPU_A.ASM)

        OSStartHighRdy()

        OSCtxSw()

        OSIntCtxSw()

        OSTickISR()

3. 用C语言编写六个简单的函数 (OS_CPU_C.C)

        OSTaskStkInit()

        OSTaskCreateHook()

        OSTaskDelHook()

        OSTaskSwHook()

        OSTaskStatHook()

        OSTimeTickHook()

        ………………

4. 一般认为上面几个方面就构成了整个移植要做的工作,其实我认为还应该包含对Includes.h和OS_CFG.H的配置,因为这些决定了操作系统的功能和编译的环境。

移植工作:

 

1、OS_CPU.H

根据凌阳的数据结构特点,定义如下:

typedef unsigned char  BOOLEAN;

typedef unsigned char  INT8U;

typedef signed   char  INT8S;

typedef unsigned int   INT16U;

typedef signed   int   INT16S;

typedef unsigned long  INT32U;

typedef signed   long  INT32S;

typedef float          FP32;

typedef double         FP;

 

typedef unsigned int   OS_STK;

 

#define  OS_CRITICAL_METHOD    2

 

#if      OS_CRITICAL_METHOD == 2               /*一般都使用第二种方法*/

#define  OS_ENTER_CRITICAL()        __asm__(INT OFF \\n\)              /*关中断*/

#define  OS_EXIT_CRITICAL()           __asm__(INT IRQ \\n\ INT FIQ \\n\)                 /*开中断*/

#endif

 

#define  OS_STK_GROWTH        1                 /*堆栈增长方式,凌阳是从高向低增长*/

 

#define  OS_TASK_SW()         OSCtxSw()               /*任务切换函数*/

 

*这里需要说明的是,很多编译器由于支持软中断,所以,这里通常使用的是软中断进入管理模式,然后进行任务切换(例如在ARM7中)。但是凌阳没有软中断,只好通过函数调用的方式进行,而不是让某个中断向量指向OSCtxSw()。

 

到这里,OS_CPU.H的工作大体就是这么多了。

2、OS_CPU_C.C

在这个文件中,最重要的是OSTaskStkInit()函数的编写,其他Hook函数用来扩展内核功能而不去修改内核结构。

先来看看一般堆栈需要初始化成什么样子:

 

l         保存参数(这个视情况而定,因为有些处理器的参数是通过寄存器传递的;如ARM7)

l         保存任务地址,用于保存当前任务(由于凌阳的段寄存器没用,为0,保存task+1)

l         处理器状态字(凌阳应该是SR吧,反正我保存的是SR)

l         中断返回地址保存(这个好像不太重要,我看ARM7里面就没有保存)

l         寄存器组

    按照这个步骤,编写如下:

    这里注意,版本不同,声明的类型也有不同,在2.52中是OS_STK。

void  *OSTaskStkInit (void (*task)(void *pd), void *pdata, void *ptos, INT16U opt)

{

    OS_STK *stk;

 

    opt    = opt;                      

    stk    = (OS_STK *)ptos;               

    *stk-- = *((INT16U*)pdata);

//         *stk-- = *((INT16U*)task);                    //为0,不需要保存

    *stk-- = *((INT16U*)task + 1);

    *stk-- = (INT16U)0x0000;                         //SR不能乱放东西的,因为这个是程序执行用到的

    *stk-- = (INT16U)0x5555;                         //这些就可以随便放了为了调试方便,可以放一些有意义的数值

    *stk-- = (INT16U)0x4444;

    *stk-- = (INT16U)0x3333;

    *stk-- = (INT16U)0x2222;

    *stk-- = (INT16U)0x1111;

    return ((void*)stk);

}

 

*说明:在标准伪码里面提到一个模拟ISR的压栈,暂时没有明白是什么意思。(就是说每次任务的调度都是一次中断)

 

3、OS_CPU_A.ASM

    首先声明需要的外部变量和要输出的变量:

.external _OSTCBCur

.external _OSTCBHighRdy

.external _OSRunning

.external _OSPrioCur

.external _OSPrioHighRdy

.external _OSIntNesting

 

.external _OSTaskSwHook

.external _OSIntEnter

.external _OSIntExit

.external _OSTimeTick

 

.public _OSStartHighRdy

.public _OSIntCtxSw

.public _OSCtxSw

.public _OSTickISR

    大概就这些吧

 

1)函数_OSStartHighRdy

    标准函数伪码:

void OSStartHighRdy(void)

{

       调用用户定义的OSTaskSwHook();

       OSRunning = TRUE;

       得到将要恢复运行的任务的堆栈指针:

Stack Pointer = OSTCBHighRdy——>OSTCBStkPtr;

       从新任务堆栈中恢复处理器的所有寄存器;

       执行中断返回指令;

}

 

    按这个标准函数,编写函数如下:

_OSStartHighRdy:

         CALL        _OSTaskSwHook

         R1 = 1

         [_OSRunning] = R1;

         R1 = [_OSTCBHighRdy]

         SP = [R1]            //注意是所指向的内容,调这个错误用了不少时间

         POPALL             //此函数只做了任务切换工作的一半,并没有保存当前任务的寄存器

         RETI

2)函数_OSCtxSw

 

    标准函数伪码:

 

void OSCtxSw(void)

{

       保存处理器寄存器;

       在当前任务的任务控制块中保存当前任务的堆栈指针:

OSTCBCur——>OSTCBStkPtr = Stack Pointer;

       OSTaskSwHook();

       OSTCBCur = OSTCBHighRdy;

       OSPrioCur = OSPrioHighRdy;

       得到将要运行的任务的堆栈指针:

Stack Pointer = OSTCBHighRdy——>OSTCBStkPtr;

       从新任务的任务堆栈中恢复处理器所有寄存器的值;

       执行中断返回指令;

}

 

    按这个标准函数,编写函数如下:

 

_OSCtxSw:

         PUSHALL

         R1 = [_OSTCBCur]

         [R1] = SP

         CALL _OSTaskSwHook

         R1 = [_OSTCBHighRdy]

         [_OSTCBCur] = R1

         R2 = [_OSPrioHighRdy]

         [_OSPrioCur] = R2

         SP = [R1]            //所指向内容

         POPALL

         RETI

 

 

3)函数_OSIntCtxSw

 

    标准函数伪码:

 

void OSIntCtxSw(void)

{

       OSTaskSwHook();

       OSTCBCur = OSTCBHighRdy;

       OSPrioCur = OSPrioHighRdy;

       得到将要运行的任务的堆栈指针:

Stack Pointer = OSTCBHighRdy——>OSTCBStkPtr;

       从新任务的任务堆栈中恢复处理器所有寄存器的值;

       执行中断返回指令;

}

 

    按这个标准函数,编写函数如下:

 

_OSIntCtxSw:

           R1 = [_OSTCBCur]              //在这之前要不要调整指针,还说不好,因为不调整暂时也可以运行

           [R1] = SP            //注意要保存当前任务的堆栈,否则不能返回——

                                       //当然最好是放在OSTickISR中,这样可以避免在后面调整堆栈指针——

           CALL _OSTaskSwHook

         R1 = [_OSTCBHighRdy]

         [_OSTCBCur] = R1

         R2 = [_OSPrioHighRdy]

         [_OSPrioCur] = R2

         SP = [R1]            //所指向内容

         POPALL

         RETI

 

    看起来,这个函数同OSCtxSw没有太多的不同,区别在于如果ISR保存了CPU寄存器,这里不用再保存了。

4)函数_OSTickISR

    标准函数伪码:

void OSTickISR(void)

{

       保存处理器寄存器;

       调用OSIntEnter()或者直接给OSIntNesting加1;

       if(OSIntNesting == 1)

       {

OSTCBCur——>OSTCBStkPtr = Stack Pointer; //在2.51之前是没有的

       }

       给产生中断的设备清中断;

       重新使能允许中断(可选);

       OSTimeTick();

       OSIntExit();

       恢复处理器寄存器;

       执行中断返回指令;

}

 

    按这个标准函数,编写函数如下:

需要注意到是,要在OS_CFG.H中设置节拍数

text             //中断应该映射到0地址 

.public _IRQ6

_IRQ6:

_OSTickISR:

         PUSHALL

         R1=C_IRQ6_TMB2                      //判断是否为IRQ_TMB2中断

         TEST R1,[P_INT_Ctrl]

         JNZ IRQ_TMB2                          //是,进入IRQ_TMB2;否,进入IRQ_TMB1

IRQ_TMB1:

         R1=C_IRQ6_TMB1                      //清中断标志

         [P_INT_Clear]=R1

          R1=0x0001

           [P_Watchdog_Clear]=R1       //清看门狗

         R1=[_OSIntNesting]          //  中断嵌套标志加1

         R1+=1                                             //  也可call _OSIntEnter

         [_OSIntNesting]=R1             //

                                                        //最好在这里加入保存步骤,省去调整SP;

                                                         //当然如果这里加了,前面就不要再保存了

         call _OSTimeTick

         call _OSIntExit

         POPALL

         reti

IRQ_TMB2:                                             //中断子程序IRQ_TMB2

         R1=C_IRQ6_TMB2                      //清中断标志

         [P_INT_Clear]=R1

         POPALL

         reti

 

*说明:由于凌阳提供两个时基中断,需要判断中断控制器的值。具体参照手册。

到这里,主要的移植工作就完成了。

 

后记:

    整个过程受制于对凌阳的不熟悉,所以中间编译的过程比较头疼,这也反映了移植工作是建立在对目标系统熟悉的基础之上。

有几个问题需要注意:

移植版本:

    移植版本不同,内核所需要的库函数也不同;这个移植过程遇到了一个string.h的问题,加上都没有用,我郁闷。

OSIntCtxSw():

    这是我后来在网上看到的,好几个实例中都调整了SP的值,目的是舍弃多余的数据。而在我的移植中,没有进行这个调整,也能够运行。可能区别在于对内存的合理使用上。

    翻看uCOS_II第二版的304-307页,我们可以找到答案:

    在以前版本中,没有在OSTickISR()中进行中断时的堆栈保存:

       if(OSIntNesting == 1)

       {

OSTCBCur——>OSTCBStkPtr = Stack Pointer; //在2.51之前是没有的

       }

    因此,在调用OSIntCtxSw()的时候,需要先做这个工作:调整堆栈指针,标准代码中给出的是:

       OSIntExit();

       OSIntCtxSw();

    不是太明白,这个调用不成了自己调用递归了么?实验过,不行的。

    然后保存当前任务的堆栈指针到当前任务控制块中。

OSTCBCur——>OSTCBStkPtr = Stack Pointer

为了避免调整指针,最好在OSTickISR中保存堆栈先——

    在凌阳中,具体操作上将SP的值加7,就是抛弃这7个数据的值。为什么要说7,据说同编译器有关,这里也没有时间去详细追究,网上也没有相关的说明,只有自己调试的时候打开调试窗口观察吧。

我调试了一会儿。在任务切换处设置一个断点观测。当执行到POPALL时,SP跳到00d0处(这个地址值为空),将之前压栈的值弹出到寄存器中。从下面可以看到,弹栈前,SP指向的是00d0——栈顶;弹栈后,堆栈中的值(包括PC)弹出到寄存器中,SP指向的是00d7,所以这个值显然是7了。这也比较符合凌阳的寄存器结构。

   

    其实最好的方法是在OSTickISR中加入中断嵌套堆栈保存的步骤,这样就不需要去根据编译器调整堆栈指针了,移植性更强。参考uCOS_II P305、P307。

    就在函数中添加这样的代码:

         CMP R1,1          //R1中保存了OSIntNesting的值

         JNZ NOT_SAVE_SP           

SAVE_SP:

         R1 = [_OSTCBCur]

         [R1] = SP

NOT_SAVE_SP:

转载请注明出处,文章来源:http://www.threeway.cc/sitecn/informationInfo.aspx?tid=1380&pid=1679

开发

uC/OS是一种体积小巧而实用的实时操作系统,由于其代码的开放性,近年来普遍受到人们的关注,许多人开始从事这一操作系统的学习及应用工作,但由于其文档较少,特别是关于安装和使用方面的指导性文章不够充足,使很多初学者,特别是刚刚接触嵌入式操作系统的朋友们不知道如何进行系统的安装及调试,为此我结合自己使用的切身体会,谈一谈它的安装及调试问题,希望对大家能起到一定的帮助作用。

文中的开发工具及开发包如下:

开发包:uC/OS2.51 点击此处下载 

  http://www.freewind.com.cn/ucos/tools/ucoscode.htm

文中开发工具:Borland C 3.1 点击此处下载 

 http://www.freewind.com.cn/ucos/tools/bc31.rar

其中开发工具是经过本人剪裁过的,只适用于此项目开发。

1 安装

1.1 下载软件包Software.rar(文件大小848KB) 

1.2 解压缩,建议解压到C盘根目录,这样文件将安装在C:\\SOFTWARE,需要空间2.12MB 

1.3 安装完毕 

1.4 以默认安装路径为例,目录结构为: 

C: \\SOFTWARE

这是根目录,是所有软件相关的文件都放在这个目录下。 

C:\\SOFTWARE\\BLOCKS

子程序模块目录。笔者将例子中μC/OS-II用到的与PC相关的函数模块编译以后放在这个目录下。 

C:\\SOFTWARE\\HPLISTC

这个目录中存放的是与范例HPLIST相关的文件(请看附录D,HPLISTC和TO)。HPLIST.C存放在C:\\SOFTWARE\\HPLISTC\\SOURCE目录下。DOS下的可执行文件(HPLIST.EXE)存放在C:\\SOFTWARE\\TO\\EXE中。 

C:\\SOFTWARE\\TO

这个目录中存放的是和范例TO相关的文件(请看附录D,HPLISTC和TO)。源文件TO.C存放在C:\\SOFTWARE\\TO\\SOURCE中,DOS下的可执行文件(TO.EXE)存放在C:\\SOFTWARE\\TO\\EXE中。注意TO需要一个TO.TBL文件,它必须放在根目录下。用户可以在C:\\SOFTWARE\\TO\\EXE目录下找到TO.TBL文件。如果要运行TO.EXE,必须将TO.TBL复制到根目录下。 

C: \\SOFTWARE\II

与μC/OS-II 相关的文件都放在这个目录下。 

C:\\SOFTWARE\II\\EX1_x86L

这个目录里包括例1的源代码(参见 1.07, 例1),可以在DOS(或Windows 95下的DOS窗口)下运行。 

C:\\SOFTWARE\II\\EX2_x86L

这个目录里包括例2的源代码(参见 1.08, 例2),可以在DOS(或Windows 95下的DOS窗口)下运行。 

C: \\SOFTWARE\II\\EX3_x86L

这个目录里包括例3的源代码(参见 1.09, 例3),可以在DOS(或Windows 95下的DOS窗口)下运行。 

C: \\SOFTWARE\II\\Ix86L

这个目录下包括依赖于处理器类型的代码。此时是为在80x86处理器上运行uC/OS-II而必须的一些代码,实模式,在大模式下编译。 

C: \\SOFTWARE\II\\SOURCE

这个目录里包括与处理器类型无关的源代码。这些代码完全可移植到其它架构的处理器上。

2 开发工具Borland C 3.1的安装

2.1 先下载Borland C 3.1 (文件大小1.65MB) 

2.2 解压缩,建议解压到C盘根目录,这样全部的文件将安装在C:\\BC31,我们强烈建议采用这个路径安装,这样您将可以直接进行项目的编译及调试,而不需要更改他们的配置,需要空间5.10MB 

2.3 如果您一定要将它安装到其他目录,请修改相应的配置文件,注意蓝色部分: 

2.3.1 打开<安装路径>\\BIN\\TURBOC.CFG,显示如下:

-IC:\\BC31\\INCLUDE

-LC:\\BC31\\LIB

将C:\\BC31该为安装的目录,例如安装目录为E:\\BorlandC31,则改后应为

-IE:\\BorlandC31\\INCLUDE

-LE:\\BorlandC31\\LIB 

2.3.2 打开<安装路径>\\BIN\\TLINK.CFG,显示如下:

-LC:\\BC31\\LIB

将C:\\BC31该为安装的目录,例如安装目录为E:\\BorlandC31,则改后应为

-LE:\\BorlandC31\\LIB 

2.4 至此安装完成 

2.5 以默认安装路径为例,目录结构为: 

C:\\BC31

这是根目录,是所有工具相关的文件都放在这个目录下。 

C:\\BC31\\BIN

全部编译链接的开发工具都放在这个目录下 

C:\\BC31\\LIB

全部链接库文件存放在这个目录下 

C:\\BC31\\INCLUDE

全部头文件都存放在这个目录 

    还有需要说明的是这个Borland C是经过本人裁减的,目的是减少需要的空间(原版需要19.6MB),如果您使用的是原版的软件,可以进行安装,则不需要进行2.3这一步骤的配置工作,如果您却是需要这个原版的软件,请与我联系 留言 写信

3 项目开发

    随开发包附带了4个例子,都具有一定的代表性,下面我们结合例1介绍项目开发的全过程,需要说明的是:安装路径均为默认值。

3.1 进入C:\\SOFTWARE\II\\EX1_x86L\\BC45目录,会看到两个目录,他们功能如下: 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\SOURCE

源文件目录 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\SOURCE\\INCLUDES.H

总的项目头文件,每个源文件都应包含 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\SOURCE\\OS_CFG.H

配置文件,用于操作系统配置和剪裁 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\SOURCE\\TEST.C

应用程序文件,在开发新项目时可以参考这个文件的格式 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\SOURCE\\TEST.LNK

链接文件,包含了链接的库和链接参数 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\TEST 

项目文件目录 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\TEST\\TEST.MAK

项目的MAKE文件,熟悉C编译器的人一定知道,通过Make文件可以智能的对整个项目进行自动的编译链接,非常方便,我们在后面会详细介绍这个文件。 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\TEST\\TEST.MAP

项目映像文件主要完成库文件地址的映射,是项目的产物 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\TEST\\MAKETEST.BAT

项目Make操作的批处理文件,它将自动完成整个编译链接的全过程 

C:\\SOFTWARE\II\\EX1_x86L\\BC45\\TEST\\TEST.EXE

项目生成的可执行文件,使项目的产物 

下面正式开始项目的开发

3.2 第一步,按上面的目录结构,建立项目目录,在例中建立了一个目录C:\\SOFTWARE\II\\EX1_x86L\\BC45\,并在下面新建了两个子目录TEST和SOURCE,并将相关文件复制到这两个目录中,注意应该尽量按这种路径和目录结构建立项目,否则可能需要更改文件中的路径信息,这是很麻烦的事情。

值得推荐的最快捷的方法就是复制一个例子的目录,如果你的新项目名称为NewProject,可以在C:\\SOFTWARE\II下新建一个目录C:\\SOFTWARE\II\\NewProject,再将C:\\SOFTWARE\II\\EX1_x86L中的两个子目录直接复制到新建的目录下,最后只需更改内部的文件的一些配置,在TEST.C中重新写入新项目的代码即可。

3.3 第二步,更改INCLUDES.H文件,文件内容为: 

#include 

#include 

#include 

#include 

#include 

#include 

#include 

#include \\software\ii\\ix86l\\bc45\\os_cpu.h

#include os_cfg.h

#include \\software\ii\\source\ii.h

#include \\software\\blocks\\pc\\bc45\\pc.h

    如果你的软件包的安装路径和项目建立路径是按推荐方式设定的,则不需要更改此文件,但你可以增加一些你想在项目中追加的头文件,如一些驱动程序或应用模块的头文件,但一定要注意路径是否正确。如果你为按标准路径建立项目,必须逐行更改兰颜色部分的代码,使路径正确。

3.4 第三步,配置OS_CFG.H文件,因这与软件包和开发工具的安装无关,只用于项目中操作系统的配置,具体的请根据你的项目来自主决定,相关的介绍很多,可以参考uC/OS-II的电子书。

3.5 第四步,修改TEST.LNK配置,这里进行开发工具链接时一些参数和链接模块的设置,可以用编辑器打开: 

/v /s /c /P- +

C:\\BC31\\LIB\\C0L.OBJ +

..\\OBJ\\TEST.OBJ +

..\\OBJ\\OS_CPU_A.OBJ +

..\\OBJ\\OS_CPU_C.OBJ +

..\\OBJ\\PC.OBJ +

..\\OBJ\II.OBJ

..\\OBJ\\TEST,..\\OBJ\\TEST

C:\\BC31\\LIB\\EMU.LIB +

C:\\BC31\\LIB\\MATHL.LIB +

C:\\BC31\\LIB\\CL.LIB

3.5.1 /v /s /c /P- 是链接的参数,建议不要更改;

3.5.2 C:\\BC31\\LIB\\C0L.OBJ 是 Turbo C 的Large模式编译库模块,如果开发工具路径不是默认设置,请改为实际的安装路径,例如将C:\\BC31改为E:\\BorlandC31

3.5.3 ..\\OBJ\\TEST.OBJ 是应用程序的目标文件,它是由TEST.C编译后生成的,并保存在临时生成的C:\\SOFTWARE\II\\EX1_x86L\\BC45\\OBJ目录中,这个目录使用与保存编译时产生的目标文件的。

3.5.4 ..\\OBJ\\OS_CPU_A.OBJ ..\\OBJ\\OS_CPU_C.OBJ ..\\OBJ\\PC.OBJ ..\\OBJ\II.OBJ 同上,都是编译时产生的目标文件,并保存在OBJ目录下,作为项目链接的组成模块,如果在OBJ目录中缺少上述文件,链接将不会成功。

3.5.5 ..\\OBJ\\TEST,..\\OBJ\\TEST是指定的项目产生的EXE文件和MAP文件的路径和名称,它们分别是TEST.EXE和TEST.MAP,并保存在OBJ目录下。

3.5.6 C:\\BC31\\LIB\\EMU.LIB C:\\BC31\\LIB\\MATHL.LIB C:\\BC31\\LIB\\CL.LIB是链接时需要的库文件,必须令路径符合Borland C的安装路径,如果安装在非推荐目录,必须更改蓝色的路径部分。其中EMU为8087仿真库,MATHL为数学函数库,CL为TurboC在Large模式下的运行库。

3.6 第五步,修改TEST.MAK文件,这是Borland C的Make文件,关于Make文件的格式及编写方法请参考相关书籍,这里仅介绍一些配置有关的内容,打开文件可以看到: 

#############################################################################

# TOOLS

#############################################################################

BORLAND=C:\\BC31

CC=$(BORLAND)\\BIN\\BCC

ASM=$(BORLAND)\\BIN\\TASM

LINK=$(BORLAND)\\BIN\\TLINK

TOUCH=$(BORLAND)\\BIN\\TOUCH

#############################################################################

# DIRECTORIES

#############################################################################

TARGET=..\\TEST

SOURCE=..\\SOURCE

WORK=..\\WORK

OBJ=..\\OBJ

LST=..\\LST

OS=\\SOFTWARE\II\\SOURCE

PC=\\SOFTWARE\\BLOCKS\\PC\\BC45

PORT=\\SOFTWARE\II\\Ix86L\\BC45

    如果你得Borland C没有安装在推荐安装的目录,需要更改BORLAND=C:\\BC31这一句,例如如果安装在E:\\BorlandC31 ,则将BORLAND=C:\\BC31改为BORLAND=E:\\BorlandC31。

CC=$(BORLAND)\\BIN\\BCC:这是编译器的名称及路径,一般不更改

ASM=$(BORLAND)\\BIN\\TASM:这是汇编器的名称及路径,一般不更改

LINK=$(BORLAND)\\BIN\\TLINK:这是链接器的名称及路径,一般不更改

TARGET=..\\TEST:项目文件目录,如果路径不是按推荐方式,必需要更改路径

SOURCE=..\\SOURCE:源文件目录,如果路径不是按推荐方式,必需要更改路径

WORK=..\\WORK:项目工作目录,MAKETEST.BAT自动编译时全部源文件都复制到这个目录,这个设定必须必须和MAKETEST.BAT中设定相一致

OBJ=..\\OBJ:目标代码目录,这个设定必须和MAKETEST.BAT中设定相一致

LST=..\\LST:列表文件目录,这个设定必须和MAKETEST.BAT中设定相一致

OS=\\SOFTWARE\II\\SOURCE:操作系统源文件目录,必须保证路径的正确,如果你的开发包没按推荐方式安装,必须更改这个设定为实际安装的目录。

PC=\\SOFTWARE\\BLOCKS\\PC\\BC45:与PC有关的源文件目录,必须保证路径的正确,如果你的开发包没按推荐方式安装,必须更改这个设定为实际安装的目录。

PORT=\\SOFTWARE\II\\Ix86L\\BC45:与处理器有关源文件目录,必须保证路径的正确,如果你的开发包没按推荐方式安装,必须更改这个设定为实际安装的目录。开发包中还有一个浮点的处理器文件目录Ix86L-FP。这2个目录是跟移植密切相关的,相当于BSP(板极开发包)。

3.7 第六步,配置MAKETEST.BAT,打开可以看见 

MD ..\\WORK

MD ..\\OBJ

MD ..\\LST

CD ..\\WORK

COPY ..\\TEST\\TEST.MAK TEST.MAK

C:\\BC31\\BIN\\MAKE -f TEST.MAK

CD ..\\TEST

    首先是建立 ..\\WORK ..\\OBJ ..\\LST 三个目录,这个目录的名称和路径必须同TEST.MAK 一致,然后进入..\\WORK目录,并将..\\TEST目录中的TEST.MAK复制到这里,最后执行Borland C的Make程序,自动进行编译和链接,这里面还是要注意C:\\BC31的路径问题。

3.8 第八步,进行相关应用程序的开发就可以了,具体的可以参见Test.c这个文件,如果你的项目包括多个源文件,最好是在Test.c中用#include语句把他们都包含进来,否则就要更改Make文件TEST.MAK和其他几个设置文件,如果你确需如此,可以自己尝试一下,这里不再列出。

 

3.9 最后,运行MAKETEST.BAT就可以在TEST目录下生成TEST.EXE文件。

4 其它

    有幸还有许多网友都在PC下用Borland C实现了激动人心的调试开发,网友Hcompute把它的心得写了出来,大家也可以参考一下这篇文章PC环境下用BC3.1编译连接uC/OS-II源文件。

    还有更多关于操作系统本身的应用问题,大家尽可以参考它的电子书 中文版 英文版

    另外这篇文章写作很匆忙,基本是一气呵成的,难免存在很多错误,其中的一些观点由于本人水平有限,可能存在一些错误,在此,我欢迎看到这篇文章并发现问题或错误的朋友及时指正,以免误导别人,同时也欢迎读罢有所感悟的朋友积极来信,提出你的观点和看法,让我们共同努力,找到uC/OS开发的最好途径。

转载请注明出处,文章来源:http://www.threeway.cc/sitecn/informationInfo.aspx?tid=1380&pid=1681

文档

ucos介绍

uC/OS-II简介uC/OS是一种免费公开源代码、结构小巧、具有可剥夺实时内核的实时操作系统。μC/OS-II的前身是μC/OS,最早出自于1992年美国嵌入式系统专家JeanJ.Labrosse在《嵌入式系统编程》杂志的5月和6月刊上刊登的文章连载,并把μC/OS的源码发布在该杂志的BBS上。μC/OS和μC/OS-II是专门为计算机的嵌入式应用设计的,绝大部分代码是用C语言编写的。CPU硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top