操作系统允许多个任务同时运行,其任务调度器的责任就是:决定某一时刻究竟运行那个任务。
有的OS调度方式:基于时间的任务调度,如Unix。但是,RTOS的任务调度器被设计为可预测的,这是实时系统所必须的,实时就要求os必须对某一个事件做出实时的响应。所以实时系统的任务调度方法是非常重要的,如给任务分配优先级。
实时:硬实时和软实时。
硬实时:规定时间内必须完成,不允许超时。
软实时:没有那么严格。任务
把功能划分为多个任务,每个任务负责实现其中一部分,每个任务都是一个简单的程序,通常是死循环。RTOS核心内容
实时内核:可剥夺型内核,内核负责管理任务,决定运行哪个任务,如某个任务告诉任务调度器表明自己准备好,可以被调度执行。
为什么选择freeRTOS:
- 开源、免费
- 很多半导体厂商的sdk使用freeRTOS
- 文件少,用C和汇编来写,学习简单,可读性极强。
一、FreeRTOS 系统配置和风格
FreeRTOS的系统配置文件为:FreeRTOSConfig.h,可以完成FreeRTOS的裁剪和配置。
1.1 变量风格
- 非stdint类型的变量使用前缀x,比如基本的Type_t和TickType_t类型,这些类型在移植层定义,定义成符合处理器架构的最高效类型;
- size_t类型的变量使用前缀x
- 枚举类型变量使用前缀e
- 指针类型变量在类型基础上附加前缀p
1.2 函数
- 在文件作用域范围的函数前缀为prv
- API函数的前缀为它们的返回类型,当返回为空时,前缀为v
- API函数名字起始部分为该函数所在的文件名。比如vTaskDelete函数定义在tasks.c,并且该函数返回空。
1.3 宏
- INCLUDE_ 开始的宏表示使能FreeRTOS中相应的API函数,FreeRTOS中的裁剪和配置就是通过这种用条件编译的方法来实现的。条件编译的好处就是节省空间,不需要的功能就不用编译,减少占用ROM和RAM大小。
- 宏的名字起始部分为该宏定义所在的文件名的一部分。比如configUSE_PREEMPTION定义在FreeRTOSConfig.h文件中。
1.4 misc
- 注释:注释单行不超过80列,特殊情况除外。不使用C++风格的双斜线(//)注释,使用 /*.. */
- 源码包中,RTOS核心代码位于三个源文件中,分别是tasks.c、queue.c和list.c
1.5 移植FreeRTOS
- 将tasks.c、queue.c和list.c这三个内核代码加入工程,将port.c和heap_1.c这两个与处理器相关代码加入工程。
- FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制。每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪FreeRTOS内核。这个配置文件是针对用户程序的,而非内核,因此配置文件一般放在应用程序目录下,不要放在RTOS内核源码目录下。配置文件具体的参数可以参考:FreeRTOS系列第6篇---FreeRTOS内核配置说明。 如: configUSE_PREEMPTION: 为1时RTOS使用抢占式调度器,为0时RTOS使用协作式调度器(时间片)。
二、 FreeRTOS任务基础知识
FreeRTOS的核心就是任务管理,必须掌握任务的创建、删除、挂起和恢复等操作。
2.1 多任务系统
未使用系统时,都是在main中使用while(1)做一个大循环来完成所有的处理,即应用程序是一个无限的循环,循环中调用相应的函数完成所需的处理。中断服务函数作为前台程序,大循环while(1)作为后台程序。
缺点:实时性差,所有任务都是排队等着轮流执行,紧急的任务不能及时执行,并且任务管理不方便。
多任务系统:把一个大问题(应用)“分而治之”,把大问题划分成很多个小问题(小任务),逐步把小问题解决掉。这些小任务是并发处理的。那么哪个任务先执行呢,可以通过任务调度器来完成。FreeRTOS是一个抢占式的实时多任务系统。
从图中可以看出,中断返回后会进行一次任务调度。永远运行的是就绪态优先级最高的任务。
2.2 FreeRTOS任务与协程
FreeRTOS可以使用任务或协程(Co-Routine),但是任务和协程使用不同的API函数,不能互传数据。
FreeRTOS的一个实时应用可以作为一个独立的任务,每个任务都有自己的运行环境,不依赖于系统中其他的任务。FreeRTOS会重复的开启、关闭每个任务,职责是确保当一个任务开始执行时,上下文环境(寄存器值、堆栈内容)和任务上一次退出时相同。因此,每个任务都有堆栈。
- 任务特性
简单,没有使用限制,支持抢占,支持优先级
每个任务都拥有堆栈导致RAM使用量增大。
如果使用抢占的话,必须仔细考虑重入的问题。
协程:为了资源很少的MCU而做的。如今使用较少了。
2.2.1 任务状态
运行态、就绪态(可以运行了)、阻塞态(等待某个外部事件)、挂起态
运行态:当前运行的
就绪态:当前已经准备好,告诉调度器可以被调度运行了
阻塞态:某个任务等待某个事件,必须等待事件发生才能被调度
挂起态:暂时不运行。
2.2.2 任务优先级
每个任务都可以分配从0~(configMAX_PRIORITIES-1)的优先级,数字越大,优先级越高,数字越低表示任务的优先级越低,0的优先级最低。
空闲任务是启动RTOS调度器时由内核自动创建的任务,这样可以确保至少有一个任务在运行。空闲任务具有最低任务优先级。
空闲任务钩子是一个函数,每一个空闲任务周期被调用一次。 通常,使用这个空闲钩子函数设置CPU进入低功耗模式。
FreeRTOS调度器确保:处于就绪态或运行态的高优先级任务获取处理器使用权,即就绪态的最高优先级的任务才会运行。
2.2.3 任务实现
任务实现:即任务的具体工作内容。
FreeRTOS中,可以使用xTaskCreate()或xTaskCreateStatic()来创建任务,参数是pxTaskCode,就是这个任务的任务函数(就是完成本任务工作的函数)
模板:
void vATaskFunction(void *pvParameters)
{
for(;;){
Specific code to implement the task; //任务应用程序
vTaskDelay();//用来引起任务调度的函数,这最好要有。
}
/* 不能从任务函数中返回或者退出,如果使用则调用configASSERT(),如果一定要从任务函数中退出的话,一定要调用函数 vTaskDelete(NULL);来删除此任务。*/
vTaskDelete(NULL);
}
example:
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0任务函数
void led0_task(void *pvParameters)
{
while(1)
{
LED0=~LED0;
vTaskDelay(500);
}
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
- 任务函数的返回值一定是void类型。
- 任务函数一般不允许跳出循环,如果想要退出,就是一定要调用vTaskDelete(NULL)删除此任务。
2.2.4 任务控制块
描述任务属性的数据结构叫做任务控制块。
FreeRTOS的每个任务都有一些属性需要存储,可以集合到一个结构体来表示。TCB_t。在使用创建任务时自动为任务分配一个任务控制块。
如:
栈顶、列表、任务优先级、任务名字。
PS:很大,如果有条件编译,先不看,没有条件编译的是核心,也是重点理解的。
任务控制块很大,很占内存,一般在创建任务(vTaskCreate())的时候,如果使用的是动态内存申请,那么函数为任务控制块自动调用创建,静态的话需要自己手动填写。
2.2.5 任务堆栈
用来保存任务现场(CPU寄存器的值)。
FreeRTOS能够恢复上一个任务的运行是因为有,任务堆栈。任务调度器切换任务时,将当前任务的现场(CPU寄存器值)保存在此任务堆栈中,等下次任务运行时,用堆栈中保存的值来恢复线程,然后从上次中断的地方开始运行。
创建任务时,需要给任务指定堆栈。xTaskCreate()动态创建时任务堆栈自动创建,xTaskCreateStatic()静态创建任务时,需要程序员自己定义任务堆栈,然后将首地址作为函数的参数(puxStackBuffer)传递给函数。
PS: 要根据任务动态的修改任务堆栈的大小。如任务堆栈分配小了,很容易卡死。