事件驱动的实时嵌入式系统的设计和实现
摘 要 嵌入式实时操作系统具有嵌入式软件共有的可裁剪、低功耗等特点;而实时操作系
统,可以满足系统对实时性的要求。但嵌入式实时系统需要增加额外的系统开销,随着系
统功能的增加,逐渐增加的开销将不容忽视。对于某些功能简单的嵌入式系统,本文提出
了一种实时嵌入式系统的设计 方法 ,采用简单的方法和代码来建立一个快速、有效地系
统。该嵌入式软件系统主要包括主控循环系统、事件驱动任务、周期循环任务及软件计数
器。在冰箱嵌入式系统中进行了具体实现,满足实时性的同时降低了对系统资源的占用率
。 关键字 主控循环;事件驱动任务;周期任务;软件计时器 1 引言 嵌入式实时系
统中采用的操作系统,我们称为嵌入式实时操作系统,它既是嵌入式操作系统,又是实时
操作系统。作为一种嵌入式操作系统,它具有嵌入式软件共有的可裁剪、低功耗等特点;
而作为一种实时操作系统,可以满足系统对实时性的要求[1]。 但是,使用嵌入式实时
操作系统还需要额外的 ROM/RAM 开销,2%~5%的 CPU 额外负荷以及内核的费用;同时
如果任务之间抢占 CPU 控制权处理不好,会产生系统崩溃、死机等严重后果;而且随着
对嵌入式实时操作系统需求的增长,将越来越多的功能添加到系统中,使其变得越来越臃
肿。对许多小型或中等嵌入式设备,尤其是对成本敏感的小型设备,使用嵌入式实时操作
系统会大大增加设备的成本,因而在本文中提出一种实时嵌入式软件系统的设计方法。本
文的设计思想主要包括主控循环系统、事件驱动任务、周期循环任务及软件计时器四部分
。2 系统设计 主控制循环 该系统将软件分成独立的任务模块,支持事件驱动任务
,将事件驱动任务输入到事件队列,当接收到恰当地触发事件时,才开始执行。否则,使
其空闲,只占用极少地处理时间;以预置地速度执行周期任务(即不需要触发就可执行地
任务)[3]。根据需要,执行速度有准确计时和相对计时(与每次主控循环的执行速度相关
联)两种方式。 该系统是非抢占式系统(其他的任务不会无法中断正在运行的任务),
不需要使用信号量来保护数据。只有当任务条目函数返回数值时,才会中断所有任务。例
如,一个有键盘、LCD、RS-232 端口、多个 I/O 和串行打印机的嵌入式系统。I/O 状态的
每次改变将导致发送一条 RS-232 信息、打印输出和 LCD 更新。RS-232 信息的接收将导
致打印输出、LCD 更新和输出状态更新。 程序 1 主控循环 int main(void){ Init_All();
for (;;) { IO_Scan(); IO_ProcessOutputs(); KBD_Scan(); PRN_Print();
LCD_Update(); RS232_RecEive(); RS232_Send(); TMR_Process(); } //
此处可以添加异常处理代码 return (0);} 在程序 1 中,无穷循环中的每个函数调用代表
一个独立的任务,无论执行哪个函数,每个任务必须在合理的时间内返回。 该系统的主
要工作是事件驱动任务。每个任务都有一个输入事件队列。例如,IO_ProcessOutputs 是
事件驱动任务,负责控制输出状态,当输出没有状态改变时,该任务处于空闲状态。需要
启动输出时,则给该任务发送一条事件消息。在该系统中,有三个任务会向
IO_ProcessOutputs 发送事件消息: ● 输入扫描器(IO_Scan)任务,当输入状态改变导
致输出状态的改变; ● RS-232 接收任务,当接收到 RS-232 消息,需要开启或关闭输
出; ● 按键扫描器任务(KBD_Scan),当完成一个条目时,需要开启或关闭输出。 其
它的任务是周期任务,无需触发器即可运行。 有些需要运行地快一些,有些需要慢一点
。例如,扫描输入需要比 LCD 的刷新快。为此需要提供一些任务间通讯的简单方法。当
输入状态发生急剧地变化时,RS-232 无法发送所有的消息。为此,应该降低从 RS-232
传送的 I/O 扫描器任务。这可以使用稳定的执行计数器技术来实现。 除上述功能外,还
需要另一外些重要功能。如使 LCD 上的光标按固定的频率刷新。这些功能由
TMR_Process 间接调用,而不是由主控循环调用。TMR_Process 是主控循环中唯一一个
非用户定义任务。 程序 2 事件输入结构 typedef unsigned int word; typedef struct
{ word InPtr; /*缓冲区头 */ word OutPtr; /* 缓冲区尾 */ word Count;
/* 计数变量*/ EVENT_TYPE Store[BUFFER_SIZE]; /* 数据存储空间
*/} INPUT_EVENT_QUEUE_TYPE; 事件驱动任务 每个事件驱动任务都有一个输入
队列作为循环缓冲区。提供两个功能:PutEvent 和 GetEvent。PutEvent 将事件插入队列
中,GetEvent 从队列中取出事件。[5]其中,任务独占 GetEvent,其他任务无法调用。参
见程序 2。 对每个任务而言,EVENT_TYPE 结构是唯一的。换句话说,任务本身决定其
期望接收的事件格式。例如,在 IO_ProcessRequests 任务中,需要包括输出数量及其新
状态。在打印任务中,只需要使 PRN_EVENT_TYPE 足够大以存储一字串。因为每个任务
的 EVENT_TYPE 不尽相同,用户需要根据 INPUT_EVENT_QUEUE_TYPE 为每个事件驱动任
务定义不同的结构。而且,每个任务都有自己的 GetEvent、PutEvent 和初始化函数。
循环缓冲区允许异步读、写缓冲区,并将其存储到 BUFFER_SIZE 目录中。任何任务(
包括任务本身)都可以将 EVENT_TYPE 事件插入到输入循环缓冲区当中。所有任务需要创
建 OUTPUT_EVENT_TYPE 事件并调用 OUTPUT_ PutEvent,如程序 3 所示。 程序 3 创建
OUTPUT_EVENT_TYPE 事件// 在 RS232 模块中 OUTPUT_EVENT_TYPE OutputEvent;
= 1; // 新状态 - = 1; // 开启输出
OUTPUT_PutEvent(&OutputEvent); // 输入一个事件 程序 4 发送事件到任务// 从主
控制循环中调用事件 void IO_ProcessOutputs(void){word ret;OUTPUT_EVENT_TYPE
OutputEvent;// 此处通常为执行计数处理// ..// 执行计数处理结束
if ((ret = OUTPUT_GetEvent(&OutputEvent) != EMPTY){ //缓冲区非空 // 处理
OutputEvent,开启/关闭所需输出 IO_OutputStateChange(,
) }}用户只需执行输出控制任务,其它的工作由 OUTPUT_ PutEvent
函数来完成。如程序 5 所示。 周期任务 该系统可以从主控循环中调用任一函数,
但必须注意两个 问题 :不能频繁地调用任务;不能长时间地延后其它任务的运行。程序
5 计数器执行处理 void LCD_Process(void){ #ifdef EXACT_TIMING disable(); //暂时禁
止中断#endif if (LCD_ExecCounter == TASK_DISABLED) {#ifdef
EXACT_TIMING enable();#endif return; }#ifdef EXACT_TIMING // 处理
此处可能存在的对中断例程的抢占……. return; } //运行自己定义的任务并重
新载入执行计数…}interrupt void TIMER_IRQ_10ms(void){ // 其他任务}对第一个问题,
有一种机制可延缓任务的执行。分为两种情况:准确计时和相对计时。为此需要两个参数
:执行计数器及重新加载数值。执行计数器从重新加载数值递减。当计数器为 0 时,调用
任务,否则退出任务的记录函数。[4]参见程序 4。在准确计时系统中,应避免定时中断抢
占任务。在多数情况下,这不是问题,因为 16 位或 32 位的读和写是原子操作。最简单
的解决 方法 是当程序处理执行计数器时,在某一段时间内中止所有的中断。参见程序 4
。其中需要说明地是:(1) 在准确计时系统中,执行计数器以固定的频率递减;在相对计
时系统中,任务自己递减计数器。在准确频率系统中,可以确定任务执行的频率。将
LCD_TASK_FREQUENCY 设为 100,使用 10ms 中断,可以确定该任务的执行频率为:在
LCD 任务执行计数器递减为 0 之前,每秒钟加上在该任务之前的其它任务的执行时间。
(2) 将 TASK_DISABLED 尽可能的定义为最大的无符号整数。将执行计数器设为
TASK_DISABLED 以中止该任务,直至有进一步的需求。可以在其它任务中实现这种操作
。例如,重要事件可以中止打印进程,直至有进一步的通知(任务间通讯的简单形式)。
(3) 在准确计时系统中,10ms 中断处理了大量的工作。但是对其中的一小部分任务,处
理器无法进行比较,因此将该中断设为较低的优先权或允许其它中断抢占。 目前 的问题
在于是否需要引入一些更简洁的机制(如德耳塔队列-delta queue)以防止在一次中断中
有太多的计数器递减。但由于该系统的任务数量不超过 30 个,因而不需要德尔塔队列。
软件计时器 软件计时器使该系统具有了真正的多任务性。有几百个事件需要在固
定的时间内激活一次或周期型激活。[5]大多数这样的事件需要准确计时,使得 10ms 的中
断非常困难。主控循环将变得冗长和繁杂。因此需要一个简洁的解决方法。 程序 5 软
件计时器模块的 应用 接口 word TMR_InstallTimeoutHandler(word timer_handle,void
(*timeout_func)(word,dword))word TMR_Start (word timer_handle,word timeout,dword
parameter);word TMR_Stop (word timer_handle); 程序 5 中的软件计时器模块为应用
任务提供了三个基本函数。用户为每个计时器定义的间歇时间函数,必须在 TMR_Start
和 TMR_Stop 函数调用之前装入。通过调用 TMR_InstallTimeoutHandler 函数来完成。随
后可以使用 TMR_Start 和 TMR_Stop 函数来启动或停止计时器。 在该系统中,10ms 中
断是终止计时器循环缓冲区的唯一计时器。由于在给定的时间内有几百个软件计时器在运
行。因此应以更加合理地方式运行该部分。在每个 10ms 中断,递减几百个计时计数器是
无法接受的。使用德尔塔队列可以解决这一问题。根据间歇时间值,将计时器插入德尔塔
队列,只递减将要终止的计时器[5]。3 结论与展望 本文提出了一种简单、快速的嵌入
式系统,并在冰箱嵌入式软件设计中予以实现。使用主控循环进行任务控制和处理,系统
设计了事件驱动任务和周期性任务类型,并利用软件计数器控制周期性任务的执行。经过
试验,冰箱嵌入式系统占用的存储空间大大缩减,并且效率和稳定性都有所提高。该论文
的思想可以快速地建立一个复杂度合理的管理系统,特别适用于对成本敏感的小型设备,
可以使其具有便利灵活、性能价格比高的特点。 但本文的设计思想仅适用于功能较少、
需处理任务数量较少的小型设备,对于功能复杂的嵌入式应用,如含 网络 等功能的嵌入
式系统,还需采用通常的嵌入式实时操作系统。 参考 文献 [1] 王鹏,尤晋元,朱鹏,敖
青云译.操作系统:设计与实现.第二版.北京: 电子 工业 出版社,2004[2] 杨立峰.
LINUX 嵌入式实时操作系统开发与应用.重庆工学院,2002[3] ,
,.Engineering and analysis of fixed priority schedulers IEEE
.1993 (9):920-934[4] Donald .On Non-Preemptive
Scheduling of Periodic and Sporadic Tasks. Proceedings of the Twelfth IEEE Real-Time
Systems Symposium.1991 (10)129-139[5] Kevin Jeffay.Analysis of a Synchronization
and Scheduling Discipline for Real-Time Tasks with Preemption Constraints.1989(5):
295-305