
一、 引言:为什么需要多任务?
在8位单片机(如ATmega328P、STC89C52等)上,资源极其有限:
-
RAM:通常只有几十到几百字节
-
Flash:几KB到几十KB
-
无MMU(内存管理单元)
-
单核CPU
在这样的环境下,运行像Linux或FreeRTOS(虽然FreeRTOS有裁剪版)这样的完整OS是不现实的。但我们仍然希望实现:
-
伪并行处理:同时处理多个任务(如按键扫描、显示刷新、数据采集)
-
更好的代码结构:将复杂功能分解为独立任务
-
响应性:确保关键任务能及时得到执行
这就是我们要实现的超轻量级多任务系统 ,通常称为协作式调度器 或超级循环任务调度器。
二、 核心原理:协作式多任务
与复杂的抢占式系统不同,我们的系统基于协作式多任务:
-
协作式:每个任务必须主动"让出"CPU,其他任务才能运行
-
无优先级:所有任务平等轮流执行
-
无任务栈:所有任务共享同一个栈,节省内存
-
无上下文切换:不需要保存/恢复任务状态
工作流程:
text
初始化系统
↓
无限循环 {
执行任务1(如果到了执行时间)
执行任务2(如果到了执行时间)
...
执行任务N(如果到了执行时间)
系统空闲处理(可选)
}
三、 核心实现:时间片轮转调度
1. 任务控制块(Task Control Block, TCB)
这是系统的核心数据结构,记录每个任务的信息:
c
// 任务函数指针类型
typedef void (*TaskFunction)(void);
// 任务控制块结构
typedef struct {
TaskFunction task; // 任务函数指针
uint16_t interval; // 执行间隔(ms)
uint16_t last_run; // 上次执行时间
uint8_t enabled; // 任务使能标志
} TCB;
// 任务列表
TCB task_list[MAX_TASKS];
uint8_t task_count = 0;
2. 系统时钟源
我们需要一个精确的时间基准,通常使用定时器中断:
c
// 全局系统时钟(每1ms递增)
volatile uint32_t system_tick = 0;
// 定时器中断服务函数(1ms中断)
ISR(TIMER1_COMPA_vect) {
system_tick++;
}
3. 任务调度器核心
c
// 初始化调度器
void scheduler_init(void) {
// 配置定时器产生1ms中断
// 这里以AVR为例
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS10); // CTC模式,无分频
OCR1A = 15999; // 16MHz / 1 = 16000000Hz,16000000/1000 = 16000-1
TIMSK1 = (1 << OCIE1A);
sei(); // 开启全局中断
task_count = 0;
}
// 添加任务到调度器
uint8_t scheduler_add_task(TaskFunction func, uint16_t interval) {
if (task_count >= MAX_TASKS) {
return 0; // 失败
}
task_list[task_count].task = func;
task_list[task_count].interval = interval;
task_list[task_count].last_run = system_tick;
task_list[task_count].enabled = 1;
task_count++;
return 1; // 成功
}
// 调度器主循环
void scheduler_run(void) {
uint8_t i;
uint32_t current_tick;
while(1) {
current_tick = system_tick;
// 遍历所有任务
for (i = 0; i < task_count; i++) {
// 检查任务是否使能且到了执行时间
if (task_list[i].enabled &&
(current_tick - task_list[i].last_run >= task_list[i].interval)) {
// 更新上次执行时间
task_list[i].last_run = current_tick;
// 执行任务
task_list[i].task();
}
}
// 空闲任务(可选)
idle_task();
}
}
四、 实战示例:三任务系统
让我们实现一个具体的例子:LED闪烁、按键扫描、串口输出。
c
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#define MAX_TASKS 8
// 系统变量
volatile uint32_t system_tick = 0;
TCB task_list[MAX_TASKS];
uint8_t task_count = 0;
// 任务1:LED闪烁(500ms间隔)
void task_led_blink(void) {
static uint8_t led_state = 0;
led_state = !led_state;
if (led_state) {
PORTB |= (1 << PB5); // LED亮
} else {
PORTB &= ~(1 << PB5); // LED灭
}
}
// 任务2:按键扫描(50ms间隔)
void task_key_scan(void) {
static uint8_t last_key_state = 0;
uint8_t current_key_state = PINB & (1 << PB0);
// 检测下降沿(按键按下)
if (last_key_state && !current_key_state) {
// 按键处理
handle_key_press();
}
last_key_state = current_key_state;
}
// 任务3:串口数据发送(1000ms间隔)
void task_uart_send(void) {
static uint8_t counter = 0;
uart_send_string("Counter: ");
uart_send_number(counter++);
uart_send_string("\r\n");
}
// 按键处理函数
void handle_key_press(void) {
uart_send_string("Key Pressed!\r\n");
}
// 空闲任务
void idle_task(void) {
// 可以在这里进入低功耗模式
// _delay_ms(1);
}
int main(void) {
// 硬件初始化
DDRB = (1 << PB5); // PB5设为输出(LED)
PORTB = (1 << PB0); // PB0上拉电阻(按键)
uart_init(); // 初始化串口
// 调度器初始化
scheduler_init();
// 添加任务
scheduler_add_task(task_led_blink, 500); // 500ms间隔
scheduler_add_task(task_key_scan, 50); // 50ms间隔
scheduler_add_task(task_uart_send, 1000); // 1000ms间隔
// 启动调度器(永不返回)
scheduler_run();
return 0;
}
五、 高级特性与优化
1. 任务优先级模拟
虽然我们是协作式调度,但可以模拟优先级:
c
void scheduler_run_priority(void) {
uint8_t i;
uint32_t current_tick = system_tick;
// 高优先级任务放在前面检查
for (i = 0; i < task_count; i++) {
if (task_list[i].enabled &&
(current_tick - task_list[i].last_run >= task_list[i].interval)) {
task_list[i].last_run = current_tick;
task_list[i].task();
// 重要:高优先级任务执行后立即返回,确保及时性
return;
}
}
// 中优先级任务...
// 低优先级任务...
}
2. 动态任务管理
c
// 启用/禁用任务
void scheduler_set_task_enable(uint8_t task_id, uint8_t enable) {
if (task_id < task_count) {
task_list[task_id].enabled = enable;
}
}
// 修改任务间隔
void scheduler_set_task_interval(uint8_t task_id, uint16_t interval) {
if (task_id < task_count) {
task_list[task_id].interval = interval;
}
}
3. 系统状态监控
c
// 获取CPU使用率
uint8_t get_cpu_usage(void) {
static uint32_t last_idle_time = 0;
static uint32_t last_total_time = 0;
uint32_t current_idle_time = idle_counter;
uint32_t current_total_time = system_tick;
uint32_t idle_delta = current_idle_time - last_idle_time;
uint32_t total_delta = current_total_time - last_total_time;
last_idle_time = current_idle_time;
last_total_time = current_total_time;
if (total_delta == 0) return 100;
return 100 - (idle_delta * 100 / total_delta);
}
六、 最佳实践与注意事项
-
任务设计原则:
-
每个任务必须短小精悍,执行时间要远小于其间隔时间
-
避免在任务中使用长延时(如
_delay_ms(100)) -
任务函数应为无阻塞设计
-
-
中断使用:
-
保持中断服务函数尽可能短
-
在ISR中只做标记,在主循环中处理
-
-
资源共享:
-
由于没有真正的任务保护,要注意共享数据(如全局变量)的访问
-
可以使用"原子操作"或简单的开关中断来保护关键代码段
-
-
内存优化:
-
根据实际任务数量设置
MAX_TASKS -
使用最小的合适数据类型(
uint8_tvsint)
-
七、 资源开销分析
以ATmega328P为例:
-
ROM占用:约 500-800 字节(核心调度器)
-
RAM占用:约 20-50 字节(任务表+变量)
-
CPU开销:< 1%(在任务设计合理的情况下)
八、 总结
这种超轻量级多任务系统的优势:
-
极低资源消耗:适合最基础的8位单片机
-
简单可靠:没有复杂的上下文切换,调试容易
-
可预测性:任务执行时间可预测
-
易于扩展:可以方便地添加新任务
虽然功能简单,但这种设计思想是许多复杂RTOS的基础。掌握了它,您就理解了嵌入式多任务的核心原理。