


第二级,,第三级,,第四级,,第五级,,第4章 基于ARM9和µC/OS-II嵌入式系统设计,第4章 基于ARM9和µC/OS-II嵌入式系统设计,,4.1 µC/OS-II的内核,,4.2 µC/OS-II的API函数,,4.3 µC/OS-II的应用程序开发,,4.4 µC/OS-II在S3C2410X上的移植,,4.5 µC/OS-II的API应用,,4.6 基于µC/OS-II操作系统的开发案例,4.1 µC/OS-II的内核,,多任务系统中,内核负责管理各个任务,或者说为每个任务分配CPU时间,并且负责任务之间的通讯内核提供的基本服务是任务切换之所以使用实时内核可以大大简化应用系统的设计,是因为实时内核允许将应用分成若干个任务,由实时内核来管理它们内核本身也增加了应用程序的额外负荷,代码空间增加ROM的用量,内核本身的数据结构增加了RAM的用量但更主要的是,每个任务要有自己的栈空间,这一块吃起内存来是相当厉害的内核本身对CPU的占用时间一般在2到5个百分点之间UC/OS-II有一个精巧的内核调度算法,实时内核精小,执行效率高,算法巧妙,代码空间很少4.1.1 µC/OS-II内核调度特点,,µC/OS-II内核调度主要有如下特点:,,●,只支持基于优先级的抢占式调度算法,不支持时间片轮训。
●,64个优先级,只能创建64个任务,用户只能创建56个任务●,每个任务优先级都不相同●,不支持优先级逆转●,READY队列通过内存映射表实现快速查询效率非常高●,支持时钟节拍●,支持信号量,消息队列,事件控制块,事件标志组,消息邮箱任务通讯机制●,支持中断嵌套,中断嵌套层数可达255层,中断使用当前任务的堆栈保存上下文●,每个任务有自己的堆栈,堆栈大小用户自己设定●,支持动态修改任务优先级●,任务TCB为静态数组,建立任务只是从中获得一个TCB,不用动态分配,释放内存●,任务堆栈为用户静态或者动态创建,在任务创建外完成,任务创建本身不进行动态内存分配●,任务的总个数(OS_MAX_TASKS)由用户决定●,0优先级最高,63优先级最低;,,●,有一个优先级最低的空闲任务,在没有用户任务运行时运行4.1.2 任务控制块 OS_TCB描述,,UC/OS-II的TCB数据结构简单,内容容易理解,保存最基本的任务信息,同时还支持裁减来减小内存消耗,TCB是事先根据用户配置,静态分配内存的结构数组,通过优先级序号进行添加,查找,删除等功能减少动态内存分配和释放因为依靠优先级进行TCB分配,每个任务必须有自己的优先级,不能和其他任务具有相同的优先级。
typedef struct os_tcb,,{,,OS_STK *OSTCBStkPtr;,,#if OS_TASK_CREATE_EXT_EN > 0,,void *OSTCBExtPtr;,,OS_STK *OSTCBStkBottom;,,INT32U OSTCBStkSize;,,INT16U OSTCBOpt;,,INT16U OSTCBId;,,#endif,,struct os_tcb *OSTCBNext;,,struct os_tcb *OSTCBPrev;,,#if ((OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN || OS_SEM_EN),,OS_EVENT *OSTCBEventPtr;,,#endif,,#if ((OS_Q_EN && (OS_MAX_QS >= 2)) || OS_MBOX_EN),,void *OSTCBMsg;,,#endif,,INT16U OSTCBDly;,,INT8U OSTCBStat;,,INT8U OSTCBPrio;,,INT8U OSTCBX;,INT8U OSTCBY;,,INT8U OSTCBBitX;,,INT8U OSTCBBitY;,,#if OS_TASK_DEL_EN,,BOOLEAN OSTCBDelReq;,,#endif,,} OS_TCB;,其中:,,OSTCBStkPtr,是指向当前任务栈顶的指针。
OSTCBExtPtr,是任务扩展模块使用;,,*OSTCBStkBottom,指向任务堆栈栈底的指针;,,OSTCBStkSize,存有栈中可容纳的指针元数目;,,OSTCBOpt,把“选择项”传给函数OSTashCreaktExt( )只有当用户将,OS_CFG.H,文件中的OS_TASK_CREATE_EXT设为1时,这个变量才有效;,,OSTCBId,用于存储任务的识别码(ID)这个变量现在没有用,保留给将来扩展用;,OSTCBNext,和,OSTCBPrev,用于任务控制块OS_TCBs的双向链表的前后链接,该链表在时钟节拍函数OSTimerTick( )中使用;,,OSTCBEventPtr,是指向事件控制块的指针;,,OSTCBMsg,是指向传给任务的消息的指针;,,OSTCBDly,当需要把任务延时若干时钟节拍时要用到这个变量,或者需要把任务挂起一段时间以等待某事件的发生;,,OSTCBStat,是任务的状态字;,,OSTCBPrio,是任务优先级,高优先级任务的OSTCBPrio值小;,OSTCBDelReq,是一个布尔量,用于表示该任务是否需要删除;,,OSTCBX, OSTCBY, OSTCBBitX和 OSTCBBitY,用于加速任务进入就绪态的过程或进入等待事件发生状态的过程。
这些值是在任务建立时算好的,或者是在改变任务优先级时算出的这些值的算法可由下面程序实现OSTCBY = priority >> 3;,,OSTCBBitY = OSMapTbl[priority >> 3];,,OSTCBX = priority & 0x07;,,OSTCBBitX = OSMapTbl[priority & 0x07];,4.1.3 就绪表(Ready List),,,UC/OS-II采用内存映射的方式来实现READY队列的加入,查找,删除功能,效率非常高但是也因此只能支持64个任务,每个任务都有自己的优先级,不能和其他任务优先级相同每个任务的就绪态标志都放入就绪表中的,就绪表中有两个变量OSRdyGrp和OSRdyTbl[]在OSRdyGrp中,任务按优先级分组,8个任务为一组OSRdyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置为1就绪表OSRdyTbl[]数组的大小取决于OS_LOWEST_PRIO(见文件OS_CFG..H)为确定下次该哪个优先级的任务运行了,UC/OS-II中的内核调度器总是将最低优先级的任务在就绪表中相应字节的相应位置1,即OS_LOWEST_PRIO=1。
OSRdyGrp和OSRdyTbl[]的关系是按以下规则给出的:,,当OSRdyTbl[i]中的任何一位是1时,OSRdyGrp的第i位置1i从0到7可用下面句使任务进入就绪态:,,OSRdyGrp |= OSMapTbl[prio >> 3];,,OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];,任务优先级的低三位用于确定任务在总就绪表OSRdyTbl[]中的所在位接下去的三位用于确定是在OSRdyTbl[]数组的第几个元素OSMapTbl[]是在ROM中的(见文件OS_CORE.C)屏蔽字,用于限制OSRdyTbl[]数组的元素下标在0到7之间下面程序从就绪表中删除一个任务if ((OSRdyTbl[prio >> 3] &=,,~OSMapTbl[prio & 0x07]) == 0),,OSRdyGrp &= ~OSMapTbl[prio >> 3];,以上代码将就绪任务表数组OSRdyTbl[]中相应元素的相应位清0,而对于OSRdyGrp,只有当被删除任务所在任务组中全组任务一个都没有进入就绪态时,才将相应位清零也就是说OSRdyTbl[prio>>3]所有的位都是0时,OSRdyGrp的相应位才清零。
为了找到那个进入就绪态的优先级最高的任务,并不需要从OSRdyTbl[0]开始扫描整个就绪任务表,只需要查另外一张表,即优先级判定表OSUnMapTbl([256])(见文件OS_CORE.C)OSRdyTbl[]中每个字节的8位代表这一组的8个任务哪些进入就绪态了,低位的优先级高于高位利用这个字节为下标来查OSUnMapTbl这张表,返回的字节就是该组任务中就绪态任务中优先级最高的那个任务所在的位置这个返回值在0到7之间确定进入就绪态的优先级最高的任务是用以下代码完成的y = OSUnMapTbl[OSRdyGrp];,,x = OSUnMapTbl[OSRdyTbl[y]];,,prio = (y << 3) + x;,,4.1.4 任务状态,,,UC/OS-II主要有,五种任务状态,,,睡眠态,就是挂起态,,阻塞态和延时态,这里统一为等待状态增加了一个被,中断状态,UC/OS-Ⅱ总是建立一个,空闲任务,,这个任务在没有其它任务进入就绪态时投入运行这个空闲任务[OSTaskIdle()]永远设为最低优先级空闲任务OSTaskIdle()什么也不做,只是在不停地给一个32位的名叫OSIdleCtr的计数器加1,统计任务使用这个计数器以确定现行应用软件实际消耗的CPU时间。
空闲任务不可能被应用软件删除睡眠态(DORMANT),指任务驻留在程序空间之中,还没有交给μC/OS-Ⅱ管理,把任务交给μC/OS-Ⅱ是通过调用下述两个函数之一:,OSTaskCreate(),或,OSTaskCreateExt(),当任务一旦建立,这个任务就进入就绪态准备运行任务的建立可以是在多任务运行开始之前,也可以是动态地被一个运行着的任务建立如果一个任务是被另一个任务建立的,而这个任务的优先级高于建立它的那个任务,则这个刚刚建立的任务将立即得到CPU的控制权一个任务可以通过调用,OSTaskDel(),返回到睡眠态,或通过调用该函数让另一个任务进入睡眠态调用,OSStart(),可以启动多任务OSStart(),函数运行进入就绪态的优先级最高的任务就绪的任务只有当所有优先级高于这个任务的任务转为等待状态,或者是被删除了,才能进入运行态正在运行的任务可以通过调用两个函数之一将自身延迟一段时间,这两个函数是,OSTimeDly(),或,OSTimeDlyHMSM(),这个任务于是进入等待状态,等待这段时间过去,下一个优先级最高的、并进入了就绪态的任务立刻被赋予了CPU的控制权等待的时间过去以后,系统服务函数,OSTimeTick(),使延迟了的任务进入就绪态(详见时钟节拍)。
正在运行的任务期待某一事件的发生时也要等待,手段是调用以下3个函数之一:,OSSemPend(),,,OSMboxPend(),,或,OSQPend(),调用后任务进入了等待状态(WAITING)当任务因等待事件被挂起(Pend),下一个优先级最高的任务立即得到了CPU的控制权当事件发生了,被挂起的任务进入就绪态事件发生的报告可能来自另一个任务,也可能来自中断服务子程序正在运行的任务是可以被中断的,除非该任务将中断关了,或者μC/OS-Ⅱ将中断关了被中断了的任务就进入了中断服务态,(ISR),响应中断时,正在执行的任务被挂起,中断服务子程序控制了CPU的使用权中断服务子程序可能会报告一个或多个事件的发生,而使一个或多个任务进入就绪态在这种情况下,从中断服务子程序返回之前,μC/OS-Ⅱ要判定,被中断的任务是否还是就绪态任务中优先级最高的如果中断服务子程序使一个优先级更高的任务进入了就绪态,则新进入就绪态的这个优先级更高的任务将得以运行,否则原来被中断了的任务才能继续运行当所有的任务都在等待事件发生或等待延迟时间结束,μC/OS-Ⅱ执行空闲任务,(idle task),,执行,OSTaskIdle(),函数。
4.1.5 任务切换,,,任务切换(ontext Switch)又称上下文切换,或CPU寄存器内容切换当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态(Context),即CPU寄存器中的全部内容这些内容保存在任务的当前状况保存区(Task’s Context Storage area),即任务自己的栈区之中把将要运行的任务的当前状况从该任务的栈中重新装入CPU的寄存器,并开始下一个任务的运行这个过程叫做任务切换4.1.6 任务调度分析,,,μC/OS-Ⅱ只支持,优先级抢占任务调度,,不支持,时间片轮训调度算法,,也不支持,优先级逆转,μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个确定哪个任务优先级最高,下面该哪个任务运行的工作是由调度器(Scheduler)完成的任务级的调度函数:OSSched(),,中断级的调度函数:OSIntExt(),OSTCBHighRdy,指向优先级最高的那个任务控制块,OS_TCB,,是将以,OSPrioHighRdy,为下标的,OSTCBPrioTbl[],数组中的那个元素赋给OSTCBHighRdy来实现的调用,OS_TASK_SW(),来完成任务切换。
任务切换由以下两步完成:,,将被挂起任务的,微处理器寄存器推入堆栈,,然后将较高优先级的任务的,寄存器值从栈中恢复到寄存器中,在μC/OS-Ⅱ中,就绪任务的栈结构总是看起来跟刚刚发生过中断一样,所有微处理器的寄存器都保存在栈中换句话说,μC/OS-Ⅱ运行就绪态的任务所要做的一切,只是,恢复所有的CPU寄存器,并运行中断返回指令为了做到任务切换,运行OS_TASK_SW(),人为模仿了一次中断多数微处理器有软中断指令或者陷阱指令TRAP来实现上述操作中断服务子程序或陷阱处理(Trap hardler),也称作事故处理(exception handler),必须提供中断向量给汇编语言函数OSCtxSw()OSCtxSw()除了需要OS_TCBHighRdy指向即将被挂起的任务,还需要让当前任务控制块OSTCBCur指向即将被挂起的任务OSSched()的所有代码都属,临界段代码,在寻找进入就绪态的优先级最高的任务过程中,为防止中断服务子程序把一个或几个任务的就绪位置位,,中断是被关掉的,为缩短切换时间,OSSched()的代码可用汇编语言写为增加可读性,可移植性和将汇编语言代码最少化,OSSched()是用C写的。
任务切换的相关函数是与CPU体系相关,用汇编语言编写完成任务切换的相关函数如下:,,,,●,OSStartHighRdy() 执行优先级最高的任务,,,●,OSCtxSw() 完成任务的上下文切换,,,●,OSIntCtxSw() 中断后的上下文切换,,,●,OSTickISR() 中断服务程序启动,,4.1.7 UC/OS-II的初始化,,,OSInit()建立空闲任务idle task,这个任务总是处于就绪态的空闲任务OSTaskIdle()的优先级总是设成最低这两个任务的任务控制块(OS_TCBs)是用双向链表链接在一起的OSTCBList指向这个链表的起始处当建立一个任务时,这个任务总是被放在这个链表的起始处换句话说,OSTCBList总是指向最后建立的那个任务链的终点指向空字符NULL(也就是零)因为这两个任务都处在就绪态,在就绪任务表OSRdyTbl[]中的相应位是设为1的还有,因为这两个任务的相应位是在OSRdyTbl[]的同一行上,即属同一组,故OSRdyGrp中只有1位是设为1的μC/OS-Ⅱ还初始化了4个空数据结构缓冲区,每个缓冲区都是单向链表,允许μC/OS-Ⅱ从缓冲区中迅速得到或释放一个缓冲区中的元素。
控制块OS_TCB的数目也就自动确定了当然,包括足够的任务控制块分配给统计任务和空闲任务4.2 µC/OS-II的API函数,,,任何一个操作系统都会提供大量的API供程序员使用,uC/OS-II也不例外由于uC/OS-II面向的是嵌入式开发,并不要求大而全,所以内核提供的API也就大多和多任务息息相关主要的有以下几类:,,1)任务类,,2)消息类,,3)同步类,,4)时间类,,5)临界区与内存类,4.2.1 任务类,1) OSTaskCreate( ),,在OSInit函数调用之后调用作用是创建一个任务有四个参数:任务的入口地址,任务的参数,任务堆栈的首地址、任务的优先级调用本函数后,系统会首先从TCB空闲列表内申请一个空的TCB指针,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态最后返回,即可成功创建一个任务2) OSTaskSuspend( ),,,这个函数可以将指定的任务挂起如果挂起的是当前任务将会引发系统执行任务切换先导函数OSShed来进行一次任务切换这个函数只有一个参数,是指定任务的优先级事实上在µC/OS-II内部,优先级除了表示一个任务执行的先后次序外,还起着分辨每一个任务的作用,换句话说,优先级也就是任务的ID。
所以uC/OS-II不允许出现相同优先级的任务3) OSTaskResume( ),,,这个函数和上面的函数作用相反,它用于将指定的已经挂起的函数恢复成就绪状态如果恢复任务的优先级高于当前任务将会引发一次任务切换其,参数类似OSTaskSuspend函数,为指定任务的优先级,需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用4.2.2 消息类,,4) OSMboxCreate( ),,这个函数用于创建消息邮箱,消息邮箱是操作系统的任务间通信的一种方式,一个任务可以阻塞或者不阻塞地等待另外一个任务发送到它的邮箱的邮箱消息,而根据消息的内容进行动作5)OSMboxPost( ),,这个函数用于一个任务向另一个任务的邮箱发邮箱消息,消息的内容在参数中指定6)OSMboxPend( ),,这个函数用于一个任务获取本任务邮箱中的消息,如果邮箱中没有消息,则等待,任务处于阻塞状态4.2.3 同步类,,7)OSSemCreate( ),,这个函数用于创建信号量,信号量是操作系统的任务间同步的一种方式,两个或者多个任务可以获知信号量的状态并根据之进行动作从而实现同步8)OSSemPost( ),,这个函数用于一个任务对一个信号量进行设置,设置的内容在参数中指定。
9)OSSemPend ( ),,这个函数用于一个任务获取本信号量的状态,如果信号量不为零则成功获取信号量并将信号量减去1,如果信号量为零,则等待,任务处于阻塞状态4.2.4 时间类,,10) OSTimeDly( ),,,这应该是程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高就绪任务的话,那么运行之简单点说,就是可以任务延时一定时间后再次执行它,或者说,暂时放弃CPU的使用权一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换4.2.5 内存操作类,,11)OSMemCreate( ),,这个函数用于创建一个内存分区,µC/OS-II对内存进行统一管理,这样利于消除简单采用malloc函数和free函数对内存操作产生的内存碎片12)OSMemGet( ),,从一个指定的的内存区中分配一个内存块13)OSMemPut( ),,释放一个内存块µC/OS-II具有约的50个API函数,除了上述列出的外,其他的在本章后面以附录的方式给出。
4.3 µC/OS-II的应用程序开发,,应用uC/OS-II,自然要为它开发应用程序,下面论述基于uC/OS-II的应用程序的基本结构以及注意事项每一个uC/OS-II应用至少要有一个任务而每一个任务必须被写成无限循环的形式以下是推荐的结构:,,void task ( void* pdata ),,{ INT8U err;,,InitTimer(); // 可选,,For( ;; ),,{,,// 你的应用程序代码,,……,,……,,OSTimeDly(1); // 可选,,},,},以上就是基本结构,至于为什么要写成无限循环的形式呢?那是因为系统会为每一个任务保留一个堆栈空间,由系统在任务切换的时候恢复上下文,并执行一条 reti 指令返回如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定所以,每一个任务必须被写成无限循环的形式程序中的,InitTimer()函数,由系统提供,需要,在优先级最高的任务内调用,它,,不能在for循环内调用,注意:这个,函数是和所使用的CPU相关的,在uC/OS –II中,不能在OSInit()或OSStart()内调用Timer初始化程序,那样会破坏系统的可移植性同时带来性能上的损失。
4.4 µC/OS-II在S3C2410X上的移植,,4.4.1 移植原理,,所谓移植,指的是一个操作系统可以在某个微处理器或者微控制器上运行uCOS-II的大部分源代码是用C 语言写成的,仍需要用C 语言和汇编语言完成一些与处理器相关的代码比如:uCOS-II 在读写处理器、寄存器时只能通过汇编语言来实现因为uCOS-II 在设计的时候就已经充分考虑了可移植性,所以,uCOS-II 的移植还是比较容易的要使uCOS-II 可以正常工作,处理器必须满足以下要求:,,1.处理器的C 编译器能产生可重入代码,,可重入的代码指的是一段代码(如一个函数)可以被多个任务同时调用,而不必担心会破坏数据例:可以比较可重入型函数和非可重入型函数:,程序1:可重入型函数,,void swap(int *x, int *y),,{,,int temp;,,temp=*x;,,*x=*y;,,*y=temp;,,},程序2:非可重入型函数,,int temp;,,void swap(int *x, int *y),,{,,temp=*x;,,*x=*y;,,*y=temp;,,},程序1 中使用的是局部变量temp 作为变量。
通常的C 编译器,把局部变量分配在栈中所以,多次调用同一个函数,可以保证每次的temp 互不受影响而程序2 中temp 定义的是全局变量,多次调用函数的时候,必然受到影响代码的可重入性是保证完成多任务的基础,除了在C 程序中使用局部变量以外,还需要C 编译器的支持笔者使用的是ARM ADS 的集成开发环境,均可以生成可重入的代码2.在程序中可以打开或者关闭中断,,OS_ENTER_CRITICAL()关闭,中断,,OS_EXIT_CRITICAL() 打开中断,,,3.处理器支持中断,并且能产生定时中断(通常在10Hz~1000Hz 之间),uCOS-II 是通过处理器产生的定时器的中断来实现多任务之间的调度的在ARM920T 的处理器上可以产生定时器中断4.处理器支持能够容纳一定量数据的硬件堆栈5.处理器有将堆栈指针和其它CPU 寄存器存储和读出到堆栈(或者内存)的指令uCOS-II 进行任务调度的时候,会把当前任务的CPU 寄存器存放到此任务的堆栈中,然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务所以,寄存器的入栈和出栈是uCOS-II 多任务调度的基础图4-1 说明了uC/OS 的结构以及它与硬件的关系。
图,4.1 Ucos-II,硬件和软件体系结构,,4.4.2 移植实现,,,本书以uCOS-II在博创实验系统上的例程为中心进行移植的说明移植相关的文件分为两类,其一是STARTUP 目录下的系统初始化、配置等文件,其二是uCOS-II 的全部源码,arch 目录下的3 个文件是和处理器架构相关的下面对移植步骤进行描述:,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 FP64;,,typedef unsigned int OS_STK;,,typedef unsigned int OS_CPU_SR;,,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(); },,#define OS_STK_GROWTH 1,,1)与编译器相关的数据类型,,,因为不同的微处理器有不同的字长,所以uCOS-II 的移植包括了一系列的类型定义以确保其可移植性。
尤其是uCOS-II 代码从不使用C 的short,int 和long 等数据类型,因为它们是与编译器相关的,不可移植例如,INT16U 数据类型总是代表16 位的无符号整数现在,uCOS-II 和用户的应用程序就可以估计出声明为该数据类型的变量的取值范围是0~65535将uCOS-II 移植到32 位的处理器上也就意味着INT16U 实际被声明为无符号短整型数据结构而不是无符号整数数据结构但是,uCOS-II 所处理的仍然是INT16U2) OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(),,,与所有的实时内核一样,uCOS-II 需要先禁止中断再访问代码的临界区,并且在访问完毕后重新允许中断这就使得uCOS-II 能够保护临界区代码免受多任务或中断服务例程(ISR)的破坏在S3C2410X 上是通过两个函数(OS_CPU_A.S)实现开关中断的INTS_OFF,,mrs r0, cpsr ; 当前 CSR,,mov r1, r0 ; 复制屏蔽,,orr r1, r1, #0xC0 ; 屏蔽中断位,,msr CPSR, r1 ; 关中断(IRQ and FIQ),,and r0, r0, #0x80 ;从初始CSR 返回FIQ 位,,mov pc,lr ; 返回,,INTS_ON,,mrs r0, cpsr ; 当前 CSR,,bic r0, r0, #0xC0 ; 屏蔽中断,,msr CPSR, r0 ; 开中断 (IRQ and FIQ),,mov pc,lr ; 返回,,3)OS_STK_GROWTH,,,绝大多数的微处理器和微控制器的堆栈是从上往下长的。
但是某些处理器是用另外一种方式工作的uCOS-II 被设计成两种情况都可以处理,只要在结构常量OS_STK_GROWTH 中指定堆栈的生长方式就可以了置OS_STK_GROWTH 为0 表示堆栈从下往上长置OS_STK_GROWTH 为1 表示堆栈从上往下长2.用C 语言编写6 个操作系统相关的函数(OS_CPU_C.C),1)OSTaskStkInit,,,OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInit()来初始化任务的堆栈结构因此,堆栈看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样图4.2 显示了OSTaskStkInt()放到正被建立的任务堆栈中的东西这里我们定义了堆栈是从上往下长的在用户建立任务的时候,用户传递任务的地址,pdata 指针,任务的堆栈栈顶和任务的优先级给OSTaskCreate()和OSTaskCreateExt()一旦用户初始化了堆栈,OSTaskStkInit()就需要返回堆栈指针所指的地址OSTaskCreate()和OSTaskCreateExt()会获得该地址并将它保存到任务控制块(OS_TCB)中。
图4.2 堆栈的初始化,OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos,INT16U opt),,{,,unsigned int * stk;,,stk = (unsigned int *)ptos; /* 装载堆栈指针 */,,opt++;,,/* 为新任务建立堆栈 */,,*--stk = (unsigned int) task; /* pc */,,*--stk = (unsigned int) task; /* lr */,,*--stk = 12; /* r12 */,,*--stk = 11; /* r11 */,,*--stk = 10; /* r10 */,*--stk = 9; /* r9 */,,*--stk = 8; /* r8 */,,*--stk = 7; /* r7 */,,*--stk = 6; /* r6 */,,*--stk = 5; /* r5 */,,*--stk = 4; /* r4 */,,*--stk = 3; /* r3 */,,*--stk = 2; /* r2 */,,*--stk = 1; /* r1 */,,*--stk = (unsigned int) pdata;/* r0 */,,*--stk = (SUPMODE); /* cpsr */,,*--stk = (SUPMODE); /* spsr */,,return ((OS_STK *)stk);,,},,2)OSTaskCreateHook,,,当用OSTaskCreate() 和OSTaskCreateExt() 建立任务的时候就会调用OSTaskCreateHook()。
该函数允许用户或使用移植实例的用户扩展uCOS-II 功能当uCOS-II设置完了自己的内部结构后,会在调用任务调度程序之前调用OSTaskCreateHook()该函数被调用的时候中断是禁止的因此用户应尽量减少该函数中的代码以缩短中断的响应时间当OSTaskCreateHook()被调用的时候,它会收到指向已建立任务的OS_TCB 的指针,这样它就可以访问所有的结构成员了函数原型:void OSTaskCreateHook (OS_TCB *ptcb),3)OSTaskDelHook,,,当任务被删除的时候就会调用OSTaskDelHook()该函数在把任务从uCOS-II 的内部任务链表中解开之前被调用当OSTaskDelHook()被调用的时候,它会收到指向正被删除任务的OS_TCB 的指针,这样它就可以访问所有的结构成员了OSTaskDelHook()可以来检验TCB扩展是否被建立(一个非空指针)并进行一些清除操作函数原型:void OSTaskDelHook (OS_TCB *ptcb),4)OSTaskSwHook,,,当发生任务切换的时候就会调用OSTaskSwHook() 。
OSTaskSwHook()可以直接访问OSTCBCur 和OSTCBHighRdy,因为它们是全局变量OSTCBCur 指向被切换出去的任务OS_TCB,而OSTCBHighRdy 指向新任务OS_TCB注意在调用OSTaskSwHook()期间中断一直是被禁止的因此用户应尽量减少该函数中的代码以缩短中断的响应时间函数原型:void OSTaskSwHook (void),5)OSTaskStatHook,,,OSTaskStatHook()每秒钟都会被OSTaskStat()调用一次用户可以用OSTaskStatHook()来扩展统计功能例如,用户可以保持并显示每个任务的执行时间,每个任务所用的CPU 份额,以及每个任务执行的频率等函数原型:void OSTaskStatHook (void),6)OSTimeTickHook,,,OSTimeTickHook()在每个时钟节拍都会被OSTaskTick()调用实际上,OSTimeTickHook()是在节拍被uCOS-II 真正处理,并通知用户的移植实例或应用程序之前被调用的函数原型:void OSTimeTickHook (void),,,后5 个函数为钩子函数,可以不加代码。
只有当OS_CFG.H 中的OS_CPU_HOOKS_EN 被置为1 时才会产生这些函数的代码3.用汇编语言编写4 个与处理器相关的函数,(OS_CPU.ASM),,,1)OSStartHighRdy ();运行优先级最高的就绪任务OSStartHighRdy,,LDR r4, addr_OSTCBCur ; 得到当前任务TCB 地址,,LDR r5, addr_OSTCBHighRdy ; 得到最高优先级任务TCB 地址,,LDR r5, [r5] ; 获得堆栈指针,,LDR sp, [r5] ; 转移到新的堆栈中,,STR r5, [r4] ; 设置新的当前任务TCB 地址,,LDMFD sp!, {r4} ;,,MSR SPSR, r4,,LDMFD sp!, {r4} ; 从栈顶获得新的状态,,MSR CPSR, r4 ; CPSR 处于SVC32Mode 摸式,,LDMFD sp!, {r0-r12, lr, pc } ; 运行新的任务,2)OS_TASK_SW ();任务级的任务切换函数,,,STMFD sp!, {lr} ; 保存pc,,STMFD sp!, {lr} ; 保存lr,,STMFD sp!, {r0-r12} ; 保存寄存器和返回地址,,MRS r4, CPSR,,STMFD sp!, {r4} ; 保存当前的PSR,,MRS r4, SPSR,,STMFD sp!, {r4} ; 保存SPSR,,; OSPrioCur = OSPrioHighRdy,,LDR r4, addr_OSPrioCur,,LDR r5, addr_OSPrioHighRdy,,LDRB r6, [r5],,STRB r6, [r4],; 得到当前任务TCB 地址,,LDR r4, addr_OSTCBCur,,LDR r5, [r4],,STR sp, [r5] ;保存sp 在被占先的任务的TCB,,; 得到最高优先级任务TCB 地址,,LDR r6, addr_OSTCBHighRdy,,LDR r6, [r6],,LDR sp, [r6] ; 得到新任务堆栈指针,,; OSTCBCur = OSTCBHighRdy,,STR r6, [r4]; 设置新的当前任务的TCB 地址,;保存任务方式寄存器,,LDMFD sp!, {r4},,MSR SPSR, r4,,LDMFD sp!, {r4},,MSR CPSR, r4,,; 返回到新任务的上下文,,LDMFD sp!, {r0-r12, lr, pc},,3)OSIntCtxSw(),;中断级的任务切换函数OSIntCtxSw,,Add r7,sp, #16 ; 保存寄存器指针,,LDR sp,=IRQStack ;FIQ_STACK,,mrs r1,SPSR ; 得到暂停的 PSR,,orr r1,r1, #0xC0 ; 关闭 IRQ, FIQ。
Msr CPSR_cxsf, r1,;转换模式 (应该是 SVC_MODE),,ldr r0,[r7,#52];从IRQ 堆栈中得到IRQ's LR (任务 PC),,sub r0, r0, #4;当前PC 地址是(saved_LR - 4),,STMFD sp!, {r0} ;保存任务 PC,,STMFD sp!, {lr} ;保存 LR,,mov lr, r7,;保存FIQ堆栈 ptr in LR (转到 nuke r7),,ldmfd lr!,{r0-r12};,从FIQ 堆栈中得到保存的寄存器,,STMFD sp!, {r0-r12} ;在任务堆栈中保存寄存器,,;在任务堆栈上保存PSR 和任务 PSR,,MRS r4, CPSR,,bic r4, r4, #0xC0 ; 使中断位处于使能态,,STMFD sp!, {r4} ; 保存任务当前 PSR,,MRS r4, SPSR,,STMFD sp!, {r4} ; SPSR,,; OSPrioCur = OSPrioHighRdy // 改变当前程序,,LDR r4, addr_OSPrioCur,,LDR r5, addr_OSPrioHighRdy,,LDRB r6, [r5],,STRB r6, [r4],,; 得到被占先的任务TCB,,LDR r4, addr_OSTCBCur,,LDR r5, [r4],,STR sp, [r5] ; 保存sp 在被占先的任务的 TCB,,; 得到新任务 TCB 地址,,LDR r6, addr_OSTCBHighRdy,,LDR r6, [r6],,LDR sp, [r6] ; 得到新任务堆栈指针,,; OSTCBCur = OSTCBHighRdy,,STR r6, [r4] ;设置新的当前任务的TCB 地址,,LDMFD sp!, {r4},,MSR SPSR, r4,,LDMFD sp!, {r4},,BIC r4,r4,#0xC0 ;必须退出新任务通过允许中断,,MSR CPSR, r4,,LDMFD sp!, {r0-r12, lr, pc},,4)OSTickISR();时钟节拍中断,,,多任务操作系统的任务调度是基于时钟节拍中断的,uCOS-II 也需要处理器提供一个定时器中断来产生节拍,借以实现时间的延时和期满功能。
但在博创实验系统移植uCOS-II 时,时钟节拍中断的服务函数并非uCOS-II 文献中提到的OSTickISR(),而直接是C 语言编写的OSTimeTick()博创实验系统uCOS-II 移植时占用的时钟资源是TIMER1在平台初始化函数ARMTargetInit()中,调用uHALr_InitTimers()函数初始化TIMER4相关寄存器; 调用uHALr_InstallSystemTimer(void) 开始系统时钟,其中通过语句SetISR_Interrupt(IRQ_TIMER4, TimerTickHandle, NULL)将TimerTickHandle 函数设置为TIMER4 的中断服务函数这些函数在文件UHAL.C 以及ISR.C 中程序中必须在开始多任务调度之后再允许时钟节拍中断,即在OSStart()调用过后,uCOS-II 运行的第一个任务中启动节拍中断如果在调用OSStart()启动多任务调度之前就启动时钟节拍中断,uCOS-II 运行状态可能不确定而导致崩溃博创实验系统是在系统任务SYS_Task 中调用uHALr_InstallSystemTimer()函数设置TIMER4的IRQ 中断的,从而启动时钟节拍。
SYS_Task()在文件OSAddTask.C 中定义,用户不必创建4. 编写一个简单的多任务程序来测试一下移植是否成功为了使uCOS-II 可以正常运行,除了上述必须的移植工作外,硬件初始化和配置文件也是必须的STARTUP 目录下的文件还包括中断处理,时钟,串口通信等基本功能函数在文件main.c 中给出了应用程序的基本框架,包括初始化和多任务的创建,启动等任务创建方法如下:,,1)在程序开头定义任务堆栈,任务函数声明和任务优先级:,,OS_STK TaskName_Stack[STACKSIZE]={0, }; //任务堆栈,,void TaskName(void *Id); //任务函数,,#define TaskName_Prio N //任务优先级,,2)在main()函数中调用OSStart()函数之前用下列语句创建任务:,,OSTaskCreate(TaskName,(void*)0,(OS_STK*)&TaskName_Stack[STACKSIZE-1],,,TaskName_Prio);,,OSTaskCreate()函数的原型是:,,INT8U OSTaskCreate (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT8U prio);,需要将任务函数TaskName,任务堆栈TaskName_Stack,任务优先级TaskName_Prio 三个参数传给OSTaskCreate()函数。
根据任务函数的内容决定堆栈大小,宏STACKSIZE 定义为4KB,可以在此基数上乘倍任务优先级越高,TaskName_Prio 值越小;uCOS-II 可以管理64个任务,由OSInit()创建的空闲任务的优先级最低为63;uCOS-II 保留4 个最高和4 个最低优先级,用户任务可以使用其余56 个优先级值3)编写任务函数内容:,,void TaskName(void *Id),,{,,//添入任务初始化语句,,for(;;),,{ //添入任务循环内容,,OSTimeDly(SusPendTime);//挂起一定时间,以使其他任务可以占用CPU,,},,},,uCOS-II 至少要有一个任务,这里已经创建一个系统任务SYS_Task, 启动系统时钟和多任务切换为了验证uCOS-II 多任务切换的进行,再编写两个简单的任务,分别在超级终端上输出run task1 和run task2可以参考main.c 的结构创建多个不同功能的任务,观察个任务的切换5. 编译并下载移植后的uCOS-II,,,所有的源代码都准备好后就可以进行编译了在ADS 环境下需要设置工程的访问路径从菜单Edit | Debug Settings 进入设置对话框,在Target | Access Paths 中选择User Paths并选上Always search user paths。
然后点Add 按钮添加路径ucos-ii 和arch这主要是设置编译器处理文件包含时的搜索范围按照映象文件下载方法实验中的操作方法将编译后的代码下载到平台的flash 中这个实验从结构上看和其他的实验没有多大区别,同样生成可执行文件system.bin将system.bin 装载到Flash 中,重启平台,然后在超级终端上观察结果4.5 µC/OS-II的API应用,,本节将以一些简单的例子针对µC/OS-II的API应用进行描述4.5.1 任务相关函数的使用,,任务相关函数主要包括:任务创建,任务的挂起和恢复,任务的优先级改变,任务删除和任务查询等在这里我们以一个最简单的“helloworld”移动的程序为例,对任务的概念和相关的API的应用进行描述例4-1,,#include "includes.h",,#include "debug.h",,#define TASK_STK_SIZE 512,,OS_STK Task_1_Stk[TASK_STK_SIZE];,,void Task(void *data);,void main (void),,{,,PC_DispClrScr(DISP_FGND_WHITE +,,DISP_BGND_BLACK);/* 清屏幕*/,,OSInit(); /* 初始化 uC/OS-II */,,PC_DOSSaveReturn();,,PC_VectSet(uCOS, OSCtxSw);,,OSTaskCreate(Task, (void *)0, &Task_1_Stk[TASK_STK_SIZE - 1], 0); /*创建任务*/,,OSStart(); /* 多任务启动*/,,},,void Task (void *pdata),,{,,INT8U x=1;,,INT8U y=1;,,INT8U judge;,,INT8U err;,,INT16S key;,,OS_ENTER_CRITICAL();,,PC_VectSet(0x08, OSTickISR);,,PC_SetTickRate(OS_TICKS_PER_SEC);,,OS_EXIT_CRITICAL();,,for (;;) {,,PC_DispClrScr(DISP_FGND_WHITE +,,DISP_BGND_BLACK); /*清屏幕*/,PC_DispStr(x, y, "helloworld!!", DISP_FGND_WHITE + DISP_BGND_RED + DISP_BLINK);,,x=(x+1)%(80-12);,,y=(y+1)%(25-1);,,,,if (PC_GetKey(&key) == TRUE) {,,if (key == 0x1B) {,,PC_DOSReturn();,,},,},,OSCtxSwCtr = 0;,,OSTimeDlyHMSM(0, 0, 0, 350);,,},,},,OSTaskCreate(Task, (void *)0,,,&Task_1_Stk[TASK_STK_SIZE - 1], 0);,,是创建任务语句,其中Task是任务的实现函数,(void *)0是传递给任务的参数,Task_1_Stk是任务的堆栈。
该程序实现的功能是将“hello。