嵌入式调度器开发学习笔记

嵌入式调度器开发学习笔记

本笔记涵盖裸机开发、协作式调度器、抢占式调度器三种开发模式的对比与选型


目录

  1. 三种开发模式总览
  2. 裸机开发
  3. 协作式调度器
  4. 抢占式调度器
  5. 核心对比分析
  6. 选型指南
  7. 实战代码模板

1. 三种开发模式总览

维度 裸机开发 协作式调度器 抢占式调度器
执行模型 单一主循环 时间片轮询 优先级抢占
任务切换 顺序执行 任务主动让出 调度器强制切换
实时性 软实时 硬实时
内存开销 最小 小 (~100B) 大 (每任务独立栈)
复杂度 简单 中等 复杂
典型代表 while(1) 轮询 时间片调度器 FreeRTOS, RT-Thread

2. 裸机开发

2.1 基本架构

int main(void) {
    System_Init();

    while(1) {
        Task_LED();      // 任务1
        Task_Key();      // 任务2
        Task_UART();     // 任务3
        Task_Sensor();   // 任务4
    }
}

2.2 时间管理方式

// 每个任务内部自己管理执行时间
void Task_LED(void) {
    static uint32_t last_tick = 0;

    if (HAL_GetTick() - last_tick >= 500) {
        last_tick = HAL_GetTick();
        HAL_GPIO_TogglePin(LED_GPIO, LED_PIN);
    }
}

void Task_Key(void) {
    static uint32_t last_tick = 0;

    if (HAL_GetTick() - last_tick >= 10) {
        last_tick = HAL_GetTick();
        Key_Scan();
    }
}

2.3 特点分析

优点 缺点
:white_check_mark: 简单直观 :cross_mark: 任务耦合度高
:white_check_mark: 资源占用极低 :cross_mark: 增删任务需改主循环
:white_check_mark: 无额外内存开销 :cross_mark: 时间管理代码重复
:white_check_mark: 无并发安全问题 :cross_mark: 一个阻塞全部卡死

2.4 执行时序

时间轴 →
┌────┬────┬────┬────┬────┬────┬────┬────┐
│LED │Key │UART│Sens│LED │Key │UART│Sens│
│判断│判断│判断│判断│判断│判断│判断│判断│
└────┴────┴────┴────┴────┴────┴────┴────┘
     ↑ 每次循环遍历所有任务

响应延迟 = T1 + T2 + T3 + T4 (所有任务执行时间之和)

3. 协作式调度器

3.1 基本架构

// 任务结构体定义
typedef struct {
    void (*func)(void);   // 任务函数指针
    uint32_t period;      // 执行周期 (ms)
    uint32_t last_run;    // 上次执行时间
    uint8_t  enable;      // 使能标志
} Task_t;

// 任务表:集中管理
Task_t tasks[] = {
    { Task_LED,    500, 0, 1 },
    { Task_Key,    10,  0, 1 },
    { Task_UART,   1,   0, 1 },
    { Task_Sensor, 100, 0, 1 },
};

int main(void) {
    System_Init();
    Scheduler_Init(tasks, 4);

    while(1) {
        Scheduler_Run();
    }
}

3.2 调度器实现

static Task_t *task_list;
static uint8_t task_count;

void Scheduler_Init(Task_t *tasks, uint8_t count) {
    task_list = tasks;
    task_count = count;
}

void Scheduler_Run(void) {
    uint32_t now = HAL_GetTick();

    for (uint8_t i = 0; i < task_count; i++) {
        if (!task_list[i].enable) continue;

        if (now - task_list[i].last_run >= task_list[i].period) {
            task_list[i].last_run = now;
            task_list[i].func();
        }
    }
}

// 动态控制接口
void Scheduler_Enable(uint8_t id, uint8_t en) {
    task_list[id].enable = en;
}

void Scheduler_SetPeriod(uint8_t id, uint32_t period) {
    task_list[id].period = period;
}

3.3 任务函数规范

// ✅ 正确:专注业务逻辑,快速返回
void Task_LED(void) {
    LED_Toggle();
}

void Task_Motor(void) {
    Motor_PID_Step();  // 执行一步就返回
}

// ❌ 错误:阻塞式代码
void Task_Wrong(void) {
    while(1) {         // 死循环,系统卡死
        // ...
    }

    HAL_Delay(1000);   // 阻塞延时,其他任务无法执行
}

3.4 与裸机的核心区别

维度 裸机 协作式调度器
时间管理 每个任务自己管理 调度器统一管理
任务增删 修改主循环 仅修改任务表
动态控制 不支持 支持启停/改周期
代码复用

3.5 执行时序

调度器检查任务表,只执行到期的任务

    t=0   t=10  t=20  t=100  t=500
    UART  Key   Key   Sensor  LED
    UART  UART  UART  Key     Key
                      UART    UART

4. 抢占式调度器

4.1 基本架构 (FreeRTOS)

int main(void) {
    // 创建任务,分配独立栈空间
    xTaskCreate(Task_LED,   "LED",   128, NULL, 1, NULL);  // 低优先级
    xTaskCreate(Task_Motor, "Motor", 256, NULL, 3, NULL);  // 高优先级
    xTaskCreate(Task_HMI,   "HMI",   512, NULL, 2, NULL);  // 中优先级

    vTaskStartScheduler();  // 启动调度器
}

// 任务函数:可以有死循环和阻塞
void Task_Motor(void *param) {
    while(1) {
        Motor_PID_Control();
        vTaskDelay(pdMS_TO_TICKS(1));  // 非阻塞等待
    }
}

void Task_LED(void *param) {
    while(1) {
        LED_Toggle();
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

4.2 抢占机制

高优先级任务可随时打断低优先级任务

时间轴 →
┌────┬──┬────┬──┬────┬──────────┐
│ T1 │T3│ T1 │T3│ T2 │    T1    │
│低  │高│低  │高│中  │    低    │
└────┴──┴────┴──┴────┴──────────┘
     ↑      ↑
  T3抢占  T3抢占

最坏响应时间 ≈ 上下文切换时间 (微秒级)

4.3 内存模型

┌─────────────────────────┐
│    Task1 栈 (256B)      │
├─────────────────────────┤
│    Task2 栈 (512B)      │
├─────────────────────────┤
│    Task3 栈 (256B)      │
├─────────────────────────┤
│   系统栈 + TCB (~64B)   │
├─────────────────────────┤
│      全局变量            │
└─────────────────────────┘

典型开销:内核 6-10KB + 每任务 128-1024B

4.4 并发安全

// 抢占式下必须保护共享资源
uint32_t g_counter = 0;
SemaphoreHandle_t mutex;

void Task_A(void *param) {
    while(1) {
        xSemaphoreTake(mutex, portMAX_DELAY);  // 加锁
        g_counter++;
        Process(g_counter);
        xSemaphoreGive(mutex);                  // 解锁
        vTaskDelay(10);
    }
}

void Task_B(void *param) {
    while(1) {
        xSemaphoreTake(mutex, portMAX_DELAY);
        g_counter += 10;
        xSemaphoreGive(mutex);
        vTaskDelay(20);
    }
}

5. 核心对比分析

5.1 任务响应时间

场景:紧急任务需要 1ms 响应
- Task_Slow:   执行 50ms
- Task_Normal: 执行 10ms
- Task_Urgent: 执行 1ms (紧急)

【协作式】
┌──────────────────────┬────────────┬─┐
│      Task_Slow       │Task_Normal │U│
│       (50ms)         │  (10ms)    │ │
└──────────────────────┴────────────┴─┘
最坏响应:60ms ❌

【抢占式】
┌────┬─┬────┬─┬────┬─┬──────────────┐
│Slow│U│Slow│U│Slow│U│   Slow继续   │
└────┴─┴────┴─┴────┴─┴──────────────┘
最坏响应:~μs ✅

5.2 内存占用对比

配置 协作式 抢占式 (FreeRTOS)
内核代码 ~500B ~6-10KB
5任务总 RAM ~200B ~3-7KB
最小可运行 1KB RAM 4KB RAM

5.3 特性对比

特性 协作式 抢占式
任务切换开销 几乎为零 上下文保存/恢复
代码可重入性 不要求 必须可重入
共享资源保护 通常不需要 需要互斥锁
任务间通信 全局变量 队列/信号量
死锁风险
调试难度 简单 复杂

6. 选型指南

6.1 决策流程图

                    ┌─────────────────┐
                    │ 有硬实时要求?   │
                    └────────┬────────┘
                             │
              ┌──────────────┴──────────────┐
              │ 是                          │ 否
              ▼                             ▼
    ┌─────────────────┐          ┌─────────────────┐
    │   抢占式 RTOS   │          │  RAM < 4KB ?    │
    └─────────────────┘          └────────┬────────┘
                                          │
                              ┌───────────┴───────────┐
                              │ 是                    │ 否
                              ▼                       ▼
                    ┌─────────────────┐    ┌─────────────────┐
                    │  协作式/裸机    │    │  任务 > 5 个?   │
                    └─────────────────┘    └────────┬────────┘
                                                    │
                                        ┌───────────┴───────────┐
                                        │ 是                    │ 否
                                        ▼                       ▼
                              ┌─────────────────┐    ┌─────────────────┐
                              │  协作式调度器   │    │    裸机开发     │
                              └─────────────────┘    └─────────────────┘

6.2 场景推荐

场景 推荐方案
简单控制 (LED、按键、少量传感器) 裸机
中等复杂度 + 软实时 协作式调度器
多任务 + 硬实时要求 RTOS
资源极度受限 (< 4KB RAM) 裸机或状态机
网络协议栈、文件系统 RTOS
电机控制、工业控制 RTOS

7. 实战代码模板

7.1 完整协作式调度器

/* ============== scheduler.h ============== */
#ifndef __SCHEDULER_H
#define __SCHEDULER_H

#include <stdint.h>

typedef struct {
    void (*func)(void);
    uint32_t period;
    uint32_t last_run;
    uint8_t  enable;
} Task_t;

void Scheduler_Init(Task_t *tasks, uint8_t count);
void Scheduler_Run(void);
void Scheduler_Enable(uint8_t id, uint8_t en);
void Scheduler_SetPeriod(uint8_t id, uint32_t period);

#endif

/* ============== scheduler.c ============== */
#include "scheduler.h"

static Task_t *task_list;
static uint8_t task_count;

extern uint32_t HAL_GetTick(void);

void Scheduler_Init(Task_t *tasks, uint8_t count) {
    task_list = tasks;
    task_count = count;

    for (uint8_t i = 0; i < count; i++) {
        task_list[i].last_run = 0;
        task_list[i].enable = 1;
    }
}

void Scheduler_Run(void) {
    uint32_t now = HAL_GetTick();

    for (uint8_t i = 0; i < task_count; i++) {
        if (!task_list[i].enable) continue;

        if (now - task_list[i].last_run >= task_list[i].period) {
            task_list[i].last_run = now;
            task_list[i].func();
        }
    }
}

void Scheduler_Enable(uint8_t id, uint8_t en) {
    if (id < task_count) {
        task_list[id].enable = en;
    }
}

void Scheduler_SetPeriod(uint8_t id, uint32_t period) {
    if (id < task_count) {
        task_list[id].period = period;
    }
}

/* ============== main.c ============== */
#include "scheduler.h"

// 任务函数声明
void Task_LED(void);
void Task_Key(void);
void Task_UART(void);

// 任务表
Task_t tasks[] = {
    { Task_LED,  500, 0, 1 },
    { Task_Key,  10,  0, 1 },
    { Task_UART, 1,   0, 1 },
};

#define TASK_COUNT (sizeof(tasks) / sizeof(tasks[0]))

int main(void) {
    HAL_Init();
    SystemClock_Config();
    GPIO_Init();

    Scheduler_Init(tasks, TASK_COUNT);

    while(1) {
        Scheduler_Run();
    }
}

void Task_LED(void) {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}

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

void Task_UART(void) {
    // 串口处理逻辑
}

7.2 混合架构 (中断 + 调度器)

// 中断处理紧急事件 + 主循环调度后台任务
void SysTick_Handler(void) {
    HAL_IncTick();
    Motor_PID_Control();  // 1ms 硬实时任务
}

int main(void) {
    System_Init();
    Scheduler_Init(tasks, TASK_COUNT);

    while(1) {
        Scheduler_Run();
        __WFI();  // 低功耗等待
    }
}

8. 总结

8.1 一句话概括

模式 核心特点
裸机 顺序执行,每个任务自己管时间
协作式 调度器管时间,任务必须主动让出 CPU
抢占式 调度器强制切换,高优先级可打断低优先级

8.2 本质区别

  • 裸机 vs 协作式:时间管理从"分散"到"集中"
  • 协作式 vs 抢占式:任务切换从"自觉"到"强制"

8.3 记忆口诀

裸机简单资源省,任务耦合维护难;
协作调度表驱动,主动让出是关键;
抢占实时响应快,互斥死锁要防范。

笔记整理时间:2026-02-21

1 个赞