原创 RTX51Tiny介绍应用和怎样实现信号量操作

2007-3-9 15:54 5948 6 8 分类: MCU/ 嵌入式
1.RTX51介绍

RTX51有2个模式:RTX51完全模式和最小模式。RTX51最小模式版是RTX51完全版的一个子集,可以很容易地运行在8051系统上,而不需要外部RAM(DXATA)。RTX51完全模式有4个任务优先级,可以和中断函数并行处理,各个任务之间通过使用“邮箱”系统来进行信号和消息的传递,可以从内存池中申请和释放内存;同时,可以强制一个任务停止执行,等待一个中断,或者是其它中断传来的信号量或者消息。RTX51对系统硬件的要求如表1所列。


1.1 RTX51任务


RTX51区分2类任务:快速任务和标准任务。快速任务有很快的响应速度,每个快速任务使用8051一个单独的寄存器组,并且有自己的堆栈区域。RTX51支持最大同时有3个快速任务。标准任务需要多一点的时间来进行任务切换,因此使用的内部RAM相对快速任务要少,所有的标准任务共用1个寄存器组和堆栈。当任务切换的时候,当前任务的寄存器状态和堆栈内容转移到外部存储器中。RTX51支持最大16个标准任务。


RTX51任务状态:


①运行(RUNNIGN)——当前正在运行的任务处于RUNNING状态,同一时间只有1个任务可以运行。


②就绪(READY)——等待运行的任务处于READY状态,在当前运行的任务退出运行状态后,就绪队列中优先级最高的任务进入到运行状态。


③阻塞(BLOCKED)——等待一个事件的任务处于BLOCKED状态,如果事件发生且优先级比正在运行的任务高,此任务进入运行状态;如果优先级比正在运行的任务低,此任务进入READY状态。


④删除(DELETED)——没有开始的任务处于删除状态。


⑤任务切换——RTX51包含一个事件驱动的任务切换机制,它能够按照任务的优先级进行切换,也就是抢占式多任务系统;另外还有一个可选的时间片轮转切换任务模式,在时间片轮转模式下,同级别的任务是按照时间片分别占用CPU的。RTX51任务有4个优先级:0、1、2可以分配给标准任务,优先级3是为快速任务保留的。每个任务都可以等待事件的发生,而并不增加系统的负担;任务可以等待消息、信号、中断、超时事件或者它们的组合。任务切换是按照一定规则进行的,包括:进入到“就绪”状态的优先级高的任务先执行;如果“就绪”状态的几个任务是同一个优先级,那么最先进入“就绪”状态的先执行。


RTX51任务切换图如图1所示。


点击看大图



  1.2 RTX51事件


◇超时(timeout):挂起运行的任务指定数量的时间周期。


◇间隔(interval):类似于超时,但是软件定时器没有复位,典型应用是产生时钟。


◇信号(signal):用于任务内部同步协调。


◇消息(message):适用于RTX51 Full,用于信息的交换。我们可以把一个消息交送到一个特定的邮箱。消息由2字节组成,可以是用户按照自己的需求定主的数据,也可以是指向数据的指针。如果邮箱的消息列表满,而且是中断发送消息,这个消息将会丢失;如果是任务发送消息,那么任务将会进入到等待状态,直到邮箱重新有了位置可以接收这一条消息。邮箱是按照FIFO的原则来管理消息的,如果几个任务都在等待接收消息,那么最先进入等待接收队列的将接收消息。一个邮箱最多可以存储8条消息。当邮箱满的时候,最多只能有16个等待任务。


◇中断(interrup):适用于RTX51 Full,信号量用于管理共享的系统资源。通过使用“令牌”,允许在同一时刻只有一个任务使用某些资源。如果几个任务申请访问同一个资源,那么首先提出申请的将允许访问,其它的任务进入等待队列,直到第1个任务操作完毕,下一个任务才能继续。


Os_wait()函数挂起一个任务来等待一个事件的发生。这样可以同步2个或几个任务。它的工作过程如下:当任务等待的事件没有发生的时候,系统挂起这个任务;当事件发生时,系统根据任务切换规则切换任务。


1.3 RTX51中断处理


RTX51完全模式提供2种方法来处理中断:一种是C51的中断函数,另一种是RTX51的断。它又可以分为快速任务中断和标准任务中断。对于中断函数这种方法,它同时也可以在不使用RTX51的情况下使用,当中断发生的时候,程序就跳到了相应的中断函数,它和正在运行的任务是互相独立的,中断的处理是在RTX51系统之外,和任务切换规则没有关联。对于任务中断的方法,不管使用快速是标准任务来处理中断,如果中断发生,等待中断的任务就从“等待”状态进入到就绪状态,并按照任务切换规则进行切换。这种中断处理是完全集成在RTX51的内部,硬件中断事件的处理和信号、信息的处理是完全相同的。在系统响应中断使能寄存器,这样才能遵守任务的切换规则并保证中断程序的无误进行。必须注意中断使能寄存器是由RTX51完全控制的,禁止用户手动的修改。


点击看大图



  2.应用实例


以下给出RTX51在单片机控制的GPS接收板上的应用。


(1)系统硬件组成


单片机W77E58,快速8051内核、32KB ROM、1KB的XDATA RAM,符合使用RTX51的硬件要求;键盘、GPS定位模块、液晶显示模块。


(2)系统软件构成


软件运行环境KEIL uVision2 6.20集成开发环境加上RTX51完成版。任务KEY-BOARD,监测键盘的情况,如果有按键按下,把按键的编码发更新到邮箱1,外部中断1等待接收GPS数据,并把数据存储起来,向DISPLAY任务发出信号。任务DISPLAY根据接收到的不同的信号和消息,进行处理。任务SEND-OUT,把接收到的数据进行处理,并发送出去。任务VOICE进行语音输出。


系统硬件、软件结构如图2所示。


下面给出简写的源程序:


#include //包含RTX51头文件


#define DISPLAY 0


#define SEND_OUT1


#define KEY_BOARD2


#define VOICE3


void main(void)


{ init system(); //系统初始化


os start system(DISPLAY); //启动RTX51


}


void task0(void)_task_DISPLAY


{ os_set_slice(1000); //设置时间片大小


os_enable_isr(0); //允许外部中断0


os_creat_task(SEND_OUT); //启动SEND_OUT任务


os_creat_task(VOICE); //启动VOICE任务


for(;;){


switch(os_wait(K_SIG+K_MBX+1,255,&keyboard))//等待接收信号和键盘消息,分类处理


{display1();break;


case EVENT_MBOX;//当从邮箱接收到数据的时候switch(keyboard)


{ case '1';



os_send_signal(SEND_OUT); //向任务SEND_OUT发送信号



os_send_signal(VOICE);} //向任务VOICE发送信号


…;}


;}


}


void task1(void)_task_SEND_OUT //处理发送数据任务


{while(1)


{ os_wait(K_SIG,255,0) //等待信号


operation_send();


}


void task3(void)_task_VOICE


{while(1){


os_wait_signal(K_SIG,255,0); //等待语音处理信号


voice();}


}


void interrupt(void)interrupt 2 using 1


{ read_gps_data(p_gps_data); //接收数据


isr_send_signal(DISPLAY); //DISPLAY任务发信号
   }


#pragma REGISTERBANK(2) //使用寄存器组2


void task2(void)_task_KEYBOARSD_priority_3//设置为快速任务


{ os_attach_interrupt(0); //绑定任务和外部中断0


while(1){


os_wait(K_INT,255,0); //等待中断的发生


KEY=iic_read_keyboard();


os_send_message(1,KEY,0);}//将键盘编码发送到邮箱1


}



3.信号量

  信号量实际上是一种约定机制,在多任务操作系统内核中普遍使用。信号量可分为二值信号量和计数式信号量。每一个信号量都有一个计数值,它表示某种资源的可用数目。二值信号量的值只能是0和1;计数式信号量的取值范围则由所使用的嵌入式操作系统内核决定。内核根据信号量的值,跟踪那些等待信号量的任务。

  对信号量的操作一般有初始化、等待和释放三种,下面简要介绍一下这三种操作过程。

① 初始化信号量:信号量初始化时要给信号量赋初值,并清空等待信号量的任务表。


② 等待信号量:需要获取信号量的任务执行等待操作。如果该信号量值大于0,则信号量值减1,任务得以继续运行;如果信号量值为0,等待信号量的任务就被列入等待该信号量的任务表。

③ 释放信号量:已经获取信号量的任务在使用完某种资源后释放其信号量。如果没有任务等待该信号量,信号量值仅仅是简单的加1;如果有任务正在等待该信号量,那么就会有一个任务进入就绪态,信号量的值也就不加1。至于哪个任务进入就绪态,要看内核是如何调度的。

4. 在RTX51 Tiny中添加信号量源程序

  RTX51 Tiny采用时间片轮转的办法来调度任务,不支持任务优先级,也不支持信号量。为了在RTX51 Tiny环境中使用信号量,必须另外添加信号量的定义及其操作过程,可以在应用程序中加入以下代码。

#include
#define uchar unsigned char
#define uint unsigned int
#define MAX_SEMAPHORES 3 /* 使用信号量的最大数目 */
/* 定义信号量 */
struct sem_set{
uchar max_count; /* 该信号量的最大计数值 */
uchar count; /* 该信号量的当前计数值 */
uint pending_tasks; /* 等待该信号量任务表 */
} sem_tab[MAX_SEMAPHORES];
/* 初始化信号量 */
#pragma disable
void init_semaphore(uchar sem_id, uchar max_count, uchar count){
sem_tab[sem_id].max_count = max_count;
sem_tab[sem_id].count = count;
sem_tab[sem_id].pending_tasks = 0;
}
/* 等待信号量 */
#pragma disable
char pend_sem(uchar sem_id){
if (sem_tab[sem_id].count > 0) {
sem_tab[sem_id].count? /* 获取信号量 */
return (-1);
}
sem_tab[sem_id].pending_tasks
|=(1 << os_running_task_id()); /* 标记为等待状态 */
return (0);
}
void pend_semaphore(sem_id){
if (pend_sem(sem_id) == 0) {
while (os_wait(K_TMO, 255, 0) != RDY_EVENT);
/*等待,直到该任务就绪*/
}
}
/* 释放信号量 */
#pragma disable
char post_sem(uchar sem_id){
uchar i;
uint temp = 1;
if ((sem_tab[sem_id].count > 0)
||(sem_tab[sem_id].pending_tasks == 0)) {
sem_tab[sem_id].count++; /* 释放信号量 */
return (-1);
}
for (i=0; i<16; i++) {
if ((sem_tab[sem_id].pending_tasks & (temp)) != 0){
/* 查找任务表 */
sem_tab[sem_id].pending_tasks &= ~(1 << i);
return (i); /* 返回等待信号量的任务号 */
}
temp <<= 1;
}
}
void post_semaphore(uchar sem_id){
char task_id;
task_id = post_sem(sem_id);
if (task_id != -1) {
os_set_ready(task_id); /* 任务task_id进入就绪状态 */
os_switch_task();
}
}

  其中函数init_semaphore用于初始化信号量,函数pend_semaphore 和post_semaphore用于等待和释放信号量。

  MAX_SEMAPHORES为应用程序中需要用到信号量的最大数目,根据设计需要做相应的修改。结构体sem_set记录信号量的相关信息,包括该信号量的最大值、当前值以及等待该信号量的任务表。其中,sem_tab[sem_id].pending_tasks中的bit0~bit15分别与任务0~任务15一一对应,如果某一位置位,则表示与之相应的任务正在等待该信号量。函数post_sem总是让等待信号量任务表中任务号最小的那个任务最先得到信号量。

  编译器伪指令#pragma disable保证程序在对信号量进行操作期间不被中断,避免发生错误。

5. 应用举例

  下面通过一个例子来说明在RTX51 Tiny环境下是如何使多个任务共享串口的。
#include
#include
#include
#define uchar unsigned char
#define uint unsigned int
extern void pend_semaphore(uchar sem_id);
extern void post_semaphore(uchar sem_id);
extern void init_semaphore(uchar sem_id, uchar max_count, uchar count);
void task0(void) _task_ 0{
SCON = 0x50;
TMOD |= 0x20;
TH1 = 221;
TR1 = 1;
TI = 1; /* 初始化串行口 */
init_semaphore(0, 1, 1); /* 初始化信号量,最大值为1 */
os_create_task(1);
os_create_task(2);
os_delete_task(0);
}
void task1(void) _task_ 1{
while (1) {
pend_semaphore(0);
puts(“Task1 is using UART!”);
post_semaphore(0);
}
}
void task2(void) _task_ 2{
while (1) {
pend_semaphore(0);
puts(“Task2 is using UART!”);
post_semaphore(0);
}
}

  该程序中的task1和task2轮流使用串口输出数据。程序执行后在串口循环输出如下内容。
Task1 is using UART!
Task2 is using UART!

结 语

  与其它实时多任务内核相比,RTX51 Tiny显得非常小巧,它最大仅占用900字节的程序存储空间,可以在没有任何外部存储器的8051系统上运行。对RTX51 Tiny添加信号量支持后,能够简化程序设计,提高开发效率,降低开发成本。


         经过我的实际使用,很不错。


                                       孙福朋 于   2006.6.18  整理

PARTNER CONTENT

文章评论2条评论)

登录后参与讨论

用户80446 2008-9-8 21:17

:(

用户741296 2007-11-13 08:08

没有

用户1279035 2007-3-10 09:40

不用谢哈,

希望大家多多评论哈!

用户66632 2007-3-9 16:54

谢谢!

相关推荐阅读
用户1279035 2007-03-16 17:04
步进电机的控制原理及其单片机控制实现
                        步进电机的控制原理及其单片机控制实现摘要:采用8051单片机来控制步进电机,给出了步进电机的控制原理及其单片机控制的具体实现方法。关键词:步进电机;单片...
用户1279035 2007-03-09 17:24
51单片机模拟串口的三种方法
51单片机模拟串口的三种方法随着单片机的使用日益频繁,用其作前置机进行采集和通信也常见于各种应用,一般是利用前置机采集各种终端数据后进行处理、存储,再主动或被动上报给管理站。这种情况下下,采集会需要一...
用户1279035 2007-03-09 17:13
KeilC51高级编程
第一节 绝对地址访问C51提供了三种访问绝对地址的方法:1. 绝对宏:在程序中,用“#include”即可使用其中定义的宏来访问绝对地址,包括:CBYTE、XBYTE、PWORD、DBYTE、CWOR...
用户1279035 2007-03-09 16:43
C语言嵌入式系统编程修炼吐
屏幕操作 现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能。例如,一个微波炉的LCD上没有必要提供显示"电子邮件"的功能;一个提供汉字显示...
用户1279035 2007-03-09 16:40
C语言嵌入式系统编程修炼
同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的...
用户1279035 2007-03-09 16:37
C语言嵌入式系统编程修炼[整理]
C语言嵌入式系统编程修炼数据指针  在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调...
EE直播间
更多
我要评论
2
6
关闭 站长推荐上一条 /3 下一条