分享

uC/OS II 学习笔记

 BeautymengRoom 2014-04-22

uC/OS II 学习笔记

(2012-09-13 10:57:48)
标签:

杂谈

uC/OS II 提供给用户通用接口函数都在Ucos_ii.h中【uC/GUI 提供给用户通用接口函数都在INC包含的各个头文件中,使用时参考官方的手册用就好了,有中文版的】;

实时操作系统RTOS
实时操作系统包括软实时与硬实时,软实时要求各个任务尽快地运行,而不要求限定某一个任务在多长的时间内完成,而硬实时系统不仅须执行无误而且要做到准时,需要在规定的时间内完成相关操作
大多数实时系统是两者的综合;
非实时操作系统指操作系统无法保证哪怕是最高优先级任务开始执行的最后时限。软实时操作系统指的是操作系统只能保证在xx时间内执行最高优先级的用户代码,但用户软件是否能及时完成操作,操作系统不管!
RTlinux就是linux的硬实时操作系统,它能够创建精确运行的符合POSIX.1B标准的实时进程;

并非所有的嵌入式系统都需要实时操作系统,只有在一些特定的场合,对时间比较敏感的应用才会使用实时操作系统。实时操作系统必须及时响应所要求的任务,在限定时间内完成任务。非实时的操作系统,多时间不是很敏感,对所要求的任务只是会保证完成,但在什么时候完成,或用多长的时间完成就不一定了。例如:手机它不需要实时性。我们发短信时,系统对它的处理早1秒或者晚1秒都不会影响到我们的使用。而对于导弹这样的应用必须具有实时性。导弹被发射出去锁定目标后要不断修正飞行方向,以保证击中目标,如果它的实时性不好的话,从传感器传来的信号没有及时响应,即使完了1毫秒的时间,那误差就会很大。用这样的导弹攻打敌方目标的话,目标很可能没有击中,美国大使馆倒是有可能被炸掉。
另外一般linux不具有实时性,它是分时操作系统一般是面向用户的,但是因为它的源代码是公开的,它是可以改造成实时系统的,但即使是这样它的实时性也不会很好,毕竟它最初的设计并不是为了实时性。我们在Linux上面同时运行好几个程序,它们会被并发的执行。我们会发现同时多运行几个程序可能会比只允许一个程序慢,这是因为操作系统把处理器按时间片分给了每一个程序。自然会慢一些。而实时操作系统,一般不同的任务会有不同的优先级,他会把拥有最高的优先级的程序一次性执行完毕。然后再执行次一级的程序。这样的系统只适用于控制,不适合一般的应用。

任务划分
目标:
    满足实时性要求;
    任务数目合理;-->合理使用系统的软硬件资源
    简化软件系统;-->合理规划任务,降低对操作系统的服务要求,使操作系统功能得到裁剪,简化系统
    降低资源需求;
   目前,各功能单独成立为一个任务,比如显示功能成为显示任务,文件系统通过一个任务来管理,这样文件系统任务要显示时,就向显示任务的消息队列里面发送显示消息,而不是直接在本任务中调用显示函数。
0:设备依赖性任务的划分
从系统的结构框图中,我们可以看到系统的输入输出各个设备。并发性是任务的基本特性,而控制输出、输出的设备的程序具有先天的并发性,把他们分别封装为不同的任务是合理的,这样就可以划分出第一批任务,键盘任务,显示任务,数据采集任务,控制输出任务和通信任务。
1:系统关键任务划分:关键是指某种功能在应用系统中的重要性,如果该功能不能正常实现,则将造成重大影响,甚至引发灾难性的后果;包含该关键功能的任务为关键任务,关键任务必须得到运行机会即使遗漏一次也不行; 对于关键功能,必须尽可能与其他的功能剥离,独立成为一个任务,通过通信的方式再触发其他的任务,完成后续操作。例如:火灾检测系统中,烟雾传感器的检测功能就是一个关键功能,必须将其与其他的报警、灭火等功能剥离。OSMboxPostOpt()消息发送函数,具有广播功能:  O在uC/OS II的较新版本中,消息发送函数OSMboxPostOpt()具有广播功能,发送一条消息就可以使所有等待该消息的任务进入就绪状态。
2:紧迫任务划分:某种功能必须在规定的时间内得到运行权(及时运行),并在规定的时刻前执行完毕(按时完成),这类功能有严格的实时性要求。大多数的紧迫任务是通过异步事件来触发,这些异步事件一般能够引发某种中断。在这种情况下,将紧迫任务安排在相应的ISR中是最有效的办法,如果不能安排在中断任务中,那么可竟尽可能提高优先级来解决“及时性”。对于按时完成,需要对紧迫任务进行瘦身,尽可能剥离不太紧迫的操作,只剩下必须立刻做的操作,被剥离的不太紧迫的操作另外封装成一个任务。例如:能谱分析仪,紧迫任务放在外部中断服务程序中,完成对脉冲峰值的采样,并将采样结果放入消息队列中。紧迫任务不一定是关键任务,所以遗漏一两次执行会导致工作品质下降,但是不会造成严重后果。
3:数据处理任务划分:用户程序中消耗时间最多的是各种数据处理程序单元,这些单元不止一个,且分别为不同的功能服务;所以需要将他们划分出来,分别包装为不同的任务,因为他们的处理较耗时,所以他们的优先级须安排低,这样让他们使用其他任务的剩余时间来进行数据处理(如果有时间片轮转,可以安排他们为同一优先级,利用时间片来轮转他们)。模拟时间片轮转:假如有3个数据处理任务A、B、C;我们可以将他们细分为A1、A2、A3,B1、B2、B3,C1、C2、C3,再交叉安排优先级就可以了。A1->1,B1->2,C1->3,A2->4,B2->5...
4:功能聚合任务划分:将关系密切的若干功能组合为一个任务,达到功能聚合的效果,关系密切:数据关联和时序关联,如果他们分开,有可能会使用大量的通信资源,造成较大的负担。
5:触发条件相同的任务划分
如果若干个功能由相同的事件触发,则可以将这些功能组合成为一个任务,从而免除将事件分发给多个任务的工作量。这样做的条件是:当以某顺序执行这些功能时,各个功能的实时性要求仍然可以得到满足,且各个功能在执行过程中不会出现问题,例如:火警检测系统中,拨打电话、启动喷淋灭火系统、保持火警记录,这些任务是不能合在一起做为一个任务的,否则其中一些功能有误其他的任务就会被耽搁。
符合本类任务的通常是内部事件,例如通过运算处理产生某个结果,根据这个结果需要执行若干功能,这些功能可组合为一个任务。
6:运行周期相同的任务划分
绝大多数功能都需要不停的重复执行,如果重复执行的条件是固定的时间间隔,则这个功能具有周期性。将周期相同的功能组合在一起封装为一个任务,就可以避免一个时间事件触发几个任务,省去事件分发操作和他们的之间的通信。
7:顺序操作任务划分
如果若干个功能按固定的顺序运行流水作业,相互之间完全没有并发现,则应该将这个功能组合为一个任务。

任务设计
任务函数结构
所设计的任务函数至少有一次对操作系统服务函数的调用,这样才能让低优先级的任务得到执行。
1:单次执行的任务
任务创建后,得到执行,执行完毕后自我删除;任务基本分为三大部分:1:准备工作代码(定义变量以及初始化工作)2:任务实体代码(完成具体的功能,一般都可以被中断)3:调用删除函数。例如启动任务(如果采用启动任务去启动各个任务,那启动任务的优先级需要比它创建的任务的优先级高,一般系统中通常将启动任务所做的事情交给系统的一个实质任务去做,节省资源);
采用“创建任务”的方式来启动任务,不仅可以省去通常的通信手段激活任务的麻烦,还可以通过*pdata来传递参数,是没有启动具有不同的工作状态(比如串口波特率),但是这样的话实时性会比较差,每一次任务的启动都需要创建,发费较多的时间,还有可能在删除时引起不必要的后遗症(如共享资源释放、任务关联);
所以通过“创建任务”来启动的任务一般是孤立的任务,他们不和其他的任务进行通信(ISR除外),只使用共享资源来获取信息和输出信息。
2:周期性执行的任务 
周期性执行的任务,通常在代码中调用系统延时函数,OSTimeDly或OSTimeDlyHMSM来调整执行周期。但是这两个函数有延时误差,至少有一个或小于一个时钟节拍的误差,如需精确的定时需采用独立的定时器。
3:事件触发执行的任务
任务的实体代码的执行需要等待某种事件的发生,在相关事件发生之前,任务被挂起,相关事件发生一次,任务执行一次。当触发条件是“时间间隔”(定时器触发)时,它既是周期任务。
如触发条件是某个信号(信号量等),那么这个触发条件仅仅是触发任务的执行。
如触发条件是某个信息(邮箱等),那么这个触发条件除了启动该任务外,还为任务提供原始数据和资料。
任务优先级安排
uC/OS II共有64个优先级:0~63。在OS_CFG.h中设置OS_LOWEST_PRIO来确定系统实际使用的优先级范围,#define OS_LOWEST_PRIO  18 ->系统裁剪到只有19个优先级,节省资源开销。
OS_LOWEST_PRIO-->空闲任务;     OS_LOWEST_PRIO-1-->统计任务;
OS_LOWEST_PRIO-2-->系统保留;   OS_LOWEST_PRIO-3-->系统保留;
系统最高的4个优先级(0、1、2、3)保留。
任务优先级安排原则
中断关联性、紧迫性、关键性、频繁性、快捷性、传递性
uC/OS <wbr>II <wbr>学习笔记

uC/OS II通信机制
uC/OS II通信机制包括有信号量(计数)、互斥信号量(可以高低优先级翻转)、事件标志组、邮箱、消息队列;
所有的通信机制都有5种功能函数:创建、删除、查询、发送、(挂起式)获取、(不挂起、不等待式)获取;
且一旦某个通信制作被使用,其创建、发送、(挂起式)获取功能是不能被裁剪的;
中断函数需要尽可能短,实时性高,在中断中,可以发送信号,其他的不要用。
除了事件标志组用OS_FLAG_GRP结构体表示外,其余的通信方式都使用OS_EVENT结构体表示;
typedef struct os_event {
    INT8U    OSEventType;                    //通信事件的类型
    void    *OSEventPtr;                     //邮箱或消息队列中指向消息实体的指针
    INT16U   OSEventCnt;                     //计数单元
    INT8U    OSEventGrp;                     //等待该通信事件的任务所在的组
    INT8U    OSEventTbl[OS_EVENT_TBL_SIZE];  //等待该通信事件的任务列表
} OS_EVENT;
1:信号量(计数型)sem
信号量是一个可被多个进程共享的数据结构,主要用于任
务间少量的信息通信。信号量通常是在多个任务访问一个共同的但
非共享的资源的情况下,用于同步各个任务之间的操作。
OS_EVENT *pevnt;
pevnt = OSSemCreate(int cnt);//创建并赋初值
OSsemPost(pevnt );
OSEventCnt>0表示该信号有效(且表示事件发生的次数),OSEventCnt==0表示该信号无效;
任务调用OSsemPost(pevnt),表示计数型信号量事件的OS_EVENT结构体中的OSEventCnt++;
任务调用OSSemPend(pevnt),如果此时OSEventCnt>0,则OSEventCnt--,且该任务接着继续往下执行;否则该任务挂起(再进行一次任务切换),直到该事件发生(OSEventCnt>0)且此时系统中该任务的优先级最高方可得到运行;
任务调用OSSemAccept(pevnt),如果此时OSEventCnt>0,则OSEventCnt--,且不论OSEventCnt的值是多少,该函数直接返回OSEventCnt的值,return(cnt);
任务调用OSSemQuery(OS_EVENT *, OS_SEM_DATA),OS_SEM_DATA是一个精简的OS_EVENT结构体,用来记录被查询信号量的计数值、任务等待列表等;
任务调用OSSemDel(pevnt )删除信号量;起初分配的OS_EVENT结构体被释放到空闲事件链表中;
2:互斥信号量mutex
在访问比较耗时的共享资源时,如果采用关中断的方法(系统此时不能被中断、任务不能被切换)来实现访问冲突,这对中断的响应是很不好的,所以这时采用互斥型信号量就可以很好的解决问题,同时可以响应中断。
互斥锁用来实现任务之间的简单同步,一个互斥锁是一个
二元信号量,它的状态只能是0(允许,开锁)和1(禁止,上锁)。
在互斥锁范围内,任何一个任务都可以对互斥负上锁,但只有锁住
该信号的任务才能开锁从而实现了任务同步。
mutex不同于sem,mutex是一个互斥型信号量,它可以通过在应用程序中翻转任务的优先级来解决资源互锁的问题;
举例:首先给一块共享资源配备一个互斥型信号量Mutex,系统中有两个任务A、B,他们的优先级分别为5,6;
假设B先运行,并通过OSMutexPend申请到了Mutex,那么它就会使用该共享资源继续运行,在某个时刻,任务A抢占了任务B,同时也通过OSMutexPend申请Mutex,由于资源已经被B占用,那么OSMutexPend会将B任务的优先级调高到4(假设),这时任务B得意继续运行,资源使用完毕后释放mutex。OSMutexPost注意到原来占有这个mutex的任务的优先级被调高了,于是将B的优先级调低,同时注意到A在申请,于是将mutex给A,做任务切换后A得以执行。
由于互斥型信号量的特性,互斥型信号量只能由于任务中(包括发送功能);
OS_EVENT *ResourceMutex;
ResourceMutex= OSMutexCreate(INT8U prio,&err);//在上述情况中 任务可以被调高到prio指定的优先级
OS_EVENT中,pevent->OSEventCnt = (INT16U)((INT16U)prio << 8) | OS_MUTEX_AVAILABLE(0xff);
高八位:PIP;低八位:占用该互斥信号量的任务的优先级(若为0xff表示无任务占用该信号量);
OSMutexPend(ResourceMutex,..,..):在访问共享资源时,先通过该函数获取互斥型信号量,如果互斥型信号量是有效的(没有被占用),则该Mutex的OSEventCnt的低八位为0xff,如果已被占用,则低八位为占用该信号量的任务的优先级;如果占用该信号量的任务的优先级比调用该申请函数的任务的信号量的优先级低,此时低优先级的任务占用共享资源,且已被高优先级的任务抢占了CPU,此时高优先级的任务再调用OSMutexPend申请互斥量,OSMutexPend内部会将占用mutex的低优先级的任务的优先级调高到信号量指定的PIP,这样来让低优先级的任务变为高优先级,尽快释放资源;
3:事件标志组event flag
typedef struct os_flag_grp {                
    INT8U         OSFlagType;               //事件类型
    void         *OSFlagWaitList;           //等待该事件标志组的任务列表
    OS_FLAGS      OSFlagFlags;              //事件标志组标志位
} OS_FLAG_GRP;
事件标志组用于实现多个任务(包括ISR)协同控制一个任务,当各个相关任务(ISR)先后发出自己的信号后(使事件标志组的对应标志位有效),预定的逻辑运算结果有效,这时将触发被控制的任务(使其进去就绪态)。
事件标志组可以选择标志位1有效或0有效,逻辑关系可以为“逻辑与”或“逻辑或”,这样有效定义与逻辑定义有4种组合,同时也可以设定只选择使用所有标志位中的其中几位;
OS_FLAG_GRP *OSFlagCreate(OS_FLAGS flags,INT8U *err);
flags为事件标志组中各个标志的初始值(1有效时,初始值为0);
发送标志到事件标志组:
OS_FLAGS  OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *perr)
pgrp:事件标志组指针;flags:指明待发送标志在事件标志组中的位置(0x1表示bit0);opt:选择操作的方式,OS_FLAG_SET(对标志位置1),OS_FLAG_CLR(对标志位置0);perr:执行结果;
等待事件标志组:
OS_FLAGS  OSFlagPend (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *perr)
pgrp:事件标志组指针;flags:指明哪些标志位用来等待,0x03(bit0、1),0x1d(bit0、2、3、4)opt:指明对这些等待的位采用的逻辑运算perr:执行结果;
opt如下:
OS_FLAG_WAIT_CLR_ALL:所以标志位为0时,将等待任务就绪;
OS_FLAG_WAIT_CLR_ANY任何一个标志位清0时,将等待任务就绪;
OS_FLAG_WAIT_SET_ALL:所以标志位为1时,将等待任务就绪;
OS_FLAG_WAIT_SET_ANY:任何一个标志位为1时,将等待任务就绪;
OS_FLAG_CONSUME:清除标志位(OS_FLAG_WAIT_SET_ANY + OS_FLAG_CONSUME
4:消息邮箱box
邮箱是μC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。
用信号量进行行为同步时,只能提供同步的时刻信息,不能提供内容信息;
当控制方在对被控制方进行控制的同时,还需要向被控制方提供内容信息时,消息邮箱是一个有效的方案;
由于消息邮箱中只能存储一条消息,在用消息邮箱进行同步控制时,必须满足一个前提:任何时候消息的生产速度都比消息的消费速度慢,即被控制的任务总是在等待消息;否则就会有消息丢失;
消息邮箱中可以放入任何类型的信息,通常用空邮箱(void * 0)表示事件没有发生;用非空邮箱(void * 1)表示事件已发生;因此邮箱也可以用来做二值信号量(注意与互斥信号量的区别);
邮箱用OS_EVENT结构体中的OSEventPtr指向消息实体;
OS_EVENT *pbox;
pbox= OSMBoxCreate(void *msg);//void * 0表示空邮箱
INT8U OSMboxPost(pbox, void * msg);//向邮箱中发送一条消息
INT8U OSMboxPostOpt(pbox,void * msg, INT8U opt)//分发消息,将消息分发给所有正在等待该消息的任务,让他们都处于就绪态
void *OSMboxPend(pbox,timeout,INT8U *err)//等待消息 当邮箱中的指针为空时,调用等待消息函数后就会被系统挂起。
5:消息队列
将要通信的信息放置在一个预定义的消息结构中,任务生成的消息指定了消息的类型,并把它放在一个由系统负责维护的消息队列中,而访问消息队列的任务可以根据消息类型,有选择地从队列中按FIFO的方式读取特定类型的消息。消息队列为用户提供了从多个生产者中获得多元信息的一种手段。
相当于一个邮箱队列,消息队列可以存放多个消息,能够有效解决消息的临时堆积问题。和计数信号量的情况类似,消息队列的使用仍然需要满足:消费速度比生产速度快,否则再大的队列也会满,从而溢出;
void *MyArrayOfMsg[SIZE];
OS_EVNT *pQ;
pQ=OSQCreate(MyArrayOfMsg,SIZE); 
INT8U OSQPost(pQ,void *Msg)//发送一条消息
void *OSQPend(pQ,0,&err)//当消息队列为空时 挂起
uC/OS <wbr>II <wbr>学习笔记
uC/OS <wbr>II <wbr>学习笔记
【各个通信机制都要无等待式获取相关的信号,均可用在中断中,中断中决不能用等待的方式】




uC/OS <wbr>II <wbr>学习笔记


uC/OS <wbr>II <wbr>学习笔记
uC/OS <wbr>II <wbr>学习笔记

相关函数使用说明:
创建任务:
INT8U  OSTaskCreateExt (void   (*task)(void *p_arg),//被创建的任务函数指针
                        void    *p_arg,               //传递给任务的参数的指针
                        OS_STK  *ptos,                //分配给任务的堆栈的栈顶指针
                        INT8U    prio,                //被创建任务的优先级
                        INT16U   id,                  //为创建的任务创建一个特殊的标识符,暂没用
                        OS_STK  *pbos,                //指向任务的堆栈栈底的指针(用于堆栈检验)
                        INT32U   stk_size,            //堆栈的容量
                        void    *pext,                //指向用户附件的数据域的指针
                        INT16U   opt)                 //指定是否卞堆栈检验,是否将堆栈清0,任务是否需要进行浮点操作
OSTaskCreate中还调用了OSTaskStkInit函数,调用该函数的目标是初始化任务的堆栈,使其看起来像发生过中断一样。OSTaskStkInit是需要移植的函数。

修改任务属性:
调用OSTaskChangePrio()函数可以动态地改变某一个任务的优先级,调用OSTaskNameGet()函数可以获取某一个任务的名称,调用OSTaskNameSet()函数可以设置一个任务的名称。
INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio);
INT8U OSTaskNameGet INT8U   prio,INT8U  *pname,INT8U  *perr);
void  OSTaskNameSet (INT8U prio, INT8U *pname, INT8U *perr)
任务的名称保存在任务对应的任务控制块中的成员:OSTCBTaskName[OS_TASK_NAME_SIZE]中;
直接挂起任务的函数:
有时将任务挂起是很有用的,挂起任务的函数可以通过INT8U  OSTaskSuspend(INT8U prio)来实现,且被挂起的任务只能通过调用INT8U  OSTaskResume (INT8U prio)来恢复。任务可以挂起自己或者其他的任务。
举例:在一些设计中,在main函数中只有简单的几行,在其中创建了一个启动任务:用于启动其他的任务与初始化系统,启动任务的优先级比它要创建的任务都要高,初始化成功与创建完相关任务之后,一般自己将自己挂起。
堆栈检验函数:
如果要使用堆栈检验函数,那么在任务的建立需要使用OSTaskCreateExt()来建立任务,且需要指定入口参数:opt(OS_TASK_OPT_STK_CHK|OS_TASK_OPT_STK_CLR);
OS_TASK_OPT_STK_CHK:指示任务需要使用堆栈校验功能
OS_TASK_OPT_STK_CLR:将任务的堆栈RAM清0(因为堆栈检验是从栈底开始计算为0(空闲)的空间大小)
在任务创建时指定栈顶、栈底,在任务运行一段时间后,一般需在堆栈使用最充分后才去检验较为准确;任务可以检查自己或者其他任务的堆栈使用情况,堆栈检验函数确定堆栈的实际空间字节数和已被占用的字节数,放在入口参数OS_STK_DATA数据结构中;
[可以阅读 嵌入式ARM系统原理与实例开发(杨宗德)的第八章,里面对该系统包含的大部分函数简要说明以及移植时的相关函数说明]

任务切换过程分析
1:任务之间抢占式切换(高优先级的任务就绪后立即抢占正在运行的低优先级的任务)
系统在任何响应后,都需要进行任务调度,确保系统中优先级最高的任务被执行,那么系统采用的任务调度切换函数就是void  OS_Sched(void);
void  OS_Sched(void)
    OS_ENTER_CRITICAL();   //进入临界区 关中断 
    if(OSIntNesting==0) {  //目前系统不处于中断态                         
        if(OSLockNesting==0) { //系统调度功能使能                    
            OS_SchedNew();   //计算出目前系统中最高的优先级,保存在INT8U OSPrioHighRdy中
            if(OSPrioHighRdy!=OSPrioCur) { //判断当前正运行的任务是不是最高优先级 否则就不用切换
                 OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];//将最高优先级任务的控制块的指针放                                                              //入任务控制块指针变量OSTCBHighRdy
                 #if OS_TASK_PROFILE_EN > 0
                    OSTCBHighRdy->OSTCBCtxSwCtr++; //目前优先级最高的任务切换次数统计        
                 #endif
                 OSCtxSwCtr++; //系统任务切换的总次数统计
                 OS_TASK_SW(); //实现任务切换
            }
        }
     }
     OS_EXIT_CRITICAL(); //恢复全局中断标志 退出临界区

uC/OS <wbr>II <wbr>学习笔记

在任务调度前,OS_SchedNew()计算出目前系统中最高的优先级,保存在INT8U OSPrioHighRdy中
#define  OS_TASK_SW()         OSCtxSw()
OSCtxSw //悬起PSV异常
    LDR     R0, =NVIC_INT_CTRL  //NVIC_INT_CTRL为SCB中的中断控制寄存器(ICSR)的地址数值 0xE000ED04
    LDR     R1, =NVIC_PENDSVSET //NVIC_PENDSVSET 为设置ICSR的值(0x10000000
    STR     R1, [R0]            //设置ICSR = NVIC_PENDSVSET  即将触发PendSV中断
    BX      LR                  //因为在OS_Sched中调用OSCtxSw时,是在临界区,中断是关闭的,所以这里通过BX LR返回OS_Sched,等退出临界区后,触发PendSV中断【任务切换时必须关中断”的原则
PendSV中断响应函数OSPendSV
//Cortex-M3进入异常服务例程时,使用的是MSP指向的堆栈空间,且自动压入了R0-R3,R12,LR(R14,连接寄存器),PSR(程序状态寄存器)和PC(R15),这些寄存器是任务被中断时的现场记录此时任务所使用的PSP的值没有变化。
//M3处理器的控制寄存器的CONTROL[1] = 1(系统复位后默认是0),表明M3的线程模式的堆栈指针SP选用PSP【handler模式只允许使用MSP】,这样两个模式下SP使用不同的堆栈指针,且访问SP时,访问到的是当前被使用的堆栈指针,这时另一个堆栈指针要通过MRS、MSR命令来访问,即R13(PSP)或R13(MSP),R13是PSP还是MSP由系统当前的状态与CONTROL[1]决定,PSP是某个任务在RAM中的堆栈指针,MSP是异常模式下在RAM中的堆栈指针。
//一个任务占用CPU运行,它的运行现场是R0-R3,R4-R11,R12,R13(PSP/MSP),R14(LR),PSR和R15(PC),如果该任务被中断,那么在进中断过程中,硬件会自动对CPU的部分现场寄存器(R0-R3,R12,R14,PSR和R15)进行压栈,其他寄存器硬件不压栈,如果CONTROL[1] = 1,那么硬件就会将相关寄存器压入PSP(R13)指向的堆栈中(线程模式时SP为PSP),CONTROL[1] = 0,硬件就会将相关寄存器压入MSP(R13)指向的堆栈中;CONTROL[1] = 1时如下图所示,线程模式与handler模式的SP不一样。
uC/OS <wbr>II <wbr>学习笔记

OSPendSV  在进入中断前 R0-R3,R12,R14,PSR和R15已被压入任务所使用的堆栈中,下面是将其他的寄            存器再压入任务的堆栈中,以及保存该任务的堆栈栈顶的值到PSP指向的堆栈中去,任务使用            的是PSP,异常模式下使用的是MSP
    MRS     R0, PSP   //R0<=PSP 异常模式下使用的是MSP,所以只能通过MRS读取被中断的任务的堆栈指针,
    CBZ     R0, OSPendSV_nosave //如果PSP==0,则跳转到 OSPendSV_nosave ,否则往下执行【在系统刚开                                 //始启动时,OSStart()会调用OS_SchedNew()与OSStartHighRdy()来使                                 //系统中最高优先级的任务运行,在OSStartHighRdy中,PSP设置为0,因为                                 //在OSStart前还没有任务运行,所以进入软中断后不用保存此时CPU的值,                                 //只要将最高优先级任务的现场恢复到CPU的各寄存器就好了
    SUBS    R0, R0, #0x20   //R0(PSP)!=0,那么R0的值减去0X20,32个字节(下面保持8个32位的寄存器)
    STM     R0, {R4-R11}    //将R4~R11这8个寄存器存储到PSP对应的空间中去,这些寄存器是当前任务被中                             //断时的现场
    LDR     R1, __OS_TCBCur //__OS_TCBCur  DCD  OSTCBCur
                            //当前任务(被中断的任务)的任务控制块OSTCBCur, OSTCBCur是当前任务控制                             //块的首地址,也代表了它的第一个成员的地址,所以下面其实是对他的第一个                             //成员OS_STK *OSTCBStkPtr(指向任务的堆栈栈顶的指针)进行赋值
    LDR     R1, [R1]        //将OSTCBStkPtr中保持的值赋给R1,该值指向任务的堆栈栈顶的指针
                            //即R1存储被中断的当前任务的堆栈栈顶的指针
    STR     R0, [R1]        //将被中断的当前任务的堆栈栈顶的指针存到PSP指向的堆栈中去,即保存被中断                             //任务所使用的堆栈栈顶的指针,上面其他的寄存器是任务被中断的线场
OSPendSV_nosave  【上面的程序执行完后,接着继续执行下面的程序】
    PUSH    {R14}           //保存R14寄存器的值 压入到MSP指向的堆栈中,该寄存器在中断返回时大有作用
    LDR     R0, __OS_TaskSwHook //调用回调函数  钩子函数        
    BLX     R0
    POP     {R14}           
    -------------------------------------------
    //相当于定义指针变量:__OS_PrioCur DCD   OSPrioCur;  __OS_PrioHighRdy  DCD  OSPrioHighRdy
    LDR     R0, __OS_PrioCur     //INT8U OSPrioCur当前任务的优先级   
    LDR     R1, __OS_PrioHighRdy //OSPrioHighRdy系统中最高的优先级  
    LDRB    R2, [R1]             //上面两句是将变量的地址传给了R0与R1,[R1]最高优先级的值存入R2
    STRB    R2, [R0]             //将R2中的值存入到OSPrioCur变量中去(即接下来运行最高优先级任务)
                                 //即实现:OSPrioCur = OSPrioHighRdy;
    -------------------------------------------------
    //相当于定义指针变量:__OS_TCBCur DCD   OSTCBCur;  __OS_TCBHighRdy  DCD  OSTCBHighRdy     
    LDR     R0, __OS_TCBCur      //R0=&OSTCBCur;  OS_TCB  *OSTCBCur; OS_TCB *OSTCBHighRdy;
    LDR     R1, __OS_TCBHighRdy  //R1=&OSTCBHighRdy;这四句同理上面,OSTCBCur = OSTCBHighRdy
    LDR     R2, [R1]             //R2 = *R1;  R2=OSTCBHighRdy 指向优先级最高的任务的任务控制块
    STR     R2, [R0]             //*R0 = R2即实现:OSTCBCur = OSTCBHighRdy;
                                 //系统中用OSPrioCur   OSTCBCur 来表示正在运行的任务
    ----------------------------------------------
   通过R2(OSTCBHighRdy)将OSTCBHighRdy->OSTCBStkPtr的值赋给R0,再通过LDM将最高优先级任务的堆栈中保存好的R4-R11恢复到当前CPU的R4-R11寄存器中(这个过程与上面的保存过程是相逆的),因为R0-R3,R12,R14,PSR和R15是硬件自动压入任务的堆栈的,在中断中后来再保存R4-R11的(是先对SP减了32后再保存,相当于SP的值没有变化),且中断退出时会自动弹出R0-R3,R12,R14,PSR,所以下面的代码只要恢复最高优先级任务的R4-R11(OSTCBHighRdy->OSTCBStkPtr通过LDM自减了32,),然后将加上32, OSTCBHighRdy->OSTCBStkPtr 相当于没有变化,再将OSTCBHighRdy->OSTCBStkPtr赋给PSP,这样硬件在中断退出时会自动恢复 R0-R3,R12,R14,PSR ,这样就完全恢复了,一运行就是最高优先级的任务了
    LDR     R0, [R2]             //[R2]为*OSTCBHighRdy,R2是4字节的寄存器,所以[R2]是OSTCBHighRdy                                  //指向地址的后4个字节的值,即OSTCBHighRdy->OSTCBStkPtr,  
                                 //即 R0 = OSTCBHighRdy->OSTCBStkPtr
    LDM     R0, {R4-R11}         //将 OSTCBHighRdy->OSTCBStkPtr(R0)指向的堆栈(向下生长)栈顶后                                  //的32个字节依次存入R4-R11(R0的值自减,后缀为!表示自增),因为堆                                   //栈保持时是自减的,这里相当于恢复最高优先级任务的现场)               
    ADDS    R0, R0, #0x20        //将R0的值加上0x20后赋给R0
    MSR     PSP, R0              //PSP <= R0                              
    ORR     LR, LR, #0x04    //按位或 将LR的第二位置1,这样中断返回时,就将从进程的堆栈中做                              //出栈操作,返回后使用PSP(否则将从主堆栈中做出栈操作,返回后                              //使用MSP)                                
    BX      LR                   //中断返回

uC/OS <wbr>II <wbr>学习笔记
xPSR、PC、LR、R12、R3、R2、R1、R0会被硬件按一定的次序压入PSP所指向的堆栈中(同时被中断的任务后面要恢复执行,还需保存R4-R11,保存后一定要保证堆栈指针指向硬件自动压栈后的原位置,这样硬件才能在恢复任务时硬件自动正确出栈)

uC/OS <wbr>II <wbr>学习笔记


2:在中断中实现任务的切换
uC/OS II  的中断服务函数必须遵循一定的架构来实现,
void SysTickHandler(void)
{
    OS_CPU_SR  cpu_sr;
    OS_ENTER_CRITICAL();  //保存全局中断标志,关总中断
    OSIntNesting++;
    OS_EXIT_CRITICAL();  //恢复全局中断标志

    OSTimeTick();    

    OSIntExit();  //在os_core.c文件里定义,如果有更高优先级的任务就绪了,则执行一次任务切换 
}

uC/OS <wbr>II <wbr>学习笔记
OSInit函数
系统初始化函数OSInit()初始化所有的变量和数据结构,同时也会建立空闲任务OS_TaskIdle(),该任务永远处于就绪态,如果统计任务使能,那么他还要建立统计任务OS_TaskStat(),并使其进入就绪态。
空闲任务
OS_TaskIdle
uC/OS II总要建立一个空闲任务,idle task,这个任务在没有其他任务进入就绪态时投入运行,它的优先级永远设为最低优先级,即OS_LOWEST_PRIO,且空闲任务是不能被应用软件删除的
空闲任务不停的给一个32位的OSIdleCtr的变量加1,统计任务用这个计数器变量确定当前应用软件实际消耗CPU的时间,计数器加1前后分别关闭打开中断,OS_TaskIdle可以借助OSTaskIdleHook()做CPU的睡眠等;OS_TaskIdle总是处于就绪态
统计任务OS_TaskStat
只要将OS_TASK_STAT_EN宏使能,那么统计任务就会建立,一旦运行,它将每秒运行一次,计算当前CPU的利用率,将值放在OSCPUsage这个8位的变量中,用百分比表示,精度为1%.如果应用程序打算使用统计任务,那么必须在初始化时建立的第一个也是唯一的任务中调用统计任务初始化函数OSStatInit(),也就是在调用系统启动函数OSStart前,用户初始化代码中必须先建立一任务,在这个任务中调用系统统计初始化函数OSStatInit,然后再建立应用程序中的其他任务。它的优先级是OS_LOWEST_PRIO-1
OSStart操作系统启动函数
uC/OS II启动之前,至少须建立一个应用程序,因为如果使能统计任务,那么统计任务要求必须先建立一个也是唯一一个用户任务后,再启动系统,再创建其他的任务,如果什么任务都没有建立,只要空闲任务,那系统也就一直空闲,所以需要先创建至少一个任务。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多