我是老温,一名热爱学习的嵌入式工程师,关注我,一起变得更加优秀!
在资源受限的单片机上面进行产品功能开发,经常会涉及“多任务”需求,比如同时处理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 遵循极简的核心设计理念,解决了单片机裸机开发过程中多任务调度的痛点,尤其适合硬件资源受限的单片机芯片。
它无需复杂的移植流程,并且核心逻辑容易理解、容易定制,是小项目快速落地的理想选择。
开发者只需要掌握“时间基准 + 任务轮询”的核心思想,就可以根据不同的单片机类型进行快速适配,在保证系统任务响应的同时,能最大限度地降低资源消耗。
451