1. 单片机裸机多任务与低功耗方案概述
在嵌入式系统开发中,资源受限的单片机往往需要在没有操作系统(RTOS)支持的情况下实现多任务处理。裸机多任务方案通过巧妙的设计,能够在有限的硬件资源上实现类似多任务的效果,同时兼顾低功耗需求。本文将深入解析8种实用的裸机多任务实现方案,从最简单的标志位轮询到复杂的事件驱动框架,帮助开发者根据项目需求选择最适合的方案。
2. 简易多任务方案
2.1 前后台if else标志位轮询
前后台架构是最基础的多任务实现方式,适合任务数量少、逻辑简单的场景。其核心思想是通过中断(后台)设置标志位,主循环(前台)轮询标志位执行相应任务。
c复制// 任务标志位定义
uint8_t flag_led1 = 0;
uint8_t flag_led2 = 0;
uint8_t flag_button = 0;
int main(void){
while(1){
// LED1任务:100ms翻转一次
if(flag_led1){
gpio_toggle(led1_pin);
flag_led1 = 0;
}
// LED2任务:20ms翻转一次
if(flag_led2){
gpio_toggle(led2_pin);
flag_led2 = 0;
}
// 按键检测任务
if(flag_button){
printf("Button state change\n");
flag_button = 0;
}
}
}
// 系统滴答中断(1ms一次)
void systick_handler(void){
static uint64_t counter = 0;
counter++;
if(counter % 100 == 0) flag_led1 = 1; // 100ms触发
if(counter % 20 == 0) flag_led2 = 1; // 20ms触发
}
// 按键中断
void exti_0_handler(void){
flag_button = 1;
}
注意事项:
- 标志位操作应保持原子性,必要时关中断
- 主循环执行时间应短于最短任务周期
- 中断服务函数应尽可能简短
3. 多定时器方案
3.1 硬件多定时器
当MCU具备多个硬件定时器时,可以为每个任务分配独立的定时器:
c复制int main(void){
// 初始化硬件定时器
timer_init(&timer1, 100, 0); // 100ms周期,优先级0
timer_init(&timer2, 20, 1); // 20ms周期,优先级1
while(1){
// 进入低功耗模式,等待中断唤醒
__WFI();
}
}
// 定时器1中断(100ms)
void timer1_handler(void){
gpio_toggle(led1_pin);
}
// 定时器2中断(20ms)
void timer2_handler(void){
gpio_toggle(led2_pin);
}
优势:
- 真正的并行执行
- 支持优先级抢占
- 实时性好
劣势:
- 受硬件定时器数量限制
- 不同MCU间移植性差
3.2 软件多定时器
更通用的方案是软件模拟多个定时器,通过链表管理任务:
c复制struct task {
uint32_t period; // 执行周期
uint32_t count; // 当前计数值
uint8_t flag; // 触发标志
void (*cb)(void); // 回调函数
struct task *next;
};
struct task *task_list = NULL;
void task_add(struct task *t) {
t->next = task_list;
task_list = t;
}
void systick_handler(void) {
struct task *p = task_list;
while(p) {
if(--p->count == 0) {
p->count = p->period;
p->flag = 1;
}
p = p->next;
}
}
int main(void) {
// 初始化任务
struct task t1 = {100, 100, 0, led1_toggle, NULL};
struct task t2 = {20, 20, 0, led2_toggle, NULL};
task_add(&t1);
task_add(&t2);
while(1) {
struct task *p = task_list;
while(p) {
if(p->flag) {
p->flag = 0;
p->cb();
}
p = p->next;
}
}
}
优化技巧:
- 使用双向链表便于任务删除
- 添加任务时按周期排序,提高tickless效率
- 关键操作关中断保证原子性
4. 状态机实现多任务
状态机非常适合处理非周期性的任务流程,如传感器数据采集:
c复制enum {S_INIT, S_START_CONV, S_WAIT_CONV, S_READ_DATA};
void temp_task(void) {
static uint8_t state = S_INIT;
static uint32_t timestamp;
switch(state) {
case S_INIT:
sensor_init();
state = S_START_CONV;
break;
case S_START_CONV:
sensor_start_conv();
timestamp = get_tick();
state = S_WAIT_CONV;
break;
case S_WAIT_CONV:
if(get_tick() - timestamp >= 20) { // 非阻塞等待20ms
state = S_READ_DATA;
}
break;
case S_READ_DATA:
float temp = sensor_read();
display_show(temp);
state = S_START_CONV;
break;
}
}
状态机设计要点:
- 每个状态应尽量简短
- 状态转移条件明确
- 避免全局变量,使用静态变量保存状态
5. 事件驱动框架
5.1 OSAL框架
OSAL(Operating System Abstraction Layer)通过事件集管理任务:
c复制typedef struct {
uint8_t task_id;
uint16_t events;
void (*event_handler)(uint8_t, uint16_t);
} osal_task_t;
void osal_run_system(void) {
while(1) {
osal_task_t *t = get_active_task();
if(t && t->events) {
uint16_t events = t->events;
t->events = 0;
if(t->event_handler) {
t->event_handler(t->task_id, events);
}
}
}
}
5.2 QP/C框架
QP/C是成熟的事件驱动框架,支持层次状态机:
c复制Q_DEFINE_THIS_MODULE("main");
typedef struct {
QActive super;
} App;
static QState initial(App * const me) {
return Q_TRAN(&active);
}
static QState active(App * const me) {
switch(Q_SIG(me)) {
case Q_ENTRY_SIG:
return Q_HANDLED();
case BUTTON_PRESSED_SIG:
return Q_TRAN(&processing);
}
return Q_SUPER(&QHsm_top);
}
int main(void) {
App app;
QActive_ctor(&app.super, Q_STATE_CAST(&initial));
QF_run();
return 0;
}
6. 协程方案
6.1 Protothreads宏协程
c复制#include "pt.h"
static int protothread1(struct pt *pt) {
PT_BEGIN(pt);
while(1) {
PT_YIELD(pt);
printf("Protothread running\n");
}
PT_END(pt);
}
int main(void) {
struct pt pt1;
PT_INIT(&pt1);
while(1) {
protothread1(&pt1);
}
}
6.2 脚本化协程
更高级的脚本组件可以简化状态管理:
c复制void task_script(struct script *s) {
SCRIPT_BEGIN(s);
SCRIPT_DELAY(100); // 延迟100ms
led_on();
SCRIPT_WAIT_EVENT(BUTTON_PRESSED, 200); // 等待按键,超时200ms
if(SCRIPT_TIMEOUT()) {
printf("Timeout!\n");
}
SCRIPT_END();
}
7. 裸机多任务优化技巧
7.1 低功耗实现
通过合理设计可以实现类似RTOS的空闲任务:
c复制void osal_main_loop(void) {
while(1) {
if(!has_active_task()) {
__WFI(); // 进入低功耗模式
}
process_events();
}
}
7.2 Tickless技术
动态调整系统滴答中断间隔:
c复制void configure_next_wakeup(uint32_t ticks) {
if(ticks > MAX_TICKS) ticks = MAX_TICKS;
SysTick->LOAD = ticks - 1;
SysTick->VAL = 0;
}
uint32_t get_next_wakeup(void) {
struct task *p = task_list;
uint32_t min = MAX_TICKS;
while(p) {
if(p->count < min) min = p->count;
p = p->next;
}
return min;
}
8. 方案选型指南
- 简单控制:前后台标志位
- 周期性任务:软件定时器
- 流程控制:状态机
- 复杂系统:事件驱动框架
- 协作式多任务:协程
- 低功耗需求:Tickless+空闲休眠
实际项目中常组合使用多种方案,例如主框��使用事件驱动,特定模块使用状态机或协程。选择时需权衡实时性、资源占用和开发效率。
