参考教程:【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、时间片调度回顾
时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍到的时候切换任务;同等优先级任务轮流地享有相同的CPU时间(可设置),叫时间片,在FreeRTOS中,一个时间片就等于SysTick(滴答定时器)中断周期。
创建三个任务——Task1、Task2、Task3,Task1、Task2、Task3的优先级均为1
运行过程如下:
首先Task1运行完一个时间片后,切换至Task2运行
Task2运行完一个时间片后,切换至Task3运行
Task3运行过程中自我阻塞了(如果还不到一个时间片,没有用完的时间片不会再使用),此时直接切换到下一个任务Task1运行
Task1运行完一个时间片后,切换至Task2运行
二、时间片调度实验
1、原理图与实验目标
(1)原理图(未画出OLED屏,接法与stm32教程中的一致):
(2)实验目标:
①将滴答定时器的中断频率设置为50ms中断一次(即一个时间片为50ms),并设计3个任务——start_task、task1、task2(同优先级):
[1]start_task:用于创建其它两个任务,然后删除自身。
[2]task1:维护一个计数值,每10ms加1(不考虑溢出),OLED屏显示计数值。
[3]task2:维护一个计数值,每10ms加1(不考虑溢出),OLED屏显示计数值。
②预期实验现象:
[1]程序下载到板子上后,OLED屏上显示两个计数值。
[2]task1维护的计数值在其时间片内自增4次,过程中task2维护的计数值不变。
[3]task2维护的计数值在其时间片内自增4次,过程中task1维护的计数值不变。
2、实验步骤
(1)将“任务创建和删除的动态方法实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)在FreeRTOSConfig.h文件中将宏configTICK_RATE_HZ配置为20,以使滴答定时器50ms中断一次,并将宏configUSE_TIME_SLICING配置为1,以使能时间片调度。
#define configTICK_RATE_HZ ( ( TickType_t ) 20 ) //系统节拍频率
#define configUSE_TIME_SLICING 1 //使能时间片调度
(3)删除FreeRTOS_experiment.c文件中task3任务的相关内容,将task1和task2的任务优先级均设置为2,并修改task1和task2的实现,各自维护的计数值在死循环内每10ms自增一次。
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "Delay.h"
//宏定义
#define START_TASK_STACK_SIZE 128 //start_task任务的堆栈大小
#define START_TASK_PRIO 2 //start_task任务的优先级
#define TASK1_STACK_SIZE 128 //task1任务的堆栈大小
#define TASK1_PRIO 2 //task1任务的优先级
#define TASK2_STACK_SIZE 128 //task2任务的堆栈大小
#define TASK2_PRIO 2 //task2任务的优先级
//任务函数声明
void start_task(void);
void task1(void);
void task2(void);
//任务句柄
TaskHandle_t start_task_handler; //start_task任务的句柄
TaskHandle_t task1_handler; //task1任务的句柄
TaskHandle_t task2_handler; //task2任务的句柄
void FreeRTOS_Test(void)
{
//创建任务start_task
xTaskCreate((TaskFunction_t)start_task, //指向任务函数的指针
"start_task", //任务名字
START_TASK_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
START_TASK_PRIO, //任务优先级
(TaskHandle_t *) &start_task_handler //任务句柄,就是任务的任务控制块
);
//开启任务调度器
vTaskStartScheduler();
}
void start_task(void)
{
//进入临界区(临界区保护,就是保护那些不想被打断的程序段)
taskENTER_CRITICAL();
//创建任务task1
xTaskCreate((TaskFunction_t)task1, //指向任务函数的指针
"task1", //任务名字
TASK1_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK1_PRIO, //任务优先级
(TaskHandle_t *) &task1_handler //任务句柄,就是任务的任务控制块
);
//创建任务task2
xTaskCreate((TaskFunction_t)task2, //指向任务函数的指针
"task2", //任务名字
TASK2_STACK_SIZE, //任务堆栈大小,单位为字
NULL, //传递给任务函数的参数
TASK2_PRIO, //任务优先级
(TaskHandle_t *) &task2_handler //任务句柄,就是任务的任务控制块
);
//删除任务自身
vTaskDelete(NULL);
//退出临界区
taskEXIT_CRITICAL();
}
u8 task1_num = 0; //task1维护的计数值
void task1(void)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区(防止下两条语句执行过程中产生任务切换)
task1_num++;
OLED_ShowNum(1,12,task1_num,3); //显示计数值
taskEXIT_CRITICAL(); //退出临界区
Delay_ms(10); //延时10ms(不能用vTaskDelay,否则实验现象体现不出来)
}
}
u8 task2_num = 0; //task2维护的计数值
void task2(void)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区(防止下两条语句执行过程中产生任务切换)
task2_num++;
OLED_ShowNum(2,12,task2_num,3); //显示计数值
taskEXIT_CRITICAL(); //退出临界区
Delay_ms(10); //延时10ms(不能用vTaskDelay,否则实验现象体现不出来)
}
}
(4)Delay.c文件中的延时函数都使用了SysTick定时器延时,然而滴答定时器的中断频率在该实验中做了修改,于是原来的延时函数便不能用了,需修改为如下所示的实现(纯占用CPU资源,这种延时函数其实是不好的,但就该实验而言,仅仅是为了观察实验现象,不需要考虑太多)。
void Delay_ms(u16 time)
{
u16 i=0;
while(time--)
{
i=12000; //可微调,使延时更加精准
while(i--) ;
}
}
(5)在main.c文件中添加如下代码。
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "FreeRTOS.h"
#include "task.h"
#include "FreeRTOS_experiment.h"
#include "Key.h"
#include "LED.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //Key初始化
LED_Init(); //LED初始化
OLED_ShowString(1,1,"task1_num:");
OLED_ShowString(2,1,"task2_num:");
FreeRTOS_Test();
while (1)
{
}
}
(6)程序完善好后点击“编译”,然后将程序下载到开发板上。
3、程序执行流程
(1)main函数全流程(省略OLED屏显示字符串部分):
①初始化OLED模块、按键模块、LED模块。
②调用FreeRTOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断,接着start_task任务依次创建任务task1、task2,然后删除自身,接着退出临界区,让出CPU资源。
②任务task1、task2同优先级,由于task1先创建,它在就绪队列的首端,于是先执行task1,task1的执行逻辑如下图所示,因为语句集合1中的语句也需要时间运行,经过实际验证,运行四次语句集合1的总耗时是大于10ms的,于是会出现在task1第四次死等时有任务切换的情况。
③task1和task2同优先级,task1享用的时间片结束以后,轮到task2享用下一个时间片,task2的执行逻辑如下图所示,因为语句集合2中的语句也需要时间运行,经过实际验证,运行四次语句集合2的总耗时是大于10ms的,于是会出现在task2第四次死等时有任务切换的情况。
④随着时间的流逝,50ms时间片的结束点一定会出现在语句集合1/2中,但由于集合中进入了临界区,故不会被打断,直至语句集合执行完毕。