版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第五章任务协作与通信
5.1信号量OS_SEM.C5.2互斥型信号量OS_MUTEX.C5.3消息邮箱OS_MBOX.C5.4消息队列OS_Q.C5.5事件标志OS_FLAG.C5.6多事件请求处理5.7µC/OS-Ⅱ组件配置OS_CFG.H5.8本章小结信号量本质上是一个全局指针变量,定义为一个任务(或中断服务程序)通知另一个任务可以执行的事件量,如果任务中使用OSSemPend函数请求信号量,当请求到信号量(即信号量的值大于0)时,则该任务可以执行,否则等待(即进入等待该信号量就绪的等待事件列表中)。发送信号量的任务中使用OSSemPost,每调用一次该函数,信号量的值加1。这样,发送信号量的任务可以通过定时发送信号量(或称释放信号量)控制请求信号量的任务的运行与否。5.1信号量OS_SEM.C当任务使用OSSemAccept请求信号量时,如果请求到信号量,即信号量的值大于0,则返回该信号量的值;如果没有请求到信号量,则返回0值。无论信号量请求到与否,使用OSSemAccept的任务都不会等待该信号量,而是继续执行。因此,OSSemAccept一般用于中断服务程序中。
信号量在使用前,应调用OSSemCreate创建信号量;如果信号量不再使用了,可调用OSSemDel删除信号量;调用OSSemQuery可查询信号量的状态;调用OSSemPendAbort使等待该信号量的所有任务就绪,如果没有任务在请求该信号量,可以调用OSSemSet设置信号量的值。
由上述分析可知,与信号量相关的函数有8个,列于第一章表1-7中。下面的实例ex5_1演示了这八个函数的使用方法。5.1.1工程ex5_1
在第四章工程ex4_3的基础上,新建工程ex5_1,保存目录为D:\ZYUCOSII\ex5_1(此时的工程ex5_1与工程ex4_3完全相同,只是工程文件名改为ex5_1),如图5-1所示。在工程ex5_1中,修改文件app.h、appfun.c即得完整的工程ex5_1。
在app.h中添加一行定义语句“MY_APPOS_EVENT*LedSem;”,可以添加在任何位置,建议放在文件中的第56行处。信号量使用OS_EVENT结构体类型定义。图5-1工程ex5-1的最初版本在文件appfun.c中的AppTaskStart函数中添加两行语句:
LedSem=OSSemCreate(3);
OSEventNameSet(LedSem,"Led_Sem",&err);
建议将上述两行语句插入到第138行处。OSSemCreate用于创建信号量,只有一个参数,即信号量的初始值。OSEventNameSet函数位于第一章表1-3中,表示为事件命名,这里为信号量LedSem命名为Led_Sem。然后,再修改appfun.c中的AppTask_1、AppTask_2和AppTask_3函数,这三个函数在第5.1.2节中给出了完整的代码,并给出了原理解释。
此时编译链接工程ex5_1,仿真运行它,串口调试助手界面如图5-2所示。图中的TimeTickCur为时钟节拍值,除以100后的单位为秒,LedSemCnt为信号量的值。工程ex5_1中只创建了一个信号量,µC/OS-Ⅱ支持创建多个信号量。
观察UP-Star板上的LED灯,可以看到这种现象:运行后前10秒内,三个LED灯每1秒闪一次;从第10秒至第30秒,LED不再闪烁;第30~50秒时又开始每1秒闪一次;第50秒后,每3秒闪一次。图5-2工程ex5_2的串口调试助手界面5.1.2工程ex5_1代码与注解
文件appfun.c中的函数AppTask_1、AppTask_2和AppTask_3的代码罗列如下:
1voidAppTask_1(void*pdata)
2{
3INT8Uerr;
4INT8Ss[80];
5INT32UtickCur; //Timetick'scurrentval
6
7OS_SEM_DATAsem_data;
8
在AppTask_1中定义了一个信号量数据类型(OS_SEM_DATA)的变量sem_data(第7行)。进入循环体后,第16~19行判断当时钟节拍值小于1000时,每隔1秒释放一次信号量。每隔1秒的操作体现在第44行,释放信号量即使信号量LedSem的计数值加1。在AppTask_3中,第76~81行判断信号量是否存在,如果存在,不断申请信号量LedSem,由于任务3没有延时,因此,只要LedSem的计数值大于0,则任务3将不断地执行。因此,程序开始执行后,任务3按任务1释放信号量的时间间隔(即1秒)运行,这里3个LED灯每隔1秒闪动一次。第20行,当时钟节拍经过了10秒,还没有达到30秒时,将任务3挂起,这时LED不再闪烁;然后,调用OSSemPendAbort将等待信号量LedSem的任务释放掉,第24行判断如果没有任务请求信号量LedSem,则第26行调用OSSemSet将信号量的计数值设为10;第27行调用OSSemQuery查询信号量LedSem的数据,查询结果存入sem_data中;第28~29行把信号量LedSem的计数值输出到串口调试助手。
第32~36行,当时钟节拍经过了30秒,还没有到达50秒时,恢复任务3的运行,同时每隔1秒释放信号量一次,此时三个LED灯又每隔1秒闪动一次。第37~43行,当时钟节拍经过了60秒,还没有达到70秒时,判断信号量是否存在,如果存在,则调用OSSemDel删除该信号量。OSSemDel有三个参数,依次为信号量指针、删除方式选项(这里使用了OS_DEL_ALWAYS)和返回信息。因此,过了50秒后,信号量LedSem不存在了。第76行判断为假,将执行第83~86行的代码,即每隔3秒三个LED闪动一次。
显然,当过了70秒以后,任务1几乎什么都不做了,只是简单地延时1秒输出时钟节拍的值。这时,任务3就一直每隔3秒使三个LED灯闪动一次地工作下去。现在,看一下任务2,即AppTask_2函数,第62行调用OSSemAccept函数请求信号量LedSem,这个函数的返回值为信号量的计数值,从图5-2中可以看出,运行第一次时返回的值为4,以后的值都为0。这是因为,在AppTaskStart函数初始化信号量LedSem的计数值为3,而后在AppTask_1中释放该信号量时,它的计数值又加了1,由于任务2的优先级比任务3的优先级要高,所以执行完任务1后执行任务2,将显示此时的信号量计数值为4。但是由于任务3没有延时,只要信号量LedSem存在且它的计数值大于0,任务3就一直运行,直到信号量LedSem的计数值为0为止。所以,直到任务3开始运行后,信号量的值将为0,故图5-2上显示后续的信号量计数值为0。由于OSSemAccept函数请求信号量并不等待,但是,如果申请到了信号量,信号量的计数值也要减1。所以,从第62~65行可看出任务2每隔3秒执行一次,即每隔3秒将信号量的计数值输出到串口调试助手上。经过分析可知,任务2在第一次运行后,再也申请不到信号量了。
在第六章中还将把信号量用于中断处理上。本节把信号量相关的8个函数都使用了,但这绝不是推荐读者也这样做。实际上,常用的信号量函数只有创建OSSemCreate、释放OSSemPost和请求OSSemPend等三个而已,作者为了使用全部的8个函数,显然使得程序有些画蛇添足。读者需要针对自己的应用需要来决定是否使用信号量、用几个信号量以及用多少个与信号量相关的函数。本章后续章节中也存在着类似的现象。全局变量可以被多个任务同时访问,称之为共享资源。互斥型信号量可以实现一个任务对共享资源的独占式访问,即当一个任务访问共享资源时,其他要访问该共享资源的任务需等到该任务使用完共享资源后再进行访问。假设两个任务需要访问同一个共享资源,当低优先级的任务A正在使用共享资源时,优先级高的任务B就绪了,由于其优先级高,所以B任务取得了CPU使用权,但是由于共享资源仍被A占用着,所以B处于等待状态,而A由于丢失了CPU使用权,也处于等待状态,这就是众所周知的“死锁”。5.2互斥型信号量OS_MUTEX.C
µC/OS-Ⅱ中处理这种现象的方法是提高A的优先级,使之高于任务B,这样任务A会先于任务B执行完,并释放共享资源,然后再恢复任务A的优先级,这种处理称为优先级反转,任务A提高的优先级称为优先级继承优先级(PIP,PriorityInheritancePriority)。
互斥信号量是二值信号量,只有0和1两个状态,其相关的函数有6个,即OSMutexCreate、OSMutexPend、OSMutexPost、OSMutexDel、OSMutexQuery和OSMutexAccept,列于第一章表1-8中。使用互斥型信号量时,需用OSMutexCreate创建一个互斥型信号量;在任务要使用共享资源前,调用函数OSMutexPend保护共享资源,在使用完共享资源后,调用OSMutexPost释放互斥型信号量;每个需要访问共享资源的任务使用共享资源的方法都是如此。当互斥型信号量不再需要时,可以调用OSMutexDel删除它,删除时要十分小心,必须同时要求它所管理的共享资源也不再使用了。可以调用OSMutexQuery查询互斥信号量的信息。函数OSMutexAccept与OSMutexPend的作用相似,都是请求互斥信号量,但是,OSMutexAccept在请求不到互斥信号量时不会等待,而是继续运行,但是如果获得了互斥信号量,必须在使用完共享资源后,调用OSMutexPost释放互斥型信号量。5.2.1工程ex5_2
在工程ex5_1的基础上新建工程ex5_2,保存目录为D:\ZYUCOSII\ex5_2,此时的工程ex5_2与工程ex5_1完全相同,只是工程文件名改为ex5_1。需要修改的文件有app.h和appfun.c。工程ex5_2如图5-3所示。图5-3工程ex5_2在app.h中原来定义信号量的语句“MY_APPOS_EVENT*LedSem;”处,替换为如图5-3中圈住的两行语句,即
MY_APPOS_EVENT*LedMutex;
MY_APPFP32x0;
上述语句定义了一个互斥信号量LedMutex和一个浮点数全局变量x0。同时,在app.h文件的末尾添加对自定义函数myRand3的声明,即
INT8UmyRand3(void);//Generaterand1~3
这个函数体位于appfun.c中。文件appfun.c中需要修改函数AppTaskStart、AppTask_1、AppTask_2和AppTask_3,并添加函数myRand3。
将AppTaskStart函数中原来创建信号量的语句
LedSem=OSSemCreate(3);
OSEventNameSet(LedSem,"Led_Sem",&err);
替换为以下语句:
1LedMutex=OSMutexCreate(4,&err);
2if(err==OS_ERR_NONE)
3{
4OSEventNameSet(LedMutex,"Led_Mutex",&err);
5}
6x0=0.412;第1行创建一个互斥信号量,变量名为LedMutex,优先级继承优先级为4。第2行判断创建互斥信号量是否成功,如果成功,则第4行将互斥信号量命名为字符串Led_Mutex,这个名称一般用作调试。当调试工程ex5_2时,在80秒内暂停运行,如图5-4所示,可以打开C-SPY查看互斥信号量LedMutex。图5-4工程ex5_2调试窗口任务AppTask_1、AppTask_2和AppTask_3以及新添加的函数myRand3的代码列于第5.2.2节中,并在那里对其工作原理进行解释。
仿真运行工程ex5_2,观察计算机显示的串口调试助手,如图5-5所示。当运行时间低于80秒时,依次显示三个任务中的随机数值,并显示互斥信号量LedMutex的PIP;当超过80秒后,显示LedMutex被删除。表现在三个LED灯上,在80秒之前呈随机闪烁状态;80秒以后,每隔3秒三个LED灯闪动一次。图5-5串口调试助手输出结果5.2.2工程ex5_2代码与注解
以下为任务AppTask_1、AppTask_2、AppTask_3以及myRand3的代码(这些函数位于appfun.c中):
1voidAppTask_1(void*pdata)
2{
3INT8Uerr;
4INT8Ss[80];上述代码第111~119行为自定义的产生1~3随机数的函数,其中x0是全局变量,这个函数是不可重入型的。因此,如果有多个任务需要使用这个函数,需要使用互斥信号量保护,即不能让多个任务同时访问该函数。产生1~3随机数的算法很简单,这里不赘述。在工程ex5_2中,任务1、2和3均调用了该函数。回到任务1的代码,第8行定义了互斥信号量结构体变量,用于保存互斥信号量的信息,其定义位于ucos_ii.h中第505~525行,请读者自己查阅,其中的成员OSMutexPIP表示相应互斥信号量的PIP。第17行判断时钟节拍小于6000,即运行时间小于60秒时,第19行请求互斥信号量(这里显然能请求到),然后,运行第20行,产生一个1~3的随机数存入局部变量rnd中,之后第21行释放LedMutex。第22行将根据产生的随机数的值使相应的LED灯闪动。第23~24行输出这个随机数。因此,在60秒以内时任务1使三个LED灯每隔1秒其中一个随机闪动一次。第27行判断时间是否大于60秒且小于80秒,在这段时间里,查询LedMutex的信息,如果查询正确,则输出它的PIP值。第38~46行是当时间超过80秒且小于90秒时,任务1将删除互斥信号量LedMutex,如果删除成功,第41行为真,将执行第43~44行,输出“Mutexhasbeendeleted!”。注意,这句话仅会输出一次,如图5-5所示,因为第41行只可能有一次为真。当然,在90秒以后,任务1只是简单地处理第13~15行,即输出时钟节拍值,然后延时1秒。现在,到任务2中,第66行调用OSMutexAccept函数,这个函数与OSMutexPend的作用相似,只是OSMutexAccept申请到互斥信号量时,返回OS_TRUE,并且要在使用完共享资源,即调用完myRand3后再调用一次OSMutexPost;申请不到时,也不挂起任务。由任务1的解释,可知80秒以前,任务2执行第69~72行,即输出随机数的值;80秒以后执行第76~77行的代码,即输出“Mutexdeleted!”。任务2每3秒运行一次。任务3中第93行首先判断互斥信号量是否存在,如果存在,则申请互斥信号量LedMutex,然后第96行使用myRand3函数,第97行释放LedMutex,第98行根据随机数的值闪动LED灯,第99~100行输出随机数的值,第101行延时2秒。如果第103行互斥信号量LedMutex不存在了,则第105~106行第3秒三个LED同时闪动一次。
工程ex5_2使用了互斥信号量的全部6个函数,这样做是为了介绍互斥信号量的用法,读者自己的程序中应视需要进行函数的选用。这里也仅创建了一个互斥型信号量,µC/OS-Ⅱ允许创建多个互斥型信号量。任务之间可以通过用户定义的扩展数据结构进行数据通信,在使用OSTaskCreateExt函数创建任务时,指定其倒数第2个参数即可,这种任务间的数据通信本质上依靠全局变量来实现。而通过消息邮箱同样可以实现任务间的数据交换,消息邮箱是很重要的一类事件,这种类型的任务间数据通信是通过局部变量和函数传递来实现的。5.3消息邮箱OS_MBOX.C消息邮箱相关的函数有8个,即OSMboxCreate、OSMboxPend、OSMboxPost、OSMboxPostOpt、OSMboxDel、OSMboxPendAbort、OSMboxAccept和OSMboxQuery,罗列在第一章表1-9中。使用消息邮箱时,必须调用OSMboxCreate函数创建一个消息邮箱,创建时可以给邮箱赋予字符串值,也可以通过赋NULL,生成一个空邮箱;任务在请求邮箱中的消息时,使用OSMboxPend和OSMboxAccept,后者在申请不到消息时,任务不会被挂起;通过调用OSMboxPost和OSMboxPostOpt向邮箱发送一则消息,OSMboxPostOpt可以向请求该邮箱的所有任务发送消息,也可仅向请求该邮箱的最高优先级任务发送消息,比OSMboxPost更灵活;当邮箱不再使用时,通过调用OSMboxDel删除邮箱,删除邮箱时要十分小心,应把请求该邮箱的任务都取消后再删除,以免程序不工作;可以调用OSMboxQuery查询消息邮箱的信息;而调用OSMboxPendAbort将使等待该邮箱的任务都放弃等待而进入就绪态。
本节的工程ex5_3演示了这些函数的使用方法。5.3.1工程ex5_3
在工程ex5_1的基础上新建工程ex5_3,存储目录为D:\ZYUCOSII\ex5_3(此时的工程ex5_3与ex5_1完全相同,只是工程文件名更改为ex5_3)。需要修改的文件有app.h和appfun.c。
将文件app.h中的代码“MY_APPOS_EVENT*LedSem;”替换为以下语句:
MY_APPOS_EVENT*LedMbox;
表示定义消息邮箱事件,这里只是变量名不同,变量类型都是OS_EVENT*,参见图5-6中部被圈住的语句。图5-6工程ex5_3调试窗口将文件appfun.c内AppTaskStart函数中的两行代码
LedSem=OSSemCreate(3);
OSEventNameSet(LedSem,"Led_Sem",&err);
替换为以下两行语句:
LedMbox=OSMboxCreate(NULL);
OSEventNameSet(LedMbox,"Led_Mbox",&err);表示创建一个空的消息邮箱,并为它命名为Led_Mbox,这个名称主要用于调试。如果调试工程ex5_3,在调试窗口中打开C-SPY窗口,如图5-6所示,可看到这个消息邮箱的名称。
文件appfun.c中的AppTask_1、AppTask_2和AppTask_3修改的地方比较多,它们的代码列于第5.3.2节。
当仿真运行工程ex5_3时,在串口调试助手上可以看到如图5-7所示的界面。图5-7仅给出了运行开始4秒时的界面,读者需要自己运行更长的时间以观察工程ex5_3的输出
规律。图5-7工程ex5_3运行时串口调试助手界面情况5.3.2工程ex5_3功能注解
文件appfun.c中的AppTask_1、AppTask_2和AppTask_3函数的代码如下:在任务1中,第7行定义字符串(即字符数组)myMessage;第16~20行,当时钟节拍小于2000时,即执行时间小于20秒时,给myMessage赋字符串“1-Led1Flash”,然后第19行将该字符串通过邮箱LedMbox释放出去。现在跳到任务3的第112行,这里判断事件类型是否为消息邮箱,然后,第114行调用OSMboxPend请求该邮箱中的消息,其中第2个参数赋为OS_TICKS_PER_SEC表示请求超时为1秒,如果1秒内请求不到邮箱,则放弃等待,而继续运行。显然,这里可以请求到消息邮箱LedMbox,返回值pmsg为myMessage的内容;第115行,提取消息字符串的第一个字符,这里应为1,则执行第117~119行,即LED1灯闪动一次,并输出字符串“1-Led1Flash”到串口调试助手。再到任务2,任务2在第67行定义了OS_MBOX_DATA类型的变量mbox_dat,用于存放消息邮箱的信息,其中的OSMsg成员即为邮箱中的消息字符串。工程ex5_3开始运行时,由于任务1的优先级最高,所以任务1将先于任务2和3运行,释放一个消息邮箱;然后由于任务2优先级高于优务3,所以,任务2得到运行,即第76行得到执行,于是,第79~81行输出消息邮箱中的消息字符串,如图5-7中最上方圈住的字符串“Task2Query:1-Led1Flash!”。然后,任务2继续运行第82行及其以后的代码,此时,pmsg的第一个字符应为1,所以第85行代码为假,故第87~90行代码得不到运行。除了第一次运行工程ex5_3,任务2中的第76行OSMboxAccept能请求到消息邮箱外,以后的每次运行,OSMboxAccept均请求不到消息邮箱LedMbox,主要是因为任务3处于无延时请求状态,即只要邮箱中包含消息,任务3就可以启动,把消息取走。所以,图5-7中第8行开始均显示“Task2failedtoAskMbox”。再回到任务1中,在20秒以前,LED1灯会每隔1秒闪动一次;第21~25行说明程序运行20~40秒间,任务1释放消息为“2-Led2Flash!”。再跳至任务3,此时,第121行判断为真,于是第123~125行运行,LED灯2闪动,并且输出“2-Led2Flash!”至串口调试助手。仍回到任务1中,在第26~30行,任务1释放消息“3-Led3Flash!”,于是导致任务3的第127~133行执行,即LED灯3闪动,并且输出“3-Led3Flash!”至串口调试助手。任务1的第31~35行,使用OSMboxPostOpt释放消息,由于使用了参数OS_POST_OPT_BROADCAST,这时,如果有多个任务请求该消息邮箱,则每个任务都将得到消息“3-Led3Flash!”。在工程ex5_3中,却只有任务3得到消息,这里需要注意,任务2是得不到消息的,因为任务2中使用OSMboxAccept函数,这个函数不会导致任务2进入该消息邮箱的等待列表中;而OSMboxPostOpt当使用参数OS_POST_OPT_
BROADCAST时,将使所有进入等待列表中的任务得到邮箱中的消息。请读者将任务2中的OSMboxAccept修改为OSMboxPend后,自行试验一下,修改后将使得60~80秒间任务2和3都能收到消息“3-Led3Flash!”。第36~48行的代码意义不大,这里仅是为了演示OSMboxPendAbort函数的使用而写了一段代码,使得80~100秒时等待消息邮箱的事件跳出等待而断续运行。在串口调试助手中将显示“NoMessageinLedMbox!”。
第49~56行调用OSMboxDel删除消息邮箱,由于删除成功后,第52行语句只可能有一次为真,故仅能执行一次第54行代码。由上述解释可知,通过消息邮箱可以在任务间传递数据。此外,Labrosse先生的书中还指出邮箱可用作二值信号量、计数用信号量以及延时处理等。常用的消息邮箱函数主要是创建邮箱OSMboxCreate、释放邮箱OSMboxPost和OSMboxPostOpt以及请求邮箱OSMboxPend三种。消息队列可以看做消息邮箱的数组形式,消息邮箱一次只能传递一则消息,而消息队列可以一次传递多则消息。使用消息队列前,调用函数OSQCreate创建消息队列;发送消息方通过函数OSQPost、OSQPostFront和OSQPostOpt释放消息至消息队列中,OSQPost是先进先出(FIFO)工作方法,OSQPostFront是后进先出(LIFO)方式,OSQPostOpt可以根据选项参数设置工作方式;5.4消息队列OS_Q.C接收消息方通过OSQPend或OSQAccept请求消息,OSQAccept无论请求到消息与否,均不挂起任务等待;调用函数OSQFlush可以清空消息队列中的消息;调用OSQQuery查询消息队列的信息;当消息队列不再使用时,可调用OSQDel删除消息队列;可调用函数OSQPendAbort取消请求消息队列的任务的等待状态,使这些任务就绪。
消息队列相关的函数即为上述的10个函数,列于第一章表1-10中,本节实例工程ex5_4演示了这些函数的使用方法。5.4.1工程ex5_4
在工程ex5_1的基础上新建工程ex5_4,保存目录为D:\ZYUCOSII\ex5_4(此时的工程ex5_4与工程ex5_1完全相同,只是工程文件名更改为ex5_4)。需要修改的文件有app.h和appfun.c。
将文件app.h中原来定义信号量的语句“MY_APPOS_EVENT*LedSem;”替换为以下两行语句:
MY_APPOS_EVENT*LedQ;
MY_APPvoid*myQMessage[10];
即定义消息队列LedQ和具有10个成员的void*型数组用于存储消息。文件appfun.c需要修改四个函数,即AppTaskStart、AppTask_1、AppTask_2和AppTask_3。其中将AppTaskStart函数中原来创建信号量的语句
LedSem=OSSemCreate(3);
OSEventNameSet(LedSem,"Led_Sem",&err);
替换为以下语句:
LedQ=OSQCreate(&myQMessage[0],10);
OSEventNameSet(LedQ,"Led_Q",&err);
即创建消息队列LedQ,最多可容纳的消息数为10,把消息队列命名为Led_Q,这个名称一般用于调试。
AppTask_1、AppTask_2和AppTask_3的内容将在第5.4.2节列出并解释其工作原理。
仿真运行ex5_4,界面如图5-8所示,在C-SPY中可以查看消息队列的状态,如图中被圈住的部分。串口调试助手的输出界面如图5-9所示。图5-8工程ex5_4仿真运行界面图5-9串口调试助手界面5.4.2工程ex5_4功能注解
文件appfun.c中任务AppTask_1、AppTask_2和AppTask_3的代码如下:第8行定义了一个二维数组myMessage,能存放20个长度不超过80的消息(注意,第5.4.1节创建了只能存放10个消息的队列)。第10~13行初始化myMessage数组。第21~25行,当执行时间低于20秒时,任务1调用OSQPost函数向队列LedQ中添加10个消息,即myMessage[0]至myMessage[9];如果程序第一次运行,则跳至任务2,第74行查询消息队列LedQ,此时,查得的信息存入q_dat变量中,显然,此时的第77~82行输出查到的消息Mymessage1。第84行OSQAccept将请求到第一条消息,任务2进入延时(第91行)。由于任务3的优先级较任务2低,任务2执行完后,任务3得到执行,第105行任务3调用OSQPend请求消息队列,由于其中的“Mymessage1”被任务2请求了,故剩下的9则消息将被任务3请求到,并通过串口调试助手显示出来(第109~110行)。这里任务3没有延时OSTimeDly等,不断执行第105行请求消息队列(第105行的OSQPend函数的超时选项设为OS_TICKS_PER_SEC,即请求等待1秒后任务3继续执行,又重回到第105行),导致在以后的运行过程中任务2很难请求到消息(只有任务3在请求到消息1至消息10的过程中,任务2就绪了才能请求到)。第26~32行说明执行时间小于40秒而大于20秒时,第28行调用OSQFlush清空队列中的所有消息;第30~31行使用OSQPostFront函数向队列释放10个消息,OSQPostFront采用插入消息的方式,即满足先进后出(LIFO)的工作方式。这样,任务3第105行不断请求消息队列后的输出应如图5-9所示,即先输出Mymessage1后,其余消息按LIFO的方式输出,即依次输出Mymessage10、Mymessage9直到最后输出Mymessage2。这里需要说明一下,由于任务3始终处于请求队列状态,Mymessage1直接送到任务3中了,所以,Mymessage1先送出来。因此,按这种工作方法,虽然队列只能装下10个消息,但实际上第30行的循环终止条件可以设为i<=10。
第33~37行与第26~32行的作用完全相同,这里使用的OSQPostOpt函数释放消息,选项为OS_POST_OPT_FRONT,表示消息入队方式是LIFO方式。如果选项为OS_POST_OPT_NONE,则为FIFO方式,即与OSQPost作用相同;如果选项为OS_POST_OPT_BROADCAST,则表示队列中的消息将发送到所有等待队列的任务中(这类任务不包括OSQAccept请求的任务);还有几个选项请参考“µC/OS-ⅡReferenceManual”手册。第38~41行介绍OSQPendAbort函数的用法,这里使用选项OS_PEND_OPT_BROADCAST将取消所有请求消息队列LedQ的任务的等待状态,使它们就绪。
第42~46行,即执行到80秒至100秒之间时,再次使用OSQPostOpt发送消息到队列中,这里发送的消息是Mymessage11至Mymessage20,任务3将每3秒(第55行)把这些消息发送到串口调试助手上。第47~54行说明第100秒的时候,将调用OSQDel函数删除消息队列LedQ,由于第50行仅可能有一次为真,所以第52行仅输出一次“LedQhasbeendeleted!”
第55行表示每3秒任务1运行一次;第91行表示每5秒任务2运行一次;当100秒过后,LedQ被删除了,任务3将执行第114~117行的代码,即每3秒三个LED灯闪动一次。事件标志可以理解为一种更加灵活的“位”信号量,基本原理为释放事件标志时,使一个OS_FLAGS变量(默认配置下为16位无符号整型)的某些位为1,另一些位为0;请求事件标志时,可以设计请求规则,即OS_FLAGS变量的某些位满足特定条件时,才执行请求事件标志的任务。5.5事件标志OS_FLAG.C事件标志相关的函数有9个,列于第一章表1-11中。使用事件标志时,应调用OSFlagCreate创建一个OS_FLAG_GRP*类型的事件标志变量(这里不是OS_EVENT*类型);使用OSFlagPost释放事件标志;使用OSFlagPend请求事件标志,请求到事件标志后,可通过调用函数OSFlagPendGetFlagsRdy获知事件标志满足的条件;查询一个事件标志的信息函数为OSFlagQuery;当不使用事件标志时,调用函数OSFlagDel删除它;在中断处理函数或要求不被挂起的任务中可使用OSFlagAccept请求事件标志;为事件标志命名使用函数OSFlagNameSet;取得事件标志的名称使用函数OSFlagNameGet,这个名称主要用于调试。下面详细介绍OSFlagCreate、OSFlagPost和OSFlagPend的用法。
OS_FLAG_GRP*OSFlagCreate(OS_FLAGSflags,INT8U*perr);
其中,在os_cfg.h中定义OS_FLAGS_NBITS为16,这里OS_FLAGS相当于INT16U,flags即为事件标志的变量,其16个位即为事件标志位。perr为OS_ERR_NONE时创建事件标志成功。OSFlagCreate返回值为指向OS_FLAG_GRP结构体类型的指针变量,简称为事件标志,这个结构体定义位于ucos_ii.h文件的第429~436行,请读者自己查阅。OS_FLAGSOSFlagPost(OS_FLAG_GRP*pgrp,
OS_FLAGSflags,
INT8Uopt,
INT8U*perr);其中,pgrp即为OSFlagCreate函数创建的事件标志。flags即事件标志的变量,这里为16位,如果想使其第13、11、8和3位为1,则flags取为0x2908,且此时opt必须取为OS_FLAG_SET;如果想其第13、11、8和3位为0,则flags仍取为0x2908,且此时opt必须取为OS_FLAG_CLR。opt取OS_FLAG_SET或OS_FLAG_CLR之一。perr为OS_ERR_NONE时释放事件标志成功。OSFlagPost返回新的事件标志值。OS_FLAGSOSFlagPend(OS_FLAG_GRP*pgrp,
OS_FLAGSflags,
INT8Uwait_type,
INT16Utimeout,
INT8U*perr);其中,pgrp即为OSFlagCreate函数创建的事件标志。flags为请求事件标志的位模式,例如想请求第13位和第8位,则flags设为0x2100。wait_type为请求flags中选定位的方式,共有四种类型:OS_FLAG_WAIT_CLR_ALL要求请求的标志位全为0;OS_FLAG_WAIT_CLR_ANY要求请求的标志位至少有一位为0;OS_FLAG_WAIT_SET_ALL要求请求的标志位全为1;OS_FLAG_WAIT_SET_ANY要求请求的标志位至少有一位为1。满足请求方式的任务得到CPU使用权。如果wait_type条件满足后,想清除该事件标志,则设置wait_type为上述四种方式的一种与OS_FLAG_CONSUME取或。timeout用于指定等待超时时间,超时1秒设为OS_TICKS_PER_SEC。perr为OS_ERR_NONE时表示调用OSFlagPend请求事件成功。
其他函数这里不再赘述。下面的工程ex5_5使用了事件标志相关的所有9个函数,将以实例的形式介绍这些函数的使用方法。5.5.1工程ex5_5
在工程ex5_1的基础上新建工程ex5_5,保存目录为D:\ZYUCOSII\ex5_5,此时的工程ex5_5与工程ex5_1完全相同,只是工程文件名更改为ex5_5。需要修改的文件有app.h和appfun.c,其中将文件app.h中原来定义信号量的语句“MY_APPOS_EVENT*LedSem;”替换为“MY_APPOS_FLAG_GRP*LedFlag;”,这里定义事件标志使用OS_FLAGS_GRP*,而不是OS_EVENT*。文件appfun.c中需要修改的函数有AppTaskStart、AppTask_1、AppTask_2和AppTask_3,其中将AppTaskStart函数中原来创建信号量的语句
LedSem=OSSemCreate(3);
OSEventNameSet(LedSem,"Led_Sem",&err);
替换为以下语句:
LedFlag=OSFlagCreate(0x0000,&err);//CreateFlag
OSFlagNameSet(LedFlag,"Led_Flag",&err);即创建一个事件标志LedFlag,并命名为Led_Flag,给事件标志命名使用OSFlagNameSet,而不是OSEventNameSet。
函数AppTask_1、AppTask_2和AppTask_3由于修改较大,将在第5.5.2节中给出完整的代码和注解。
工程ex5_5调试时的窗口如图5-10所示,串口调试助手显示界面如图5-11所示。图5-10工程ex5_5调试界面图5-11串口调试助手显示界面5.5.2工程ex5_5功能注解
文件appfun.c中函数AppTask_1、AppTask_2和AppTask_3的代码如下:任务1中第16行~20行说明当执行时间小于20秒时,每隔1秒(第33行)释放一个事件标志LedFlag,设置LedFlag标志值的第1、3、5、7位为1。当程序刚开始执行时,任务1释放LedFlag后,进入任务2(由于任务2的优先级低于任务1而高于任务3),执行第52~57行,查询LedFlag的信息,并输出它的值,此时输出的值为图5-11上部圈住的值,即0x00AA。然后,第58行使用不挂起等待的OSFlagAccept函数请求LedFlag,请求的方式为事件标志值的第5、7位为1,申请到后并不清除事件标志。第59~66行输出申请到的事件标志值,即0x00A0。第67行说明任务2延时3秒后再次请求事件标志。然后,进入任务3,任务3第81行判断事件标志还存在否,如果存在,则第83~87行使用OSFlagPend请求事件标志LedFlag,请求方式为事件标志的第1、3位为1,请求成功后清除事件标志的第1、3位,请求超时为2秒。如果请求成功,则第88~96行输出请求到的标志位,即0x000A。注意第89~96行和第61~65行的不同点,由于OSFlagPend请求事件标志时当前任务会进入就绪事件列表中,所以,可以调用OSFlagPendGetFlagsRdy函数获得请求成功的事件标志值,而任务2中使用OSFlagAccept函数请求事件标志,所以任务2中不能使用OSFlagPendGetFlagsRdy函数。回到任务1第16~20行,由于每1秒释放事件标志一次,并且任务3不断请求,请求成功后还要清除相应的事件标志位,所以,在前20秒内,任务3每1秒请求到事件标志一次。由于任务2请求事件标志不清除相应的事件标志位,所以,任务2每3秒能请求到事件标志一次。但是第52行查询事件标志信息时,由于0x000A被任务3请求并清除了,所以在随后的执行过程中,查询到的值为0x00A0。第21~23行,当执行时间大于20秒小于60秒时,没有释放事件标志。由于任务2的申请并不清除事件标志,所以任务2在这段时间内仍然每3秒申请到事件标志一次。由于任务3每次申请到后,要清除相应的事件标志,所以这段时间内任务3申请不到事件标志,每2秒超时一次。第24~31行,当执行时间大于60秒而小于80秒时,将删除事件标志。由于第27行仅有一次为真,故第29行仅执行一次,即仅输出一次事件标志删除信息到串口调试助手。事件标志被删除后,任务2几乎什么都不做了。任务3第81行为假,故执行第100~103行代码,每3秒三个LED灯同时闪烁一下。
80秒以后,任务1只是每1秒简单地执行第12~14行代码,任务2没做什么,任务3每3秒三个LED灯同时闪烁一下。从第5.5节关于事件标志的介绍可知,一个事件标志可以管理多个任务的请求。虽然一个信号量也可以有多个任务请求,但是信号量只有一个,请求一个信号量的任务之间互相关联着(指按优先级高低获得执行权);而事件标志可以相当于多个信号量的组合,多个任务按不同的请求模式,可以相互间毫无关联地工作着(请求不同事件标志位模式的任务间按就绪先后获得执行权)。因此,事件标志可以看做“一事件多任务”的请求处理模式。5.6多事件请求处理
µC/OS-ⅡV2.86版也支持“多事件一任务”的请求处理模式,即一个任务同时请求多个事件(这里的事件是指信号量、消息邮箱或消息队列),只要其中一个事件满足要求,则任务即可运行。这时,可以有多个任务请求多个事件,但是在这种情况下,只有优先级最高的就绪任务得到执行权。
“多事件一任务”请求函数为OSEventPendMulti,列于第一章表1-3中,本节的工程ex5_6将介绍这一函数的用法,同时将介绍表1-3中的OSSchedLock和OSSchedUnlock函数,这两个函数用于给当前任务加锁和解锁,加锁后的任务在执行过程中不能被调度,任务加锁后不能使用能使任务挂起的函数,否则程序会死锁。一般地,这两个函数配对使用。中断服务程序中也不能使用具有挂起等待功能的函数。5.6.1工程ex5_6
在工程ex5_1的基础上新建工程ex5_6,保存目录为D:\ZYUCOSII\ex5_6,此时的工程ex5_6与工程ex5_1完全相同,只是工程文件名更改为ex5_6。需要修改的文件有app.h和appfun.c。其中,文件app.h中在原来定义信号量的语句“MY_APPOS_EVENT*LedSem;”下面,添加以下语句:
MY_APPOS_EVENT*LedMbox; //Mbox
MY_APPOS_EVENT*LedQ; //Q
MY_APPvoid*myQMessage[10];
即又定义了消息邮箱和消息队列。
文件appfun.c中需要修改的函数有AppTaskStart、AppTask_1、AppTask_2和AppTask_3,其中AppTaskStart函数在原来创建信号量的语句(将OSSemCreate(3)中的3改为1)
LedSem=OSSemCreate(3);
OSEventNameSet(LedSem,"Led_Sem",&err);
的下面添加以下语句:
LedMbox=OSMboxCreate(NULL);//CreateMbox
OSEventNameSet(LedMbox,"Led_Mbox",&err);
LedQ=OSQCreate(&myQMessage[0],10); //CreateQ
OSEventNameSet(LedQ,"Led_Q",&err);
函数AppTask_1、AppTask_2和AppTask_3由于修改较大,将在第5.6.2节中给出完整的代码和注解。
完整的工程ex5_6的调试窗口如图5-12所示,程序开始运行时,串口调试助手的显示界面如图5-13所示。图5-12工程ex5_6调试窗口图5-13串口调试助手显示界面5.6.2工程ex5_6功能注解
文件appfun.c中函数AppTask_1、AppTask_2和AppTask_3的代码如下:第7~11行定义了10条消息的变量myMessage,并赋了值,后面程序仅使用了myMessage[0]~myMessage[5]。第20~23行,当程序执行时间小于10秒时释放信号量LedSem;第24~28行,当程序执行时间大于10秒而小于20秒时,释放消息邮箱LedMbox;第29~34行,当程序执行时间大于20秒而小于30秒时,释放消息队列LedQ;第35~44行,当程序执行时间大于30秒而小于40秒时,依次释放信号量LedSem、消息邮箱LedMbox和消息队列LedQ;第45~53行,当程序执行时间大于40秒而小于50秒时,依次释放消息邮箱LedMbox、信号量LedSem和消息队列
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 和声技巧试题及答案分享
- 核医学放射性测量
- 婚前医学相关试题及答案
- 泌尿副高职称考试试题与答案
- (辽宁专用)2024-2025版高中语文 第三单元 22 拿来主义教学设计(必修4)
- 传染病监测与预警系统优化
- 医学课件-系统解剖学 四肢骨
- 2025-2026学年部编版登勃朗峰教学设计
- 2026年江西省安全员C证考试模拟题及答案详解
- 象山事业单位招聘2026年考试模拟题及答案详解
- 生物系统建模与仿真课件
- 2025-2026学年江苏省苏州市吴中区木渎高级中学高二上学期10月月考数学试卷(含答案)
- 建设工程施工图设计方案
- 2025年9月27日安徽省市遴选笔试真题及解析(省直卷)
- T/CECCEDA 1-2025企业管理创新体系要求及实施指南
- 老旧小区改造施工安全文明管理方案解读
- 《基于WEB漏洞检测系统的设计与实现》10000字(论文)
- 初二物理第一、二单元测试试卷
- 实验室安全事故案例
- GA/T 1991-2022法庭科学疑似毒品中卡西酮等5种卡西酮类毒品检验气相色谱和气相色谱-质谱法
- 沈阳地铁6号线一期工程环评报告
评论
0/150
提交评论