第十三章RTX51实时多任务操作系统资料讲解
- 格式:ppt
- 大小:479.00 KB
- 文档页数:40
作者:大海橡树前段时间给单片机写个跑马灯的程序,它是将单片机4组IO口全用了,一个引脚一个LED 一共32个,当初是拿出去当作品展用的,写了个程序,要求好几个花样,但是写程序的时候发现,单独控制一组端口还可以,要是同时控制4组IO口,并且显示的花样都不一样?该怎么办呢?这就要用的RTX51了,MCS-51单片机的RTOS(实时操作系统)。
RTX51简介:图(1)RTX51是德国KEIL公司开发的专门针对于8051内核兼容MCU所作的实时操作系统(RTOS),RTX51有两个版本:RTX51-FULL与RTX51-Tiny。
FULL版本支持四级任务优先级,最大256任务,它工作在类似于中断功能的状态下,同时支持抢占式与时间片循环调度、支持信号(signal)、消息队列、二进制信号量(semaphore)和邮箱(mailbox),其功能强大,仅仅占用6~8KB的程序存储器空间。
RTX51_Tiny是RTX51FULL的子集,是一个很小的内核,只占用大约800byte的存储空间(主要程序RTX51TNY.A51仅有不足一千行)它适用于对实时性要求不非常严格的、仅要求多任务管理且任务间通信功能不要求非常强大的应用。
它仅使用51内部寄存器来实现所有功能,应用程序只需要以系统调用(system call)的方式引用RTX51中的函数即可,RTX51-Tiny可以支持16个任务,多个任务遵循时间片轮转的规则,任务间以信号signal的方式进行通信,任务可以等待另一任务给他发出signal然后再从挂起状态恢复运行,它并不支持抢占式任务切换的方式。
上面只是RTX51的简单介绍,具体的用法自己到百度文库下载RTX51实时操作系统的使用方法:/view/0902ce1755270722192ef70d.html。
上面有详细的介绍,由于篇幅有限,这里就不做过多的介绍。
大家好好阅读文档上的内容,才知道怎么使用RTOS,刚开始接触的时候有点不好理解,多写几个程序练练手,就有点理解了,确实,要建立起操作系统的概念不是一两天的事情,其中要设计很多的抽象的概念,只有把他们完全理解了,才好应用。
rtx51小型实时操作系统的应用体会2007年06月27日星期三 23:53最近在做设计的时候老遇到一些几个任务需要同时进行的情况,刚开始想自己去做一个多任务的程序(因为称不上系统),想用一个脉冲计数的方法来实现多任务切换,但是感觉实现起来比较麻烦。
无意中发现了51单片机下个rtx51实时操作系统(本人以前不知道的),后来就去找相关资料。
发现这方面的资料在网上很少,也很杂乱。
经过一段时间的学习,我有了个大概的了解。
下面给大家讲述一下:RTX51是一个用于8051系列处理器多任务实时操作系统。
RTX51可以简化那些复杂而且时间要求严格的工程的软件设计工作。
有二个不同的RTX51版本可以利用:RTX51 Full 使用四个任务优先权完成同时存在时间片轮转调度和抢先的任务切换 RTX51工作在与中断功能相似的状态下信号和信息可以通过邮箱系统在任务之间互相传递你可以从一存池中分配和释放内存你可以强迫一个任务等待中断超时或者是从另一个任务或中断发出的信号或信息RTX51 Tiny 是一个RTX51的子集它可以很容易地在没有任何外部存储器的单片8051系统上运转。
除了下列例外 RTX51 Tiny支持许多在RTX51中的特征。
RTX51 Tiny仅支持时间片轮转任务切换和使用信号进行任务切换不支持抢先式的任务切换。
不包括消息历程。
没有存储器池分配程序。
RTX51使用一个8051硬件计时器中断作为定时程序。
产生的周期性中断用于驱动 RTX51时钟。
RTX51不需要在你的程序中拥有一个主函数它将自动开始执行任务 0 如果你确实有一个主函数你必须利用 RTX51 Tiny中的 os_create_task函数或 RTX51中的os_start_system函数手工启动 RTX51。
下列例子显示一个只使用时间片轮转任务调度的简单的 RTX51应用程序在本程序里的二个任务是简单计数器回路 rtx51开始执行函数名为 job0的任务 0 。
51单片机多任务操作系统的原理与实现51单片机多任务操作系统的原理与实现-- 一个超轻量级的操作系统前言想了很久,要不要写这篇文章最后觉得对操作系统感兴趣的人还是很多,写吧.我不一定能造出玉,但我可以抛出砖.包括我在内的很多人都对51使用操作系统呈悲观态度,因为51的片上资源太少.但对于很多要求不高的系统来说,使用操作系统可以使代码变得更直观,易于维护,所以在51上仍有操作系统的生存机会.流行的uCos,Tiny51等,其实都不适合在2051这样的片子上用,占资源较多,唯有自已动手,以不变应万变,才能让51也有操作系统可用.这篇贴子的目的,是教会大家如何现场写一个OS,而不是给大家提供一个OS版本.提供的所有代码,也都是示例代码,所以不要因为它没什么功能就说LAJI之类的话.如果把功能写全了,一来估计你也不想看了,二来也失去灵活性没有价值了.下面的贴一个示例出来,可以清楚的看到,OS本身只有不到10行源代码,编译后的目标代码60字节,任务切换消耗为20个机器周期.相比之下,KEIL内嵌的TINY51目标代码为800字节,切换消耗100~700周期.唯一不足之处是,每个任务要占用掉十几字节的堆栈,所以任务数不能太多,用在128B内存的51里有点难度,但对于52来说问题不大.这套代码在36M主频的STC12C4052上实测,切换任务仅需2uS.#include <>#define MAX_TASKS 2 须和实际任务数一至#define MAX_TASK_DEP 12 低不得少于2个,保守值为12.unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];unsigned char task_id; 指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.void task_load(unsigned int fn, unsigned char tid){task_sp[tid] = task_stack[tid] + 1;task_stack[tid][0] = (unsigned int)fn & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}用该宏后,将永不返回.#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}/*======================以下为测试代码======================*/void task1(){static unsigned char i;while(1){i++;task_switch(); 么是操作系统?人脑比较容易接受"类比"这种表达方式,我就用"公交系统"来类比"操作系统"吧.当我们要解决一个问题的时候,是用某种处理手段去完成它,这就是我们常说的"方法",计算机里叫"程序"(有时候也可以叫它"算法").以出行为例,当我们要从A地走到B地的时候,可以走着去,也可以飞着去,可以走直线,也可以绕弯路,只要能从A地到B地,都叫作方法.这种从A地到B的需求,相当于计算机里的"任务",而实现从A地到B地的方法,叫作"任务处理流程"很显然,这些走法中,并不是每种都合理,有些傻子都会采用的,有些是傻子都不采会用的.用计算机的话来说就是,有的任务处理流程好,有的任务处理流程好,有的处理流程差.可以归纳出这么几种真正算得上方法的方法:有些走法比较快速,适合于赶时间的人;有些走法比较省事,适合于懒人;有些走法比较便宜,适合于穷人.用计算机的话说就是,有些省CPU,有些流程简单,有些对系统资源要求低.现在我们可以看到一个问题:如果全世界所有的资源给你一个人用(单任务独占全部资源),那最适合你需求的方法就是好方法.但事实上要外出的人很多,例如10个人(10个任务),却只有1辆车(1套资源),这叫作"资源争用".如果每个人都要使用最适合他需求的方法,那司机就只好给他们一人跑一趟了,而在任一时刻里,车上只有一个乘客.这叫作"顺序执行",我们可以看到这种方法对系统资源的浪费是严重的.如果我们没有法力将1台车变成10台车来送这10个人,就只好制定一些机制和约定,让1台车看起来像10台车,来解决这个问题的办法想必大家都知道,那就是制定公交线路.最简单的办法是将所有旅客需要走的起点与终点串成一条线,车在这条线上开,乘客则自已决定上下车.这就是最简单的公交线路.它很差劲,但起码解决客人们对车争用.对应到计算机里,就是把所有任务的代码混在一起执行.这样做既不优异雅,也没效率,于是司机想了个办法,把这些客户叫到一起商量,将所有客人出行的起点与终点罗列出来,统计这些线路的使用频度,然后制定出公交线路:有些路线可以合并起来成为一条线路,而那些不能合并的路线,则另行开辟行车车次,这叫作"任务定义".另外,对于人多路线,车次排多点,时间上也优先安排,这叫作"任务优先级".经过这样的安排后,虽然仍只有一辆车,但运载能力却大多了.这套车次/路线的按排,就是一套"公交系统".哈,知道什么叫操作系统了吧它也就是这么样的一种约定.操作系统:我们先回过头归纳一下:汽车系统资源.主要指的是CPU,当然还有其它,比如内存,定时器,中断源等.客户出行任务正在走的路线进程一个一个的运送旅客顺序执行同时运送所有旅客多任务并行按不同的使用频度制定路线并优先跑较繁忙的路线任务优先级计算机内有各种资源,单从硬件上说,就有CPU,内存,定时器,中断源,I/O端口等.而且还会派生出来很多软件资源,例如消息池.操作系统的存在,就是为了让这些资源能被合理地分配.最后我们来总结一下,所谓操作系统,以我们目前权宜的理解就是:为"解决计算机资源争用而制定出的一种约定".二.51上的操作系统对于一个操作系统来说,最重要的莫过于并行多任务.在这里要澄清一下,不要拿当年的DOS来说事,时代不同了.况且当年IBM和小比尔着急将PC搬上市,所以才抄袭PLM(好象是叫这个名吧记不太清)搞了个今天看来很"粗制滥造"的DOS出来.看看当时真正的操作系统---UNIX,它还在纸上时就已经是多任务的了.对于我们PC来说,要实现多任务并不是什么问题,但换到MCU却很头痛:1.系统资源少在PC上,CPU主频以G为单位,内存以GB为单位,而MCU的主频通常只有十几M,内存则是Byts.在这么少的资源上同时运行多个任务,就意味着操作系统必须尽可能的少占用硬件资源.2.任务实时性要求高PC并不需要太关心实时性,因为PC上几乎所有的实时任务都被专门的硬件所接管,例如所有的声卡网卡显示上都内置有DSP以及大量的缓存.CPU只需坐在那里指手划脚告诉这些板卡如何应付实时信息就行了.而MCU不同,实时信息是靠CPU来处理的,缓存也非常有限,甚至没有缓存.一旦信息到达,CPU必须在极短的时间内响应,否则信息就会丢失.就拿串口通信来举例,在标准的PC架构里,巨大的内存允许将信息保存足够长的时间.而对于MCU来说内存有限,例如51仅有128字节内存,还要扣除掉寄存器组占用掉的8~32个字节,所以通常都仅用几个字节来缓冲.当然,你可以将数据的接收与处理的过程合并,但对于一个操作系统来说,不推荐这么做.假定以115200bps通信速率向MCU传数据,则每个字节的传送时间约为9uS,假定缓存为8字节,则串口处理任务必须在70uS内响应.这两个问题都指向了同一种解决思路:操作系统必须轻量轻量再轻量,最好是不占资源(那当然是做梦啦).可用于MCU的操作系统很多,但适合51(这里的51专指无扩展内存的51)几乎没有.前阵子见过一个"圈圈操作系统",那是我所见过的操作系统里最轻量的,但仍有改进的余地.很多人认为,51根本不适合使用操作系统.其实我对这种说法并不完全接受,否则也没有这篇文章了.我的看法是,51不适合采用"通用操作系统".所谓通用操作系统就是,不论你是什么样的应用需求,也不管你用什么芯片,只要你是51,通通用同一个操作系统.这种想法对于PC来说没问题,对于嵌入式来说也不错,对AVR来说还凑合,而对于51这种"贫穷型"的MCU来说,不行.怎样行量体裁衣,现场根据需求构建一个操作系统出来!看到这里,估计很多人要翻白眼了,大体上两种:1.操作系统那么复杂,说造就造,当自已是神了2.操作系统那么复杂,现场造一个会不会出BUG哈哈,看清楚了问题出在"复杂"上面,如果操作系统不复杂,问题不就解决了事实上,很多人对操作系统的理解是片面的,操作系统不一定要做得很复杂很全面,就算仅个多任务并行管理能力,你也可以称它操作系统.只要你对多任务并行的原理有所了解,就不难现场写一个出来,而一旦你做到了这一点,为各任务间安排通信约定,使之发展成一个为你的应用系统量身定做的操作系统也就不难了.为了加深对操作系统的理解,可以看一看<<演变>>这份PPT,让你充分了解一个并行多任务是如何一步步从顺序流程演变过来的.里面还提到了很多人都在用的"状态机",你会发现操作系统跟状态机从原理上其实是多么相似.会用状态机写程序,都能写出操作系统.三.我的第一个操作系统直接进入主题,先贴一个操作系统的示范出来.大家可以看到,原来操作系统可以做得么简单.当然,这里要申明一下,这玩意儿其实算不上真正的操作系统,它除了并行多任务并行外根本没有别的功能.但凡事都从简单开始,搞懂了它,就能根据应用需求,将它扩展成一个真正的操作系统.好了,代码来了.将下面的代码直接放到KEIL里编译,在每个task()函数的"task_switch();"那里打上断点,就可以看到它们的确是"同时"在执行的.#include <>#define MAX_TASKS 2 须和实际任务数一至#define MAX_TASK_DEP 12 低不得少于2个,保守值为12.unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];unsigned char task_id; 指定的函数(参数1)装入指定(参数2)的任务槽中.如果该槽中原来就有任务,则原任务丢失,但系统本身不会发生错误.void task_load(unsigned int fn, unsigned char tid){task_sp[tid] = task_stack[tid] + 1;task_stack[tid][0] = (unsigned int)fn & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}用该宏后,将永不返回.#define os_start(tid) {task_id = tid,SP = task_sp[tid];return;}/*==================以下为测试代码=====================*/void task1(){static unsigned char i;while(1){i++;task_switch();现在来看看这个多任务系统的原理:这个多任务系统准确来说,叫作"协同式多任务".所谓"协同式",指的是当一个任务持续运行而不释放资源时,其它任务是没有任何机会和方式获得运行机会,除非该任务主动释放CPU.在本例里,释放CPU是靠task_switch()来完成的.task_switch()函数是一个很特殊的函数,我们可以称它为"任务切换器".要清楚任务是如何切换的,首先要回顾一下堆栈的相关知识.有个很简单的问题,因为它太简单了,所以相信大家都没留意过:我们知道,不论是CALL还是JMP,都是将当前的程序流打断,请问CALL和JMP的区别是什么你会说:CALL可以RET,JMP不行.没错,但原因是啥呢为啥CALL过去的就可以用RET跳回来,JMP过去的就不能用RET来跳回呢很显然,CALL通过某种方法保存了打断前的某些信息,而在返回断点前执行的RET指令,就是用于取回这些信息.不用多说,大家都知道,"某些信息"就是PC指针,而"某种方法"就是压栈.很幸运,在51里,堆栈及堆栈指针都是可被任意修改的,只要你不怕死.那么假如在执行RET前将堆栈修改一下会如何往下看:当程序执行CALL后,在子程序里将堆栈刚才压入的断点地址清除掉,并将一个函数的地址压入,那么执行完RET后,程序就跳到这个函数去了.事实上,只要我们在RET前将堆栈改掉,就能将程序跳到任务地方去,而不限于CALL里压入的地址.重点来了......首先我们得为每个任务单独开一块内存,这块内存专用于作为对应的任务的堆栈,想将CPU交给哪个任务,只需将栈指针指向谁内存块就行了.接下来我们构造一个这样的函数:当任务调用该函数时,将当前的堆栈指针保存一个变量里,并换上另一个任务的堆栈指针.这就是任务调度器了.OK了,现在我们只要正确的填充好这几个堆栈的原始内容,再调用这个函数,这个任务调度就能运行起来了.那么这几个堆栈里的原始内容是哪里来的呢这就是"任务装载"函数要干的事了.在启动任务调度前将各个任务函数的入口地址放在上面所说的"任务专用的内存块"里就行了!对了,顺便说一下,这个"任务专用的内存块"叫作"私栈",私栈的意思就是说,每个任务的堆栈都是私有的,每个任务都有一个自已的堆栈.话都说到这份上了,相信大家也明白要怎么做了:1.分配若干个内存块,每个内存块为若干字节:这里所说的"若干个内存块"就是私栈,要想同时运行几少个任务就得分配多少块.而"每个子内存块若干字节"就是栈深.记住,每调一层子程序需要2字节.如果不考虑中断,4层调用深度,也就是8字节栈深应该差不多了.unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP]当然,还有件事不能忘,就是堆指针的保存处.不然光有堆栈怎么知道应该从哪个地址取数据啊unsigned char idata task_sp[MAX_TASKS]上面两项用于装任务信息的区域,我们给它个概念叫"任务槽".有些人叫它"任务堆",我觉得还是"槽"比较直观对了,还有任务号.不然怎么知道当前运行的是哪个任务呢unsigned char task_id当前运行存放在1号槽的任务时,这个值就是1,运行2号槽的任务时,这个值就是2....2.构造任务调度函函数:void task_switch(){task_sp[task_id] = SP;}3.装载任务:将各任务的函数地址的低字节和高字节分别入在task_stack[任务号][0]和task_stack[任务号][1]中:为了便于使用,写一个函数: task_load(函数名, 任务号)void task_load(unsigned int fn, unsigned char tid){task_sp[tid] = task_stack[tid] + 1;task_stack[tid][0] = (unsigned int)fn & 0xff;task_stack[tid][1] = (unsigned int)fn >> 8;}4.启动任务调度器:将栈指针指向任意一个任务的私栈,执行RET指令.注意,这可很有学问的哦,没玩过堆栈的人脑子有点转不弯:这一RET,RET到哪去了嘿嘿,别忘了在RET前已经将堆栈指针指向一个函数的入口了.你别把RET看成RET,你把它看成是另一种类型的JMP就好理解了.SP = task_sp[任务号];return;做完这4件事后,任务"并行"执行就开始了.你可以象写普通函数一个写任务函数,只需(目前可以这么说)注意在适当的时候(例如以前调延时的地方)调用一下task_switch(),以让出CPU控制权给别的任务就行了.最后说下效率问题.这个多任务系统的开销是每次切换消耗20个机器周期(CALL和RET都算在内了),贵吗不算贵,对于很多用状态机方式实现的多任务系统来说,其实效率还没这么高--- case switch和if()可不像你想像中那么便宜.关于内存的消耗我要说的是,当然不能否认这种多任务机制的确很占内存.但建议大家不要老盯着编译器下面的那行字"DATA = XXXbyte".那个值没意义,堆栈没算进去.关于比较省内存多任务机制,我将来会说到.概括来说,这个多任务系统适用于实时性要求较高而内存需求不大的应用场合,我在运行于36M主频的STC12C4052上实测了一把,切换一个任务不到3微秒.下回我们讲讲用KEIL写多任务函数时要注意的事项.下下回我们讲讲如何增强这个多任务系统,跑步进入操作系统时代.四.用KEIL写多任务系统的技巧与注意事项C51编译器很多,KEIL是其中比较流行的一种.我列出的所有例子都必须在KEIL 中使用.为何不是因为KEIL好所以用它(当然它的确很棒),而是因为这里面用到了KEIL的一些特性,如果换到其它编译器下,通过编译的倒不是问题,但运行起来可能是堆栈错位,上下文丢失等各种要命的错误,因为每种编译器的特性并不相同.所以在这里先说清楚这一点.但是,我开头已经说了,这套帖子的主要目的是阐述原理,只要你能把这几个例子消化掉,那么也能够自已动手写出适合其它编译器的OS.好了,说说KEIL的特性吧,先看下面的函数:sbit sigl = P1^7;void func1(){register char data i;i = 5;do{sigl = !sigl;}while(--i);}你会说,这个函数没什么特别的嘛!呵呵,别着急,你将它编译了,然后展开汇编代码再看看:193: void func1(){194: register char data i;195: i = 5;C:0x00C3 7F05 MOV R7,#0x05196: do{197: sigl = !sigl;C:0x00C5 B297 CPL sigl198: }while(--i);C:0x00C7 DFFC DJNZ R7,C:00C5199: }C:0x00C9 22 RET看清楚了没这个函数里用到了R7,却没有对R7进行保护!有人会跳起来了:这有什么值得奇怪的,因为上层函数里没用到R7啊.呵呵,你说的没错,但只说对了一半:事实上,KEIL编译器里作了约定,在调子函数前会尽可能释放掉所有寄存器.通常性况下,除了中断函数外,其它函数里都可以任意修改所有寄存器而无需先压栈保护(其实并不是这样,但现在暂时这样认为,饭要一口一口吃嘛,我很快会说到的).这个特性有什么用呢有!当我们调用任务切换函数时,要保护的对象里可以把所有的寄存器排除掉了,就是说,只需要保护堆栈即可!现在我们回过头来看看之前例子里的任务切换函数:void task_switch(){task_sp[task_id] = SP;}看到没,一个寄存器也没保护,展开汇编看看,的确没保护寄存器.好了,现在要给大家泼冷水了,看下面两个函数:void func1(){register char data i;i = 5;do{sigl = !sigl;}while(--i);}void func2(){register char data i;i = 5;do{func1();}while(--i);}父函数fun2()里调用func1(),展开汇编代码看看: 193: void func1(){194: register char data i;195: i = 5;C:0x00C3 7F05 MOV R7,#0x05 196: do{197: sigl = !sigl; C:0x00C5 B297 CPL sigl 198: }while(--i);C:0x00C7 DFFC DJNZ R7,C:00C5 199: }C:0x00C9 22 RET200: void func2(){201: register char data i;202: i = 5;C:0x00CA 7E05 MOV R6,#0x05 203: do{204: func1();C:0x00CC 11C3 ACALL func1(C:00C3) 205: }while(--i);C:0x00CE DEFC DJNZ R6,C:00CC206: }C:0x00D0 22 RET看清楚没函数func2()里的变量使用了寄存器R6,而在func1和func2里都没保护.听到这里,你可能又要跳一跳了:func1()里并没有用到R6,干嘛要保护没错,但编译器是怎么知道func1()没用到R6的呢是从调用关系里推测出来的.一点都没错,KEIL会根据函数间的直接调用关系为各函数分配寄存器,既不用保护,又不会冲突,KEIL好棒哦!!等一下,先别高兴,换到多任务的环境里再试试:void func1(){register char data i;i = 5;do{sigl = !sigl;}while(--i);}void func2(){register char data i;i = 5;do{sigl = !sigl;}while(--i);}展开汇编代码看看:193: void func1(){194: register char data i;195: i = 5;C:0x00C3 7F05 MOV R7,#0x05 196: do{197: sigl = !sigl; C:0x00C5 B297 CPL sigl 198: }while(--i);C:0x00C7 DFFC DJNZ R7,C:00C5 199: }C:0x00C9 22 RET200: void func2(){201: register char data i;202: i = 5;C:0x00CA 7F05 MOV R7,#0x05203: do{204: sigl = !sigl;C:0x00CC B297 CPL sigl205: }while(--i);C:0x00CE DFFC DJNZ R7,C:00CC206: }C:0x00D0 22 RET看到了吧哈哈,这回神仙也算不出来了.因为两个函数没有了直接调用的关系,所以编译器认为它们之间不会产生冲突,结果分配了一对互相冲突的寄存器,当任务从func1()切换到func2()时,func1()中的寄存器内容就给破坏掉了.大家可以试着去编译一下下面的程序:sbit sigl = P1^7;void func1(){register char data i;i = 5;do{sigl = !sigl;task_switch();} while (--i);}void func2(){register char data i;i = 5;do{sigl = !sigl;task_switch();}while(--i);}我们这里只是示例,所以仍可以通过手工分配不同的寄存器避免寄存器冲突,但在真实的应用中,由于任务间的切换是非常随机的,我们无法预知某个时刻哪个寄存器不会冲突,所以分配不同寄存器的方法不可取.那么,要怎么办呢这样就行了:sbit sigl = P1^7;void func1(){static char data i;while(1){i = 5;do{sigl = !sigl;task_switch();}while(--i);}}void func2(){static char data i;while(1){i = 5;do{sigl = !sigl;task_switch();}while(--i);}}将两个函数中的变量通通改成静态就行了.还可以这么做:sbit sigl = P1^7;void func1(){register char data i;while(1){i = 5;do{sigl = !sigl;}while(--i);task_switch();}}void func2(){register char data i;while(1){i = 5;do{sigl = !sigl;}while(--i);task_switch();}}即,在变量的作用域内不切换任务,等变量用完了,再切换任务.此时虽然两个任务仍然会互相破坏对方的寄存器内容,但对方已经不关心寄存器里的内容了.以上所说的,就是"变量覆盖"的问题.现在我们系统地说说关于"变量覆盖".变量分两种,一种是全局变量,一种是局部变量(在这里,寄存器变量算到局部变量里).对于全局变量,每个变量都会分配到单独的地址.而对于局部变量,KEIL会做一个"覆盖优化",即没有直接调用关系的函数的变量共用空间.由于不是同时使用,所以不会冲突,这对内存小的51来说,是好事.但现在我们进入多任务的世界了,这就意味着两个没有直接调用关系的函数其实是并列执行的,空间不能共用了.怎么办呢一种笨办法是关掉覆盖优化功能.呵呵,的确很笨.比较简单易行一个解决办法是,不关闭覆盖优化,但将那些在作用域内需要跨越任务(换句话说就是在变量用完前会调用task_switch()函数的)变量通通改成静态(static)即可.这里要对初学者提一下,"静态"你可以理解为"全局",因为它的地址空间一直保留,但它又不是全局,它只能在定义它的那个花括号对{}里访问.静态变量有个副作用,就是即使函数退出了,仍会占着内存.所以写任务函数的时候,尽量在变量作用域结束后才切换任务,除非这个变量的作用域很长(时间上长),会影响到其它任务的实时性.只有在这种情况下才考虑在变量作用域内跨越任务,并将变量申明为静态.事实上,只要编程思路比较清析,很少有变量需要跨越任务的.就是说,静态变量并不多.说完了"覆盖"我们再说说"重入".所谓重入,就是一个函数在同一时刻有两个不同的进程复本.对初学者来说可能不好理解,我举个例子吧:有一个函数在主程序会被调用,在中断里也会被调用,假如正当在主程序里调用时,中断发生了,会发生什么情况void func1(){static char data i;i = 5;do{sigl = !sigl;}while(--i);}假定func1()正执行到i=3时,中断发生,一旦中断调用到func1()时,i的值就被破坏了,当中断结束后,i == 0.以上说的是在传统的单任务系统中,所以重入的机率不是很大.但在多任务系统中,很容易发生重入,看下面的例子:void func1()....delay();....}void func2(){....delay();....}void delay(){static unsigned char i;申明为static会发生重入问题.麻烦啊for(i=0;i<10;i++)task_switch();两个并行执行的任务都调用了delay(),这就叫重入.问题在于重入后的两个复本都依赖变量i来控制循环,而该变量跨越了任务,这样,两个任务都会修改i值了.重入只能以防为主,就是说尽量不要让重入发生,比如将代码改成下面的样子:#define delay() {static unsigned char i; for(i=0;i<10;i++)task_switch();}void func1(){....delay();....}void func2(){....delay();....用宏来代替函数,就意味着每个调用处都是一个独立的代码复本,那么两个delay实际使用的内存地址也就不同了,重入问题消失.但这种方法带来的问题是,每调用一次delay(),都会产生一个delay的目标代码,如果delay的代码很多,那就会造成大量的rom空间占用.有其它办法没本人所知有限,只有最后一招了:void delay() reentrant{unsigned char i;for(i=0;i<10;i++)task_switch();}加入reentrant申明后,该函数就可以支持重入.但小心使用,申明为重入后,函数效率极低!最后附带说下中断.因为没太多可说的,就不单独开章了.中断跟普通的写法没什么区别,只不过在目前所示例的多任务系统里因为有堆栈的压力,所以要使用using来减少对堆栈的使用(顺便提下,也不要调用子函数,同样是为了减轻堆栈压力)用using,必须用#pragma NOAREGS关闭掉绝对寄存器访问,如果中断里非要调用函数,连同函数也要放在#pragma NOAREGS的作用域内.如例所示:#pragma SAVE#pragma NOAREGS 是说,如果你在不用中断时任务栈深定为8的话,现在就要定为8+4 = 12了.另外说句废话,中断里处理的事一定要少,做个标记就行了,剩下的事交给对应的任务去处理.现在小结一下:切换任务时要保证没有寄存器跨越任务,否则产生任务间寄存器覆盖. 使用静态变量解决切换任务时要保证没有变量跨越任务,否则产生任务间地址空间(变量)覆盖. 使用静态变量解决两个不同的任务不要调用同时调用同一个函数,否则产生重入覆盖. 使用重入申明解决。
Round-robin 任务切换RTX51 Tiny可以配置成使用round-robin多任务。
Round-robinp容许quasi-parallel执行多任务。
任务并不是连续执行的,而是分时间片执行的(可用的CPU时间被分成时间片,RTX51 Tiny把时间片分配给各个任务)。
时间片的时间很短(以毫秒为单位),所以任务看起来像连续执行一样任务在分配给他的时间片内执行(除非放弃)。
然后切换到下一个就绪的任务。
这个时间片在RTX51 Tiny Configuration.配置文件中定义.下面的例子是一个使用round-robin多任务的RTX51 Tiny的程序。
这个程序中的两个任务都是循环计数器。
RTX51 Tiny执行称为job0的任务0。
这个函数创建了另一个任务job1。
Job0执行完它的时间片后,RTX51 Tiny开始执行job1。
Job1执行完它的时间片后,RTX51 Tiny又返回到job0开始执行。
然后再切换到job1,如此循环。
#includeint 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挂起当前任务直到特定的事件发生。
在这期间任何其他的任务都可以执行。
Cooperative 任务切换如果你禁止了round-robin多任务,你必须设计并执行你的任务从而让他你们以cooperativ方式工作。
实时多任务操作系统RTX-51 TINY的应用
张红兵
【期刊名称】《湖北科技学院学报》
【年(卷),期】2004(024)006
【摘要】RTX-51 Tiny是集成于Keil C51开发环境中的实时多任务操作系统,该系统可以应用于所有具有8051内核的单片机.它对于从前后台编程模式走向基于实时多任务操作系统的编程模式提供了非常好的平台.本文详细叙述了应用该操作系统进行单片机开发的过程,并给出了实例.
【总页数】4页(P30-33)
【作者】张红兵
【作者单位】咸宁学院,信息工程学院计算机系,湖北,咸宁,437005
【正文语种】中文
【中图分类】TP368.2
【相关文献】
1.基于RTX51TINY实时操作系统的应用 [J], 雷霞;
2.嵌入式实时多任务操作系统在强实时系统中的应用 [J], 雷杰;文顺安
3.实时多任务操作系统在MCS-51单片机中的应用 [J], 张林生;袁勇;毛朝庆
4.实时操作系统RTX51Tiny在教学中的应用 [J], 卢勇威
5.基于RTX51TINY实时操作系统的应用 [J], 雷霞
因版权原因,仅展示原文概要,查看原文内容请购买。
单片机操作系统RTX51Tiny的使用步骤一般地,下面三步是使用RTX51 Tiny要实现的l 编写RTX51程序l 编译并连接程序l 测试和调试程序一、编写程序写RTX51 Tiny程序时,必须用关键字对任务进行定义,并使用在RTX51TNY.H中声明的RTX51 Tiny核心例程。
1、包含文件RTX51 Tiny仅需要包含一个文件:RTX51TNY.H。
所有的库函数和常数都在该头文件中定义。
你可以在你的源文件中包含它:#include2、编程原则以下是建立RTX51 Tiny程序时必须遵守的原则:①、确保包含了RTX51TNY.H头文件。
②、不要建立main函数,RTX51 Tiny有自己的mian函数。
③、程序必须至少包含一个任务函数。
④、中断必须有效(EA=1),在临界区如果要禁止中断时一定要小心。
参见概述中的中断一节。
⑤、程序必须至少调用一个RTX51 Tiny库函数(象os_wait)。
否则,连接起将不包含RTX51 Tiny库。
⑥、Task 0是程序中首先要执行的函数,必须在任务0中调用os_create_task 函数以运行其余任务。
⑦、任务函数必须是从不退出或返回的。
任务必须用一个while(1)或类似的结构重复。
用os_delete_task函数停止运行的任务。
⑧、必须在uvison中指定RTX51 Tiny,或者在连接器命令行中指定。
更多技术文档参见keil软件知识库。
3、定义任务实时或多任务应用是由一个或多个执行具体操作的任务组成的,RTX51 Tiny支持最多16个任务。
任务就是一个简单的C函数,返回类型为void,参数列表为void,并且用_task_声明函数属性。
例如:void func (void)_task_task_id这里,func是任务函数的名字,task_id是从0到15的一个任务ID号。
下面的例子定义函数job0编号为0的任务。
该任务使一个计数器递增并不断重复。
RTX51 Tiny第二版(版本2.02)是RTX51 Tiny 1.xx的升级版本,他完全集成在keil的集成开发环境中。
RTX51 Tiny是一款可以运行在大多数8051兼容的器件及其派生器件上的实时操作系统(准实时),相对与传统的开发方式而言,用实时操作系统进行开发是一种效率更高的方式。
作为实时操作系统,RTX51 Tiny虽然比较简陋,但它还是具备了一些实时操作系统的基本要素,完全可以充当我们进入实时操作系统(RTOS)世界的领路者,更为重要的是,它是免费的。
:-)从本节起,我会陆续将keil帮助文件中的RTX51 Tiny第二版的使用手册翻译为中文,并发布给大家,供大家参考。
RTX51 Tiny第2版用户手册第一章概述RTX51 Tiny是一种实时操作系统(RTOS),可以用它来建立多个任务(函数)同时执行的应用。
嵌入式应用系统经常有这种需求。
RTOS可以提供调度、维护、同步等功能。
实时操作系统能灵活的调度系统资源,像CPU和存储器,并且提供任务间的通信。
R TX51 Tiny是一个功能强大的RTOS,且易于使用,它用于8051系列的微控制器。
RTX51 Tiny的程序用标准的C语言构造,由Keil C51 C编译器编译。
用户可以很容易的定义任务函数,而不需要进行复杂的栈和变量结构配置,只需包含一个指定的头文件。
一、What’s NewRTX51 Tiny第二版增加了许多新特性,使得实时软件的开发更加简单,如:●支持Code Banking该选项必须在CONF_TNY.A51配置文件中允许,还要在L51_BANK.A51文件中定义Co de Banking硬件配置。
●直接任务切换新增加的函数(os_swich_task)允许一个任务立即切换到另一个处于就绪态的任务。
●任务就绪标志新的库函数isr_set_ready和os_set_ready允许用户给一个任务设置就绪标志。
就绪标志可以用于将一个正在等待时间间隔、超时或信号(参见os_wait)的任务置为就绪态,该任务在下一个运行时机恢复。