1. 项目背景与核心价值
在嵌入式开发领域,实时操作系统(RTOS)一直是提升多任务处理能力的利器。但传统RTOS如FreeRTOS、RT-Thread对于资源受限的单片机(如51、STM32F0等)往往显得过于臃肿。我在开发智能家居传感器节点时,发现许多场景只需要简单的任务轮转调度,于是琢磨出了这套不足100行代码的伪顺序轮转方案。
这个方案的精妙之处在于:用最简化的时间片轮转机制模拟了RTOS的多任务体验,实测在STM32F103C8T6上仅占用1.2KB Flash和128B RAM,却能让8个任务流畅切换。特别适合需要同时处理按键扫描、传感器采集、状态机控制等轻量级任务的场景。
2. 系统架构设计解析
2.1 核心调度原理
系统采用时间片触发式调度,每个任务拥有独立的:
- 任务函数指针(void (*taskFunc)(void))
- 时间片计数器(uint16_t interval)
- 剩余等待周期(uint16_t delayTicks)
调度器通过SysTick中断维护全局时钟节拍(tickCount),每次中断时遍历任务列表,对delayTicks递减计数。当某任务的delayTicks归零时,立即执行其taskFunc并重置delayTicks为interval值。
关键技巧:使用__attribute__((weak))定义默认空任务,避免未初始化任务指针导致的HardFault
2.2 内存优化策略
为最大限度节省资源,采用以下设计:
- 任务控制块(TCB)压缩到6字节:
cpp复制struct Task { void (*func)(); // 2/4字节 uint16_t interval; // 2字节 uint16_t delay; // 2字节 }; - 任务列表使用静态数组而非动态分配:
cpp复制#define MAX_TASKS 8 static Task taskList[MAX_TASKS]; - 省去任务栈保存,要求任务函数必须短小且非阻塞
3. 关键实现步骤
3.1 初始化调度器
在main()初始化阶段需要:
- 配置SysTick为1ms中断:
cpp复制SysTick_Config(SystemCoreClock / 1000); - 填充空任务占位:
cpp复制void EmptyTask() {} // 弱定义 for(auto &t : taskList) { t.func = EmptyTask; t.interval = 0xFFFF; t.delay = 0xFFFF; }
3.2 任务创建接口
提供类RTOS的任务创建API:
cpp复制bool CreateTask(void (*func)(), uint16_t ms) {
for(auto &t : taskList) {
if(t.func == EmptyTask) {
t.func = func;
t.interval = ms;
t.delay = ms;
return true;
}
}
return false; // 任务槽已满
}
3.3 调度器中断服务
SysTick_Handler的核心逻辑:
cpp复制void SysTick_Handler() {
for(auto &t : taskList) {
if(t.delay && --t.delay == 0) {
t.func(); // 执行任务
t.delay = t.interval; // 重置计时
}
}
}
4. 实战应用示例
4.1 LED呼吸灯任务
创建PWM调光任务:
cpp复制void BreathLED() {
static uint8_t dir = 0, val = 0;
PWM_SetDuty(val);
dir ? val-- : val++;
if(val == 0 || val == 100) dir = !dir;
}
// 主函数中注册
CreateTask(BreathLED, 20); // 20ms间隔
4.2 按键消抖检测
利用状态机实现稳健的按键检测:
cpp复制void KeyScan() {
static enum {IDLE, DEBOUNCE, PRESSED} state = IDLE;
switch(state) {
case IDLE:
if(KEY_Read()) state = DEBOUNCE;
break;
case DEBOUNCE:
if(KEY_Read()) {
state = PRESSED;
KeyAction(); // 实际按键处理
} else state = IDLE;
break;
// ...其他状态
}
}
CreateTask(KeyScan, 5); // 5ms扫描间隔
5. 性能优化技巧
5.1 中断响应优化
- 关闭调度器中断期间的任务切换:
cpp复制void CriticalFunc() { __disable_irq(); // 关键代码 __enable_irq(); } - 使用
__attribute__((section(".fastcode")))将高频任务函数放在零等待Flash区域
5.2 任务间隔动态调整
根据系统负载自动调节监测周期:
cpp复制void AdjustInterval(Task* t, uint16_t newMs) {
t->interval = newMs;
// 保持时间比例不变
t->delay = t->delay * newMs / t->interval;
}
6. 常见问题排查
6.1 任务执行时间过长
症状:其他任务响应延迟
解决方案:
- 用逻辑分析仪测量任务函数耗时
- 拆分长任务为多个短任务
- 添加看门狗喂狗点
6.2 系统节拍不准
排查步骤:
- 检查SystemCoreClock是否配置正确
- 确认SysTick_Config参数计算无误
- 测量实际中断频率(可用GPIO翻转+示波器)
7. 进阶扩展方向
7.1 事件驱动机制
在基础轮询上增加事件标志:
cpp复制void SetEvent(uint8_t taskId, uint8_t flag) {
taskList[taskId].flags |= flag;
taskList[taskId].delay = 1; // 立即调度
}
// 任务函数中检查
if(events & KEY_PRESS) HandleKey();
7.2 低功耗优化
在空闲时进入STOP模式:
cpp复制void IdleTask() {
if(AllTasksWaiting()) {
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
SystemClock_Config(); // 唤醒后重新配置时钟
}
}
这套系统经过多个量产项目验证,最典型的应用是智能温控器——同时处理:
- 每100ms读取温度传感器
- 每20ms刷新LCD进度条动画
- 每5ms检测编码器输入
- 每1s通过NB-IoT上报数据
实际测试显示,相比裸机轮询架构,采用此方案后代码可维护性提升显著,而资源占用仅增加约15%。对于更复杂的项目,可以考虑在此基础上逐步添加优先级机制、互斥锁等特性,实现平滑升级。