找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2229

积分

0

好友

317

主题
发表于 19 小时前 | 查看: 4| 回复: 0

在资源受限的单片机上进行产品开发时,我们常常会遇到“多任务”处理的需求,比如需要同时控制 LED 闪烁、扫描按键、采集传感器数据等。

如果使用传统的 while(1) 超级大循环,单个任务的阻塞很容易导致整个系统卡顿;而引入完整的实时操作系统 (RTOS) 又可能占用过多的芯片资源,显得过于“笨重”。

此时,一个名为 Simple Task Scheduler (STS) 的极简任务调度器恰好能填补这一空白。它仅用几十行 C 语言代码便实现了基础的任务调度功能,硬件资源占用极少,堪称小资源单片机裸机开发的优选方案之一。

资源占用与特点

STS 调度器在资源占用上表现出显著优势:

  • RAM 占用:仅需存储任务结构体数组。例如配置 8 个任务时,RAM 占用大约为 80 字节。
  • FLASH 占用:调度器核心逻辑编译后仅占用 500~1000 字节,无需额外的函数库依赖。
  • CPU 占用:采用主循环轮询调度,没有额外的调度开销,CPU 占用率完全由任务自身的执行时间决定。

适用与不适用场景

适用场景

  • 小资源单片机项目:如 51、STM8 等 RAM/FLASH 资源紧张的单片机,用于实现简单的多任务控制(LED、按键、串口通信等)。
  • 低复杂度裸机项目:无需信号量、队列等同步机制,仅需按固定周期执行任务的场景(如小家电控制、传感器数据采集)。
  • 快速原型验证:快速实现多任务逻辑,无需花费时间移植 RTOS,有助于理解任务调度的核心思想。

不适用场景

  • 需要抢占式调度,对实时性有严格要求的项目。
  • 需要多任务同步(如信号量、互斥锁)的复杂项目。
    对于此类场景,建议选择 FreeRTOS 或 RT-Thread 等成熟的 RTOS。

代码实现与解析 (以STM32为例)

以下将代码分为“调度器实现”、“硬件适配”和“任务定义”三部分进行展示,这是一个非常清晰的 C/C++ 工程结构。

1. 调度器头文件 (scheduler.h)

此文件定义了任务结构体和调度器的接口函数。

#ifndef __SCHEDULER_H
#define __SCHEDULER_H

#include “stm32f10x.h”
#include <stdint.h>
#include <stdbool.h>
#define MAX_TASKS 8  // 最大任务数,按需调整

// 任务结构体
typedef struct {
    void (*task_func)(void);  // 任务函数指针
    uint32_t interval_ms;     // 执行间隔(ms)
    uint32_t last_run_time;   // 上次执行时间戳
    bool is_enabled;          // 任务使能标志
} Task_t;

// 函数声明
void Scheduler_Init(void);                  // 调度器初始化
bool Scheduler_AddTask(void (*func)(void), uint32_t interval, bool enable);  // 添加任务
void Scheduler_Run(void);                   // 调度器主循环
uint32_t Scheduler_GetSysTimeMs(void);      // 获取系统时间(ms)

#endif

2. 调度器源文件 (scheduler.c)

此文件包含了调度器的核心逻辑和 1ms 系统时基的实现。

#include “scheduler.h”

static Task_t task_list[MAX_TASKS] = {0};
static uint8_t task_count = 0;
static uint32_t sys_time_ms = 0;

// SysTick中断服务函数:1ms触发,更新系统时间
void SysTick_Handler(void){
    sys_time_ms++;
}

// 初始化1ms时间基准(STM32F103 72MHz主频)
static void SysTime_Init(void){
    if (SysTick_Config(SystemCoreClock / 1000)) {
        while (1);  // 初始化失败,可添加错误处理
    }
}

// 获取系统时间
uint32_t Scheduler_GetSysTimeMs(void){
    return sys_time_ms;
}

// 调度器初始化
void Scheduler_Init(void){
    SysTime_Init();
    task_count = 0;
    sys_time_ms = 0;
    // 清空任务列表
    for (uint8_t i = 0; i < MAX_TASKS; i++) {
        task_list[i].task_func = NULL;
        task_list[i].interval_ms = 0;
        task_list[i].last_run_time = 0;
        task_list[i].is_enabled = false;
    }
}

// 添加任务
bool Scheduler_AddTask(void (*func)(void), uint32_t interval, bool enable){
    if (func == NULL || interval == 0 || task_count >= MAX_TASKS) {
        return false;
    }
    task_list[task_count].task_func = func;
    task_list[task_count].interval_ms = interval;
    task_list[task_count].last_run_time = Scheduler_GetSysTimeMs();
    task_list[task_count].is_enabled = enable;
    task_count++;
    return true;
}

// 调度器主循环
void Scheduler_Run(void){
    uint32_t current_time = Scheduler_GetSysTimeMs();
    for (uint8_t i = 0; i < task_count; i++) {
        if (!task_list[i].is_enabled) continue;
        // 检查执行间隔(处理时间溢出)
        if ((current_time - task_list[i].last_run_time) >= task_list[i].interval_ms) {
            task_list[i].task_func();
            task_list[i].last_run_time = current_time;
        }
    }
}

3. 主函数与任务实现 (main.c)

这里展示了如何初始化硬件、定义具体任务,并在主循环中运行调度器。

#include “stm32f10x.h”
#include “scheduler.h”

// 任务1:LED闪烁(500ms一次)
void Task_LED_Flash(void){
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, !GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13));
}

// 任务2:按键扫描(10ms一次)
void Task_Key_Scan(void){
    if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == RESET) {
        GPIO_SetBits(GPIOB, GPIO_Pin_0);  // 按键按下,点亮LED
    } else {
        GPIO_ResetBits(GPIOB, GPIO_Pin_0); // 按键松开,熄灭LED
    }
}

// 硬件初始化
void Hardware_Init(void){
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);

    // LED引脚配置(PC13、PB0)
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
    GPIO_Init(GPIOC, &GPIO_InitStruct);
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 按键引脚配置(PA0上拉输入)
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
}

int main(void){
    Hardware_Init();          // 硬件初始化
    Scheduler_Init();         // 调度器初始化
    // 添加任务
    Scheduler_AddTask(Task_LED_Flash, 500, true);
    Scheduler_AddTask(Task_Key_Scan, 10, true);

    // 主循环
    while (1) {
        Scheduler_Run();  // 执行调度器
    }
}

跨MCU适配要点

STS 的核心逻辑是通用的,关键在于为不同平台提供准确的 1ms 时间基准 (sys_time_ms)。

  • C51 单片机:使用定时器0/1中断来更新 sys_time_ms,注意精确配置定时器初值以实现 1ms 中断。
  • STM8:使用其高级定时器实现 1ms 中断,替代STM32的SysTick。
  • ESP8266/ESP32:使用芯片提供的定时器API(如 millis())获取毫秒级时间戳,调度器核心逻辑无需修改。

理解不同 计算机基础 架构下的定时器工作原理,能帮助你更好地完成适配。

使用注意事项

  1. 任务函数必须短小:禁止在任务函数中进行死循环或长时间延时。若要处理耗时逻辑,应将其拆分为状态机。
  2. 合理设置任务间隔:建议任务间隔 >=1ms,避免高频任务过度占用 CPU,影响其他任务执行。
  3. 按需调整任务数量:根据实际任务数量修改 MAX_TASKS 宏,以减少不必要的 RAM 占用。
  4. 保证时基准确:确保 sys_time_ms 的 1ms 时基精准稳定,否则会导致任务执行周期产生累积误差。

总结

Simple Task Scheduler (STS) 秉承极简的设计理念,有效解决了单片机裸机开发中多任务调度的痛点,特别适合硬件资源受限的应用场景。

它无需复杂的移植流程,核心代码易于理解和定制,是小项目快速落地和原型验证的理想工具。开发者只需掌握“时间基准 + 任务轮询”这一核心思想,即可针对不同的单片机平台进行快速适配,在确保系统响应能力的同时,最大限度地降低资源消耗。


本文讨论了在资源受限环境下进行任务调度的轻量级解决方案。如果你想了解更多嵌入式开发技巧或与其他开发者交流,欢迎访问 云栈社区

AT命令解析器C++类代码示例




上一篇:阿里&武大联合提出Agentic Memory,统一LLM智能体长短期记忆管理
下一篇:微服务架构分解设计实践指南:从DDD到Strangler模式
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-16 22:07 , Processed in 0.226739 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表