【ALIENTEK 战舰STM32开发板例程系列连载+教学】第五十八章 UCOSII实验1-任务调度

2019-10-16 06:14发布

{if 编辑问题=='编辑问题'}  

第五十八章 UCOSII实验1-任务调度

     前面我们所有的例程都是跑的裸机程序(裸奔),从本章开始,我们将分3个章节向大家介绍UCOSII(实时多任务操作系统内核)的使用。本章,我们将向大家介绍UCOSII最基本也是最重要的应用:任务调度。本章分为如下几个部分: 58.1 UCOSII简介 58.2 硬件设计 58.3 软件设计 58.4 下载验证
58.1 UCOSII简介 UCOSII的前身是UCOS,最早出自于1992 年美国嵌入式系统专家Jean J.Labrosse 在《嵌入式系统编程》杂志的5月和6月刊上刊登的文章连载,并把UCOS 的源码发布在该杂志的BBS 上。目前最新的版本:UCOSIII已经出来,但是现在使用最为广泛的还是UCOSII,本章我们主要针对UCOSII进行介绍。 UCOSII是一个可以基于ROM运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。为了提供最好的移植性能,UCOSII最大程度上使用ANSI C语言进行开发,并且已经移植到近40多种处理器体系上,涵盖了从8位到64位各种CPU(包括DSP) UCOSII是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。用户只要有标准的ANSI C交叉编译器,有汇编器、连接器等软件工具,就可以将UCOSII嵌人到开发的产品中。UCOSII具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB UCOSII已经移植到了几乎所有知名的CPU 上。        UCOSII构思巧妙。结构简洁精练,可读性强,同时又具备了实时操作系统的全部功能,虽然它只是一个内核,但非常适合初次接触嵌入式实时操作系统的朋友,可以说是麻雀虽小,五脏俱全。UCOSIIV2.91版本)体系结构如图58.1.1所示:  58.1.1 UCOSII体系结构图        注意本章我们使用的是UCOSII的最新版本:V2.91版本,该版本UCOSII比早期的UCOSII(如V2.52)多了很多功能(比如多了软件定时器,支持任务数最大达到255个等),而且修正了很多已知BUG。不过,有两个文件:os_dbg_r.cos_dbg.c,我们没有在上图列出,也不将其加入到我们的工程中,这两个主要用于对UCOS内核进行调试支持,比较少用到。        从上图可以看出,UCOSII的移植,我们只需要修改:os_cpu.hos_cpu_a.asmos_cpu.c等三个文件即可,其中:os_cpu.h,进行数据类型的定义,以及处理器相关代码和几个函数原型;os_cpu_a.asm,是移植过程中需要汇编完成的一些函数,主要就是任务切换函数;os_cpu.c,定义一些用户HOOK函数。        图中定时器的作用是为UCOSII提供系统时钟节拍,实现任务切换和任务延时等功能。这个时钟节拍由OS_TICKS_PER_SEC(在os_cfg.h中定义)设置,一般我们设置UCOSII的系统时钟节拍为1ms~100ms,具体根据你所用处理器和使用需要来设置。本章,我们利用STM32SYSTICK定时器来提供UCOSII时钟节拍。 关于UCOSIISTM32的详细移植,请参考光盘资料(《UCOSIISTM32的移植详解.pdf》),这里我们就不详细介绍了。 UCOSII早期版本只支持64个任务,但是从2.80版本开始,支持任务数提高到255个,不过对我们来说一般64个任务都是足够多了,一般很难用到这么多个任务。UCOSII保留了最高4个优先级和最低4个优先级的总共8个任务,用于拓展使用,单实际上,UCOSII一般只占用了最低2个优先级,分别用于空闲任务(倒数第一)和统计任务(倒数第二),所以剩下给我们使用的任务最多可达255-2=253个(V2.91)。 所谓的任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这样的任务(最多255个),UCOSII对这些任务进行调度管理,让这些任务可以并发工作(注意不是同时工作!!,并发只是各任务轮流占用CPU,而不是同时占用,任何时候还是只有1个任务能够占用CPU),这就是UCOSII最基本的功能。 前面我们学习的所有实验,都是一个大任务(死循环),这样,有些事情就比较不好处理,比如:MP3实验,在MP3播放的时候,我们还希望显示歌词,如果是1个死循环(一个任务),那么很可能在显示歌词的时候,MP3声音出现停顿(尤其是高码率的时候),这主要是歌词显示占用太长时间,导致VS1053由于不能及时得到数据而停顿。而如果用UCOSII来处理,那么我们可以分2个任务,MP3播放一个任务(优先级高),歌词显示一个任务(优先级低)。这样,由于MP3任务的优先级高于歌词显示任务,MP3任务可以打断歌词显示任务,从而及时给VS1053提供数据,保证音频不断,而显示歌词又能顺利进行。这就是UCOSII带来的好处。 UCOSII的任何任务都是通过一个叫任务控制块(TCB)的东西来控制的,每个任务管理块有3个最重要的参数:1,任务函数指针;2,任务堆栈指针;3,任务优先级;任务控制块就是任务在系统里面的身份证(UCOSII通过优先级识别任务),任务控制块我们就不再详细介绍了,详细介绍请参考任哲老师的《嵌入式实时操作系统UCOSII原理及应用》一书第二章。 UCOSII中,使用CPU的时候,优先级高(数值小)的任务比优先级低的任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得CPU使用权,只有高优先级的任务让出CPU使用权(比如延时)时,低优先级的任务才能获得CPU使用权。UCOSII不支持多个任务优先级相同,也就是每个任务的优先级必须不一样。 任务的调度其实就是CPU运行环境的切换,即:PC指针、SP指针和寄存器组等内容的存取过程,关于任务调度的详细介绍,请参考《嵌入式实时操作系统UCOSII原理及应用》一书第三章相关内容。 UCOSII的每个任务都是一个死循环。每个任务都处在以下 5种状态之一的状态下,这5种状态是:睡眠状态、 就绪状态、 运行状态、 等待状态(等待某一事件发生)和中断服务状态。 睡眠状态,任务在没有被配备任务控制块或被剥夺了任务控制块时的状态。 就绪状态,系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,任务已经准备好了,但由于该任务的优先级比正在运行的任务的优先级低, 还暂时不能运行,这时任务的状态叫做就绪状态。   运行状态,该任务获得CPU使用权,并正在运行中,此时的任务状态叫做运行状态。 等待状态,正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给别的任务而使任务进入等待状态。 中断服务状态,一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态。 UCOSII任务的5个状态转换关系如图58.1.2所示:

58.1.2 UCOSII任务状态转换关系 接下来,我们看看在UCOSII中,与任务相关的几个函数: 1)  建立任务函数 如果想让UCOSII管理用户的任务,必须先建立任务。UCOSII提供了我们2个建立任务的函数:OSTaskCreatOSTaskCreatExt,我们一般用OSTaskCreat函数来创建任务,该函数原型为:OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)。该函数包括4个参数:task:是指向任务代码的指针;pdata:是任务开始执行时,传递给任务的参数的指针;ptos:是分配给任务的堆栈的栈顶指针;prio是分配给任务的优先级。 每个任务都有自己的堆栈,堆栈必须申明为OS_STK类型,并且由连续的内存空间组成。可以静态分配堆栈空间,也可以动态分配堆栈空间。 OSTaskCreatExt也可以用来创建任务,详细介绍请参考《嵌入式实时操作系统UCOSII原理及应用》3.5.2节。 2)  任务删除函数 所谓的任务删除,其实就是把任务置于睡眠状态,并不是把任务代码给删除了。UCOSII提供的任务删除函数原型为:INT8U OSTaskDel(INT8U prio),其中参数prio就是我们要删除的任务的优先级,可见该函数是通过任务优先级来实现任务删除的。 特别注意:任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删除! 3)  请求任务删除函数 前面提到,必须确保被删除任务的资源被释放的前提下才能将其删除,所以我们通过向被删除任务发送删除请求,来实现任务释放自身占用资源后再删除。UCOSII提供的请求删除任务函数原型为:INT8U OSTaskDelReq(INT8U prio),同样还是通过优先级来确定被请求删除任务。 4)  改变任务的优先级函数 UCOSII在建立任务时,会分配给任务一个优先级,但是这个优先级并不是一成不变的,而是可以通过调用UCOSII提供的函数修改。UCOSII提供的任务优先级修改函数原型为:INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio) 5)  任务挂起函数 任务挂起和任务删除有点类似,但是又有区别,任务挂起只是将被挂起任务的就绪标志删除,并做任务挂起记录,并没有将任务控制块任务控制块链表里面删除,也不需要释放其资源,而任务删除则必须先释放被删除任务的资源,并将被删除任务的任务控制块也给删了。被挂起的任务,在恢复(解挂)后可以继续运行。UCOSII提供的任务挂起函数原型为:INT8U OSTaskSuspend(INT8U prio) 6)  任务恢复函数 有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能够重新调度该函数。UCOSII提供的任务恢复函数原型为:INT8U OSTaskResume(INT8U prio) UCOSII与任务相关的函数我们就介绍这么多。最后,我们来看看在STM32上面运行UCOSII的步骤: 1) 移植UCOSII 要想UCOSIISTM32正常运行,当然首先是需要移植UCOSII,这部分我们已经为大家做好了(参考光盘源码,想自己移植的,请参考光盘UCOSII资料)。 这里我们要特别注意一个地方,ALIENTEK提供的SYSTEM文件夹里面的系统函数直接支持UCOSII,只需要在sys.h文件里面将:SYSTEM_SUPPORT_UCOS宏定义改为1,即可通过delay_init函数初始化UCOSII的系统时钟节拍,为UCOSII提供时钟节拍。 2) 编写任务函数并设置其堆栈大小和优先级等参数。 编写任务函数,以便UCOSII调用。 设置函数堆栈大小,这个需要根据函数的需求来设置,如果任务函数的局部变量多,嵌套层数多,那么相应的堆栈就得大一些,如果堆栈设置小了,很可能出现的结果就是CPU进入HardFault,遇到这种情况,你就必须把堆栈设置大一点了。另外,有些地方还需要注意堆栈字节对齐的问题,如果任务运行出现莫名其妙的错误(比如用到sprintf出错),请考虑是不是字节对齐的问题。 设置任务优先级,这个需要大家根据任务的重要性和实时性设置,记住高优先级的任务有优先使用CPU的权利。 3) 初始化UCOSII,并在UCOSII中创建任务 调用OSInit,初始化UCOSII,通过调用OSTaskCreate函数创建我们的任务。 4) 启动UCOSII 调用OSStart,启动UCOSII 通过以上4个步骤,UCOSII就开始在STM32上面运行了,这里还需要注意我们必须对os_cfg.h进行部分配置,以满足我们自己的需要。 58.2 硬件设计 本节实验功能简介:本章我们在UCOSII里面创建3个任务:开始任务、LED0任务和LED1任务,开始任务用于创建其他(LED0LED1)任务,之后挂起;LED0任务用于控制DS0的亮灭,DS0每秒钟亮80msLED1任务用于控制DS1的亮灭,DS1300ms,灭300ms,依次循环。 所要用到的硬件资源如下: 1)  指示灯DS0 DS1    58.3 软件设计 本章,我们在第六章实验 (实验1 )的基础上修改,在该工程源码下面加入UCOSII文件夹,存放UCOSII源码(我们已经将UCOSII源码分为三个文件夹:COREPORTCONFIG)。 打开工程,新建UCOSII-COREUCOSII-PORTUCOSII-CONFIG三个分组,分别添加UCOSII三个文件夹下的源码,并将这三个文件夹加入头文件包含路径,最后得到工程如图58.3.1所示:  58.3.1 添加UCOSII源码后的工程 UCOSII-CORE分组下面是UCOSII的核心源码,我们不需要做任何变动。 UCOSII-PORT分组下面是我们移植UCOSII要修改的3个代码,这个在移植的时候完成。 UCOSII-CONFIG分组下面是UCOSII的配置部分,主要由用户根据自己的需要对UCOSII进行裁剪或其他设置。 本章,我们对os_cfg.h里面定义OS_TICKS_PER_SEC的值为200,也就是设置UCOSII的时钟节拍为5ms,同时设置OS_MAX_TASKS10,也就是最多10个任务(包括空闲任务和统计任务在内),其他配置我们就不详细介绍了,请参考本实验源码。 前面提到,我们需要在sys.h里面设置SYSTEM_SUPPORT_UCOS1,以支持UCOSII,通过这个设置,我们不仅可以实现利用delay_init来初始化SYSTICK,产生UCOSII的系统时钟节拍,还可以让delay_usdelay_ms函数在UCOSII下能够正常使用(实现原理请参考5.1节),这使得我们之前的代码,可以十分方便的移植到UCOSII下。虽然UCOSII也提供了延时函数:OSTimeDlyOSTimeDLyHMSM,但是这两个函数的最少延时单位只能是1UCOSII时钟节拍,在本章,即5ms,显然不能实现us级的延时,而us级的延时在很多时候非常有用:比如IIC模拟时序,DS18B20等单总线器件操作等。而通过我们提供的delay_usdelay_ms,则可以方便的提供usms的延时服务,这比UCOSII本身提供的延时函数更好用。 在设置SYSTEM_SUPPORT_UCOS1之后,UCOSII的时钟节拍由SYSTICK的中断服务函数提供,该部分代码如下: //systick中断服务函数,使用ucos时用到 void SysTick_Handler(void) {                                    OSIntEnter();               //进入中断     OSTimeTick();       //调用ucos的时钟服务程序                   OSIntExit();           //触发任务切换软中断 } 以上代码,其中OSIntEnter是进入中断服务函数,用来记录中断嵌套层数(OSIntNesting增加1);OSTimeTick是系统时钟节拍服务函数,在每个时钟节拍了解每个任务的延时状态,使已经到达延时时限的非挂起任务进入就绪状态;OSIntExit是退出中断服务函数,该函数可能触发一次任务切换(当OSIntNesting==0&&调度器未上锁&&就绪表最高优先级任务!=被中断的任务优先级时),否则继续返回原来的任务执行代码(如果OSIntNesting不为0,则减1)。 事实上,任何中断服务函数,我们都应该加上OSIntEnterOSIntExit函数,这是因为UCOSII是一个可剥夺型的内核,中断服务子程序运行之后,系统会根据情况进行一次任务调度去运行优先级别最高的就绪任务,而并不一定接着运行被中断的任务! 最后,我们打开test.c,输入如下代码: /////////////////////////UCOSII任务设置/////////////////////////////////// //START 任务 #define START_TASK_PRIO                        10          //设置任务优先级 #define START_STK_SIZE                             64           //设置任务堆栈大小 OS_STK START_TASK_STK[START_STK_SIZE];         //任务堆栈     void start_task(void *pdata);                                            //任务函数                        //LED0任务 #define LED0_TASK_PRIO                         7            //设置任务优先级 #define LED0_STK_SIZE                            64           //设置任务堆栈大小 OS_STK LED0_TASK_STK[LED0_STK_SIZE];             //任务堆栈     void led0_task(void *pdata);                                                  //任务函数   //LED1任务 #define LED1_TASK_PRIO                         6            //设置任务优先级 #define LED1_STK_SIZE                               64           //设置任务堆栈大小 OS_STK LED1_TASK_STK[LED1_STK_SIZE];             //任务堆栈     void led1_task(void *pdata);                                                  //任务函数 ////////////////////////////////////////////////////////////////////////////// int main(void) {                                  Stm32_Clock_Init(9); //系统时钟设置        delay_init(72);            //延时初始化              LED_Init();                         LED_Init();                  //初始化与LED连接的硬件接口        OSInit();         OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE -1],START_TASK_PRIO );//创建起始任务        OSStart();              }       //开始任务 void start_task(void *pdata) {     OS_CPU_SR cpu_sr=0;        pdata = pdata;       OS_ENTER_CRITICAL();                  //进入临界区(无法被中断打断)          OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1], LED0_TASK_PRIO);                                                OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1], LED1_TASK_PRIO);                                         OSTaskSuspend(START_TASK_PRIO);       //挂起起始任务.        OS_EXIT_CRITICAL();                            //退出临界区(可以被中断打断) } //LED0任务 void led0_task(void *pdata) {                 while(1)        {               LED0=0; delay_ms(80);               LED0=1; delay_ms(920);        }; }   //LED1任务 void led1_task(void *pdata) {              while(1)        {               LED1=0; delay_ms(300);               LED1=1; delay_ms(300);

{if 编辑问题=='编辑问题'}  

第五十八章 UCOSII实验1-任务调度

     前面我们所有的例程都是跑的裸机程序(裸奔),从本章开始,我们将分3个章节向大家介绍UCOSII(实时多任务操作系统内核)的使用。本章,我们将向大家介绍UCOSII最基本也是最重要的应用:任务调度。本章分为如下几个部分: 58.1 UCOSII简介 58.2 硬件设计 58.3 软件设计 58.4 下载验证
58.1 UCOSII简介 UCOSII的前身是UCOS,最早出自于1992 年美国嵌入式系统专家Jean J.Labrosse 在《嵌入式系统编程》杂志的5月和6月刊上刊登的文章连载,并把UCOS 的源码发布在该杂志的BBS 上。目前最新的版本:UCOSIII已经出来,但是现在使用最为广泛的还是UCOSII,本章我们主要针对UCOSII进行介绍。 UCOSII是一个可以基于ROM运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。为了提供最好的移植性能,UCOSII最大程度上使用ANSI C语言进行开发,并且已经移植到近40多种处理器体系上,涵盖了从8位到64位各种CPU(包括DSP) UCOSII是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。用户只要有标准的ANSI C交叉编译器,有汇编器、连接器等软件工具,就可以将UCOSII嵌人到开发的产品中。UCOSII具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB UCOSII已经移植到了几乎所有知名的CPU 上。        UCOSII构思巧妙。结构简洁精练,可读性强,同时又具备了实时操作系统的全部功能,虽然它只是一个内核,但非常适合初次接触嵌入式实时操作系统的朋友,可以说是麻雀虽小,五脏俱全。UCOSIIV2.91版本)体系结构如图58.1.1所示:  58.1.1 UCOSII体系结构图        注意本章我们使用的是UCOSII的最新版本:V2.91版本,该版本UCOSII比早期的UCOSII(如V2.52)多了很多功能(比如多了软件定时器,支持任务数最大达到255个等),而且修正了很多已知BUG。不过,有两个文件:os_dbg_r.cos_dbg.c,我们没有在上图列出,也不将其加入到我们的工程中,这两个主要用于对UCOS内核进行调试支持,比较少用到。        从上图可以看出,UCOSII的移植,我们只需要修改:os_cpu.hos_cpu_a.asmos_cpu.c等三个文件即可,其中:os_cpu.h,进行数据类型的定义,以及处理器相关代码和几个函数原型;os_cpu_a.asm,是移植过程中需要汇编完成的一些函数,主要就是任务切换函数;os_cpu.c,定义一些用户HOOK函数。        图中定时器的作用是为UCOSII提供系统时钟节拍,实现任务切换和任务延时等功能。这个时钟节拍由OS_TICKS_PER_SEC(在os_cfg.h中定义)设置,一般我们设置UCOSII的系统时钟节拍为1ms~100ms,具体根据你所用处理器和使用需要来设置。本章,我们利用STM32SYSTICK定时器来提供UCOSII时钟节拍。 关于UCOSIISTM32的详细移植,请参考光盘资料(《UCOSIISTM32的移植详解.pdf》),这里我们就不详细介绍了。 UCOSII早期版本只支持64个任务,但是从2.80版本开始,支持任务数提高到255个,不过对我们来说一般64个任务都是足够多了,一般很难用到这么多个任务。UCOSII保留了最高4个优先级和最低4个优先级的总共8个任务,用于拓展使用,单实际上,UCOSII一般只占用了最低2个优先级,分别用于空闲任务(倒数第一)和统计任务(倒数第二),所以剩下给我们使用的任务最多可达255-2=253个(V2.91)。 所谓的任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这样的任务(最多255个),UCOSII对这些任务进行调度管理,让这些任务可以并发工作(注意不是同时工作!!,并发只是各任务轮流占用CPU,而不是同时占用,任何时候还是只有1个任务能够占用CPU),这就是UCOSII最基本的功能。 前面我们学习的所有实验,都是一个大任务(死循环),这样,有些事情就比较不好处理,比如:MP3实验,在MP3播放的时候,我们还希望显示歌词,如果是1个死循环(一个任务),那么很可能在显示歌词的时候,MP3声音出现停顿(尤其是高码率的时候),这主要是歌词显示占用太长时间,导致VS1053由于不能及时得到数据而停顿。而如果用UCOSII来处理,那么我们可以分2个任务,MP3播放一个任务(优先级高),歌词显示一个任务(优先级低)。这样,由于MP3任务的优先级高于歌词显示任务,MP3任务可以打断歌词显示任务,从而及时给VS1053提供数据,保证音频不断,而显示歌词又能顺利进行。这就是UCOSII带来的好处。 UCOSII的任何任务都是通过一个叫任务控制块(TCB)的东西来控制的,每个任务管理块有3个最重要的参数:1,任务函数指针;2,任务堆栈指针;3,任务优先级;任务控制块就是任务在系统里面的身份证(UCOSII通过优先级识别任务),任务控制块我们就不再详细介绍了,详细介绍请参考任哲老师的《嵌入式实时操作系统UCOSII原理及应用》一书第二章。 UCOSII中,使用CPU的时候,优先级高(数值小)的任务比优先级低的任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得CPU使用权,只有高优先级的任务让出CPU使用权(比如延时)时,低优先级的任务才能获得CPU使用权。UCOSII不支持多个任务优先级相同,也就是每个任务的优先级必须不一样。 任务的调度其实就是CPU运行环境的切换,即:PC指针、SP指针和寄存器组等内容的存取过程,关于任务调度的详细介绍,请参考《嵌入式实时操作系统UCOSII原理及应用》一书第三章相关内容。 UCOSII的每个任务都是一个死循环。每个任务都处在以下 5种状态之一的状态下,这5种状态是:睡眠状态、 就绪状态、 运行状态、 等待状态(等待某一事件发生)和中断服务状态。 睡眠状态,任务在没有被配备任务控制块或被剥夺了任务控制块时的状态。 就绪状态,系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,任务已经准备好了,但由于该任务的优先级比正在运行的任务的优先级低, 还暂时不能运行,这时任务的状态叫做就绪状态。   运行状态,该任务获得CPU使用权,并正在运行中,此时的任务状态叫做运行状态。 等待状态,正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任务就会把CPU的使用权让给别的任务而使任务进入等待状态。 中断服务状态,一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程序,这时任务的状态叫做中断服务状态。 UCOSII任务的5个状态转换关系如图58.1.2所示:

58.1.2 UCOSII任务状态转换关系 接下来,我们看看在UCOSII中,与任务相关的几个函数: 1)  建立任务函数 如果想让UCOSII管理用户的任务,必须先建立任务。UCOSII提供了我们2个建立任务的函数:OSTaskCreatOSTaskCreatExt,我们一般用OSTaskCreat函数来创建任务,该函数原型为:OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)。该函数包括4个参数:task:是指向任务代码的指针;pdata:是任务开始执行时,传递给任务的参数的指针;ptos:是分配给任务的堆栈的栈顶指针;prio是分配给任务的优先级。 每个任务都有自己的堆栈,堆栈必须申明为OS_STK类型,并且由连续的内存空间组成。可以静态分配堆栈空间,也可以动态分配堆栈空间。 OSTaskCreatExt也可以用来创建任务,详细介绍请参考《嵌入式实时操作系统UCOSII原理及应用》3.5.2节。 2)  任务删除函数 所谓的任务删除,其实就是把任务置于睡眠状态,并不是把任务代码给删除了。UCOSII提供的任务删除函数原型为:INT8U OSTaskDel(INT8U prio),其中参数prio就是我们要删除的任务的优先级,可见该函数是通过任务优先级来实现任务删除的。 特别注意:任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删除! 3)  请求任务删除函数 前面提到,必须确保被删除任务的资源被释放的前提下才能将其删除,所以我们通过向被删除任务发送删除请求,来实现任务释放自身占用资源后再删除。UCOSII提供的请求删除任务函数原型为:INT8U OSTaskDelReq(INT8U prio),同样还是通过优先级来确定被请求删除任务。 4)  改变任务的优先级函数 UCOSII在建立任务时,会分配给任务一个优先级,但是这个优先级并不是一成不变的,而是可以通过调用UCOSII提供的函数修改。UCOSII提供的任务优先级修改函数原型为:INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio) 5)  任务挂起函数 任务挂起和任务删除有点类似,但是又有区别,任务挂起只是将被挂起任务的就绪标志删除,并做任务挂起记录,并没有将任务控制块任务控制块链表里面删除,也不需要释放其资源,而任务删除则必须先释放被删除任务的资源,并将被删除任务的任务控制块也给删了。被挂起的任务,在恢复(解挂)后可以继续运行。UCOSII提供的任务挂起函数原型为:INT8U OSTaskSuspend(INT8U prio) 6)  任务恢复函数 有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能够重新调度该函数。UCOSII提供的任务恢复函数原型为:INT8U OSTaskResume(INT8U prio) UCOSII与任务相关的函数我们就介绍这么多。最后,我们来看看在STM32上面运行UCOSII的步骤: 1) 移植UCOSII 要想UCOSIISTM32正常运行,当然首先是需要移植UCOSII,这部分我们已经为大家做好了(参考光盘源码,想自己移植的,请参考光盘UCOSII资料)。 这里我们要特别注意一个地方,ALIENTEK提供的SYSTEM文件夹里面的系统函数直接支持UCOSII,只需要在sys.h文件里面将:SYSTEM_SUPPORT_UCOS宏定义改为1,即可通过delay_init函数初始化UCOSII的系统时钟节拍,为UCOSII提供时钟节拍。 2) 编写任务函数并设置其堆栈大小和优先级等参数。 编写任务函数,以便UCOSII调用。 设置函数堆栈大小,这个需要根据函数的需求来设置,如果任务函数的局部变量多,嵌套层数多,那么相应的堆栈就得大一些,如果堆栈设置小了,很可能出现的结果就是CPU进入HardFault,遇到这种情况,你就必须把堆栈设置大一点了。另外,有些地方还需要注意堆栈字节对齐的问题,如果任务运行出现莫名其妙的错误(比如用到sprintf出错),请考虑是不是字节对齐的问题。 设置任务优先级,这个需要大家根据任务的重要性和实时性设置,记住高优先级的任务有优先使用CPU的权利。 3) 初始化UCOSII,并在UCOSII中创建任务 调用OSInit,初始化UCOSII,通过调用OSTaskCreate函数创建我们的任务。 4) 启动UCOSII 调用OSStart,启动UCOSII 通过以上4个步骤,UCOSII就开始在STM32上面运行了,这里还需要注意我们必须对os_cfg.h进行部分配置,以满足我们自己的需要。 58.2 硬件设计 本节实验功能简介:本章我们在UCOSII里面创建3个任务:开始任务、LED0任务和LED1任务,开始任务用于创建其他(LED0LED1)任务,之后挂起;LED0任务用于控制DS0的亮灭,DS0每秒钟亮80msLED1任务用于控制DS1的亮灭,DS1300ms,灭300ms,依次循环。 所要用到的硬件资源如下: 1)  指示灯DS0 DS1    58.3 软件设计 本章,我们在第六章实验 (实验1 )的基础上修改,在该工程源码下面加入UCOSII文件夹,存放UCOSII源码(我们已经将UCOSII源码分为三个文件夹:COREPORTCONFIG)。 打开工程,新建UCOSII-COREUCOSII-PORTUCOSII-CONFIG三个分组,分别添加UCOSII三个文件夹下的源码,并将这三个文件夹加入头文件包含路径,最后得到工程如图58.3.1所示:  58.3.1 添加UCOSII源码后的工程 UCOSII-CORE分组下面是UCOSII的核心源码,我们不需要做任何变动。 UCOSII-PORT分组下面是我们移植UCOSII要修改的3个代码,这个在移植的时候完成。 UCOSII-CONFIG分组下面是UCOSII的配置部分,主要由用户根据自己的需要对UCOSII进行裁剪或其他设置。 本章,我们对os_cfg.h里面定义OS_TICKS_PER_SEC的值为200,也就是设置UCOSII的时钟节拍为5ms,同时设置OS_MAX_TASKS10,也就是最多10个任务(包括空闲任务和统计任务在内),其他配置我们就不详细介绍了,请参考本实验源码。 前面提到,我们需要在sys.h里面设置SYSTEM_SUPPORT_UCOS1,以支持UCOSII,通过这个设置,我们不仅可以实现利用delay_init来初始化SYSTICK,产生UCOSII的系统时钟节拍,还可以让delay_usdelay_ms函数在UCOSII下能够正常使用(实现原理请参考5.1节),这使得我们之前的代码,可以十分方便的移植到UCOSII下。虽然UCOSII也提供了延时函数:OSTimeDlyOSTimeDLyHMSM,但是这两个函数的最少延时单位只能是1UCOSII时钟节拍,在本章,即5ms,显然不能实现us级的延时,而us级的延时在很多时候非常有用:比如IIC模拟时序,DS18B20等单总线器件操作等。而通过我们提供的delay_usdelay_ms,则可以方便的提供usms的延时服务,这比UCOSII本身提供的延时函数更好用。 在设置SYSTEM_SUPPORT_UCOS1之后,UCOSII的时钟节拍由SYSTICK的中断服务函数提供,该部分代码如下: //systick中断服务函数,使用ucos时用到 void SysTick_Handler(void) {                                    OSIntEnter();               //进入中断     OSTimeTick();       //调用ucos的时钟服务程序                   OSIntExit();           //触发任务切换软中断 } 以上代码,其中OSIntEnter是进入中断服务函数,用来记录中断嵌套层数(OSIntNesting增加1);OSTimeTick是系统时钟节拍服务函数,在每个时钟节拍了解每个任务的延时状态,使已经到达延时时限的非挂起任务进入就绪状态;OSIntExit是退出中断服务函数,该函数可能触发一次任务切换(当OSIntNesting==0&&调度器未上锁&&就绪表最高优先级任务!=被中断的任务优先级时),否则继续返回原来的任务执行代码(如果OSIntNesting不为0,则减1)。 事实上,任何中断服务函数,我们都应该加上OSIntEnterOSIntExit函数,这是因为UCOSII是一个可剥夺型的内核,中断服务子程序运行之后,系统会根据情况进行一次任务调度去运行优先级别最高的就绪任务,而并不一定接着运行被中断的任务! 最后,我们打开test.c,输入如下代码: /////////////////////////UCOSII任务设置/////////////////////////////////// //START 任务 #define START_TASK_PRIO                        10          //设置任务优先级 #define START_STK_SIZE                             64           //设置任务堆栈大小 OS_STK START_TASK_STK[START_STK_SIZE];         //任务堆栈     void start_task(void *pdata);                                            //任务函数                        //LED0任务 #define LED0_TASK_PRIO                         7            //设置任务优先级 #define LED0_STK_SIZE                            64           //设置任务堆栈大小 OS_STK LED0_TASK_STK[LED0_STK_SIZE];             //任务堆栈     void led0_task(void *pdata);                                                  //任务函数   //LED1任务 #define LED1_TASK_PRIO                         6            //设置任务优先级 #define LED1_STK_SIZE                               64           //设置任务堆栈大小 OS_STK LED1_TASK_STK[LED1_STK_SIZE];             //任务堆栈     void led1_task(void *pdata);                                                  //任务函数 ////////////////////////////////////////////////////////////////////////////// int main(void) {                                  Stm32_Clock_Init(9); //系统时钟设置        delay_init(72);            //延时初始化              LED_Init();                         LED_Init();                  //初始化与LED连接的硬件接口        OSInit();         OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE -1],START_TASK_PRIO );//创建起始任务        OSStart();              }       //开始任务 void start_task(void *pdata) {     OS_CPU_SR cpu_sr=0;        pdata = pdata;       OS_ENTER_CRITICAL();                  //进入临界区(无法被中断打断)          OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1], LED0_TASK_PRIO);                                                OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1], LED1_TASK_PRIO);                                         OSTaskSuspend(START_TASK_PRIO);       //挂起起始任务.        OS_EXIT_CRITICAL();                            //退出临界区(可以被中断打断) } //LED0任务 void led0_task(void *pdata) {                 while(1)        {               LED0=0; delay_ms(80);               LED0=1; delay_ms(920);        }; }   //LED1任务 void led1_task(void *pdata) {              while(1)        {               LED1=0; delay_ms(300);               LED1=1; delay_ms(300);

39条回答
liklon   
  • 250  
  • 231  实习生
    1楼-- · 2019-10-16 09:16
    有个小问题,在使用OSTaskCreate的时候根据什么去确定堆栈需要设置多大的长度。
    查看更多
    回复【2楼】liklon:
    ---------------------------------
    用OSTaskCreateExt()创建任务可以看到任务消耗的堆栈大小
    查看更多
    回复【楼主位】正点原子:
    ---------------------------------
    代码打开编译后有个错误,
    ..SYSTEMdelaydelay.c(106): error:  #20: identifier "TRUE" is undefined

    请问这是怎么回事?
    查看更多
    回复【4楼】everyday666:
    ---------------------------------
    我用MDK3.80A编译,无误。
    查看更多
    回复【5楼】正点原子:
    ---------------------------------
    《UCos-ii 在STM32 上的移植详解》这个电子书在论坛可以下载么,我的电脑光驱坏了,读不出来或者E给我,liyuanmrwhite@163.com
    谢谢
    查看更多
    回复【6楼】 liyuanmrwhite :
    ---------------------------------
    可以下载,在我们开发板光盘资料里面就有:
    http://www.openedv.com/posts/list/13912.htm
    查看更多
    回望   
  • 62  
  • 53  实习生
    7楼-- · 2019-10-17 11:51
    我怎么没找到第五十七章
    查看更多
    回复【8楼】回望:
    ---------------------------------
    你看的是什么资料?
    查看更多
    Ie-w   
  • 5  
  • 386  实习生
    9楼-- · 2019-10-17 18:28
    回复【4楼】everyday666:
    ---------------------------------
    sys.h  文件:  SYSTEM_SUPPORT_UCOS宏定义 改为1,这是上面说明了的;

          再加一句:typedef enum {FALSE = 0, TRUE = !FALSE} bool;

    TRUE这个变量就有定义了,原子的程序增加了这句,但是没有说明
    查看更多
    Ie-w   
  • 5  
  • 386  实习生
    10楼-- · 2019-10-17 21:59
    回复【5楼】正点原子:
    ---------------------------------
    第一点:
    sys.h  文件:  未说明增加定义 
                                                  typedef enum {FALSE = 0, TRUE = !FALSE} bool;

    第二点:
    未说明 stm32f10x_it.c 文件 注释掉

    //void endSV_Handler(void)
    //{
    //}

    //void SysTick_Handler(void)
    //{
    //}

    这样就不会出现重复定义的了,这个没有说出来, 我这种菜鸟折腾了好一会

    另外,UCOSII-CORE,UCOSII-PORT分组下,不要多添加文件,像我这种菜鸟就折腾了一下:
    原子上文原话:新建UCOSII-CORE、UCOSII-PORT和UCOSII-CONFIG三个分组,分别添加UCOSII三个文件夹下的源码,
    一不注意全部添加了,仔细对照,CORE,PORT文件夹下的文件 在分组中 不要全部添加的
    查看更多
    回复【11楼】Ie-w:
    ---------------------------------
    谢谢指出。
    我们的例程是基于2.0库的,和3.5的库有些区别。
    typedef enum {FALSE = 0, TRUE = !FALSE} bool;
    在stm32f10x_type.h头文件里面有定义了。
    这是该头文件自带的。
    另外,我的例程用的寄存器版本,没有用到库函数,所以不存在stm32f10x_it.c文件。
    如果要用库函数,请看我们库函数版本的例程。
    查看更多
    Ie-w   
  • 5  
  • 386  实习生
    12楼-- · 2019-10-18 02:46
    回复【12楼】正点原子:
    ---------------------------------
    我就看的库函数版本
    查看更多
    Ie-w   
  • 5  
  • 386  实习生
    13楼-- · 2019-10-18 07:30
    回复【12楼】正点原子:
    ---------------------------------
    第六章实验 (实验1 )的基础上,把本实验工程下的UCOS文件夹复制加进去的话,是会产生上述问题的
    查看更多
    请问这个实验为什么不用OSTimeDly
    查看更多
    回复【15楼】玛丽玛丽哄:
    ---------------------------------
    呵呵,没看完代码,原来在delay_ms中调用了OSTimeDly
    查看更多
    z1234zz   
  • 21  
  • 203  实习生
    16楼-- · 2019-10-18 19:32
    不错  赞一个  好导师呀
    查看更多
    回复【16楼】玛丽玛丽哄:
    ---------------------------------
    你没看清楚吧,#ifdef   OS_CRITICAL_METHOD  //如果OS_CRITICAL_METHOD定义了,说明使用ucosII了.这没定义
    查看更多
    429893437   
  • 188  
  • 226  实习生
    18楼-- · 2019-10-19 01:24
    原子哥,我的开发板是V1.9的   把对应的IO口改完,烧写进去   没放应,这是啥情况呢?
    查看更多
    429893437   
  • 188  
  • 226  实习生
    19楼-- · 2019-10-19 02:39
    回复【12楼】正点原子:
    ---------------------------------
    原子哥,我的开发板是V1.9的   把对应的IO口改完,烧写进去   没放应,这是啥情况呢?
    查看更多
    429893437   
  • 188  
  • 226  实习生
    20楼-- · 2019-10-19 03:55
    回复【21楼】429893437:
    ---------------------------------
    仿真找下问题。
    查看更多
    回复【22楼】正点原子:
    ---------------------------
    可以了,原子哥,每个任务的堆栈空间大小是怎么定义出来的?
    查看更多
    429893437   
  • 188  
  • 226  实习生
    22楼-- · 2019-10-19 13:58
    回复【23楼】429893437:
    ---------------------------------
    根据任务里面局部变量的多少,自己去权衡。
    一般最少我都设置64。
    然后如果局部变量多,就设置更大
    查看更多
    原子哥,我发现你发的这个寄存器版的函数中,us延时函数中是关闭ucos调度了的,可是在光盘中的库函数版本却是没有关闭ucos调度,是否是由于粗心导致,还是有其他内因??
    另外,这里us级别延时函数,关闭ucos调度,我是否可以理解为:比如延时个900us,刚好要调度时候开始延时,那如果关闭调度了,那不这次本该有的调度,却没进行调度,也就是少了一次调度的机会。也就是为了得到精确延时,关闭ucos调度,牺牲掉一次可能的调度机会,是这样吗?
    另外关闭调度后,OSTimeDly延时函数就不准了,这点也要注意,也就是关闭调度,延时最大误差是一个时间片,理解对吗?
    查看更多
    newcomsky   
  • 228  
  • 252  实习生
    24楼-- · 2019-10-19 21:54
    回复【25楼】newcomsky:
    ---------------------------------
    必须关闭调度,库函数的bug.
    查看更多
    回复【16楼】玛丽玛丽哄:
    ---------------------------------
    看了回复找到OSTimeDly了,菜鸟折腾半天不知道怎么挂起的。。
    查看更多