智果芯
服务于百万大学生和电子工程师!

在51单片机上跑RTX-Tiny实时操作系统

摘要STC89C52RC单片机作为一款经典的单片机,许多同学在自学51单片机时,第一个点灯例程就是跑在89C82RC上,同时在我们的单片机课程和微机原理课程里面STC89C52RC也是经常被用来做展示的单片机平台,对应的课程设计也常常采用这一款单片机,今天我分享的内容不是如何使用这款单片机,而是简单分享一下Keil软件自带的一个实时操作系统—RTX-Tiny操作系统,对这款操作系统做一个简单的介绍,分享一下使用流程,帮助大家在单片机的课程设计或者其他项目上使用这一个操作系统,初步了解系统编程的思想。

一、什么是RTX51

RTX51是keil公司开发的一款实时操作系统,由汇编编写,其有两个版本:Tiny版本和Full版本。

其中Tiny版本采用分时调度的方式,占用资源小,可以运行在STC89C52RC这种只有256个字节的单片机上,而Full版本是抢占式调度,支持任务间通信和内存管理等功能,功能强但占用资源多,适合RAM更大的单片机上,不适合STC89C52RC单片机,所以这里我们只做Tiny版本的分享。

运行流程

RTX51 Tiny 本质上是一个实时操作系统(RTOS),他通过不同任务间的切换,允许单片机同时(实际上是伪并行)完成多个功能或者运行多个任务。在裸机编程中,我们往往会在没有RTOS的条件下实现一个特定的实时程序(在一个单循环中实现一种或多种功能,或者运行一个或多个任务);这样的设计往往会遇到资源分配、运行时间以及程序维护的问题,而像RTX51这样的RTOS就可以帮我们很大程度上解决这些问题,在程序结构中,我们创建多个任务死循环体,每个任务循环体运行很短的一段时间后就会释放CPU资源,给其他循环体(又称为任务)来运行,因为切换的时间极短,所以在我们感官上,这些循环体就是在同时运行!

学习方式

一个实时操作系统(RTOS) 可以更灵活有效地分配系统资源,让原本复杂的逻辑简单化,其程序设计使用标准C语言进行开发,并可以用Keil的编译器进行编译。因为其底层源码为汇编,学习原理比较复杂,所以我们只要学会如何操作对应的API函数就行,至于具体学习RTOS内核,则可以去找uC/OS、RT-Thread、FreeRTOS等实时系统学习。

具体参数

Tiny的具体资源参数

任务数目就是上面所提到的循环体数目。

CODE空间指的是ROM空间,STC89C52RC用有8KROM大小,900字节对他来说微不足道。

DATA空间指内部RAM,STC89C52RC有128个字节,XDATA指外部RAM,STC89C52RC有128个字节外部RAM。

定时器0用来做单片机的时间基准,用于Tiny内核做参考进行任务调度。

二、移植

准备工作

既然是编写在STC89C52RC上移植RTOS,首先要准备的工具则是一个Keil软件和一个软件工程(默认已经完成)。这里我准备的一个LED例程程如下,编译通过。

找到Tiny源文件

右击keil,打开文件所在位置

返回此文件夹的上一级,找到C51文件夹,点击进去,找到如下文件路径

C51\RtxTiny2\SourceCode

复制配置文件(.a51)和库文件(.lib)到我们的工程下

复制到点灯例程,这里我新建了一个文件夹专门放源文件

在keil内添加文件

配置keil

在keil设置里面按下图配置选择RTX-Tiny系统

配置完成后我们在main函数复制以下内容

#include "RTX51TNY.h"

/*******************************************************************************
* 函 数 名       : task_create
* 函数功能             : 任务0
* 输    入       : 无
* 输    出         : 无
*******************************************************************************/
void task_create(void) _task_ 0
{
    while(1)
    {
        ;
    }       
}

编译一下,结果如下,无报错

我们系统的移植就完成了,下面就是根据项目需要对内容的具体修改以及调用API了

三、API介绍

在编译完成之后,我们点击RTX51TINY.H的头文件里面

进入之后,我们会看到如下代码,其中声明了许多调用函数,这些就是Tiny的API接口函数了

详细讲解一下这些接口和参数

任务创建与删除函数

unsigned char os_create_task     (unsigned char task_id);//创建任务,传入的参数为目标任务的ID
unsigned char os_delete_task     (unsigned char task_id);//删除任务,传入的参数为目标任务的ID

阻塞延时函数

阻塞当前任务(任务变为等待态)直到指定的时间到来(任务变为就绪态), 继续往下执行,等待的期间该任务释放CPU使用权,不再参与调度。

unsigned char os_wait  (unsigned char typ, unsigned char ticks,unsigned int dummy);

参数 typ:阻塞类型

参数功能
K_SIG等待一个信号
K_IVL等待时间间隔(滴答数为单位)
K_TMO等待超时时间(滴答数为单位)

tick:阻塞的心跳时钟数目(最大255)

dummy:无用参数

阻塞延时函数的简单版本

unsigned char os_wait1   (unsigned char typ);//唯一参数只能是K_SIG,等待信号。
unsigned char os_wait2   (unsigned char typ,
                                  unsigned char ticks);//(要等待的事件,要等待的滴答数),参数和os_wait一样,少一个无用参数

阻塞函数的返回值

返回值功能
RDY_EVENT表示任务的就绪标志是被函数置位的
SIG_EVENT收到一个信号
TMO_EVENT超时完成,或者时间间隔到
NOT_OK参数的值无效

任务信号API

任务信号用于任务间同步,配合阻塞函数使用,当任务在阻塞在等待信号量时,使用send信号量置位对应任务编号的信号量,就可以使其解除等待,运行程序

unsigned char os_send_signal     (unsigned char task_id);//向其他任务发送信号。如果此任务在等待信号,则会使该任务取消等待准备执行,但不是马上执行,信号储存在任务的信号标志中
unsigned char os_clear_signal    (unsigned char task_id);//清除对应编号任务的信号标志。
unsigned char isr_send_signal    (unsigned char task_id);//中断中使用和os_send_signal功能相同

强制就绪API

使任务强制脱离其他状态,进入就绪态,加入任务调度中

void   os_set_ready   (unsigned char task_id);//函数中调用,传入参数为强制就绪的目标任务编号
void   isr_set_ready   (unsigned char task_id);//中断中使用,和上面功能相同

强制切换API

强制停止当前任务,切换到下一个就绪任务

unsigned char os_switch_task   (void); // 停止当前任务,立即切换到另一个就绪的任务。

读取当前运行任务编号API

返回当正在执行的任务编号。

unsigned char os_running_task_id (void);

纠正时钟偏差API

用于纠正可能由os_wait引起的等待时间错乱问题,因为由信号事件K_SIG引起的 退出,时间间隔定时器并不调整,通常会调用此函数进行调整。

void    os_reset_interval  (unsigned char ticks);

注意:Tiny的内核在使用过程中与中断息息相关,要确保EA=1,中断处于开启状态,如果有特殊需求,比如任务在通信的时候不想被打断可以短暂的EA=0,但一定要短,并且在EA=0的时间段内,千万千万千万不要调用内核函数!不然系统崩溃!

四、创建基本任务

时钟配置

创建任务之前,我们先打开Conf配置文件,进入配置我们的时间周期,因为任务的切换是以conf内的时间周期为基准了,配置错了系统则会运行异常,具体配置代码在打开配置文件后的33-39行。

INT_CLOCK是系统的心跳时钟,每个心跳时钟到达后会进行一次中断,在中断中根据时间片来切换任务,此处使用了EQU汇编相当于#define,后面的数值表示的是心跳时钟是机器周期多少倍。因为我使用的是12M晶振,机器周期为1us,所以我如果要设置心跳时钟为1ms,则需要把这个数字设置为1000(这个数的最大值为65536)

;  Define Hardware-Timer tick time in 8051 machine cycles.
INT_CLOCK    EQU 1000    ; default is 10000 cycles

这样心跳时间就设置完毕了,下面到设置时间片的长度-TIMESHARING,表示给每个任务设置的运行时间上限,到达时则必须要切换任务,切换到其他就绪的任务(不在延时状态的任务),这里我使用默认值,不改变他,一般程序基本会在1ms内完成程序,然后进入延时状态,脱离调度,切换其他任务

任务形式

有了上面的时间周期配置后,下面我们就开始创建任务,tiny没有复杂的启动步骤,其创建任务方式非常简单,创建一个函数后,在其名称旁边加上任务编号说明就可以,形式如下

/*********************************
* 函 数 名       : task_create
* 函数功能             : 任务0
* 输    入       : 无
* 输    出         : 无
***********************************/
void task_create(void) _task_ 0
{
    while(1)
    {
        ;
    }       
}

每个创建的任务都有必须有一个死循环,防止cpu彻底跳出任务体,引起系统崩溃,工程开始时首先创建一个任务0,不需要调用创建api,系统直接从0开始运行,在任务0里面我们一般放硬件初始化以及以及调用其他任务的创建声明,在最后调用删除API删除任务0,释放任务0资源,创建和删除API如下,传入参数

此处我们创建两个任务1和2,在任务0中对他们进行创建,同时设置两个led端口led0和led1(定义如下),在任务1和任务2中设置不同的延时周期取反,代码如下:

#include "RTX51TNY.h"
#include "reg52.h"
sbit led0=P1^0;
sbit led1=P2^0;
/**********************************
* 函 数 名       : task_create
* 函数功能             : 任务0
* 输    入       : 无
* 输    出         : 无
***********************************/
void task_create(void) _task_ 0
{
    os_create_task(1);
    os_create_task(2);
    os_delete_task(0);
    while(1)
    {
        ;
    }       
}
/************************************
* 函 数 名       : task_1
* 函数功能             : 任务1
* 输    入       : 无
* 输    出         : 无
*************************************/
void task_1(void) _task_ 1
{
    while(1)
    {
        led0=!led0;
        os_wait2(K_IVL,50);
    }       
}

/**************************************
* 函 数 名       : task_2
* 函数功能             : 任务2
* 输    入       : 无
* 输    出         : 无
***************************************/
void task_2(void) _task_ 2
{
    while(1)
    {
        led1=!led1;
        os_wait2(K_IVL,150);
    }       
}

代码编译一下,没有报错

此处我们keil仿真观察一下波形

首先点击debug设置仿真模式

进入仿真,点击波形分析

配置端口P1

改变最低位掩码,使我们只看到P1.0的波形

设置P1.0的范围,同样的操作对P2.0在来一次

点击运行,一段时间后停止,可以看到波形

分析波形我们发现任务1和任务2基本同时在运行,任务二的时间间隔是任务1的三倍!

五、任务间同步

任务间同步主要使用信号的传递来操作,这里我设置任务1,500ms置位一次任务二的信号,任务二等待到信号后运行100ms具体代码如下。

任务1运行过程中500ms置位一次任务二的信号,任务2则100ms扫描一次信号,检测到信号置位,反转电平运行100ms再恢复。

#include "RTX51TNY.h"
#include "reg52.h"
sbit led0=P1^0;
sbit led1=P2^0;
/***************************************
* 函 数 名       : task_create
* 函数功能             : 任务0
* 输    入       : 无
* 输    出         : 无
****************************************/
void task_create(void) _task_ 0
{
    os_create_task(1);
    os_create_task(2);
    os_delete_task(0);
    while(1)
    {
        ;
    }       
}
/*****************************************
* 函 数 名       : task_1
* 函数功能             : 任务1
* 输    入       : 无
* 输    出         : 无
******************************************/
void task_1(void) _task_ 1
{
    while(1)
    {
        led0=!led0;
        os_send_signal(2);
        os_wait2(K_IVL,250);
        os_wait2(K_IVL,250);
    }       
}

/*******************************************
* 函 数 名       : task_2
* 函数功能             : 任务2
* 输    入       : 无
* 输    出         : 无
*******************************************/
void task_2(void) _task_ 2
{
    unsigned char sig;
    while(1)
    {


        sig=os_wait2(K_SIG|K_IVL,10);
        os_reset_interval(10);
        if(sig==SIG_EVENT)
        {
            led1=0;
        }else
        {
            led1=1;
        }
        os_wait2(K_IVL,100);
    }       
}

程序下载之后进行仿真。

我们可以看到每次任务一电平反转时发送信号给任务二,任务二电平就反转一次,反转的时常为100ms和我们程序相同!!!这个就是任务间的同步!

六、总结

这次我分享了关于RTX-Tiny的使用总结,内容浏览一遍基本上就可以对RTX-Tiny有基本的了解,如果在尝试编写一下例程,马上就能掌握RTX-Tiny的核心了,之后要是用STC89C52RC或者其他性能相近的单片机做课设做有意思的小项目,就可以上这个微型RTOS,提高自己系统编程的思维。

七、中文手册

RTX-Tiny有一个前辈写的中文手册,大家在使用过程中有不会的地方可以查询手册,手册获取方式:公众号后台回复关键词:rtx-51,获取下载链接了。

赞(6) 打赏
未经允许不得转载:智果芯 » 在51单片机上跑RTX-Tiny实时操作系统

评论 1

  1. #0

    匿名3年前 (2021-10-07)回复

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏