820 likes | 946 Views
第五章 C/OS-II 在 ARM 系统中的应用与开发. 绪、 Jean J. Labrosse 的故事 80 年代末,我设计了一个基于 Intel 80C188 的产品,需要一个实时内核。 使用一个知名的内核太贵了,廉价的内核 B (当时大约 1000 美元以下 ) 让我总给该厂商打电话求援。该厂商声称内核 B 是用 C 语言写的,可我还得用汇编语言初始化程序的每个对象,实在是烦透了,产品的开发也耽误了。 后来我得知我是该厂商的第一个客户. 至今, μC/OS 的书已售出了 15 , 000 多册。 μC/OS 已被移植到以下一些 CPU 上。
E N D
第五章 C/OS-II在ARM系统中的应用与开发 绪、 Jean J. Labrosse的故事 • 80年代末,我设计了一个基于Intel 80C188的产品,需要一个实时内核。 • 使用一个知名的内核太贵了,廉价的内核B(当时大约1000美元以下)让我总给该厂商打电话求援。该厂商声称内核B是用C语言写的,可我还得用汇编语言初始化程序的每个对象,实在是烦透了,产品的开发也耽误了。 • 后来我得知我是该厂商的第一个客户
至今, μC/OS的书已售出了15,000多册。 μC/OS已被移植到以下一些CPU上。 • Analog 设备公司 AD21xx • ARM公司 ARM 6, ARM7 • 日立公司 64180,H8/3xx,SH系列 • Intel公司 80x86(Real and PM),Pentium, Pentium II, 8051,8052, MCS-251,80196,8096 • 三菱公司 M16和M32 • 摩托罗拉公司 PowerPC, • 飞利浦公司 XA • 西门子公司 80C166和TriCore • TI公司 TMS320 • Zilog公司 Z—80 和Z—180
μC/OS-II意为“微控制器操作系统版本2”。世界上已有数千人在各个领域使用μC/OS,例如,照相机行业、医疗器械、音响设施、发动机控制、网络设备、高速公路电话系统、自动提款机、工业机器人等等。很多高等院校将μC/OS用于实时系统教学。μC/OS-II意为“微控制器操作系统版本2”。世界上已有数千人在各个领域使用μC/OS,例如,照相机行业、医疗器械、音响设施、发动机控制、网络设备、高速公路电话系统、自动提款机、工业机器人等等。很多高等院校将μC/OS用于实时系统教学。
μC/OS 的几个典型应用 NSA2010便携式电话,在日本大约有15000台投入市场。使用μC/OS实时操作系统。 CYCLONE移动电话,Hitachi H8S/2318k微程序控制器,256K闪存和8K Ram, μC/OS 实时操作系统。 选择μC/OS的原因: INFEA R&D的职员从1996年以来开始应用Micriμm实时操作系统。通过比较,还没有发现比μC/OS更好的实时操作系统。我们将继续应用μC/OS以及Micriμm的其它产品包括下一代μC/OS-II V2.52的产品。
三轴运动控制卡 ——Hitachi SH2微处理器; ——7个任务; ——时钟频率10Hz; • 用于加工眼镜的塑料镜片的计算机控制车床的运动控制。 选择μC/OS-II的原因: 主要原因是它与其它市场上的实时操作系统相比的相对低廉的费用。另一个主要原因是资源和内设的可获得性。最后一点,μC/OS-II有足够的能力使我们能够顺利完成工作。SH-2快速,有效的执行与μC/OS-II的实时内核是使工作顺利完成的最重要的条件。
MB-20-M信用卡处理装置 • TCP/IP协议; • 20MHz Am188ES; • 10项任务; • 时钟频率100Hz; MB-20-M被用于对很多的教学和商务设备的控制使用和收费,包括身份证,安全卡和图书馆借阅卡,现在只要应用标准磁条的用户卡都可以在MB-20-M终端上使用。 选择μC/OS-II的原因: 价格便宜,代码尺寸小,缩短开发周期
独立静态交换机 • Hitachi H8S/2357 CPU • 4个任务 • 时钟频率1000Hz • 独立静态交换机(SIEL交换机)是一个可以连续的瞬时改变电源的装置从而控制两条电线的状态,最终保证负载的最佳电力供给。这种机器同样可以保护负载以防短路。 选择μC/OS—II的原因: 与其它实时方案相比低廉的价格,与很多微处理器可以进行数据传输,对源代码的完全控制。
5.1 C/OS-II系统的特点及结构 • µC/OS-Ⅱ是一个免费的、源代码公开的实时嵌入式内核,其内核提供了实时系统所需要的一些基本功能。其中包含全部功能的核心部分代码占用8.3 KB,全部的源代码约5500行,结构合理、清晰易懂,且注解详尽,非常适合初学者进行学习分析。µC/OS-Ⅱ不仅使用户得到廉价的解决方案,而且由于µC/OS-Ⅱ的开放源代码特性,还使用户可针对自己的硬件优化代码,获得更好的性能。 • µC/OS-Ⅱ是在PC机上开发的,C编辑器使用的是Borland C/C++3.1版。从早期使用的µCOS到现在的µC/OS-Ⅱ V2.52版,应用的实例也进一步说明了该内核的实用性和可靠性。
5.1.1 µC/OS-Ⅱ系统的特点 • 1.有源代码,µC/OS-Ⅱ源代码是开放的,用户可登录µC/OS-Ⅱ的网站(www.uCOS-II.com)下载针对不同微处理器的移植代码。这极大地方便了实时嵌入式系统µC/OS-Ⅱ的开发,降低了开发成本。 • 2.可移植(Portable),µC/OS-Ⅱ的源代码中,除了与微处理器硬件相关的部分是使用汇编语言编写的,其绝大部分是使用移植性很强的ANSI C来编写的。并且把用汇编语言编写的部分已经压缩到最低的限度,以使µC/OS-Ⅱ更方便于移植到其他微处理器上使用。如Intel公司、Zilog公司、Motorola公司的微控制器和TI公司的DSP,以及包括ARM公司、Analog Device公司、三菱公司、日立公司、飞利浦公司和西门子公司的各种微处理器。
3.可固化(ROMable),µC/OS-Ⅱ是为嵌入式应用而设计的操作系统,只要具备有合适的软硬件工具,就可将µC/OS-Ⅱ嵌入到产品中去,从而成为产品的一部分。 • 4.可裁剪(Scalable),µC/OS-Ⅱ可根据实际用户的应用需要使用条件编译来完成对操作系统的裁剪,这样就可以减少µC/OS-Ⅱ对代码空间和数据空间的占用。 • 5.可剥夺型(Preemptive),µC/OS-Ⅱ是完全可剥夺型的实时内核,运行就绪条件下优先级最高的任务。 • 6.多任务,µC/OS-Ⅱ可管理64个任务。一般情况下,建议用户保留8个任务给µC/OS-Ⅱ。这样,留给用户应用程序的任务最多可有56个。系统赋给每个任务的优先级必须不同,这意味着µC/OS-Ⅱ不支持时间片轮转调度法(Round-robin Scheduling)。 • 7.可确定性,绝大多数µC/OS-Ⅱ的函数调用和服务的执行时间具有确定性。在任何时候用户都能知道µC/OS-Ⅱ的函数调用与服务的执行时间。
8.任务栈,µC/OS-Ⅱ的每个任务都有自己单独的栈和栈空间。使用µC/OS-Ⅱ的栈空间校验函数可确定每个任务到底需要多少栈空间。 • 9.系统服务,提供了例如信号量、互斥信号量、消息邮箱、事件标志、数据队列、块大小固定的内存的申请与释放及时间管理函数等。 • 10.中断管理,中断可使正在执行的任务暂时挂起,如果优先级更高的任务被中断唤醒,则高优先级的任务在中断嵌套全部退出后立即执行。中断嵌套层数可达255层。 • 11.稳定性与可靠性,2000年7月,µC/OS-Ⅱ在一个航空项目中得到了美国联邦航空管理局对商用飞机的符合RTCA DO--178B标准的认证。可以说,µC/OS-Ⅱ的每一种功能、每一个函数及每一行代码都经过了考验与测试。
5.1.2 µC/OS-Ⅱ系统的内核结构 • 与其他操作系统不同,µC/OS-Ⅱ其实只有一个内核,提供任务调度、任务间的通信与同步、任务管理、时间管理和内存管理等基本功能。 • 1) 任务 • 在µC/OS-Ⅱ中,一个任务通常是一个无限的循环。一个任务看起来像其他c语言的函数一样,有函数返回类型,有形式参数变量,但任务是决不会返回的。故返回参数必须定义成void,例如: • Void YourTask(void *pdata) • { • for(;;){ • /*用户代码*/ • /*调用µC/OS-II的某种系统服务:*/ • /*用户代码*/ • } • }
2) 任务调度 • µC/OS-II可以管理多达64个任务,其优先级可以从0开始,优先级号越低,其任务的优先级就越高。但目前版本的µC/OS-II有两个任务已经被系统占用了,而且保留了优先级0、1、2、3、和OS_LOWEST_PRIO-3、OS_LOWEST__PRIO-2、0S_LOWEST_PRIO-1以及OS_LOWEST_PRIO这8个任务已备将来使用。OS_LOWEST_PRIO是作为常数在OS_CFG.H文件中用定义常数语句#define constant来定义的。因此用户可以使用多达56个应用任务,但首先要给每个任务赋以不同的优先级。µC/OS-II总是运行进入就绪态的优先级最高的任务。目前版本的µC/OS-II中,任务的优先级号就是任务编号(ID)。优先级号(或任务的ID号)也可以被一些内核服务函数调用,比如改变优先级函数OSTaskChangePrio()或者OSTaskDel()。 • 为了使µC/OS-II能管理用户任务,用户必须在建立一个任务的时候,将任务的起始地址与其他参数一起传给OSTaskCreate()或者OSTaskCreateExt()这两个函数中的任何一个函数。图5-1是µC/OS-II控制下的任务状态转换图,在任一时刻,任务的状态一定是这五种状态之一。
5.1.3 主要模块介绍 • 1.内存管理 • 在ANSI C中,一般采用内存分配函数malloc()和内存释放函数free()两个函数动态地分配和释放内存。为了消除多次动态分配与释放内存所引起的内存碎片和分配、释放函数执行时间的不确定性的现象,µC/OS-Ⅱ把连续的大块内存按分区来进行管理。每个分区中都包含若干个存储容量大小相同的内存块,但不同分区之间的内存块容量大小是可以不同的。在需要动态分配内存时,可选择一个适当的分区,按块来分配内存。在释放内存时,将该块放回它以前所属的分区。这样,就能有效解决内存碎片问题。而且每次调用malloc()和free()分配和释放的都是整数倍的固定内存块长,这样执行时间就是确定的了。
(1)内存管理控制块OS_MEM • 为便于内存的管理,µC/OS-II中使用内存控制块(Memory Control Blocks)的数据结构跟踪每一个内存分区系统,每个分区都有属于自己的内存控制块,系统是通过内存控制块数据结构OS_MEM来管理内存的。 • (2)内存管理 • 内存管理主要通过以下4个函数来实现: • ①OSMemCreate()函数,用于建立一个内存分区。该函数共有4个参数:内存分区的起始地址、分区内的内存块数、每个内存块的字节数和一个指向错误信息代码的指针。 • ②OSMemGet()函数,用于分配一个内存块。当调度某任务执行时,必须先从已建立的内存分区中为该任务申请一个内存块。 • ③OSMemPut()函数,释放一个内存块。当某一任务不再使用一个内存块时,必须及时地把它放回到相应的内存分区中,以便下一次的分配操作。 • ④OSMemQuery()函数,用于查询一个特定内存分区的状态。如查询某内存分区中内存块的大小、可用内存块数和正在使用的内存块数等信息。
(3)时间管理 • 与大部分内核一样,µC/OS-Ⅱ要求提供定时中断,以实现延时与超时控制等功能。这个定时中断也可以被叫作为时钟节拍。在相关的内容中,介绍了时钟的中断服务子程序(ISR)和时钟节拍函数OSTimeTick()。时钟节拍函数的作用是用于通知µC/OS-Ⅱ发生了时钟节拍中断,下面再介绍几个可以处理时间问题的函数。 • ①任务延时函数OSTimeDIy() • 调用该函数会使µC/OS-Ⅱ进行一次任务调度,并且执行下一个优先级最高的就绪态任务。任务调用OSTimeDly()后,一旦规定的时间期满或者有其他任务通过调用OSTimeDlyResume()取消了延时,它就会立即进入就绪状态。只有当该任务在所有就绪任务中具有最高的优先级时,它才会立即运行。 • ②恢复延时的任务函数OSTimeDlyResume() • µC/OS-II具有允许结束正处于延时期的任务的功能。具体方法是通过调用OSTimeDlyResume()和指定要恢复的任务的优先级的方式,这样延时的任务就可以不用等待延时期满,而是通过其他任务取消延时来使自己处于就绪态。实际上,OSTimeDlyResume()也可唤醒正在等待事件的任务。
③按时、分、秒、毫秒延时函数OSTimeDlyHMSM() • OSTimeDly()是一个非常有用的函数,但用户的应用程序须要知道延时时间所对应的时钟节拍的数目。增加了OSTimeDlyHMSM()函数后,就可按时、分、秒和毫秒来定义时间了,这样会显得更加方便。与OSTimeDly()一样,调用OSTimeDIyHMSM()函数也会使µC/OS-II进行一次任务调度,并且执行下一个优先级最高的就绪态任务。任务调用OSTimeDlyHMSM()后,一旦规定的时间期满或有其他任务通过调用OSTimeDlyResume()取消了延时,它就会立即处于就绪态。同样,只有当该任务在所有就绪态任务中具有最高的优先级时,它才会立即运行。 • ④系统时间函数OSTimeGet()和OSTimeSet() • 无论时钟节拍何时发生,µC/OS-II都会将一个32位的计数器加1。这个计数器在调用OSStart()初始化多任务和4294967295个节拍执行完一遍后,从0开始计数。在时钟节拍频率等于100Hz时,这个32位的计数器每隔497天就重新开始计数。在执行的过程中可以通过调用OSTimeGet()函数来获得该计数器的当前值,也可以通过调用OSTimeSet()函数来改变该计数器的值。
2、任务的管理 • µC/OS-II提过大量的API函数实现对任务的管理,主要的任务有: • (1)建立任务 • µC/OS-II要管理用户的任务,就必须先建立任务。通过将任务的地址和其他参数传递给以下两个函数来建立任务。OSTaskCreate()和带有扩展附加功能的OSTaskCreateExt()函数。在main()函数内开始多任务调度(OSStart()前,必须至少建立一个任务,而且任务不能由中断服务程序(ISR)建立。 • 创建一个任务控制块,并通过任务控制块把任务代码和任务堆栈关联起来形成一个完整的任务。还有使刚创建的任务进入就绪状态,并引发一次任务调度(取决于任务是否处于多多任务工作状态)。
两个函数OSTaskCreate()和OSTaskCreateExt()原型如下:两个函数OSTaskCreate()和OSTaskCreateExt()原型如下: • INT8U OSTaskCreate( void (*task)(void *pd); //指向任务的指针 void * pdata; //传递给任务的参数 OS_STK * ptos;//指向任务堆栈栈顶的指针 INT8U prio //任务的优先级 )
INT8U OSTaskCreateExt( void (*task)(void *pd); //指向任务的指针 void * pdata; //传递给任务的参数 OS_STK * ptos;//指向任务堆栈栈顶的指针 INT8U prio //任务的优先级 INT16U id //任务的标识 OS_STK * pbos;//指向任务堆栈栈低的指针 INT32U stk_siaze; //任务堆栈容量 void * pext; //指向附加数据域的指针 INT16U opt //用于设定操作选项 ) • 在调用任务建立函数后, µC/OS-II内核会首先从TCB空闲列表内申请一个空的TCB指针;然后根据用户给出的参数初始化任务堆栈,并在内部的任务就绪表中标记该任务为就绪状态;最后返回。这样就建立了一个任务。
(2)任务堆栈 • 在µC/OS-II中,每个任务都有自己的堆栈空间。堆栈必须声明为OS_STK类型,并且由连续的内存空间组成。可以静态分配堆栈空间(在编译时分配),也可以动态分配堆栈空间(在运行时分配),这两种声明方式都应放置在函数外面。 • 任务所需堆栈的容量由应用程序确定。但必须考虑到任务调用的所有函数的嵌套情况、任务调用的所有函数为局部变量分配的所有内存的数目,以及所有可能的中断服务子程序嵌套对堆栈的需求。此外,堆栈必须能够保存CPU所有的寄存器。 • µC/OS-II提供了堆栈检验函数OSTaskStkChk(),用来确定任务实际需要的堆栈空间的大小。这样能够避免为任务分配过多的堆栈空间,从而减少应用程序代码所需的RAM数量。调用堆栈检验函数后,所得到的只是一个大致的堆栈使用情况,并不能说明堆栈使用的全部实际情况。为了适应系统以后的升级和扩展,应该多分配10%~100%的堆栈空间。
(3)任务的挂起和恢复 • 挂起一个任务,就是停止这个任务的运行。在uC/OS-II中,用户任务可以通过调用系统提供的函数OSTaskSuspend()来挂起自身或者除空闲任务之外的其他任务。挂起的任务,只能在其他任务中通过调用恢复函数OSTaskResume()使其恢复为就绪状态。该函数并不要求和挂起函数OSTaskSuspend()成对使用。 • 但是,如果任务在被挂起的同时还在等待延迟时间到,则需要对任务取消挂起操作,并且要继续等待延迟时间到,任务才能转入就绪状态。
(4)任务的删除 删除一个任务,就是把该任务置于睡眠状态,任务的代码不再被uC/OS-II使用,而并不是说任务的代码被删除了。调用OSTaskDel()后,先进行条件判断,当所有的条件都满足后,就会从所有可能的uC/OS-II的数据结构中去除任务的任务控制块OS_TCB,这样就不会被其他的任务或中断服务子程序置于就绪态,即任务置于休眠状态。 函数原型如下: INT8U OSTaskDel(INT8U prio) 可删除任务自身或者除了空闲任务之外的其他任务。删除自己参数为: OS_PRIO_SELF 直接调用这样的删除任务,可能出现某些问题,如果任务拥有一些动态的内存或者信号量之类的资源,那么如果它被删除了,它的资源就不会被释放而丢失,会造成同样使用资源的其他任务进入死等待,出现错误情况。要慎重使用。提供了一个可以在请求删除方和被删除方通信完成删除的函数。原型如下: INT8U OSTaskDelReq(INT8U prio) 返回是否被删除和是否有要删除自己的要求。被删除方调用得知要删除自己,释放资源后,在删除自己。
(5)其他任务管理函数 • 任务优先级别修改 任务运行过程中,用户可以根据需要来改变任务的优先级别。调用的函数原型如下: INT8U OSTaskChangePrio( INT8U oldprio; //任务现在的优先级别 INT8U newprio //要修改的优先级别 ) • 查询任务的信息 查询一些任务中的信息,函数原型如下: INT8U OSTaskQuery( INT8U prio; OS_TCB * pdata )
3、任务间同步与通信的管理 • uC/OS-II中,使用信号量、邮箱(消息邮箱)和消息队列来实现任务相互同步或相互之间的通信。 uC/OS-II把关于它们的操作都定义为全局函数,以供应用程序的所有任务来调用 • 等待任务列表 采用INT8U类型的数组OSEventTbl[]作为记录等待事件任务的记录表,叫做等待任务表,每个任务占1位,为1表示是等待任务。 • 任务的等待时限,记录在等待任务的任务控制块TCB的成员OSTCBDly中
(1)事件控制块 uC/OS-II使用叫做事件控制块ECB的数据结构来描述诸如信号量、邮箱和消息队列这些事件。事件控制块包含包括等待任务表在内的所有有关事件的数据。
操作事件控制块的函数 uC/OS-II有4个对事件控制块进行基本操作的函数(定义在OS_CORE.C中)。 • 事件控制块的初始化函数 void OS_EventWaitListInit(OS_ENENT * pevent ) 把变量OSEventGrp及任务等待表中的每一位都清0,即令事件的任务等待表中不含有任何等待任务。该函数被OSXXXCreate()创建时所调用。 XXX Sem 信号量 Mutex 互斥信号量 Mbox 消息邮箱 Q 消息队列
使一个任务进入等待状态的函数 void OS_EventTaskWait( OS_ENENT * pevent) 将在任务调用函数OSXXXPend()请求一个事件时调用。 • 使一个正在等待任务进入就绪状态的函数 INT8U OS_EventTaskRdy( OS_EVENT * pevent, void *msg , INT8U msk) 作用:把调用这个函数的任务在任务等待表中的位置清0后,再把任务在任务就绪表中的对应的位置1,然后引发一次任务调度 将在任务调用函数OSXXXPost()发送一个事件时,被调用。 • 使一个等待超时的任务仅需就绪状态的函数 void OS_EventTo(OS_EVENT *pevent) 作用:当任务已经超过了等待的时间,却要使它进入就绪状态。 将在任务调用函数OSXXXPend()请求一个事 件时,被调用
(2)信号量管理 • 使用信号量可以在任务间传递信息,实现任务与任务或中断服务子程序的同步。 uC/OS-II中的信号量由两部分组成:16位的无符号整数信号量的计数值(0~65535);另一部分是由等待该信号量的任务组成的等待任务列表。 uC/OS-II提供了以下6个函数对信号量进行操作。 • 操作 • 创建信号量OSSemCreat(INT16U cnt)创建,返回已创建信号量的指针。 • 请求信号量OSSemPend(OS_EVENT *pevent INT16U timeout INT8U *err)
time为0,则表示无限等待。 不等待调用的函数为OSSemAccept(OS_EVENT * pevent)。 • 发送信号量 INT8U OSSemPost(OS_EVENT * pevent) 当获得信号量,访问共享资源结束以后,释放信号量,调用该函数。先检查是否有等待该信号量的任务。没有,信号量计数器加1,有,则调用调度器OS_Sched()。
删除信号量 OS_EVENT *OSSemDel( OS_EVENT * pevent, INT8U opt, INT8U *err) opt OS_DEL_NO_PEND 没有等待任务删除 OS_DEL_ALLWAYS 立即删除 只能任务执行,不能在中断服务程序中删除 • 查询信号量的状态 INT8U OSSemQuery(OS_EVENT * pevent OS_SEM_DATA *pdata) pdata是一个结构指针,存储信号量的状态。
(3)消息邮箱管理 • 消息邮箱是uC/OS-II中的一种通信机制,通常使用时要先定义一个指针型的变量该指针指向一个包含了消息内容的特定数据结构。发送消息的任务或中断服务子程序把这个变量送往邮箱,接收消息的任务从邮箱中取出该指针变量,完成信息交换。 uC/OS-II提供6种对消息邮箱的操作,它们通过以下函数实现: • 创建 OS_EVENT * OSMoxCreate( void * msg) Msg为消息指针,一般初始为NULL。
向消息邮箱发送消息 INT8U OSMboxPost(OS_EVENT * pevent, void * msg) 发送广播消息 INT8U OSMboxPostOpt(OS_EVENT * pevent, void * msg, INT8U opt) opt: OS_POST_OPT_BROADCAST 广播消息 OS_POST_OPT_NONE 最高优先级
请求消息邮箱 void * OSMboxPend(OS_EVENT * pevent, INT16U timeout, INT8U *err) • 查询邮箱状态 INT8U OSMboxQuery( OS_EVENT * pevent, OS_MBOX_DATA *pdata) • 删除邮箱 OS_EVENT *OSMboxDel( OS_EVENT * pevent, INT8U opt, INT8U *err)
(4)消息队列管理 • 消息队列是uC/OS-II的另一种通信机制,它可以使一个任务或中断服务子程序向另一个任务发送以指针定义的变量。 uC/OS-II提供了9个对消息队列进行操作的函数。 • 创建 先创建一个指针数组,然后用该数组来创建消息队列 OS_EVENT OSQCreate( void ** start,INT16U size) • 请求消息队列 void* OSQPend(OS_EVENT * pevent, INT16U timeout,INT8U *err)
向消息队列发送消息 INT8U OSQPost(OS_EVENT * pevent,void * msg) 工作方式FIFO INT8U OSQPostFront(OS_EVENT * pevent,void * msg) 工作方式LIFO 发送广播消息 INT8U OSQPostOpt(OS_EVENT * pevent,void * msg, INT8U opt)
清空消息队列 INT8U OSQFlush ( OS_EVENT * pevent ) • 删除消息队列 OS_EVENT * OSQDel( OS_EVENT * pevent ) • 查询消息队列 INT8U OSQQuery( OS_EVENT * pevent, OS_Q_DATA *pdata)
4.µC/OS-II操作系统的文件系统 • µC/OS-II操作系统的文件体系结构如图2所示,其核心主要可分为以下3部分: • (1)应用软件层,指的是基于µC/OS-II的应用程序代码。 • (2)内核的核心代码层,主要包括8个源代码文件。这8个源代码文件为OS_CORE.C、OS_MBOX.C、OS_MEM.C、OS_SEM.C、OS_TIME.C、uCOS_II.C、OS_Q.C和OS_TASK.C,其主要实现的功能分别是核心管理、事件管理、存储管理、消息队列管理、定时管理、信号量处理、消息管理和任务调度等,这部分代码与处理器无关。 • (3)系统设置与移植层。系统设置部分的代码由两个头文件OS_CFG.H和INCLUDES.H组成。其主要功能是用来配置事件控制块的数目以及是否包含消息管理的相关代码等。与处理器相关的移植代码部分包括:一个头文件OS_CPU.H、一个汇编文件OS_CPU_A.ASM和一个C代码文件OS_CPU_C.C。系统设置与移植层与具体应用和处理器相关,在随后的µC/OS-II的移植和开发过程中,用户所需要关注的就是这部分文件。
5.1.4 µC/OS-II 操作系统的初始化 • 在调用µC/OS-II操作系统的其他服务之前,µC/OS-II操作系统要求用户首先调用系统初始化函数OSInit()。执行OSInit()函数后将初始化µC/OS-II所有的变量和数据结构,另外OSInit()会建立空闲任务,并且这个任务总是处于就绪状态的。空闲任务OSTaskldle()函数的优先级总是设置成为最低级别,即OS_LOWEST_PRIO。 • 多任务的启动是用户通过调用OSStart()函数来实现的。然而,在启动µC/OS-II之前,用户至少要建立一个应用任务,例如: • void main() • { • OSInit(); • .. • 通过OSTaskCreate()或OSTaskCreateExt()创建至少一个任务 • .. • OSStart(); /*开始多任务调度,OSStart()永远都不会返回*/ • }
5.2 µC/OS-II系统在ARM系统中的移植 所谓移植,就是指使一个实时操作系统能够在其他的微处理器平台上进行运行。由于µC/OS-II的主要代码都是由标准的C语言写成的,所以,一般来说移植过程并不复杂。 • 5.2.1 µC/OS-II移植条件 • 虽然µC/OS-II的大部分源代码是用C语言写成的,但是,仍需要用汇编语言完成一些与微处理器相关的代码。例如,µC/OS-II在读写微处理器、寄存器时只能通过汇编语言来实现。这是因为µC/OS-II在设计的时候就已经充分考虑了可移植性。为了要使µC/OS-II可以正常工作,处理器必须要满足如下要求:
1).微处理器的C编译器能产生可重入代码 • 可重入的代码指的是一段代码(如一个函数)可以被多个任务同时调用,而不必担心会破坏其内部的数据。也就是说,可重入型函数在任何时候都可以被中断执行,也不会因为在函数中断的时候被其他的任务重新调用,影响函数中的数据。可重入代码或者只使用局部变量,即变量保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。 • 通常的C编译器,把局部变量分配在栈中。所以,多次调用同一个函数,可以保证每次的局部变量互不受影响。而全局变量,在多次调用函数的时候,必然受到影响。 • 代码的可重入性是保证完成多任务的基础,除了在C程序中使用局部变量以外,还需要C编译器的支持。基于ARM的SDT、ADS等集成开发环境,都可以生成可重入的代码。
2).在程序中可以使用c语言打开或者关闭中断 • 在µC/OS-II中,可以通过进入中断屏蔽的宏定义OS_ENTER_CRITICAL()或者退出中断屏蔽的宏定义OS EXIT_CRITICAL()来控制系统关闭中断或者打开中断,这需要微处理器的支持。在目前的ARM系列的微处理器上,都可以设置相应的寄存器来关闭或者打开系统的所有中断。 • 3).微处理器支持中断,并且能产生定时中断(通常在10Hz-1000Hz之间)。µC/OS-II是通过微处理器产生定时的中断来实现多任务之间的调度的。 • 4).微处理器支持能够容纳一定量数据的硬件堆栈,并具有将堆栈指针和其他CPU寄存器读写到堆栈(或者内存)的指令。 • 5)µC/OS-II进行任务调度的时候,会把当前任务的CPU内部寄存器的内容存放到此任务的堆栈中。然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务。所以,寄存器中内容的入栈和出栈是µC/OS-II多任务调度的基础。
5.2.2 µC/OS-II的移植步骤 • 在的移植过程中,使用的是基于ARM公司架构的软件开发工具作为编译器,所值得关注的问题是与微处理器相关的代码,这部分主要包括一个头文件OS_CPU.H、一个汇编文件OS_CPU_A.ASM和一个C代码文件OS_CPU_C.C。 • 1).设置头文件OS_CPU.H中与处理器和编译器相关的代码 • (1)与编译器相关的数据类型 • #define INT8U unsigned char • #define INTl6U unsigned short • #define INT32U unsigned long • #define OS_STK unsigned long • #define BOOLEAN int • #define OS_CPU_SR unsigned long • #define INT8S char • 因为不同的微处理器有不同的字长,所以µC/OS-Ⅱ的移植包括了一系列的类型定义以确保其可移植性。
用户必须将任务堆栈的数据类型定义到µC/OS-II操作系统中,这个过程是通过为OS_STK声明正确的C语言数据类型来完成的。由于使用的微处理器上的堆栈成员是16位的,所以将OS_TSK声明为无符号整形数据类型。值得注意的是,所有的任务堆栈都必须使用OS_STK声明数据类型。用户必须将任务堆栈的数据类型定义到µC/OS-II操作系统中,这个过程是通过为OS_STK声明正确的C语言数据类型来完成的。由于使用的微处理器上的堆栈成员是16位的,所以将OS_TSK声明为无符号整形数据类型。值得注意的是,所有的任务堆栈都必须使用OS_STK声明数据类型。 • (2)进入中断屏蔽的宏定义OS_ENTER_CRITICAL()和退出中断屏蔽的宏定义OS_EXIT_CRITICAL() extern int INTS_OFF(void); extern void INTS_ON(void); #define OS_ENTER_CRITICAL() {cpu_sr = INTS_OFF();} #define OS_EXIT_CRITICAL() {if(cpu_sr==0) INTS_ON(); } • 与所有的实时内核一样,µC/OS-II操作系统在进行任务切换时需要先禁止中断在访问代码的临界区,并且在访问完毕后重新允许中断。这就使得µC/OS-II能够保护临界区代码免受多任务或中断服务例程(ISR)的破坏。在S3C44B0微处理器上是通过OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()两个函数来实现开、关中断的。
(3)栈增长方向标OS_STK_GROWTH • #define OS_STK_GROWTH 1 • #define STACKSIZE 256 • 绝大多数的微处理器的堆栈是从高地址向低地址增长的,但是有些微处理器是采用相反方式工作的。鉴于这种情况µC/OS-II操作系统被设计成为这两种情况都可以处理,只要在结构常量OS_STK_GROWTH中指定堆栈的生长方式就可以了。例如: • 设OS_STK_GROWTH为0表示堆栈从下往上增长。 • 设OS_STK_GROWTH为1表示堆栈从上往下增长。
2). 用汇编语言在OS_CPU_A.ASM文件中编写4个与微处理器相关的函数 • (1) 调用优先级最高的就绪任务函数 OSStartHighRdy() • (2)任务级的任务切换函数 OSCtxSw() • (3)中断级的任务切换函数 OSIntCtxSw() • (4)时钟节拍中断服务函数 OSTickISR() • 3).用C语言编写6个操作系统相关的函数(OS_CPU_C.C) • 这里主要涉及6个函数:OSTaskStkInit()、OSTaskCreateHook()、OSTaskDelHook()、OSTaskSwHook()、OSTaskStatHook()及OSTimeTickHook()。 • 这些函数中,惟一必须移植的是任务堆栈初始化函数OSTaskStkInit()。这个函数在任务创建时被调用,负责初始化任务的堆栈结构并返回新堆栈的指针stk。在ARM体系结构下,任务堆栈空间由高至低依次保存着PC、LR、R12、R11、R10、…、R1、R0、CPSR及SPSR 。堆栈初始化工作结束后,返回新的堆栈栈顶指针。
以下5个Hook函数,又称为钩子函数,主要用来扩展µC/OS-Ⅱ功能,使用前必须被声明,但并不一定要包含任何代码。 • (1) 0STaskCreateHook()函数 • 当用OSTaskCreate()函数或OSTaskCreateExt()函数建立任务时,就会调用OSTaskCreateHook()函数。µC/OS-Ⅱ设置完自己的内部结构后,会在调用任务调度程序之前调用OSTaskCreateHook()函数。该函数被调用时中断是禁止的,因此应尽量减少该函数中的代码,以缩短中断的响应时问。 • (2)OSTaskDelHook()函数 • 当任务被删除时,就会调用OSTaskDelHook()函数。该函数在把任务从µC/OS-Ⅱ的内部任务链表中解开之前被调用。当OSTaskDelHook()函数被调用时,会收到指向正被删除任务的OS_TCB的指针,这样它就可访问所有的结构成员了。OSTaskDelHook()函数可用来检验TCB扩展是否被建立了(一个非空指针),并进行一些清除操作。注意,此函数不返回任何值。
(3)OSTaskSwHook()函数 • 当发生任务切换时,调用OSTaskSwHook()函数。不管任务切换是通过OSCtxSw()函数,还是通过OSIntCtxSw()函数来执行的,都会调用该函数。OSTaskSwHook()函数可直接访问OSTCBCur和OSTCBHighRdy,这是因为它们都是全局变量。OSTCBCur指向被切换出去的任务的OS_TCB,而OSTCBHighRdy指向新任务的OS_TCB。 • 在调用OSTaskSwHook()函数期间,中断一直是被禁止的。这时因为代码的多少会影响到中断的响应时间,所以应尽量使代码简化。此函数没有任何参数,也不返回任何值。 • (4)OSTaskStatHook()函数 • OSTaskStatHook()函数每秒都会被OSTaskStat()函数调用一次,可用OSTaskStatHook()函数来扩展统计功能。该函数没有任何参数,也不返回任何值。 • (5)OSTimeTickHook()函数 • OSTimeTickHook()函数在每个时钟节拍都会被0STimeTick()函数调用。实际上,OSTimeTickHook()函数是在节拍被µC/OS-Ⅱ处理,并在通知用户的移植实例或应用程序之前被调用的。OSTimeTickHook()函数没有任何参数,也不返回任何值。