扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

在资源有限的单片机上,运行一个极简的任务调度框架

01/16 09:53
451
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

我是老温,一名热爱学习的嵌入式工程师,关注我,一起变得更加优秀!

在资源受限的单片机上面进行产品功能开发,经常会涉及“多任务”需求,比如同时处理LED闪烁,按键扫描,传感器数据采集,等逻辑。

传统的超级大循环while(1)容易因为单个任务阻塞导致系统卡顿,而重量级RTOS又会占用过多的芯片资源。

一个名为 Simple Task Scheduler(简称STS)的极简任务调度器,恰好填补了这一空白,它用几十行 C 语言代码实现了基础任务调度,其硬件资源占用非常少,几乎是小资源单片机裸机开发的最优选择之一。

资源占用特性:

RAM占用:仅需存储任务结构体的数组,配置8个任务,RAM占用约80个Bytes。

FLASH占用:调度器核心任务逻辑编译后仅占用500~1000个Bytes,没有额外的函数库依赖。

CPU占用:主循环轮询调度没有额外的开销,CPU占用率完全由任务本身进行决定。

具体适用场景:

小资源单片机项目:51、STM8等RAM/FLASH受限的单片机,比如实现简单的多任务(LED灯控、按键处理、串口数据收发,等等)。

低复杂度裸机项目:无需信号量和队列等同步机制,仅需按照固定周期执行任务场景(小家电逻辑控制,传感器数据采集,等等)。

快速原型验证:快速实现多任务逻辑,无需花费时间移植RTOS,帮助理解任务调度的核心思想,比RTOS更容易上手。

不适用场景:

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

以适配STM32为例,以下代码分为“调度器实现”、“硬件适配”、“任务定义”三个部分。

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)

#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适配要点

C51单片机:使用定时器0/1来替换STM32的Systick,然后在定时器中断里面更新sys_time_ms时基,需要注意的是,C51需要精确配置1毫秒初值。

STM8:使用高级定时器实现1毫秒中断,用来替代STM32的Systick中断,更新sys_time_ms时基,与C51一致。

ESP8266/ESP32:使用芯片自带的定时器API来实现毫秒级时间戳,调度器的核心逻辑不需要修改。

使用注意事项

1、任务函数必须要为“短任务”,禁止在任务里面进行死循环和长时间延时,如果需要处理较长的逻辑,需要拆分为状态机

2、任务间隔建议大于等于1毫秒,避免高频任务占用过多CPU资源,影响其他任务的执行。

3、根据实际的任务数量调整 MAX_TASKS 参数宏,以减少不必要的RAM占用,达到资源优化的目的。

4、需要确保时基 sys_time_ms 的1毫秒精准稳定,避免任务执行周期产生偏移。(比如C51需要校准定时器初值)

总结

Simple Task Scheduler 遵循极简的核心设计理念,解决了单片机裸机开发过程中多任务调度的痛点,尤其适合硬件资源受限的单片机芯片。

它无需复杂的移植流程,并且核心逻辑容易理解、容易定制,是小项目快速落地的理想选择。

开发者只需要掌握“时间基准 + 任务轮询”的核心思想,就可以根据不同的单片机类型进行快速适配,在保证系统任务响应的同时,能最大限度地降低资源消耗。

相关推荐