3. 时间片轮询架构

时间片轮询架构 学习卡片

任务定义

在裸机环境下,使用定时器产生固定节拍(Tick),基于时间片轮询调度多个任务,使3个LED分别以200ms、300ms、500ms的周期闪烁。任务调度器不依赖操作系统,通过主循环查询各任务的时间标志实现非抢占式调度。

调度器设计

调度器核心为一个任务控制块数组,每个任务包含:任务函数指针、任务周期(以Tick为单位)、倒计时计数器。定时器中断每1ms触发一次,在中断中递减所有任务的倒计时,当倒计时归零时置位任务就绪标志。主循环扫描就绪标志并执行对应任务函数,执行完毕后重新装载周期值。

定时器中断 每1ms触发一次 中断服务函数 递减所有任务计数器 主循环 扫描就绪标志并执行 任务控制块数组(调度核心) 任务函数 周期(Tick) 计数器 就绪标志 功能 LED1_Task 200 递减至0 置1 翻转LED1(周期200ms) LED2_Task 300 递减至0 置1 翻转LED2(周期300ms) LED3_Task 500 递减至0 置1 翻转LED3(周期500ms)

代码实现

// 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,用示波器测量周期。