FreeRTOS从入门到精通 第九章(时间片调度)

发布于:2025-02-10 ⋅ 阅读:(44) ⋅ 点赞:(0)

参考教程:【正点原子】手把手教你学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中,但由于集合中进入了临界区,故不会被打断,直至语句集合执行完毕。


网站公告

今日签到

点亮在社区的每一天
去签到