MCU 裸机时间片调度系统

1. 系统概述

本代码实现了一个基于 SysTick 定时器的合作式时间片轮询调度器,适用于资源受限的 Cortex-M 系列 MCU(如 STM32F4、GD32F4、N32G4 等)裸机开发环境。

核心思想:

  • 使用 SysTick 定时器产生每 1ms 一次的中断,作为全局时间基准。
  • 在中断中执行最高实时性任务。
  • 在主循环中通过任务函数数组轮询方式,实现多个不同周期任务的准时执行。
  • 无需操作系统,代码体积小、开销低、确定性强、易于调试和维护。

适用于:

  • 电机控制、传感器采集、通信协议处理、状态机、LED 指示等典型嵌入式应用。
2. 整体架构图
复制代码
SysTick (1ms 中断)
    │
    ├── 更新 systick_cnt (全局毫秒计数器)
    └── 执行 Task1ms()(最高优先级任务)

主循环 (while(1))
    │
    └── 遍历 tasks[] 数组
            │
            └── 对于每个任务:
                    如果 (当前时间 - 上次执行时间 >= 周期)
                        更新上次执行时间
                        执行任务函数
3. 代码详细说明
3.1 头文件与任务声明
复制代码
#include "stm32f4xx.h"

// 任务函数声明
void Task1ms(void);
void Task10ms(void);
void Task100ms(void);
void Task1000ms(void);
  • 所有任务函数必须无参数、无返回值(void)。
  • 建议按执行频率命名,便于管理。
3.2 任务结构体与数组
复制代码
typedef struct {
    void (*func)(void);     // 任务函数指针
    uint32_t period_ms;     // 执行周期(单位:毫秒)
    uint32_t last_run;      // 上次执行的时间戳(基于 systick_cnt)
} Task_t;

// 任务调度表
Task_t tasks[] = {
    { Task10ms,    10,   0 },
    { Task100ms,  100,   0 },
    { Task1000ms,1000,   0 },
};

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

优点

  • 添加新任务只需在数组中增加一行,无需修改调度逻辑。
  • 所有周期一目了然,便于调整。
  • last_run 初始化为 0,上电后各任务会在对应时间点后首次执行。
3.3 全局时间基准
复制代码
volatile uint32_t systick_cnt = 0;  // 1ms 递增的全局计数器
  • volatile 防止编译器优化,确保中断与主循环可见一致值。
  • 32 位计数器约 49.7 天溢出一次,使用减法比较方式可安全处理溢出。
3.4 SysTick 中断服务函数
复制代码
void SysTick_Handler(void)
{
    systick_cnt++;
    Task1ms();  // 最高实时性任务在此执行
}
  • 每 1ms 执行一次。
  • 建议只放执行时间极短(<50~100μs)的任务,如:
    • PWM 占空比更新
    • 控制环计算
    • ADC 触发
    • 编码器采样
3.5 主函数
复制代码
int main(void)
{
    SystemClock_Config();                  // 系统时钟初始化
    SysTick_Config(SystemCoreClock / 1000); // 配置 1ms 中断

    GPIO_Init();
    UART_Init();

    // 初始化任务上次执行时间(防止启动瞬间全部执行)
    for (uint32_t i = 0; i < TASKS_COUNT; i++) {
        tasks[i].last_run = systick_cnt;
    }

    while (1)
    {
        uint32_t current = systick_cnt;

        // 遍历所有任务
        for (uint32_t i = 0; i < TASKS_COUNT; i++) {
            if (current - tasks[i].last_run >= tasks[i].period_ms) {
                tasks[i].last_run = current;
                tasks[i].func();
            }
        }

        // __WFI();  // 可选:空闲时进入睡眠,降低功耗
    }
}

关键点

  • current - last_run >= period_ms:防溢出安全比较方式。
  • 主循环仅负责调度,可执行复杂逻辑(如浮点运算、字符串处理)。
  • 可开启 __WFI() 实现低功耗待机,仅由 SysTick 中断唤醒。
3.6 示例任务实现
复制代码
void Task1ms(void)     { /* 高实时任务 */ }
void Task10ms(void)    { /* 传感器读取、滤波、协议处理 */ }
void Task100ms(void)   { /* LED 闪烁、按键扫描 */ }
void Task1000ms(void)  { /* 串口打印日志、心跳包、参数保存 */ }
4. 使用指南
4.1 添加新任务
  1. 声明函数原型:void NewTask(void);

  2. 实现函数体。

  3. 在 tasks[] 数组中添加一行:

    复制代码
    { NewTask, 50, 0 },  // 每 50ms 执行一次
4.2 调整任务优先级
  • 要求最高实时性的任务 → 放入 SysTick_Handler 中(如 Task1ms)。
  • 次高优先级 → 放在周期较小的任务(例如 5ms、10ms)。
  • 低优先级、非实时任务 → 周期较大(如 500ms、1000ms)。
4.3 性能与实时性注意事项
  • 任何单个任务执行时间不应超过其周期的 50%(建议 <30%),避免阻塞其他任务。
  • 中断中任务总执行时间建议 <100μs。
  • 若任务执行时间较长,可拆分为状态机,分多次完成。
5. 优点总结
特性 说明
确定性强 所有任务周期固定,无抢占延迟
资源占用极低 无栈切换、无信号量、无队列
易调试 单线程逻辑,断点跟踪简单
可维护性高 任务数组集中管理,新增/删除方便
低功耗支持 支持 __WFI() 睡眠
6. 适用场景与升级路径

适用:中小型嵌入式系统(<20 个任务,实时要求中等)

不适用:复杂多任务、需要优先级抢占、动态创建任务的场景

升级路径

  • 需要抢占式调度 → 移植 FreeRTOS
  • 需要消息队列、信号量 → 使用 RT-Thread 或 uCOS

本调度框架已在众多工业控制、仪表、消费电子裸机项目中稳定运行,是嵌入式开发者首选的时间管理方案。

源码:

复制代码
// main.c
#include "stm32f4xx.h"

// ==================== 任务函数声明 ====================
void Task1ms(void);      // 每 1ms 执行(放在中断中)
void Task10ms(void);     // 每 10ms 执行
void Task100ms(void);    // 每 100ms 执行
void Task1000ms(void);   // 每 1000ms 执行

// ==================== 任务结构体定义 ====================
typedef struct {
    void (*func)(void);   // 任务函数指针
    uint32_t period_ms;   // 执行周期(毫秒)
    uint32_t last_run;    // 上次执行时间(基于 systick_cnt)
} Task_t;

// ==================== 任务数组 ====================
// 注意:Task1ms 单独处理(放在中断中),其余放在主循环数组中
Task_t tasks[] = {
    { Task10ms,    10,   0 },
    { Task100ms,  100,   0 },
    { Task1000ms,1000,   0 },
    // 如需添加新任务,直接在此添加一行即可
    // { NewTask,    50,   0 },
};

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

// ==================== 全局变量 ====================
volatile uint32_t systick_cnt = 0;  // 1ms 时间基准计数器

// ==================== SysTick 中断服务函数 ====================
void SysTick_Handler(void)
{
    systick_cnt++;

    // 高实时性任务直接放在这里执行(执行时间必须极短!)
    Task1ms();
}

// ==================== 主函数 ====================
int main(void)
{
    // 系统时钟配置(例如 168MHz)
    SystemClock_Config();

    // 配置 SysTick 为 1ms 中断
    SysTick_Config(SystemCoreClock / 1000);

    // 初始化外设(GPIO、UART 等)
    GPIO_Init();
    UART_Init();

    // 初始化任务数组的 last_run(可选,防止启动瞬间全部执行)
    for (uint32_t i = 0; i < TASKS_COUNT; i++) {
        tasks[i].last_run = systick_cnt;
    }

    while (1)
    {
        uint32_t current = systick_cnt;

        // 遍历任务数组,检查是否到达执行时间
        for (uint32_t i = 0; i < TASKS_COUNT; i++) {
            if (current - tasks[i].last_run >= tasks[i].period_ms) {
                tasks[i].last_run = current;  // 更新上次执行时间
                tasks[i].func();              // 执行任务
            }
        }

        // 主循环空闲时可进入低功耗模式
        // __WFI();  // 等待中断唤醒,节省功耗
    }
}

// ==================== 任务函数实现示例 ====================
void Task1ms(void)
{
    // 高优先级、硬实时任务
    // 示例:电机 PWM 更新、ADC 采样触发、控制环计算等
    // 注意:执行时间建议 < 50~100us
}

void Task10ms(void)
{
    // 中等频率任务
    // 示例:传感器数据读取、滤波、PID 计算、CAN 报文处理等
}

void Task100ms(void)
{
    // 低频任务
    // 示例:LED 闪烁、按键消抖扫描、状态机更新
    static uint8_t led_state = 0;
    if (led_state) {
        GPIOC->BSRR = GPIO_PIN_13;                    // 点灯(假设低电平点亮)
    } else {
        GPIOC->BSRR = (uint32_t)GPIO_PIN_13 << 16;     // 灭灯
    }
    led_state = !led_state;
}

void Task1000ms(void)
{
    // 1秒周期任务
    // 示例:心跳包发送、运行时间统计、参数保存、串口打印日志等
}
相关推荐
安科瑞刘鸿鹏1715 小时前
工业自动化系统中抗晃电保护的协同控制研究
运维·网络·嵌入式硬件·物联网
深圳市方中禾科技16 小时前
LED驱动芯片FZH02,应用开发相关数据技术手册
单片机·嵌入式硬件·led
第二层皮-合肥16 小时前
光耦瞬态响应特性:上升时间与下降时间的测试方法与误差分析
嵌入式硬件
yuanmenghao17 小时前
CAN系列 — (6) CAN FD 带宽、CPU、中断:工程上是如何一起算的?
网络·驱动开发·单片机·mcu·自动驾驶·信息与通信
Arciab19 小时前
51单片机_蜂鸣器
单片机·嵌入式硬件·51单片机
SmartRadio19 小时前
在CH585M代码中如何精细化配置PMU(电源管理单元)和RAM保留
linux·c语言·开发语言·人工智能·单片机·嵌入式硬件·lora
qq_4112624220 小时前
纯图像传感器(只出像素),还是 Himax WiseEye/WE1/WE-I Plus 这类带处理器、能在端侧跑模型并输出“metadata”的模块
人工智能·嵌入式硬件·esp32·四博智联
不脱发的程序猿20 小时前
嵌入式开发中C++内存泄漏的场景与解决办法
单片机·嵌入式硬件·嵌入式
至为芯20 小时前
IP5326至为芯支持TYPE-C协议的2.4A充放电移动电源方案芯片
单片机·嵌入式硬件