实时程序必须快速响应实时发生的事件。没有实时操作系统(RTOS),事件的创建就不会那么轻松。随着更多事件被添加,复杂性和程序复杂程度增加,就凸显了RTOS的好处。
嵌入式和标准C程序都是从main C函数开始执行。在嵌入式应用中,main通常按死循环执行,可以看做是一个连续执行的单任务。例如:
void main (void)
{
while (1) /* repeat forever */
{
do_something (); /* execute the do_something 'task' */
}
}
在这个示例中,do_something函数可以看做是一个单任务。此处只有一个任务执行,不需要多任务性能或多任务此处系统。
很多复杂的C程序可以执行一个伪多任务,多个函数(或任务)在一个循环中执行。例如:
void main (void)
{
int counter = 0;
while (1) /* repeat forever */
{
check_serial_io (); /* check for serial input */
process_serial_cmds (); /* process serial input */
check_kbd_io (); /* check for keyboard input */
process_kbd_cmds (); /* process keyboard input */
adjust_ctrlr_parms (); /* adjust the controller */
counter++; /* increment counter */
}
}
在这个示例中,每一个函数执行一个单独的操作或任务。函数(或任务)按顺序一个一个的执行。
由于增加了更多的任务,调度开始成为一个问题。例如,如果process_kbd_cmds函数执行很长时间,main循环需要很长时间才能返回到check_serial_io函数,串口数据将会丢失。当然,check_serial_io函数可以在main循环中多次调用来解决这个问题,但是最终这种方式是行不通的。
当使用RTX51 Tiny时,在应用中为每个任务创建分离的函数。例如:
void check_serial_io_task (void) _task_ 1
{
/* This task checks for serial I/O */
}
void process_serial_cmds_task (void) _task_ 2
{
/* This task processes serial commands */
}
void check_kbd_io_task (void) _task_ 3
{
/* This task checks for keyboard I/O */
}
void process_kbd_cmds_task (void) _task_ 4
{
/* This task processes keyboard commands */
}
void startup_task (void) _task_ 0
{
os_create_task (1); /* Create serial_io Task */
os_create_task (2); /* Create serial_cmds Task */
os_create_task (3); /* Create kbd_io Task */
os_create_task (4); /* Create kbd_cmds Task */
os_delete_task (0); /* Delete the Startup Task */
}
这个示例中,每个函数定义为一个RTX51 Tiny任务。RTX51 Tiny程序没有main C函数。而是从task 0开始执行。典型应用中,task 0轻松的创建其它的任务。
RTX51 Tiny使用和管理目标系统的资源。RTX51 Tiny的很多方面可以通过project basis在project中配置。
- 定时器滴答(Timer Tick)中断
主要论述驱动RTOS的周期性时钟中断。
- 任务
论述RTX51 Tiny任务的概念。
- 任务管理
论述RTX51 Tiny如何管理任务及各种任务的状态。
- 事件
论述如何用事件触发任务。
- 任务调度程序
提供调度管理任务的详细信息。
- Round-Robin(循环)任务切换
描述如何使用round-robin任务切换。
- Cooperative(联合)任务切换
描述如何使用cooperative任务切换。
- Idle(空闲)任务
描述RTX51 Tiny的idle任务
- 堆栈管理
提供RTX51 Tiny管理8051硬件堆栈的详细信息。
RTX51 Tiny使用标准8051定时器0(模式1)创建周期中断。这个中断是RTX51 Tiny的定时器滴答。为RTX51 Tiny库程序指定的超时和区间值需要使用RTX51 Tiny时钟滴答测试。
默认的,RTX51定时器滴答中断每10000个机器周期出现一次。因此,对于运行在12MHz的标准的8051,定时器滴答周期中是0.01秒或100Hz的频率(12MHz/12/10000)。只可以在CONF_TNY.A51配置文件中修改。
注意:
- 可以在RTX51 Tiny滴答中断中添加自己的代码。更多信息请参考“CONF_TNY.A51配置文件”。
- RTX51 Tiny如何使用中断系统的详细信息请参考“综述”中的“中断”章节。
RTX51 Tiny主要的任务切换开关。要创建一个RTX51 Tiny程序,必须创建一个有一个或多个任务函数的应用。以下的详细信息可以帮助你快速理解RTX51。
- 在C程序设计语言中定义任务,使用Keil C51编译器支持的新关键字。
- RTX51 Tiny将每一个任务维持一种状态(Running(运行),Ready(就绪),Waiting(等待),Deleted(删除)或Time-Out(超时))。
- 同一时间只有一个任务是Running状态。
- 多个任务可以是Ready,Waiting,Deleted或Time-Out状态。
- Idle任务永远是Ready状态,所有自定义的任务都是阻塞的。
每一个RTX51 Tiny任务都有一个确定的状态表明任务处理方式。
状态 | 描述 |
RUNNING | 当前执行的任务,状态为RUNNING状态。同时只能有一个任务是这个状态。os_running_task_id 返回当前执行任务的任务号 |
READY | 就绪的任务是READY状态。一旦运行的任务处理完成,RTX51 Tiny选择并开始执行下一个ready的任务。任务可以使用os_set_ready或isr_set_ready函数通过设置其ready标志直接置为ready状态(即使任务在等待超时或信号)。 |
WAITING | 正在等待时间的任务,其状态是WAITING。一旦事件发生,任务将切换到READY状态。os_wait函数用于将任务置为WAITING状态。 |
DELETED | 那些从未启动的任务或已被删除的任务为DELETED状态。os_delete_task程序将已启动的任务(使用os_create_task)置为DELETED状态。 |
TIME-OUT | 被Round-Robin超时中断的任务为TIME-OUT状态。这个状态相当于Round-Robin 程序的READY 状态。 |
在实时操作系统中,事件可以用来控制程序中任务的执行。一个任务可以等待一个事件或为其它任务设置事件。
Os_wait函数允许一个任务等待一个或多个事件。
- Timeout 对于一个可等待的任务,Timeout是一个普通的事件。超时仅仅是一些时钟周期。当任务等待超时的时候,其它任务可以执行。一旦指定数量的时钟周期过去,任务可以接着继续执行。
- Interval 是一个超时的变种。Interval像一个超时,除非指定数量的时钟周期与最后一次被任务调用的os_wait函数相关。Interval可以用于创建一个按周期执行的任务,同步调度(如:每秒一次),不管调用os_wait函数之间需要多长时间。如果指定数量的时钟周期已经过(从os_wait函数最后一次执行),任务立即重新执行——没有其它任务执行。
- Signal 是任务间通信的一种简单形式。一个任务可以等待另一个任务发送一个信号(os_send_signal和isr_send_signal)。
- 每个任务都有一个Ready标志,可以被其它任务(os_set_ready和isr_set_ready)设置。等待timeout、interval或signal的任务可以通过设置ready标志开始执行。
每一个任务有一个由RTX51 Tiny维持的关联的事件标志。以下事件选择器可以被os_wait函数使用来指定等待的是哪个:
事件选择器 | 描述 |
K_IVL | 等待指定的interval |
K_SIG | 等待signal |
K_TMO | 等待指定的timeout |
当os_wait函数返回,发送的时间由返回值说明:
返回值 | 说明 |
RDY_EVENT | 任务的ready标志被设置 |
SIG_EVENT | 接收到一个signal |
TMO_EVENT | 超时已过或interval已消逝 |
Os_wait函数可以等待以下事件的组合:
- K_SIG | K_TMO: os_wait延迟任务,直到signal发送给它或指定数量的时钟周期已经消逝。
- K_SIG | K_IVL: os_wait延时任务,直到signal发送给它或指定的interval已消逝。这种情况下,必须使用os_reset_interval函数消除定时器延时问题。
注意:
- K_IVL和K_TMO事件选择器不能联合使用。
任务调度为任务分配处理器。RTX51 Tiny调度程序使用以下规则决定运行那个任务:
以下情况会使当前任务中断:
- 任务调用os_switch_task函数或其它ready状态的任务。
- 任务调用os_wait函数,并且指定的事件没有发生。
- 任务执行的事件超过定义的round-robin时间片。
以下情况会启动另一个任务:
- 没有任务在运行。
- READY状态或TIME-OUT状态的任务被启动。
Round-Robin任务切换
RTX51 Tiny可以配置使用Round-Robin多任务(或任务切换)。Round-Robin允许多个任务伪并行执行。任务不是正真的同时执行,而是按时间片(可用的CPU时间被分成时间片,RTX51 Tiny为每一个任务分配一个时间片)执行。如果时间片足够短(仅几个毫秒)任务看起来就像同时执行。
任务在其连续的时间片执行(除非任务的时间片已放弃)。此外,RTX51 Tiny切换到下一个ready状态的任务。连续的时间片可以由RTX51 Tiny “配置”进行定义。
以下示例演示一个简单的使用Round-Robin多任务的RTX51 Tiny程序。这个程序中的两个任务是计数循环。RTX51 Tiny开始执行任务0,是一个名为job0的函数。这个函数创建一个任务名为job1。Job0执行完它的时间片后,RTX51 Tiny切换至job1。Job1的时间片执行完后,RTX51 Tiny切换至job0。程序无限期循环。
#include <rtx51tny.h>
int counter0;
int counter1;
void job0 (void) _task_ 0 {
os_create (1); /* mark task 1 as ready */
while (1) { /* loop forever */
counter0++; /* update the counter */
}
}
void job1 (void) _task_ 1 {
while (1) { /* loop forever */
counter1++; /* update the counter */
}
}
注意:
- 可以使用os_wait函数或os_switch_task函数让RTX51 Tiny切换至其它任务而不需要等待任务的时间片执行完。os_wait函数暂停当前任务(更改为WAITING状态),直到指定的事件发生(任务更改为READY状态)。这期间,其它的任务可以执行。
Cooperative任务切换
如果禁用Round-Robin多任务,则必须设计并执行自己的任务,保证它们协作进行。通常,必须在每个任务中调用os_wait函数或os_switch_task函数。这些函数指示RTX51 Tiny切换至其它任务。
os_wait函数和os_switch_task函数的不同点在于,os_wait函数允许任务等待事件,os_switch_task函数直接切换到其它ready的任务。
Idle任务
当没有任务是ready状态时,RTX51 Tiny执行idle任务。Idle任务是一个简单的死循环。例如:
SJMP $
一些8051兼容设备提供一个idle模式通过停止程序执行来降低功耗,直到一个中断发生。在这种模式下,所有外设,包括中断系统依然继续操作。
RTX51 Tiny允许在Idle任务中发起idle模式(当没有任务处于ready状态)。当RTX51 Tiny时钟滴答中断(或其它中断)发生,微控制器恢复程序执行。被Idle任务执行的代码可以在CONF_TNY.A51配置文件中使能或配置。
RTX51 Tiny为每个任务维持一个堆栈,仅使用8051的内部数据存储器(IDATA)。当任务运行时,它可以获得最大数量的堆栈空间。当任务切换出现时,前面的任务堆栈会被压缩和重新定位,当前任务的堆栈会被扩展和重新定位。
下图说明一个使用3个分离堆栈的示例程应用的内部存储器的分布。
?STACK符号表示堆栈的起始地址。在示例中,处于堆栈下面的对象包含全局变量、寄存器和可位寻址存储器。剩余的存储器用于任务堆栈。存储器的顶部可以在“配置”中指定。