FreeRTOS — 任务管理

以下内容转载自安富莱电子:http://forum.armfly.com/forum.php

了解任务管理的目的就是让初学者从裸机的,单任务编程过渡到带 OS 的,多任务编程上来。搞清楚了这一点,那么 FreeRTOS 学习就算入门了。

 1 单 任 务 系统

  学习多任务系统之前,我们先来回顾下单任务系统的编程框架,即裸机时的编程框架。裸机编程主要是采用超级循环(super-loops)系统,又称前后台系统。应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看做后台行为;中断服务程序处理异步事件,这部分可以看做是前台行为。后台也可以叫做任务级前台也叫作中断级。

对于前后台系统的编程思路主要有以下两种方式:

1.1 查询方式

  对于一些简单的应用,处理器可以查询数据或者消息是否就绪,就绪后进行处理,然后再等待,如此循环下去。对于简单的任务,这种方式简单易处理。但大多数情况下,需要处理多个接口数据或者消息,那就需要多次处理,如下面的流程图所示:

用查询方式处理简单的应用,效果比较好,但是随着工程的复杂,采用查询方式实现的工程就变得很难维护,同时,由于无法定义查询任务的优先级,这种查询方式会使得重要的接口消息得不到及时响应。比如程序一直在等待一个非紧急消息就绪,如果这个消息后面还有一个紧急的消息需要处理,那么就会使得紧急消息长时间得不到执行。

1.2 中断方式

  对于查询方式无法有效执行紧急任务的情况,采用中断方式就有效地解决了这个问题,下面是中断方式简单的流程图:

 

采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了:

2 多 任 务 系统

  针对这些情况,使用多任务系统就可以解决这些问题了。下面是一个多任务系统的流程图:

多任务系统或者说 RTOS 的实现,重点就在这个调度器上,而调度器的作用就是使用相关的调度算法来决定当前需要执行的任务。如上图所示的那样,创建了任务并完成 OS 初始化后,就可以通过调度器来决定任务 A,任务 B 和任务 C 的运行,从而实现多任务系统。另外需要初学者注意的是,这里所说的多任务系统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。为了更好的说明这个问题,再举一个详细的运行例子,运行条件如下:

 

下图 10.2 所示是任务的运行过程,其中横坐标是任务优先级由低到高排列,纵坐标是运行时间,时间刻度有小到大。

通过上面实例的讲解,大家应该对多任务系统完整的运行过程有了一个全面的认识。随着教程后面对调度器,任务切换等知识点的讲解,大家会对这个运行过程有更深刻的理解。

FreeRTOS 就是一款支持多任务运行的实时操作系统,具有时间片,抢占式和合作式三种调度方法。通过 FreeRTOS 实时操作系统可以将程序函数分成独立的任务,并为其提供合理的调度方式。

 3 .FreeRTOS的 任 务 栈 设置

  不管是裸机编程还是 RTOS 编程,栈的分配大小都非常重要。局部变量,函数调用时的现场保护和返回地址,函数的形参,进入中断函数前和中断嵌套等都需要栈空间,栈空间定义小了会造成系统崩溃。

裸机的情况下,可以在这里配置栈大小:

STM32F429 工程中栈大小的配置文件

 

  不同于裸机编程,在 RTOS 下,每个任务都有自己的栈空间。对于 FreeRTOS 来说,任务栈空间是在任务创建的时候从 FreeRTOSConfig.h 文件中定义的 heap 空间中申请的

#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) )    /* 堆空间大小,内核在创建各种对象时需要用到,单位为字节 */

具体每个任务的栈大小是在创建 FreeRTOS 的任务时进行设置的:

 4.FreeRTOS 的 系 统 栈 设置

上面讲解了什么是任务栈,这里的系统栈又是什么呢?裸机的情况下,凡是用到栈空间的地方都是在这里配置的栈空间:

  在 RTOS 下,上面两个截图中设置的栈大小有了一个新的名字叫系统栈空间而任务栈是不使用这里的空间的。任务栈不使用这里的栈空间,哪里使用这里的栈空间呢?答案就在中断函数和中断嵌套

  对于这个问题,简单的描述如下,

    * *  由于 Cortex-M3 和 M4 内核具有双堆栈指针,MSP 主堆栈指针和 PSP 进程堆栈指针,或者叫 PSP任务堆栈指针也是可以的。在 FreeRTOS 操作系统中,主堆栈指针 MSP 是给系统栈空间使用的,进程堆栈指针 PSP 是给任务栈使用的。也就是说,在 FreeRTOS 任务中,所有栈空间的使用都是通过PSP 指针进行指向的。一旦进入了中断函数以及可能发生的中断嵌套都是用的 MSP 指针。这个知识点要记住它,当前可以不知道这是为什么,但是一定要记住。

  * *  实际应用中系统栈空间分配多大,主要是看可能发生的中断嵌套层数,下面我们就按照最坏执行情况进行考虑,所有的寄存器都需要入栈,此时分为两种情况:

 5、FreeRTOS的 任 务 状  态

下面是任务在各个状态之间切换的关系图,通过这个图,基本可以对任务的运行状态有了一个整体的认识。在后面章节的学习中会对各个状态有一个深入的认识。

6 、FreeRTOS 启  动 

  使用如下函数即可启动 FreeRTOS:vTaskStartScheduler();

关于这个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册:

 

7 、FreeRTOS 的 任务 创建

使用如下函数可以实现 FreeRTOS 的任务创建:xTaskCreate()

 

8、FreeRTOS 的 任 务 删除 

使用如下函数可以实现 FreeRTOS 的任务删除:vTaskDelete()

 

9、FreeRTOS  的 任 务 挂 起

使用如下函数可以实现 FreeRTOS 的任务挂起:xTaskSuspend()

10、FreeRTOS 的 任务 恢复 

使用如下函数可以实现 FreeRTOS 的任务恢复:xTaskResume()

 

11 、FreeRTOS  的 任 务 恢 复(中 断 方 式) 

使用如下函数可以实现 FreeRTOS 的任务恢复(中断方式):xTaskResumeFromISR()

12 、FreeRTOS 的 空 闲 任 务

  几乎所有的小型 RTOS 中都会有一个空闲任务,空闲任务属于系统任务,是必须要执行的,用户程序不能将其关闭。不光小型系统中有空闲任务,大型的系统里面也有的,比如 WIN7,下面的截图就是WIN7 中的空闲进程。

 

空闲任务主要有以下几个作用:

  用户不能让系统一直在执行各个应用任务,这样的话系统利用率就是 100%,系统就会一直超负荷运行,所以空闲任务很有必要。

  为了更好的实现低功耗,空闲任务也很有必要,用户可以在空闲任务中实现睡眠,停机等低功耗措施。

FreeRTOS 任务调试信息(按 K1 按键,串口打印):

 

 

按下K2后LED任务被删除:

K2的中断函数:

 

void KEY2_IRQHandler(void)
{
     //确保是否产生了EXTI Line中断
    if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 
    {
        ucKeyCode = 2;
        
        EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);     
    }  
}

vTaskTaskUserIF()函数实现。

static void vTaskTaskUserIF(void *pvParameters)
{
    uint8_t pcWriteBuffer[500];
   while(1)
    {    
            if(ucKeyCode != 0)
            {
                switch( ucKeyCode)
                {
                    /* K1键按下 打印任务执行情况 */
                    case 1:
                    printf("=================================================\r\n");
                    printf("任务名      任务状态 优先级   剩余栈 任务序号\r\n");
                    vTaskList((char *)&pcWriteBuffer);
                    printf("%s\r\n", pcWriteBuffer);
                
                    printf("\r\n任务名       运行计数         使用率\r\n");
                    vTaskGetRunTimeStats((char *)&pcWriteBuffer);
                    printf("%s\r\n", pcWriteBuffer);
                    
                    ucKeyCode = 0;
                    break;
                    
                    /* K2键按下 删除任务vTaskLED */
                    case 2:
                        printf("K2键按下,删除任务vTaskLED\r\n");
                    if(xHandleTaskLED1 != NULL)
                    {
                        vTaskDelete(xHandleTaskLED1);
                        xHandleTaskLED1 = NULL;
                    }
                    ucKeyCode = 0;
                    break;
                
                        /* 其他的键值不处理 */
                default:                     
                    break;                    
                }    
                vTaskDelay(20);
            }
        }
}

 

posted @ 2017-07-16 10:49  Liu_Jing  Views(2761)  Comments(0Edit  收藏  举报