一、为什么需要时间片轮询?

在资源受限的STM32应用中,直接引入RTOS可能带来额外RAM/ROM开销与移植复杂度。而传统的顺序执行(super-loop)无法应对“多事件并发”需求——例如同时处理按键扫描、LED刷新、传感器读取和串口发送。时间片轮询(Time-Slice Polling)是一种基于周期调度的裸机方案,它利用SysTick定时器产生稳定时基,将任务划分为多个时间片,在主循环中按序执行。

二、时间片轮询的原理与架构

2.1 核心机制

  • 时基:配置SysTick每1ms产生一次中断,作为系统心跳。
  • 任务表:定义结构体数组,包含任务函数指针、任务周期(ms)、当前计数值、运行标志。
  • 调度流程:每次SysTick中断中,遍历任务表,将计数值减1;当计数值归零时,重置为周期值并置位运行标志。主循环检测标志,执行相应任务,执行后清除标志。

2.2 STM32架构优势

STM32的SysTick是内核级定时器,不占用通用外设定时器,且中断响应快(优先级可配置)。利用其嵌套向量中断控制器(NVIC),可确保时基的精准性,同时主循环保持最低优先级,避免打断任务执行中的关键操作。

三、实现步骤

3.1 配置SysTick时基

使用标准库或HAL库初始化:

void SysTick_Init(uint32_t freq) {
    SysTick_Config(SystemCoreClock / freq); // 例如1kHz -> 1ms
}

3.2 定义任务控制块

#define TASK_MAX 4

typedef struct {
    void (*func)(void);       // 任务函数指针
    uint16_t period;          // 任务周期(ms)
    uint16_t counter;         // 递减计数器
    uint8_t runFlag;          // 运行标志
} TaskCtrl_t;

TaskCtrl_t taskTable[TASK_MAX];

3.3 SysTick中断处理

void SysTick_Handler(void) {
    for (int i = 0; i < TASK_MAX; i++) {
        if (taskTable[i].counter > 0) {
            taskTable[i].counter--;
            if (taskTable[i].counter == 0) {
                taskTable[i].counter = taskTable[i].period;
                taskTable[i].runFlag = 1;
            }
        }
    }
}

3.4 任务注册与主循环

void Task_Init(void) {
    taskTable[0].func = Task_KeyScan;
    taskTable[0].period = 5;    // 5ms
    taskTable[0].counter = 5;
    
    taskTable[1].func = Task_LedRefresh;
    taskTable[1].period = 20;   // 20ms
    taskTable[1].counter = 20;
    
    taskTable[2].func = Task_SensorRead;
    taskTable[2].period = 100;  // 100ms
    taskTable[2].counter = 100;
}

int main(void) {
    SysTick_Init(1000);  // 1ms时基
    Task_Init();
    while (1) {
        for (int i = 0; i < TASK_MAX; i++) {
            if (taskTable[i].runFlag) {
                taskTable[i].runFlag = 0;
                taskTable[i].func();
            }
        }
    }
}

3.5 任务函数定义

void Task_LedRefresh(void) {
    static uint8_t toggle = 0;
    toggle = !toggle;
    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, toggle);
}

void Task_KeyScan(void) {
    // 按键扫描逻辑
}

四、注意事项

  • 中断处理时间:SysTick中断中的循环尽量简短,仅做计数与标志操作,避免实时性受损。
  • 任务执行时长:任务应保证在时间片内完成,尤其是高优先级任务。若任务耗时超过周期,会占用后续任务执行时机,导致调度偏移。
  • 任务间数据共享:当任务与中断函数(如串口接收)共享变量时,使用volatile修饰,并在必要时进入临界区(如关闭中断)保护。
  • 优先级平衡:周期短的任务排在任务表前面,以便更快获得CPU。但需注意,轮询模式下所有任务平等,没有抢占,所以长任务不应阻塞。
  • 动态周期调整:可根据运行模式修改period字段,但需在非中断状态下更新,避免竞态条件。

五、总结

时间片轮询是一种优雅的裸机调度方案,它利用了STM32内核的SysTick硬件,以极少的资源占用实现多任务并发。尤其适用于设备控制、简单数据采集等场景。相比RTOS,它没有信号量、消息队列等同步机制,但通过合理的任务划分和周期设计,足以胜任大多数中低复杂度产品。理解其架构,也为后续迁移到RTOS打下基础。