3. 时间片轮询架构
任务定义
在裸机环境下,使用定时器产生固定节拍(Tick),基于时间片轮询调度多个任务,使3个LED分别以200ms、300ms、500ms的周期闪烁。任务调度器不依赖操作系统,通过主循环查询各任务的时间标志实现非抢占式调度。
调度器设计
调度器核心为一个任务控制块数组,每个任务包含:任务函数指针、任务周期(以Tick为单位)、倒计时计数器。定时器中断每1ms触发一次,在中断中递减所有任务的倒计时,当倒计时归零时置位任务就绪标志。主循环扫描就绪标志并执行对应任务函数,执行完毕后重新装载周期值。
代码实现
// scheduler.h
#ifndef SCHEDULER_H
#define SCHEDULER_H
#include <stdint.h>
#define MAX_TASKS 3
#define TICK_MS 1 // 系统节拍1ms
typedef struct {
void (*func)(void); // 任务函数指针
uint16_t period; // 任务周期(Tick数)
uint16_t counter; // 倒计时计数器
uint8_t ready; // 就绪标志
} TaskCB;
void Scheduler_Init(void);
void Scheduler_Tick(void); // 由定时器中断调用
void Scheduler_Run(void); // 主循环调用
#endif
// scheduler.c
#include "scheduler.h"
static TaskCB taskTable[MAX_TASKS];
// 外部任务函数声明
extern void LED1_Task(void);
extern void LED2_Task(void);
extern void LED3_Task(void);
void Scheduler_Init(void)
{
taskTable[0].func = LED1_Task;
taskTable[0].period = 200; // 200ms
taskTable[0].counter = 200;
taskTable[0].ready = 0;
taskTable[1].func = LED2_Task;
taskTable[1].period = 300; // 300ms
taskTable[1].counter = 300;
taskTable[1].ready = 0;
taskTable[2].func = LED3_Task;
taskTable[2].period = 500; // 500ms
taskTable[2].counter = 500;
taskTable[2].ready = 0;
}
// 在定时器中断中调用(每1ms一次)
void Scheduler_Tick(void)
{
uint8_t i;
for (i = 0; i < MAX_TASKS; i++) {
if (taskTable[i].counter > 0) {
taskTable[i].counter--;
if (taskTable[i].counter == 0) {
taskTable[i].ready = 1; // 任务就绪
}
}
}
}
// 主循环中调用
void Scheduler_Run(void)
{
uint8_t i;
for (i = 0; i < MAX_TASKS; i++) {
if (taskTable[i].ready) {
taskTable[i].ready = 0;
taskTable[i].counter = taskTable[i].period; // 重新装载
taskTable[i].func(); // 执行任务
}
}
}
// main.c
#include "scheduler.h"
#include <reg52.h> // 以8051为例,实际MCU按需替换
sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
// 任务函数:翻转LED1
void LED1_Task(void)
{
LED1 = ~LED1;
}
// 任务函数:翻转LED2
void LED2_Task(void)
{
LED2 = ~LED2;
}
// 任务函数:翻转LED3
void LED3_Task(void)
{
LED3 = ~LED3;
}
// 定时器0初始化:1ms中断(假设12MHz晶振,模式1)
void Timer0_Init(void)
{
TMOD |= 0x01;
TH0 = 0xFC; // 1ms定时初值
TL0 = 0x18;
ET0 = 1;
EA = 1;
TR0 = 1;
}
void Timer0_ISR(void) interrupt 1
{
TH0 = 0xFC; // 重装初值
TL0 = 0x18;
Scheduler_Tick(); // 通知调度器一个Tick到达
}
void main(void)
{
Scheduler_Init();
Timer0_Init();
while (1) {
Scheduler_Run(); // 非阻塞扫描执行
}
}
落地与验证
工程配置:将scheduler.h、scheduler.c、main.c加入工程。定时器初值需根据实际晶振频率计算,确保中断周期为1ms。LED引脚定义按开发板实际连接修改。
验证方法:编译烧录后,观察3个LED的闪烁周期。用秒表或逻辑分析仪测量:LED1每200ms翻转一次(即亮100ms灭100ms),LED2每300ms翻转一次,LED3每500ms翻转一次。三个LED独立运行互不阻塞。
判定标准:若LED1在10秒内翻转约50次(±2次),LED2约33次(±2次),LED3约20次(±2次),且三个LED同时闪烁时无相互延迟,则调度器功能正确。若需精确验证,可在LED任务中翻转一个测试IO,用示波器测量周期。