9、任务调度
9.1 开启任务调度器
vTaskStartSchedure()
作用:用于启动任务调度器,任务调度器启动后,FreeRTOS便会开始进行任务调度。
该函数内部实现,如下:
1、创建空闲任务
2、如果使能软件定时器,则创建定时器任务
3、关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
4、初始化全局变量,并将任务调度器的运行标志设置为已运行
5、初始化任务运行时间统计功能的时基定时器
6、调用函数 xPortStartScheduler()
9.2 启动第一个任务
prvStartFirstTask(); /*开启第一个任务*/
该函数用于初始化第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断。
1、什么是MSP指针:程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时, MCU 会自动更新 SP 指针,ARM Cortex-M 内核提供了两个栈空间
主堆栈指针(MSP):由OS内核、异常服务例程以及所有需要特权访问的应用程序代码使用。
进程堆栈指针(PSP):用于常规的应用程序代码。
FreeRTOS中,中断使用MSP(主堆栈),中断以外使用PSP(进程堆栈)
vPortSVCHandler(); /*SVC中断服务函数*/
如何启动第一个任务:
假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器值恢复到CPU寄存器
任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边。
ldmia指令:出栈,将从R4开始到R11寄存器内的数据转移到CPU上。
需要注意的是,CortexM3系列不需要对R14进行出栈处理,而M4、M7系列则需要进行出栈处理,因为M4、M7系列支持FPU。
出栈/压栈汇编指令详解:
1、出栈(恢复现场),方向:从下往上(低地址往高地址):假设r0地址为0x04汇编指令示例:
ldmia r0!, {r4-r6} /* 任务栈r0地址由低到高,将r0存储地址里面的内容手动加载到 CPU寄存器r4、r5、r6 */
r0地址(0x04)内容加载到r4,此时地址r0 = r0+4 = 0x08
r0地址(0x08)内容加载到r5,此时地址r0 = r0+4 = 0x0C
r0地址(0x0C)内容加载到r6,此时地址r0 = r0+4 = 0x10
2、压栈(保存现场),方向:从上往下(高地址往低地址):假设r0地址为0x10汇编指令示例:
stmdb r0!, {r4-r6} } /* r0的存储地址由高到低递减,将r4、r5、r6里的内容存储到r0的任务栈里面。 */
地址:r0 = r0-4 = 0x0C,将r6的内容(寄存器值)存放到r0所指向地址(0x0C)
地址:r0 = r0-4 = 0x08,将r5的内容(寄存器值)存放到r0所指向地址(0x08)
地址:r0 = r0-4 = 0x04,将r4的内容(寄存器值)存放到r0所指向地址(0x04)
9.3 任务切换
任务切换的本质:就是CPU寄存器的切换。
由任务A切换到任务B,主要分为两步:
1、暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈---这个过程就是保存现场
2、将任务B的各个寄存器的值(存于任务堆栈中)恢复到CPU寄存器中---这个过程就是恢复现场
整体的过程就被称之为:上下文切换
PendSV中断是如何触发的?
1、滴答定时器中断调用
2、执行FreeRTOS提供的相关API函数:PortYIELD()
本质:通过向中断控制和状态寄存器ICSR的bit28写入1挂起PendSV中断
由上图可以看出,CortexM3内核的自动压栈、出栈使用的是PSP
前导置零指令:
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
所谓的前导置零指令,可以理解为计算一个32位数头部0的个数,通过前导置零指令,可以获得最高优先级。
获取最高优先级任务的任务控制块:
9.4 FreeRTOS时间片调度
针对同等优先级任务
什么是时间片调度?
同等优先级的任务轮流享有CPU时间(可设置-指滴答计时器的时钟周期),即为时间片,在FreeRTOS中,一个时间片就等于Systick中断周期。
假设创建了三个任务:task1、task2、task3,他们的任务优先级相同,当程序执行时:
1、首先task1执行一个时间片,切换至task2运行
2、task2执行一个时间片,切换至task3运行
3、task3在运行的过程中(不到一个时间片)阻塞,此时直接切换到下一个任务task1
4、task1运行完一个时间片后,切换至task2运行
在使用时间片调度前需要把宏configUSE_TIME_SLICING 和 configUSE_PREEMPTION 置1
10、FreeRTOS任务相关API函数
10.1 FreeRTOS任务相关API函数介绍
uxTaskPriorityGet(const TaskHandle_t xTask):该函数能够获取任务的优先级,使用前需将宏INCLUDE_uxTaskPriorityGet 置 1。该函数只有一个形参,即要查找的任务句柄。
vTaskPrioritySet(TaskHandle_t xTask , UBaseType_t uxNewPriority):该函数能够重设任务的优先级,使用前需将宏INCLUDE_vTaskPrioritySet置1。该函数有两个形参,分别为任务句柄以及需要重设的优先级。
uxTaskGetNumberOfTasks(void):该函数能够获取系统中任务的数量,无形参,该函数会自动返回任务数量。
uxTaskGetSystemState(TaskStatus_t * const pxTaskStatusArray, const UBaseType_t uxArraySize, configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime);该函数能够获取所有任务状态信息,使用该函数需将宏 configUSE_TRACE_FACILITY 置 1。
vTaskGetInfo(TaskHandle_t xTask,
TaskStatus_t * pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState);该函数能够获取指定任务的信息
xTaskGetCurrentTaskHandle( void );该函数能够获取当前任务的任务句柄
xTaskGetHandle(const char * pcNameToQuery);该函数能够通过任务名获取任务句柄
uxTaskGetStackHighWaterMark( TaskHandle_t xTask );该函数能够获取任务堆栈历史剩余最小值
eTaskGetState(TaskHandle_t xTask);该函数能够获取任务的当前状态
vTaskList(char * pcWriteBuffer);该函数能够以表格的形式获取系统中任务的信息
vTaskGetRunTimeStats( char * pcWriteBuffer );该函数能够统计任务的运行时间信息。使用此函数需将宏 configGENERATE_RUN_TIME_STAT 、configUSE_STATS_FORMATTING_FUNCTIONS 置1
代码:
freertos_demo.c:
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 精英F103开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
TaskHandle_t Start_Task_Handler;
void start_task( void * pvParameters );
/* Task1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Task1_Handler;
void task1( void * pvParameters );
/* Task2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Task2_Handler;
void task2( void * pvParameters );
/******************************************************************************************************/
/*
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate(( TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIORITY,
(TaskHandle_t * ) &Start_Task_Handler );
vTaskStartScheduler();//开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/*创建任务1*/
xTaskCreate(( TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &Task1_Handler );
/*创建任务2*/
xTaskCreate(( TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &Task2_Handler );
vTaskDelete(NULL);//删除开始任务
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*任务一,实现LED每500ms翻转一次*/
void task1( void * pvParameters )
{
while(1)
{
LED0_TOGGLE();
vTaskDelay(500);
}
}
char Task_Buffer[300];
/*任务二,实现任务状态查询API函数使用*/
void task2( void * pvParameters )
{
UBaseType_t Tasks_Num = 0;
UBaseType_t Priority_Num = 0;
UBaseType_t Tasks_Num2 = 0;
TaskStatus_t * Status_Array = 0;
TaskStatus_t * Status_Array2 = 0;
TaskHandle_t Task_Handler = 0;
UBaseType_t Task_Stack_Min = 0;
eTaskState TaskState = 0;
uint8_t i = 0;
vTaskPrioritySet(Task2_Handler,4);
Priority_Num = uxTaskPriorityGet( NULL );
printf("Task2任务优先级为:%d\r\n",Priority_Num);
Tasks_Num = uxTaskGetNumberOfTasks();
printf("任务数量为:%d\r\n",Tasks_Num);
Status_Array = mymalloc(SRAMIN,(sizeof(TaskStatus_t) * Tasks_Num));
Tasks_Num2 = uxTaskGetSystemState(Status_Array,Tasks_Num,NULL);
printf("任务名\t\t任务优先级\t任务编号\r\n");
for(i = 0;i<Tasks_Num2;i++)
{
printf("%s\t\t%ld\t%ld\r\n",
Status_Array[i].pcTaskName,
Status_Array[i].uxCurrentPriority,
Status_Array[i].xTaskNumber);
}
Status_Array2 = mymalloc(SRAMIN,sizeof(TaskStatus_t));
vTaskGetInfo( Task2_Handler,Status_Array2 ,pdTRUE,eInvalid );
printf("任务名:%s\r\n",Status_Array2->pcTaskName);
printf("任务优先级:%ld\r\n",Status_Array2->uxCurrentPriority);
printf("任务编号:%ld\r\n",Status_Array2->xTaskNumber);
printf("任务状态:%d\r\n",Status_Array2->eCurrentState);
Task_Handler = xTaskGetHandle("task1");
printf("任务句柄:%#x\r\n",(int)Task_Handler);
printf("Task1任务句柄:%#x\r\n",(int)Task1_Handler);
TaskState = eTaskGetState(Task1_Handler);
printf("Task1的任务状态为%d\r\n",TaskState);
vTaskList(Task_Buffer);
printf("%s\r\n",Task_Buffer);
while(1)
{
// Task_Stack_Min = uxTaskGetStackHighWaterMark(Task2_Handler);
// printf("Task2历史剩余最小堆栈为:%ld\r\n",Task_Stack_Min);
vTaskDelay(1000);
}
}
10.2 时间管理-延时函数
FreeRTOS提供了两种延时函数:
1、vTaskDelay();-相对延时,指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束
2、vTaskDelayUntil();-绝对延时,指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务
所谓的相对延时与绝对延时,可以理解为当一个500ms的任务执行时,相对延时是从调用函数vTaskDelay()后才开始计时500ms,而绝对延时则是这一整个任务加起来共执行500ms。
代码:
freertos_demo.c:
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 精英F103开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
TaskHandle_t Start_Task_Handler;
void start_task( void * pvParameters );
/* Task1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Task1_Handler;
void task1( void * pvParameters );
/* Task2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Task2_Handler;
void task2( void * pvParameters );
/******************************************************************************************************/
/*
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate(( TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIORITY,
(TaskHandle_t * ) &Start_Task_Handler );
vTaskStartScheduler();//开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/*创建任务1*/
xTaskCreate(( TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &Task1_Handler );
/*创建任务2*/
xTaskCreate(( TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &Task2_Handler );
vTaskDelete(NULL);//删除开始任务
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*任务一,演示相对延时函数*/
void task1( void * pvParameters )
{
while(1)
{
LED0_TOGGLE();
delay_ms(10);
vTaskDelay(500);
}
}
/*任务二,演示绝对延时函数*/
void task2( void * pvParameters )
{
TickType_t xLastWakeTime;
xLastWakeTime = xTaskGetTickCount();
while(1)
{
LED1_TOGGLE();
delay_ms(10);
vTaskDelayUntil(&xLastWakeTime,500);
}
}