在进行嵌入式设备开发时,一个清晰、高效的软件框架是成功的一半。今天要分享的,就是一个在单片机(MCU)上广泛使用的通用调度器框架——基于任务和事件的OSAL调度器。
OSAL,全称操作系统抽象层(Operating System Abstraction Layer)。这个概念最初由德州仪器(TI)在其ZigBee协议栈Z-Stack中引入。严格来说,它并非一个完整的操作系统,但它巧妙地实现了一套事件驱动的任务调度机制,为资源受限的单片机编程提供了结构化的解决方案。
在这个框架中,事件(Event)是最基本的执行单位,而多个相关的事件则组合成一个任务(Task)。整个调度过程可以概括为:当某个事件(比如定时器超时、收到数据)发生时,OSAL负责将这个事件分配给能够处理它的任务;接着,任务会判断事件的类型,并调用对应的事件处理函数来完成具体操作。
其核心工作流程如下图所示:

这个OSAL调度器主要实现了两大核心功能:任务调度和时间管理。
- 任务调度:它通过一个任务事件数组
tasks_events 来管理各个任务的状态,并通过任务处理函数数组 tasks_arr 来关联具体的处理逻辑。开发者可以使用 osal_set_event() 和 osal_clear_event() 函数来动态设置或清除某个任务的事件标志,从而实现任务的立即执行或取消。
- 时间管理:OSAL通常利用MCU的硬件定时器(如STM32的SysTick)作为时间基准,在此之上虚拟出多个软件定时器。通过
osal_start_timer() 等函数,可以方便地设置延时任务事件,实现了周期或单次触发的定时功能。
这套调度器的代码结构清晰,主要由以下几组C/C++源文件构成:

osal.c 和 osal.h:这是调度的核心,实现了任务的注册、事件循环调度,并对外提供了事件设置/清除的接口函数。
osal_clock.c 和 osal_clock.h:负责与硬件时钟对接,更新系统时基,并检查软件定时器是否超时。
osal_timers.c 和 osal_timers.h:采用链表管理所有的软件定时器实体,提供了启动和停止定时器的底层操作。
OSAL调度器如何使用?
理解其执行流程是使用的第一步。下面的流程图清晰地展示了从初始化到事件处理的完整过程:

系统启动后,首先进行板级外设初始化和OSAL任务初始化(通常在 main 函数中完成)。随后,程序进入主循环,不断调用 run_system() 函数。这个函数会更新定时器、查找就绪的任务事件,并执行对应的处理函数,执行完毕后再清除事件标志。
run_system() 的核心实现逻辑如下,它展示了如何查找就绪任务并安全地调用事件处理函数:
void run_system(void) {
unsigned char idx = 0;
osal_time_update(); //更新系统时间,查找定时器任务
do {
if (tasks_events[idx]) break; //查找当前就绪的任务事件
} while (++idx < tasks_cnt);
if (idx < tasks_cnt) { //当前有任务就绪
unsigned short events;
_disable_irq();
events = tasks_events[idx];
tasks_events[idx] = 0;
_enable_irq();
events = (tasks_arr[idx])(idx, events); //调用事件处理函数
_disable_irq();
tasks_events[idx] |= events; //保存未处理的事件
_enable_irq();
}
}
实战:添加一个看门狗任务
假设我们需要添加一个独立看门狗(IWDG)任务,用于监控系统运行并定时“喂狗”。我们可以按以下步骤操作:
- 在
osal.h 中为看门狗任务定义一个唯一的任务ID,例如 IWDG_TASK_ID。
- 创建任务源文件
iwdg_task.c 和头文件 iwdg_task.h。在头文件中定义该任务所包含的事件,例如定时喂狗事件和复位事件。

- 在
iwdg_task.c 中实现任务初始化函数和事件处理函数。每个任务都必须提供这两个标准接口。

在 iwdg_task_init() 函数中,我们通过 register_task_array() 将任务ID与处理函数 iwdg_task() 绑定,初始化硬件看门狗外设,最后调用 osal_start_timer() 启动一个周期性的喂狗定时事件。
关键API接口
在实际开发中,以下几个API函数会被频繁使用:
-
立即事件管理:

osal_set_event(task_id, event_flag) 和 osal_clear_event(task_id, event_flag) 用于立即设置或清除某个任务的事件。事件一旦被设置,在下次 run_system() 循环中就会立即得到执行。
-
定时器事件管理:

osal_start_timer(task_id, event_id, timeout_value, reload_timeout_value) 和 osal_stop_timer(...) 用于管理延时任务。timeout_value 是首次执行的超时时间,reload_timeout_value 是后续周期执行的重载时间。若将重载时间设为0,则该定时事件仅触发一次。
需要说明的是,原生的TI OSAL还包含一套消息队列机制,用于任务间通信。但为了让调度器更加轻量、易于理解和移植,本文分享的这个版本暂未集成此机制。多个任务之间的数据交换,目前仍通过共享全局变量(即共享内存)的方式来实现。
这个基于任务和事件的OSAL调度器,已经应用在多个实际物联网设备端的MCU项目中,证明了其稳定性和实用性。
完整的项目源码已开源,你可以通过以下地址获取并深入了解:
如果你正在为单片机项目寻找一个结构清晰、不依赖复杂RTOS的轻量级调度方案,不妨试试这个OSAL框架,或许它能为你带来全新的开发体验。
|