51单片机实时操作系统作者:徐少伟日期:2013年12月07日摘要本文着重介绍了运行在51单片机上基于片轮询式实时操作系统RTOS的构建,讨论了实时操作系统的运行原理和设计思路。
关键词:51单片机、片轮询、实时操作系统RTOS1前言随着计算机技术的发展,计算机已经被广泛地应用到各个领域中。
而在控制领域,人们更多地关心计算机的低成本、小体积、运行的可靠性和控制的灵活性。
特备是智能仪表、智能传感器、智能家电、智能办公设备、汽车及军事电子设备等应用系统要求计算机嵌入这些设备中。
而作为嵌入式计算机的单片机因其体积小、可靠性高、控制功能强以及非凡的嵌入式应用形态,使得单片机应用技术已经成为电子应用系统设计中最为常用的技术手段。
在工业控制方面,因工业环境对计算机的可靠性和实时性的要求特别高,而诸如51系列的单片机的片上资源比较有限,因此开发并构建一种应用于单片机上的实时多任务操作系统已成为一种迫切的需求。
2实时操作系统设计概述2.1实时多任务操作系统(RTOS)简介过去一个单片机应用程序所控制的任务和外设不多,采用一个主程序和几个子程序模块的调用,即可满足要求。
但随着应用的复杂化,对单片机软件提出了更高的要求。
一个控制器系统可能需要同时控制或监控很多外设,要求有实时响应;有很多处理的任务,各种任务之间有信息的传递。
如果仍采用原来的程序设计方法,将会存在两个问题。
一是中断可能得不到及时响应,处理时间过长。
二是系统任务多,要考虑的各种可能也多,各种资源如调度不当就会发生死锁,降低软件的可靠性,程序编写的任务量成指数增加。
实时操作系统是一段系统启动后首先执行的背景程序,用户的应用程序是运行在RTOS之上的各个任务。
RTOS根据各个任务的要求,进行资源(包括存储器、外设等)管理、消息管理、任务调度、异常处理等工作。
实时多任务操作系统,以分时方式运行的多个任务,看上去好像是多个任务“同时”运行。
标准的RTOS应具有任务调度、中断处理、事件管理、定时器管理、循环队列管理、资源管理、存储管理、自动掉电管理等功能,基于优先服务方式的RTOS才是真正的实时操作系统。
本文主要讨论了基于时间分片轮询方式,即片轮询方式的多任务操作系统,重点介绍多任务实时操作系统的原理和构建方法,为深入研究真正意义上的实时多任务操作系统RTOS奠定一定的理论和思想基础。
2.2实时多任务操作系统(RTOS)任务切换在实时操作系统RTOS中,任务的切换方式有三种:协同方式、时间片轮询方式以及抢占优先级方式。
2.2.1协同方式所谓“协同方式”,是指一个任务在持续运行而不释放资源,其他任务是没有机会获得运行的,除非此任务主动释放所占用的资源。
一个运行着的任务主动释放所占资源,是依靠TASK_SWTICH:函数(任务调度器)实现的。
在任务程序必要的地方,调用函数TASK_SWITCH:就可以将CPU切换到其他的任务中。
下面利用汇编指令CALL和JMP来进一步说明TASK_SWITCH:的工作原理。
1)指令CALL和RETCALL addr11;SP←(SP)+1,(SP)←(PC.L);SP←(SP)+1,(SP)←(PC.H);PC←addr11RET;(PC.H)←((SP)),SP←(SP)-1;(PC.L)←((SP)),SP←(SP)-1从以上命令中可以看出,当执行指令CALL后,指令计数器PC的当前值被压入了堆栈(堆栈指针SP)。
程序末尾调用指令RET 后,PC的值从堆栈中弹出,从而结束子程序,继续执行原来的程序。
如果在指令RET前,将存储在堆栈中的指令计数器PC的值更改为另一个程序的地址,执行指令RET后,程序就会跳转到该程序上去执行,而不限于CALL 里压入的地址。
这个过程可以视为是任务调度器TASK_SWICH:的基本工作原理。
2)指令JMPJMP addr11;(PC)←(PC)+2,PC←addr11从此命令可以看出,当执行指令JMP之后,指令计数器PC的值更改为新程序的地址,但当前PC的值(即原来程序的地址)没有被保存,故执行完新地址的程序后,指令计数器PC不会跳转回原来的程序地址,也就是说程序将继续往下执行而不会再返回。
2.2.2时间片轮询方式所谓时间片轮询方式,是指CPU让每个任务都处于平等的地位,然后给每个任务相同的时间片(如10ms)。
当一个任务的运行时间用完了,操作系统就马上把CPU切换给下一个需要的任务。
这种方法的实时性不高,但它确保了每个任务都有相同的执行时间。
该方式的时间片产生是利用定时器实现的,一般定时时间为10ms。
时间太小,系统在各个任务之间来回切换,浪费了CPU的资源;时间太长,任务切换不及时,从而致使任务的实时性降低。
时间片轮询方式在启动任务后,就立即启动CPU的定时器。
当定时时间到了之后,系统会自动调用任务调度器TASK_SWITCH:将CPU切换到下一个需要运行的任务。
2.2.3抢占优先级方式所谓抢占优先级方式,是指操作系统在任何时候都要保证拥有最高优先级的那个任务处于运行状态。
如系统正在运行着优先级=2的任务,但因一信号的到达,优先级=1的那个任务解除了阻塞,处于就绪状态,这时操作系统就必须马上停止优先级=2的任务,切换到优先级=1的任务,切换的时间越短越好。
2.3实时多任务操作系统(RTOS)任务调度器与任务切换有关的事情都要调用到任务调度器。
当操作系统需要将CPU从正在运行的任务1切换到任务2时,就要调用任务调度器TASK_SWITCH:才能实现任务之间的这种切换。
任务调度器主要完成以下功能:1、所有寄存器全部入栈;2、保护当前任务的动态断点;3、切换到下一任务;4、将下一任务被保护的寄存器弹出栈。
2.3.1当前任务的所有寄存器全部入栈将当前任务的所有寄存器全部入栈,是为了保护当前任务程序的出口,以便之后CPU能够正确地返回到该任务程序的断点处,继续执行该任务未被执行的部分。
寄存器入栈用到的是指令PUSH。
具体的入栈操作如下:TASK_SWITCH:PUSH ACC/*断点入栈*/PUSH BPUSH PSWPUSH0PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7以上程序完成了11个寄存器的入栈操作。
在51单片机中,它们都是经常被使用到的寄存器资源,具体可以分为四类。
1)ACC:累加器累加器ACC用于向算术/逻辑运算单元ALU提供操作数和存放运算的结果,是51单片机中最常被使用的寄存器。
2)B:寄存器在乘、除运算时用来存放一个操作数,也用来存放运算后的一部分结果。
在不进行乘、除运算时,可以作为普通的寄存器使用。
3)PSW:程序状态字寄存器程序状态寄存器PSW用来保存算术/逻辑运算单元ALU运算结果的特征(如:结果是否为0,是否有溢出等)和处理器状态。
4)R0~R7:工作寄存器工作寄存器R0~R7共占用32个片内RAM单元。
分成4组,每组8个单元。
当前工作寄存器组由PSW的RS1和RS0位指定。
2.3.2保护当前任务的动态断点当当前任务的出口(所有工作寄存器)被保护入栈之后,接下来要做的是保存入栈的地址,即栈底。
保存所有寄存器(即当前任务出口)的内存(RAM空间)是单独开出来给每一个任务的。
也就是说每一个任务都有一块单独的内存以保护任务断点的信息,而这块内存的起始地址就是栈底,内存的最大值为栈顶,内存空间即栈底至栈顶的空间大小就是栈深,这块内存被称为任务的私栈。
保护当前任务的动态断点,其实是将任务对应的这块内存的栈底地址保存到内存RAM里,以便通过它找到任务断点(所有寄存器)被保存的内存空间(私栈)的位置。
保护当前任务动态断点的操作,具体如下:MOV A,#50H;设置存储栈底的起始地址ADD A,TASK_IDMOV R0,AMOV A,SP;经过PUSH断点入栈后,SP增加了11ADD A,#-11;调整堆栈指针:PUSH命令使SP增加,故需相应调整SP,即减栈MOV@R0,A;以(50H+任务号)为地址将SP保存在此段程序中,TASK_ID是当前任务的任务号,每一个任务都被赋予一个相应的任务号(如任务1的任务号为00),而这部分操作则是在主程序里完成的。
运行完这段程序之后,保护任务断点的内存空间(私栈)的栈底就被保存在(50H+任务号)的地址里了(50H为存储私栈栈底的起始地址,可被自定义)。
2.3.3切换到下一任务切换到下一任务实质是将任务号(TASK_ID)赋给对应要切换的任务。
在任务调度器中,找到要切换任务对应的任务号,是为了通过任务号找到要切换任务之前被保护的断点,再将断点取出并恢复。
基于时间片轮询方式的实时系统是通过将任务号(TASK_ID)依次加“1”来实现对应任务的任务号赋值和任务切换的。
当然也可以依据其他原则来赋值任务号,如抢占优先级式的实时系统就是通过任务的优先级来赋值相应的任务号和任务切换的。
具体的操作如下:INC TASK_ID;任务号+1MOV A,TASK_IDCJNE A,#04,ORI;通过任务号(<=3)判断任务是否全部执行完毕CLR AMOV TASK_ID,AORI:MOV A,#50HADD A,TASK_ID;指向50H+(任务号+1)MOV R0,AMOV A,@R0ADD A,#11;因为下面有POP出栈命令,需调整SP,即增栈MOV SP,A;将下一任务的私栈取出2.3.4将下一任务的断点出栈通过“切换到下一任务”的操作,系统找到下一任务断点被保存的内存空间的栈底,并经过堆栈指针的SP的调整,找到该内存空间的栈顶,从栈顶开始将所有寄存器依次弹出栈。
寄存器出栈用到的是指令POP,具体的操作如下:POP7/*断点出栈*/POP6POP5POP4POP3POP2POP1POP0POP PSWPOP BPOP ACC执行完断点的出栈之后,堆栈指针SP正好指到内存空间的底部即栈底,而栈底的2字节保存的就是下一任务的程序断点地址,执行指令RET(或RETI)后,断点地址就被弹出栈,CPU就被切换到下一任务去执行。
2.4任务装载任务装载的实质是将各个任务的函数地址的高、低字节分别装入分配给任务的私栈(内存空间)当中,并将指向该私栈的地址信息保存在(50H+任务号)的地址里(50H为存储私栈栈底的起始地址,可被自定义)。
任务装载函数为TASK_LOAD:。
2.4.1保存私栈的地址信息任务装载函数TASK_LOAD:首先将任务的私栈(内存空间)的地址信息保存在(存储栈底的起始地址+任务号)的地址里,如(50H+任务号00)。
具体操作如下:TASK_LOAD:MOV A,TASK_IDMOV B,#18;栈深为18MUL AB;槽号ADD A,#12;私栈栈底MOV R4,A;R4中暂时存放着栈深地址MOV A,#50HADD A,TASK_IDMOV R0,AMOV@R0,0x04;以(50H+任务号)为地址存放栈深地址单元2.4.2装入任务函数的高、低字节将任务函数的高、低字节装入私栈(内存空间)里才是任务装载函数TASK_LOAD:最主要做的事情。