重新开始学stm32 FreeRTOS系统(1)——任务

今天我们开始来讲系统的内容,我们学习的这个系统名叫 FreeRTOS,RTOS 全称是 Real Time Operating System,中文名就是实时操作系统。除此之外,还有其它的操作系统比如 UCOS,FreeRTOS,RTX,RT-Thread 等这些都是 RTOS 类操作系统。好了,关于 FreeRTOS 我们不再过多介绍,我们来说说我们为什么要使用系统?我们之前不同系统不是也可以写很多东西吗?系统有什么用?

我们来举个例子,比如我们平时使用电脑在某软件看视频时,同时又在用另一个软件在播放音乐,这时播放视频是一个“任务”,播放音乐又是另一个“任务”,这两个任务同时运行且互不干扰,想要实现多任务同时运行这样的功能,我们就需要使用到系统。这只是一个例子,关于系统还有很多作用,接下来就开始系统的学习。关于 FreeRTOS 系统的移植和配置我们在这里就不讲了,我们直接从任务的基础知识开始。


前后台系统

回想一下我们以前在使用 STM32 单片机裸机(未使用系统)的时候一般都是在主函数里面用while(1)做一个大循环来完成所有的处理,即应用程序是一个无限的循环,循环中调用相应的函数完成所需的处理。有时候我们也需要中断中完成一些处理。相对于多任务系统而言,这个就是单任务系统,也称作前后台系统,中断服务函数作为前台程序,大循环while(1)作为后台程序

 前后台系统的实时性差,前后台系统各个任务(应用程序)都是排队等着轮流执行,不管你这个程序现在有多紧急,没轮到你就只能等着!相当于所有任务(应用程序)的优先级都是一样的。难道前后台系统就没有优点了吗?有的!前后台系统简单,资源消耗也少。但是在稍微大一点的嵌入式应用中前后台系统就明显力不从心了,此时就需要多任务系统出马了。


多任务系统

多任务系统会把一个大问题“分而治之”,把大问题划分成很多个小问题,逐步的把小问题解决掉,大问题也就随之解决了,这些小问题可以单独的作为一个小任务来处理。这些小任务是并发处理的,注意,并不是说同一时刻一起执行很多个任务,而是由于每个任务执行的时间很短,导致看起来像是同一时刻执行了很多个任务一样。多个任务带来了一个新的问题,究竟哪个任务先运行,哪个任务后运行呢?完成这个功能的东西在 RTOS 系统中叫做任务调度器。不同的系统其任务调度器的实现方法也不同,比如 FreeRTOS 是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的

 高优先级的任务可以打断低优先级任务的运行而取得 CPU 的使用权,这样就保证了那些紧急任务的运行。这样我们就可以为那些对实时性要求高的任务设置一个很高的优先级,比如自动驾驶中的障碍物检测任务等。高优先级的任务执行完成以后重新把 CPU 的使用权归还给低优先级的任务,这个就是抢占式多任务系统的基本原理。


任务

任务状态

在使用 RTOS 的时候一个实时应用可以作为一个独立的任务。我们可以根据我们的需求配置我们的每一个任务。FreeRTOS 中的任务一共有四种状态:运行态就绪态阻塞态挂起态

运行态

当一个任务正在运行时,那么就说这个任务处于运行态,处于运行态的任务就是当前正在使用处理器的任务。如果使用的是单核处理器的话那么不管在任何时刻永远都只有一个任务处于运行态。

就绪态 

处于就绪态的任务是那些已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务,
但是处于就绪态的任务还没有运行,因为有一个同优先级或者更高优先级的任务正在运行。

阻塞态

 如果一个任务当前正在等待某个外部事件的话就说它处于阻塞态,比如说如果某个任务调
用了函数 vTaskDelay()的话就会进入阻塞态,直到延时周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临。

挂起态

像阻塞态一样,任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()xTaskResume()

 

 任务优先级

每 个 任 务 都 可 以 分 配 一 个 从 0~(configMAX_PRIORITIES-1) 的优先级configMAX_PRIORITIES 在文件 FreeRTOSConfig.h 中有定义。

优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为 0。

FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。当宏 configUSE_TIME_SLICING 定义为 1 的时候多个任务可以共用一个优先级,数量不限。默认情况下宏 configUSE_TIME_SLICING 在文件 FreeRTOS.h 中已经定义为 1。

任务实现

在使用 FreeRTOS 的过程中,我们要使用函数 xTaskCreate()xTaskCreateStatic()来创建任务,一个是动态创建一个是静态创建,我们正常情况下都是使用动态创建xTaskCreate()。这两个函数的第一个参数 pxTaskCode,就是这个任务的任务函数。什么是任务函数?任务函数就是完成本任务工作的函数。我这个任务要干嘛?要做什么?要完成什么样的功能都是在这个任务函数中实现的。 比如我要做个任务,这个任务要点个流水灯,那么这个流水灯的程序就是任务函数中实现的。FreeRTOS 官方给出的任务函数模板如下:

void vATaskFunction(void *pvParameters)     (1)
{
    for( ; ; )                              (2)
    {
    --任务应用程序--                         (3)
    vTaskDelay();                           (4)
    }
    /*不能从任务函数中返回或者退出,从任务函数中返回或退出的话就会调用configASSERT(),前提是你定义了configASSERT()。如果一定要从任务函数中退出的话那一定要调用函数 vTaskDelete(NULL)来删除此任务。*/
    vTaskDelete(NULL);                      (5)
}
  • (1)任务函数本质也是函数,所以肯定有任务名什么的,不过这里我们要注意:任务函数的返回类型一定要为 void 类型,也就是无返回值,而且任务的参数也是 void 指针类型的!任务函数名可以根据实际情况定义。
  • (2)任务的具体执行过程是一个大循环,for(; ; )就代表一个循环,作用和 while(1)一样,笔者习惯用 while(1)。
  • (3)循环里面就是真正的任务代码了,此任务具体要干的活就在这里实现。
  • (4)FreeRTOS 的延时函数,此处不一定要用延时函数,其他只要能让 FreeRTOS 发生任务切换的 API 函数都可以,比如请求信号量、队列等,甚至直接调用任务调度器。只不过最常用的就是 FreeRTOS 的延时函数。
  • (5)任务函数一般不允许跳出循环,如果一定要跳出循环的话在跳出循环以后一定要调用函数 vTaskDelete(NULL)删除此任务。

任务控制块

 FreeRTOS 的每个任务都有一些属性需要存储,FreeRTOS 把这些属性集合到一起用一个结
构体来表示,这个结构体叫做任务控制块:TCB_t,在使用函数 xTaskCreate()创建任务的时候就
会自动的给每个任务分配一个任务控制块。在老版本的 FreeRTOS 中任务控制块叫做 tskTCB,
新版本重命名为 TCB_t,但是本质上还是 tskTCB,本教程后面提到任务控制块的话均用 TCB_t
表示,此结构体在文件 tasks.c 中有定义,如下:

typedef struct tskTaskControlBlock
{
    volatile StackType_t *pxTopOfStack;            //任务堆栈栈顶
    ListItem_t xStateListItem;                     //状态列表项
    ListItem_t xEventListItem;                     //事件列表项
    UBaseType_t uxPriority;                        //任务优先级
    StackType_t *pxStack;                          //任务堆栈起始地址
    char pcTaskName[ configMAX_TASK_NAME_LEN ];    //任务名字
}tskTCB;

实际上这个结构体的定义里面还要一些MPU的相关配置,那些我们不需要关注,我们需要用到的是上面的这些。

任务堆栈

FreeRTOS 之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场(CPU 寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行。

创建任务的时候需要给任务指定堆栈,如果使用的函数 xTaskCreate()创建任务(动态方法)的话那么任务堆栈就会由函数 xTaskCreate()自动创建。如果使用函数 xTaskCreateStatic()创建任务(静态方法)的话就需要我们自行定义任务堆栈,然后堆栈首地址作为函数的参数 puxStackBuffer 传递给函数,如下:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
                                const char * const pcName,
                                const uint32_t ulStackDepth,
                                void * const pvParameters,
                                UBaseType_t uxPriority,
                                StackType_t * const puxStackBuffer,         (1)
                                StaticTask_t * const pxTaskBuffer )
  • (1)任务堆栈,需要用户定义,然后将堆栈首地址传递给这个参数。
堆栈大小

任务堆栈大小怎么计算? 我们不管是使用函数 xTaskCreate()还是 xTaskCreateStatic()创建任务都需要指定任务堆栈大小。任务堆栈的数据类型为 StackType_t,StackType_t 本质上是 uint32_t,在 portmacro.h 中有定义,如下:

#define portSTACK_TYPE uint32_t

ypedef portSTACK_TYPE StackType_t;

所以 StackType_t 类型的变量为 4 个字节,那么任务的实际堆栈大小就应该是我们所定义的 4 倍


这一期就到这里,我们下期接着学习! 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值