目录
0. 前言
硬件平台:
- STM32F103
1. 任务调度策略
什么是任务调度?
- 任务调度是FreeRTOS实时操作系统中确保系统实时性的一种机制。
- FreeRTOS通过任务调度器对任务进行调度与管理,从而确保任务在指定的时间内执行。
FreeRTOS中的任务调度机制:
- 抢占式调度策略:在FreeRTOS中,任务分为不同的优先级,高优先级的任务可以打断低优先级的任务,从而执行高优先级的任务。
- 时间片轮询策略:在FreeRTOS中,如果相同优先级的任务,采用时间片轮询的机制进行调度管理,时间片即FreeRTOS系统分配给任务的执行时间,当执行时间消耗完后,切换下一个任务进行执行。
1.1 抢占式调度示例
示例说明:
- 在起始任务中创建两个任务
- Task1
- Task2
- 任务1的优先级设置为3,每500ms打印一次Task1,这里让高优先级的任务1s后再执行
- 任务2的优先级设置为2,每500ms打印一次Task2
FreeRTOSConfig配置文件:
起始任务相关配置:
/* 起始任务相关配置 */
void startFunc(void *args); /* 起始任务的执行函数 */
#define START_NAME "Start" /* 起始任务的名称 */
#define START_STACK_DEPTH 128 /* 起始任务执行函数的参数 */
#define START_PRIORITY 5 /* 起始任务优先级 */
TaskHandle_t startHandle; /* 起始任务代码控制块结构体:存放起始任务的信息 */
Task1相关配置:
/* Task1相关配置 */
void task1Func(void *args); /* Task1任务的执行函数 */
#define TASK1_NAME "Start" /* Task1任务的名称 */
#define TASK1_STACK_DEPTH 128 /* Task1任务执行函数的参数 */
#define TASK1_PRIORITY 3 /* Task1任务优先级 */
TaskHandle_t task1Handle; /* Task1任务代码控制块结构体:存放起始任务的信息 */
Task2相关配置:
/* Task2相关配置 */
void task2Func(void *args); /* Task2任务的执行函数 */
#define TASK2_NAME "Start" /* Task2任务的名称 */
#define TASK2_STACK_DEPTH 128 /* Task2任务执行函数的参数 */
#define TASK2_PRIORITY 2 /* Task2任务优先级 */
TaskHandle_t task2Handle; /* Task2任务代码控制块结构体:存放起始任务的信息 */
系统启动函数:
- 创建起始任务
- 启动任务调度器
/* 启动操作系统 */
void FreeRTOS_Start(void)
{
/* 1. 创建起始任务 */
xTaskCreate(
startFunc,
START_NAME,
START_STACK_DEPTH,
NULL,
START_PRIORITY,
&startHandle
);
/* 2. 启动任务调度器 */
vTaskStartScheduler();
}
起始任务执行函数:
- 让代码进入临界区,进入临界区确保代码执行的时候不被中断打断
- 创建任务1和任务2
- 代码退出临界区
- 回收起始任务,参数为NULL表示回收任务本身,也可以输入任务的控制块结构体
void startFunc(void *args) /* 起始任务的执行函数 */
{
/* 1. 让代码进入临界区,防止中断被打断 */
vPortEnterCritical();
debug_printfln("起始任务开始执行...");
/* 2. 创建Task1和Task2 */
xTaskCreate( /* Task1 */
task1Func,
TASK1_NAME,
TASK1_STACK_DEPTH,
NULL,
TASK1_PRIORITY,
&task1Handle
);
xTaskCreate( /* Task2 */
task2Func,
TASK2_NAME,
TASK2_STACK_DEPTH,
NULL,
TASK2_PRIORITY,
&task2Handle
);
/* 3. 代码退出临界区 */
debug_printfln("起始任务执行完毕...");
portEXIT_CRITICAL();
/* 4. 回收起始任务 */
vTaskDelete(NULL);
}
任务1与任务2执行函数:
- Delay_ms与vTaskDelay的区别:
- Delay_ms是自己写的延时函数,本质是让cpu计算计数,达到指定时间,占用cpu资源
- vTaskDelay是FreeRTOS提供的延时函数,让任务进入阻塞状态,暂停程序的运行
void task1Func(void *args) /* Task1任务的执行函数 */
{
vTaskDelay(1000); /* 延时1s后再执行 */
while (1)
{
debug_printfln("Task1...");
Delay_ms(500);
}
}
void task2Func(void *args) /* Task2任务的执行函数 */
{
while (1)
{
debug_printfln("Task2...");
Delay_ms(500);
}
}
在main函数中调用系统启动函数:
执行状况:
- 1s之前执行低优先级任务2
- 1s之后执行高优先级任务1
1.2 时间片轮询调度示例
在抢占式调度示例中的代码保持不变,只将对应的任务优先级都改为2
- 同时删除任务1的延时1s
执行状况:
- 每个任务根据时间片进行轮询执行
这里可以看到,代码在答应的时候,时间片轮询时间到了,暂停输出打印另外的任务。
解决方法:
- 将打印函数放入临界区内执行
FreeRTOS_Demo.h
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H
#include "FreeRTOS.h"
#include "task.h"
/* 调式 */
#include "Common_Debug.h"
#include "Common_Delay.h"
/* 1. 启动操作系统 */
void FreeRTOS_Start(void);
#endif
FreeRTOSDemo.c
#include "FreeRTOS_Demo.h"
/* 起始任务相关配置 */
void startFunc(void *args); /* 起始任务的执行函数 */
#define START_NAME "Start" /* 起始任务的名称 */
#define START_STACK_DEPTH 128 /* 起始任务执行函数的参数 */
#define START_PRIORITY 5 /* 起始任务优先级 */
TaskHandle_t startHandle; /* 起始任务代码控制块结构体:存放起始任务的信息 */
/* Task1相关配置 */
void task1Func(void *args); /* Task1任务的执行函数 */
#define TASK1_NAME "Start" /* Task1任务的名称 */
#define TASK1_STACK_DEPTH 128 /* Task1任务执行函数的参数 */
#define TASK1_PRIORITY 2 /* Task1任务优先级 */
TaskHandle_t task1Handle; /* Task1任务代码控制块结构体:存放起始任务的信息 */
/* Task2相关配置 */
void task2Func(void *args); /* Task2任务的执行函数 */
#define TASK2_NAME "Start" /* Task2任务的名称 */
#define TASK2_STACK_DEPTH 128 /* Task2任务执行函数的参数 */
#define TASK2_PRIORITY 2 /* Task2任务优先级 */
TaskHandle_t task2Handle; /* Task2任务代码控制块结构体:存放起始任务的信息 */
/* 启动操作系统 */
void FreeRTOS_Start(void)
{
/* 1. 创建起始任务 */
xTaskCreate(
startFunc,
START_NAME,
START_STACK_DEPTH,
NULL,
START_PRIORITY,
&startHandle
);
/* 2. 启动任务调度器 */
vTaskStartScheduler();
}
void startFunc(void *args) /* 起始任务的执行函数 */
{
/* 1. 让代码进入临界区,防止中断被打断 */
vPortEnterCritical();
debug_printfln("起始任务开始执行...");
/* 2. 创建Task1和Task2 */
xTaskCreate( /* Task1 */
task1Func,
TASK1_NAME,
TASK1_STACK_DEPTH,
NULL,
TASK1_PRIORITY,
&task1Handle
);
xTaskCreate( /* Task2 */
task2Func,
TASK2_NAME,
TASK2_STACK_DEPTH,
NULL,
TASK2_PRIORITY,
&task2Handle
);
/* 3. 代码退出临界区 */
debug_printfln("起始任务执行完毕...");
portEXIT_CRITICAL();
/* 4. 回收起始任务 */
vTaskDelete(NULL);
}
void task1Func(void *args) /* Task1任务的执行函数 */
{
while (1)
{
portENTER_CRITICAL();
debug_printfln("Task1...");
portEXIT_CRITICAL();
Delay_ms(500);
}
}
void task2Func(void *args) /* Task2任务的执行函数 */
{
while (1)
{
portENTER_CRITICAL();
debug_printfln("Task2...");
portEXIT_CRITICAL();
Delay_ms(500);
}
}
extern void xPortSysTickHandler(void);
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}
2. 任务挂起与恢复
什么是任务挂起?
- 任务挂起是FreeRTOS提供的一中让任务进入暂停状态的机制,方便开发者在指定情况下对任务进行挂起以等待其他时间的发生。
2.1 任务挂起与恢复相关API
任务挂起与恢复常用API:
- vTaskSuspend:挂起任务API
- vTaskResume:恢复任务API
- xTaskResumeFromISR:在中断中恢复任务API
2.1.1 挂起任务API - vTaskSuspend
函数说明:
- 注意实现: 宏定义INCLUDE_vTaskSuspend设置为1
- 参数:挂起任务的控制块结构体
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
2.1.2 恢复任务API - vTaskResume
函数说明:
- 注意实现: 宏定义INCLUDE_vTaskSuspend设置为1
- 参数:恢复任务的控制块结构体
void vTaskResume( TaskHandle_t xTaskToResume );
2.1.3 在中断中恢复任务API - xTaskResumeFromISR
函数说明:
- 注意实现: 宏定义INCLUDE_vTaskSuspend与INCLUDE_xTaskResumeFromISR均设置为1
- 参数:恢复任务的控制块结构体
- return:
- pdTRUE:恢复任务同时切换上下文
- pdFALSE:ISR 使用此信息来确定 ISR 之后是否需要上下文切换。
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );
2.2 挂起与恢复示例
示例说明:
- 使用时间片轮询调度策略的代码进行修改
- 添加任务3:
- 对按键进行扫描,KEY1挂起任务1,KEY2恢复任务1
Task3相关配置:
/* Task3相关配置 */
void task3Func(void *args); /* Task3任务的执行函数 */
#define TASK3_NAME "Start" /* Task3任务的名称 */
#define TASK3_STACK_DEPTH 128 /* Task3任务执行函数的参数 */
#define TASK3_PRIORITY 3 /* Task3任务优先级 */
TaskHandle_t task3Handle; /* Task3任务代码控制块结构体:存放起始任务的信息 */
起始任务执行函数:
- 创建任务3
void startFunc(void *args) /* 起始任务的执行函数 */
{
/* 1. 让代码进入临界区,防止中断被打断 */
vPortEnterCritical();
debug_printfln("起始任务开始执行...");
/* 2. 创建Task1和Task2 */
xTaskCreate( /* Task1 */
task1Func,
TASK1_NAME,
TASK1_STACK_DEPTH,
NULL,
TASK1_PRIORITY,
&task1Handle
);
xTaskCreate( /* Task2 */
task2Func,
TASK2_NAME,
TASK2_STACK_DEPTH,
NULL,
TASK2_PRIORITY,
&task2Handle
);
xTaskCreate( /* Task3 */
task3Func,
TASK3_NAME,
TASK3_STACK_DEPTH,
NULL,
TASK3_PRIORITY,
&task3Handle
);
/* 3. 代码退出临界区 */
debug_printfln("起始任务执行完毕...");
portEXIT_CRITICAL();
/* 4. 回收起始任务 */
vTaskDelete(NULL);
}
任务3执行代码;
- 带按键进行扫描
- 每50ms进行扫描一次
void task3Func(void *args) /* Task3任务的执行函数 */
{
uint8_t key;
while (1)
{
key = Inf_Key_Detect();
switch (key)
{
case KEY1_PRESS: /* 按键1被按下 */
debug_printfln("Task1挂起...");
vTaskSuspend(task1Handle); /* 挂起任务 */
break;
case KEY2_PRESS: /* 按键2被按下 */
debug_printfln("Task1恢复...");
vTaskResume(task1Handle); /* 恢复任务 */
break;
}
vTaskDelay(50);
}
}
执行状况:
- 按下key1,任务1挂起,剩下任务2在打印信息
- 按下key2,任务1恢复,恢复任务1执行,继续打印信息
3. 结语
FreeRTOS通过任务调度器对任务进行调度,从而确保系统的实时性,FreeRTOS的调度策略分为抢占式调度和时间片轮询调度,抢占式调度是高优先级打断低优先级任务,执行高优先级任务,时间片轮询为相同任务优先级,每个任务执行相同的时间片。
文章声明:
- 本文章为作者学习FreeRTOS实时操作系统的笔记与巩固输出,欢迎同行交流与指教,文章借鉴来源小破站的某谷RTOS教程。
FreeRTOSDemo.h文件代码
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H
#include "FreeRTOS.h"
#include "task.h"
/* 调式 */
#include "Common_Debug.h"
#include "Common_Delay.h"
/* 按键驱动 */
#include "Inf_Key.h"
/* 1. 启动操作系统 */
void FreeRTOS_Start(void);
#endif
FreeRTOSDemo.c文件代码
#include "FreeRTOS_Demo.h"
/* 起始任务相关配置 */
void startFunc(void *args); /* 起始任务的执行函数 */
#define START_NAME "Start" /* 起始任务的名称 */
#define START_STACK_DEPTH 128 /* 起始任务执行函数的参数 */
#define START_PRIORITY 5 /* 起始任务优先级 */
TaskHandle_t startHandle; /* 起始任务代码控制块结构体:存放起始任务的信息 */
/* Task1相关配置 */
void task1Func(void *args); /* Task1任务的执行函数 */
#define TASK1_NAME "Start" /* Task1任务的名称 */
#define TASK1_STACK_DEPTH 128 /* Task1任务执行函数的参数 */
#define TASK1_PRIORITY 2 /* Task1任务优先级 */
TaskHandle_t task1Handle; /* Task1任务代码控制块结构体:存放起始任务的信息 */
/* Task2相关配置 */
void task2Func(void *args); /* Task2任务的执行函数 */
#define TASK2_NAME "Start" /* Task2任务的名称 */
#define TASK2_STACK_DEPTH 128 /* Task2任务执行函数的参数 */
#define TASK2_PRIORITY 2 /* Task2任务优先级 */
TaskHandle_t task2Handle; /* Task2任务代码控制块结构体:存放起始任务的信息 */
/* Task3相关配置 */
void task3Func(void *args); /* Task3任务的执行函数 */
#define TASK3_NAME "Start" /* Task3任务的名称 */
#define TASK3_STACK_DEPTH 128 /* Task3任务执行函数的参数 */
#define TASK3_PRIORITY 3 /* Task3任务优先级 */
TaskHandle_t task3Handle; /* Task3任务代码控制块结构体:存放起始任务的信息 */
/* 启动操作系统 */
void FreeRTOS_Start(void)
{
/* 1. 创建起始任务 */
xTaskCreate(
startFunc,
START_NAME,
START_STACK_DEPTH,
NULL,
START_PRIORITY,
&startHandle
);
/* 2. 启动任务调度器 */
vTaskStartScheduler();
}
void startFunc(void *args) /* 起始任务的执行函数 */
{
/* 1. 让代码进入临界区,防止中断被打断 */
vPortEnterCritical();
debug_printfln("起始任务开始执行...");
/* 2. 创建Task1和Task2 */
xTaskCreate( /* Task1 */
task1Func,
TASK1_NAME,
TASK1_STACK_DEPTH,
NULL,
TASK1_PRIORITY,
&task1Handle
);
xTaskCreate( /* Task2 */
task2Func,
TASK2_NAME,
TASK2_STACK_DEPTH,
NULL,
TASK2_PRIORITY,
&task2Handle
);
xTaskCreate( /* Task3 */
task3Func,
TASK3_NAME,
TASK3_STACK_DEPTH,
NULL,
TASK3_PRIORITY,
&task3Handle
);
/* 3. 代码退出临界区 */
debug_printfln("起始任务执行完毕...");
portEXIT_CRITICAL();
/* 4. 回收起始任务 */
vTaskDelete(NULL);
}
void task1Func(void *args) /* Task1任务的执行函数 */
{
while (1)
{
portENTER_CRITICAL();
debug_printfln("Task1...");
portEXIT_CRITICAL();
Delay_ms(500);
}
}
void task2Func(void *args) /* Task2任务的执行函数 */
{
while (1)
{
portENTER_CRITICAL();
debug_printfln("Task2...");
portEXIT_CRITICAL();
Delay_ms(500);
}
}
void task3Func(void *args) /* Task3任务的执行函数 */
{
uint8_t key;
while (1)
{
key = Inf_Key_Detect();
switch (key)
{
case KEY1_PRESS: /* 按键1被按下 */
debug_printfln("Task1挂起...");
vTaskSuspend(task1Handle); /* 挂起任务 */
break;
case KEY2_PRESS: /* 按键2被按下 */
debug_printfln("Task1恢复...");
vTaskResume(task1Handle); /* 恢复任务 */
break;
}
vTaskDelay(50);
}
}
extern void xPortSysTickHandler(void);
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
}