µCOS-III从入门到精通 第八章(时间片调度)
参考教程:【正点原子】手把手教你学UCOS-III实时操作系统_哔哩哔哩_bilibili
一、时间片调度简介
1、时间片调度回顾
时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时,同等优先级任务轮流地享有相应的CPU时间(可分别设置),叫时间片,在µC/OS-III中,一个时间片就等于SysTick(滴答定时器)中断周期。
创建三个任务——Task1、Task2、Task3,Task1、Task2、Task3的优先级均设置为1,时间片默认值(或者说时间片的数量)均设置为100
运行过程如下:
首先Task1运行完100个时间片后,切换至Task2运行
Task2运行完100个时间片后,切换至Task3运行
Task3运行过程中被挂起了(如果还不到一个时间片,没有用完的时间片不会再使用),此时直接切换到下一个任务Task1运行
Task1运行完一个时间片后,切换至Task2运行
2、OSSchedRoundRobinCfg函数
(1)该函数用于开启时间片调度,并设置每个任务的时间片的默认值(或者说每个任务的默认时间片数量)。
(2)函数接口定义:
void OSSchedRoundRobinCfg ( CPU_BOOLEAN en,
OS_TICK dflt_time_quanta,
OS_ERR *p_err )
形参 | 描述 |
en | 是否使能时间片调度 OS_TRUE——使能,OS_FALSE——失能 |
dflt_time_quanta | 传入0则设置每个任务的默认时间片数量为µC/OS-III默认的时间片数量,否则采用传入的时间片数量为每个任务的默认时间片数量(创建任务时如果设置任务的时间片数量为0,则该任务的时间片数量为OSSchedRoundRobinCfg函数设置的默认值) |
p_err | 指向接收错误代码变量的指针 |
(3)使用时间片调度需把宏OS_CFG_SCHED_ROUND_ROBIN_EN置1(在os_cfg.h文件中)。
二、时间片调度实验
1、原理图与实验目标
(1)原理图(未画出OLED屏,接法与stm32教程中的一致):
(2)实验目标:
①设计3个任务——start_task、task1、task2(同优先级):
[1]start_task:用于开启时间片调度,设置每个任务的时间片的默认数量为10个(即10ms),并创建其它的2个任务。
[2]task1:设置时间片数量为10个(即10ms),并维护一个计数值,不断自增(不考虑溢出),OLED屏显示计数值。
[3]task2:设置时间片数量为5个(即5ms),并维护一个计数值,不断自增(不考虑溢出),OLED屏显示计数值。
②预期实验现象:
[1]程序下载到板子上后,OLED屏上显示两个计数值。
[2]task1维护的计数值和task2维护的计数值交替自增,task1计数值自增速度约等于task2计数值自增速度的两倍。
2、实验步骤
(1)将“任务创建和删除实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)Delay.c文件中的延时函数需修改为如下所示的实现(纯占用CPU资源,这种延时函数其实是不好的,但就该实验而言,仅仅是为了观察实验现象,不需要考虑太多)。
void Delay_ms(u16 time)
{
u16 i=0;
while(time--)
{
i=12000; //可微调,使延时更加精准
while(i--) ;
}
}
(3)修改UCOS_experiment.c文件:
①删除task3任务的相关内容。
②在start_task中调用OSSchedRoundRobinCfg函数开启时间片调度,并设置每个任务的时间片的默认值为10个,同时创建task1时设置其时间片数量为默认值,创建task2时设置其时间片数量为5个。
③将task1和task2的任务优先级均设置为2,并修改task1和task2的实现,各自维护的计数值在死循环内每20ms自增一次。
#include "stm32f10x.h" // Device header
#include "os.h"
#include "cpu.h"
#include "OLED.h"
#include "Delay.h"
#include <stdio.h>
/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIO 2
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
OS_TCB start_task_tcb;
void start_task(void);
/* TASK1 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 256
CPU_STK task1_stack[TASK1_STACK_SIZE];
OS_TCB task1_tcb;
void task1(void);
/* TASK2 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 256
CPU_STK task2_stack[TASK2_STACK_SIZE];
OS_TCB task2_tcb;
void task2(void);
void UCOS_Test(void)
{
OS_ERR err;
OSInit(&err); //初始化μC/OS-III
//创建Start Task
OSTaskCreate (&start_task_tcb,
"start_task",
(OS_TASK_PTR)start_task,
NULL,
START_TASK_PRIO,
start_task_stack,
START_TASK_STACK_SIZE / 10,
START_TASK_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
/* 开始任务调度 */
OSStart(&err);
}
void start_task(void)
{
OS_ERR err; //接收错误代码使用,对错误代码进行分情况处理可增强程序鲁棒性
CPU_Init(); //初始化CPU库
OSSchedRoundRobinCfg(OS_TRUE, 10, &err); //使能时间片调度并设置默认时间片数量为10(即10ms)
CPU_SR_ALLOC();
//滴答定时器重装载值 = 系统主频 / 滴答定时器中断频率(滴答定时器是递减计数)
CPU_INT32U cnts = SystemCoreClock / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); //配置Systick中断及优先级
CPU_CRITICAL_ENTER(); //进入临界区(关中断)
//创建task1
OSTaskCreate (&task1_tcb,
"task1",
(OS_TASK_PTR)task1,
0,
TASK1_PRIO,
task1_stack,
TASK1_STACK_SIZE / 10,
TASK1_STACK_SIZE,
0,
0, //task1时间片数量设置为默认时间片数量
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
//创建task2
OSTaskCreate (&task2_tcb,
"task2",
(OS_TASK_PTR)task2,
0,
TASK2_PRIO,
task2_stack,
TASK2_STACK_SIZE / 10,
TASK2_STACK_SIZE,
0,
5, //task2时间片数量设置为5个
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
CPU_CRITICAL_EXIT(); //退出临界区(开中断)
OSTaskDel(NULL, &err); //删除任务自身
}
u8 task1_num = 0; //task1维护的计数值
void task1(void)
{
CPU_SR_ALLOC();
while(1) //task1永远不停歇(除非被其它任务删除)
{
CPU_CRITICAL_ENTER(); //进入临界区(防止在下两条语句产生任务切换)
task1_num++;
OLED_ShowNum(1,12,task1_num,3); //显示计数值
CPU_CRITICAL_EXIT(); //退出临界区(开中断)
Delay_ms(20); //20ms死延时
}
}
u8 task2_num = 0; //task2维护的计数值
void task2(void)
{
CPU_SR_ALLOC();
while(1) //task2永远不停歇(除非被其它任务删除)
{
CPU_CRITICAL_ENTER(); //进入临界区(防止在下两条语句产生任务切换)
task2_num++;
OLED_ShowNum(2,12,task2_num,3); //显示计数值
CPU_CRITICAL_EXIT(); //退出临界区(开中断)
Delay_ms(20); //20ms死延时
}
}
(4)在main.c文件中添加如下代码。
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "UCOS_experiment.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
OLED_ShowString(1,1,"task1_num:");
OLED_ShowString(2,1,"task2_num:");
UCOS_Test();
while (1)
{
}
}
(5)程序完善好后点击“编译”,然后将程序下载到开发板上。
3、程序执行流程
(1)main函数全流程(省略OLED屏显示字符串部分):
①初始化OLED模块。
②调用UCOS_Test函数。
(2)测试函数全流程:
①创建任务start_task。
②开启任务调度器。
(3)多任务调度执行阶段(发生在开启任务调度器以后):
①start_task任务函数首先进入临界区,在临界区中start_task任务不会被其它任务打断,接着start_task任务依次创建任务task1、task2,然后删除自身,接着退出临界区,让出CPU资源。
②任务task1、task2同优先级,由于task1先创建,它在就绪队列的首端,于是先执行task1,task1的执行逻辑如下图所示(透明蓝色块代表任务不在运行态),方便起见,认为语句集合1中的语句的运行时间为5ms,则语句集合1的执行周期约为35ms。
③task1和task2同优先级,task1享用的时间片结束以后,轮到task2享用下一个时间片,task2的执行逻辑如下图所示(透明蓝色块代表任务不在运行态),方便起见,认为语句集合2中的语句的运行时间为5ms,则语句集合2的执行周期约为75ms。