单片机:实现简单的时间片轮询调度(附带源码)
单片机:实现简单的时间片轮询调度
1. 项目背景与目标
在嵌入式系统中,时间片轮询(Round Robin Scheduling)是一种常用的多任务调度算法。通过将可执行的任务按照时间片进行轮流调度,可以使得系统在多个任务之间进行切换,保证每个任务都能够得到足够的处理时间,避免某些任务一直占用CPU导致其他任务无法得到执行。
本项目的目标是基于单片机实现一个简单的时间片轮询调度系统,能够在不同任务之间进行切换,实现一个多任务的基本调度模型。
2. 硬件设计
2.1 硬件组件
- 单片机:如STM32、8051、AVR等,本项目假设使用STM32单片机。
- 外设设备:如LED、蜂鸣器、按键、LCD显示等,用于展示调度结果和测试不同任务的执行。
- 定时器:使用定时器中断机制来触发时间片轮询调度。
2.2 硬件连接
- LED或蜂鸣器:用于显示任务的切换或执行结果。可以通过GPIO连接STM32的不同引脚。
- 按键:模拟任务的输入,可以用来触发任务的执行或调度。
- LCD显示器(可选):用于显示当前执行任务的信息,可以通过I2C或SPI与STM32连接。
3. 软件设计
3.1 时间片轮询调度原理
时间片轮询调度的核心思想是将CPU的时间划分为多个时间片,每个任务被分配一个时间片。系统按照固定的时间片顺序执行任务,每当时间片用完,系统便会切换到下一个任务。简单来说,它通过定时器中断来触发任务切换。
3.2 调度策略
- 任务划分:将任务划分为多个时间片,每个任务在其时间片内执行。
- 任务调度:使用定时器中断来定时触发任务的调度,模拟任务切换。
- 任务执行:每个任务可以是简单的LED闪烁、蜂鸣器响声等,用来测试调度系统的切换能力。
3.3 程序设计思路
- 任务管理:将不同的任务放入一个任务队列,每个任务有一个固定的执行周期(时间片)。在时间片到期后,系统会切换到下一个任务。
- 时间片控制:使用定时器中断来定时触发任务调度,每个任务占用一个时间片。
- 任务调度:当一个任务的时间片用尽后,系统自动调度下一个任务,按照轮询的方式执行任务。
3.4 代码实现
以下是基于STM32单片机实现简单时间片轮询调度的代码示例:
#include "stm32f4xx_hal.h"
// 定义LED引脚
#define LED_PIN GPIO_PIN_5
#define LED_PORT GPIOA
// 定义任务数量
#define MAX_TASKS 3
// 定义每个任务的时间片
#define TIME_SLICE 100 // 每个任务的时间片,单位:毫秒
// 任务控制结构体
typedef struct {
void (*task_func)(void); // 任务函数指针
uint32_t time_slice; // 时间片
} Task_t;
// 任务队列
Task_t task_queue[MAX_TASKS];
// 当前执行的任务索引
uint8_t current_task = 0;
// 定时器中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
// 切换到下一个任务
current_task++;
if (current_task >= MAX_TASKS) {
current_task = 0;
}
}
// 任务1:LED闪烁
void Task1(void) {
HAL_GPIO_TogglePin(LED_PORT, LED_PIN); // 切换LED状态
}
// 任务2:控制蜂鸣器
void Task2(void) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0); // 切换蜂鸣器状态
}
// 任务3:显示当前任务
void Task3(void) {
printf("Executing Task %d\n", current_task); // 输出当前任务
}
// 初始化任务队列
void Task_Init(void) {
task_queue[0].task_func = Task1;
task_queue[0].time_slice = TIME_SLICE;
task_queue[1].task_func = Task2;
task_queue[1].time_slice = TIME_SLICE;
task_queue[2].task_func = Task3;
task_queue[2].time_slice = TIME_SLICE;
}
// 初始化定时器
void Timer_Init(void) {
TIM_HandleTypeDef htim;
__HAL_RCC_TIM2_CLK_ENABLE();
htim.Instance = TIM2;
htim.Init.Prescaler = 8399; // 设定定时器预分频
htim.Init.CounterMode = TIM_COUNTERMODE_UP;
htim.Init.Period = 999; // 定时器周期为100ms
htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim);
HAL_TIM_Base_Start_IT(&htim); // 启动定时器并使能中断
}
// 主程序
int main(void) {
HAL_Init(); // 初始化HAL库
// 初始化LED引脚
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = LED_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct);
// 初始化蜂鸣器引脚(假设连接在GPIOB的PIN0)
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始化任务
Task_Init();
// 初始化定时器
Timer_Init();
while (1) {
// 执行当前任务
task_queue[current_task].task_func();
HAL_Delay(TIME_SLICE); // 每个任务执行完后,等待时间片结束
}
}
3.5 代码解释
-
任务管理:
Task_t
结构体用于管理每个任务的函数指针和时间片。每个任务函数指针指向一个具体的任务函数,time_slice
表示任务的时间片。task_queue[MAX_TASKS]
是任务队列,存储所有任务及其执行信息。-
任务函数:
Task1()
:通过控制LED引脚的状态,模拟一个任务的执行。Task2()
:通过控制蜂鸣器引脚的状态,模拟另一个任务的执行。Task3()
:输出当前任务信息,模拟第三个任务的执行。-
定时器中断:
- 使用STM32的定时器2(TIM2)来创建一个周期性的中断,每100ms触发一次。定时器中断回调函数
HAL_TIM_PeriodElapsedCallback()
负责切换任务,模拟时间片轮询。 -
任务切换:
- 在主循环中,程序会按照当前任务的时间片执行任务。每次执行完一个任务后,等待
TIME_SLICE
的时间(通过HAL_Delay()
实现),然后切换到下一个任务。 -
任务调度:
current_task
记录当前执行的任务索引,每当一个任务执行完一个时间片后,current_task
会增加1,指向下一个任务。如果任务索引超过最大任务数,则从头开始调度。
4. 总结
本项目实现了基于STM32单片机的简单时间片轮询调度系统,通过定时器中断周期性地切换任务,模拟了一个简单的多任务调度。每个任务有一个固定的时间片,任务会轮流执行。系统的时间片可以通过调整TIME_SLICE
的值来改变。虽然这只是一个简单的示范,但为实现更加复杂的调度器奠定了基础。在实际应用中,调度算法可以根据不同的需求进行优化,支持优先级调度、任务挂起、任务切换等功能。
作者:Katie。