在资源受限的单片机开发中,如何平衡实时性和系统复杂度一直是开发者面临的难题。传统RTOS虽然功能强大,但对于简单应用往往显得过于笨重。我最近在为一个STM32F103C8T6项目开发时,设计了一套不足50行的C++伪实时调度框架,其核心思想是通过宏封装时间片轮转逻辑,实现多任务并行处理的假象。
这个框架最显著的特点是:
框架本质上是将每个任务转化为状态机,通过Thread_Tick(系统时钟)和current数组(任务状态)实现调度。关键设计在于:
cpp复制// 状态存储结构
unsigned long** sleep_count; // 各任务等待结束时间点
signed char* current; // 当前执行状态(-1表示未运行)
当Thread_Tick >= sleep_count[index][id]时触发状态转移,这种设计带来两个优势:
通过预处理宏实现语法糖是框架的精髓所在。以_delay宏为例:
cpp复制#define _delay(index,id,ms) \
{_sleep(index,id,ms);current[index]=id;}\
}if(current[index]==id&&Thread_Tick>=sleep_count[index][id]){
这个看似复杂的宏实际展开后相当于:
cpp复制{
sleep_count[index][id] = Thread_Tick + ms;
current[index] = id;
}
if(current[index]==id && Thread_Tick>=sleep_count[index][id]){
采用动态内存分配适应不同任务规模:
cpp复制Thread(unsigned char _count=1,unsigned char delay_count=4){
sleep_count = new unsigned long*[_count]; // 二维数组存储各任务等待点
current = new signed char[_count]; // 状态标识数组
}
注意:在资源极度受限的MCU(如51系列)中,建议改为静态数组以避免堆碎片问题
框架提供三种流程控制方式:
Delay串联Thread_GotoThread_Break典型按键检测实现对比:
cpp复制// 传统阻塞式
if(KEY==0){
delay(20);
if(KEY==0) return true;
}
// 本框架实现
Thread_Init(
if(KEY==1) goto TASK_END;
Delay(0,20);
if(KEY==1) goto TASK_END;
Thread_Break(return true)
, return false)
下面这个案例展示了如何同时运行:
cpp复制class AppTasks : public Thread {
public:
// 阻塞式按键检测
bool KeyScan(u8 index=0) {
Thread_Init(
if(KEY==1) goto TASK_END;
Delay(0,20); // 消抖延时
if(KEY==1) goto TASK_END;
Thread_Break(return true)
, return false)
}
// LED呼吸灯任务
void BreathLED(u8 index=1) {
static u16 pwm = 0;
static bool dir = false;
Thread_Init(
pwm = dir ? pwm-1 : pwm+1;
if(pwm>=1000) dir = true;
if(pwm==0) dir = false;
analogWrite(LED_PIN, pwm/10);
Sleep(-1,1); // 1ms周期
,)
}
};
时间片对齐:将相关任务的唤醒时间设为整数倍关系
cpp复制// 好:任务周期成倍数关系
TaskA: Sleep(-1,10);
TaskB: Sleep(-1,20);
// 差:质数周期会导致频繁冲突
TaskC: Sleep(-1,17);
状态复用:多个任务共享相同状态ID可减少判断次数
cpp复制// 任务1和任务2共用状态0
Delay(0,100); // 任务1
Delay(0,200); // 任务2
当任务中存在长延时(>100ms)时,可能影响其他任务响应。解决方案:
Thread_Goto实现异步等待cpp复制// 原长延时(不推荐)
Delay(0,1000);
// 改进方案
static u8 wait_phase = 0;
Thread_Init(
if(wait_phase < 10){
Delay(0,100);
wait_phase++;
Thread_Goto(0,); // 跳回开始
}
wait_phase = 0;
,)
由于采用类继承方式,每个任务实例会单独保存状态数据。对于大量简单任务,可以改用静态方法共享状态:
cpp复制class SharedTask {
public:
static Thread shared;
static void Task1(u8 index=0){
shared.Thread_Init(/*...*/,)
}
};
只需修改Thread_Tick定义即可移植到不同平台:
cpp复制// STM32 HAL库
#define Thread_Tick HAL_GetTick()
// Arduino
#define Thread_Tick millis()
// 51单片机(需自行实现)
extern volatile unsigned long sys_tick;
#define Thread_Tick sys_tick
通过重定义_sleep宏可改变时间粒度:
cpp复制// 以10ms为单位(节省内存)
#define _sleep(index,id,ms) \
sleep_count[index][id] = Thread_Tick + (ms+9)/10;
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 任务不执行 | current[]未初始化 | 构造函数中设置current[i]=-1 |
| 延时不准 | Thread_Tick溢出 | 使用(tick + ms) % MAX_TICK计算 |
| 宏展开错误 | 缺少括号 | 检查宏定义中的{}匹配 |
添加调试代码统计任务执行时间:
cpp复制#define Thread_Start \
unsigned long _start = Thread_Tick; \
if(current[index]==-1&&Thread_Tick>=delay_ms[index]){
#define Thread_End(task) \
LOG("Task%d cost:%lu",index,Thread_Tick-_start); \
current[index]=-1;task;}END_TASK:;
通过修改Thread_Start宏实现简单优先级:
cpp复制#define Thread_Start \
for(int _pri=0;_pri<MAX_PRI;_pri++)\
if(current[index]==-1&&Thread_Tick>=delay_ms[index]&&index%MAX_PRI==_pri){
结合中断实现事件触发:
cpp复制volatile bool event_flag = false;
void EXTI_IRQHandler(){
event_flag = true;
}
void EventTask(u8 index=0){
Thread_Init(
if(event_flag){
event_flag = false;
// 处理事件
}
Sleep(-1,10);
,)
}
这套框架在我多个实际项目中表现出色,特别是在需要快速原型开发的场景。对于资源受限又要实现多任务并行的应用,这种轻量级方案比传统RTOS节省至少5KB的Flash空间。它的价值不在于替代完整RTOS,而是提供一种"刚好够用"的解决方案。