+]#VJ@`(hzvP44606 实时操作系统RTOS的核心是中断,利用中断进行任务切换。在大部分RTOS如μC/OS-II中,每个任务都有自己的堆栈,用来保存任务的一些信息,任务之间通过信号量、邮箱、消息队列等传递信息。在很多情况下并不需要这些功能,只需要使单片机在接收到控制信号后,切换到不同的工作状态,也就是只要进行任务切换,不需要保存任务的相关信息。舍弃这些复杂的功能可以使程序结构变得简洁易用。电子园51单片机学习网!dL [1Y-lNTZ
电子园51单片机学习网 EaO A.a两种机制在应用实例中的比较
+g%t yV8x^)v1a K44606 下面用一个应用实例来说明本设计的思路。要设计一个智能安防系统,它的功能包括:当有人入侵时执行报警工作;用户可以通过键盘板进行功能设置;主板能与管理中心进行通讯,当发生火灾、地震等灾情时,管理中心能通知用户。其结构如图1所示。平时状态下,主板的CPU不断地扫描各个传感器的状态。当检测到传感器的异常信号(有人闯入)时,CPU进入入侵报警状态,执行响警铃、拨打户主电话、通知管理中心等工作。当发生火灾地震时,管理中心发送一个串口代码给主板CPU,使CPU进入灾难报警状态,执行响警铃、语音报警等操作。用户需要进行功能设置时可以通过键盘板使主板CPU进入功能设置状态。因此主板的 CPU有4种不同的工作状态。
;\0e*X1f3^/P44606740)this.width=740" border="undefined">电子园51单片机学习网%Mh[*i3R9E"Pw:x
/`_R r~0LfR)O44606图1 智能安防系统结构示意图电子园51单片机学习网_.~(O,N+Md&u
如果采用单任务机制, 主板的程序流程如图2所示。在主函数中循环检测传感器状态,如有异常则调用报警函数,灾难报警和功能设置在串口中断中完成。这种单任务结构有两个缺点。首先,在各种非平时状态中,程序需要不停地检测是否收到撤除信号,这个要求在程序代码量大、执行工作较多的情况下很难实现。其次,各状态之间的切换十分困难,用C语言写的程序为求模块化,一般函数数量较多,函数调用的嵌套层数也多,要从一个较深的嵌套立刻跳出到主函数,是非常困难的。一般的解决方法或是使用C51的库函数setjmp()和longjmp()实现长跳转,但是这两个函数在中断函数内部是无能为力的;再或是在C函数中嵌入汇编指令。虽然用汇编指令可以实现程序的长距离跳转,但是这种方法的调试过程十分烦琐,而且程序的可移植性差。对于习惯用C51编程而不想用汇编的设计者,该部分程序是一个难题。
B]eh!q44606 电子园51单片机学习网(Z1JR,D$@8f-K740)this.width=740" border="undefined">电子园51单片机学习网&I1Y)o.q(H DK7k
图2 单任务机制程序流程电子园51单片机学习网P RiC d I2F
实现多任务机制的程序结构
$e h.p/fdt1wS4}44606 本文提供了一种方法,可以在完全不使用汇编指令的前提下实现可移植性强的多任务程序,程序流程如图3所示。
'x.z!R([$wv;{Dmb44606 740)this.width=740" border="undefined">电子园51单片机学习网,n%c#f mmo&[Q
电子园51单片机学习网%v&~pMr b-y"s
图3 多任务结构程序流程电子园51单片机学习网v+J7g"y0HT
0r%@y4L2OO U44606 实现这个多任务机制的完整源代码如下:
!v}*}0xF44606word idata PC_Value, SP_Value; file://储存中断返回点、SP初值的全局变量
+k5DP k*pJ:y+Gw@44606byte idata Ctrl_Code; file://控制任务切换的全局变量,在中断函数里被赋值电子园51单片机学习网3T.F;F%HT,z5Mr
void main() 电子园51单片机学习网7Z/D9SAE&b.a"x
{电子园51单片机学习网&d(K&L6K^
Initial(); file://初始化函数,与程序结构无关
2?MgCe%GoOe#hx44606 SP_Value=SP; file://获取SP的初始值
0A i&CR+s[1fP44606 PC_Value=Get_Next_PC(); file://获取下一条指令的地址电子园51单片机学习网o muL*x)D
EA=1; file://获取PC、SP初值后再开中断保证稳定性
zy'R`%d JN*s.lK44606 if(Ctrl_Code!=0)电子园51单片机学习网0bG M,A K-y |
SP="SP"_Value; file://重置堆栈指针,防止堆栈溢出
%i"va xk^M*p44606 switch( Ctrl_Code) file://任务入口地址,即中断的返回点 电子园51单片机学习网;n\)MKd0mK
{
r!^ c k,E:H4y2}44606 case 1: goto TASK1;电子园51单片机学习网RF+RO+[-d
case 2: goto TASK2;电子园51单片机学习网W#z+b9t1oUt"q
case 3: goto TASK3;电子园51单片机学习网*IZ/R&u#RhG7AV
default: break;
u:cZ c.fMut#`44606 }电子园51单片机学习网*?N2X |(l
TASK1: for( ; ; )电子园51单片机学习网"N]&G&C4C+@3Yr
{ file://任务1代码 }电子园51单片机学习网RPk6KO/^(w
TASK2: for( ; ; )
y W%GrcX44606 { file://任务2代码 }
WHp^U7U:cx44606TASK3: for( ; ; )电子园51单片机学习网 lk7nK#C1sEvaq/r
{ file://任务2代码 }
(oM-O7a`'QU\44606}
3qh Om(}44606word Get_Next_PC(void) file://获取下一条指令的地址
n2\ W1p9k0Y44606{
A$\8np2_]S S44606 word address;电子园51单片机学习网 ]l-f3]|*O
address=*((unsigned char *)SP); file://PC的高字节电子园51单片机学习网\3i&mo4Y
address <<= 8;电子园51单片机学习网2[(gu@7G
address+=*((unsigned char *)(SP-1)); file://PC的低字节
B#m:[Q-Gj.b#tf y6M$Z44606 return address+4; file://查看反汇编代码,计算所得电子园51单片机学习网 h uu|(Oh,@9x
}电子园51单片机学习网p9O-i'na
void Chuan_Kou_Interrupt(void) interrupt 4 using 0
ESlHV%vVv44606{
]o.[c}4h+@(ji44606 byte a1,a2;电子园51单片机学习网%fqgX'DU7_3KU
a1=a1*a2;电子园51单片机学习网 z3eI.^7`$|
*((unsigned char *)(SP-5))=PC_Value>>8;电子园51单片机学习网 R ctRmru6|M
*((unsigned char *)(SP-6))=PC_Value & 0x00ff;
Fz#[/O]S7x} G44606 {
Phg!M9B%j9S44606 file://接收串口代码并根据代码修改Ctrl_Code的值电子园51单片机学习网P&F| hD9jH
file://其他操作电子园51单片机学习网Fa9w4v@!D/X,k
}电子园51单片机学习网 [7\F3s5m nKwQY j
}电子园51单片机学习网{X*S*oZ4i
M.eC \-d7{M4C44606任务调度原理与实现电子园51单片机学习网5g9Y!}:]r ~B]i
程序的整体思路是在主函数main中依次放置几个死循环作为任务框架,即每个任务都是一个死循环,利用中断进行任务切换。以刚才所说的安防系统为例,由于主板、键盘、管理中心之间是通过串口通讯的,因此串口是用来触发任务切换的理想中断源。程序为所有任务设置一个总入口并放在主函数中,串口中断每次返回时必须先经过这个总入口,在总入口处检查任务控制变量(全局变量)的值,任务控制变量已在串口中断中被赋值,其值决定要切换到哪个任务。电子园51单片机学习网gj2s|w*t{
WoMoz9C}$IP44606 设计中可以把平时状态、入侵报警状态、危机报警状态、功能设置状态分别作为任务1、任务2、任务3、任务4。主板CPU平常工作在平时状态,即任务1;当串口收到管理中心的危机代码,在串口中断函数中令Ctrl_Code = 3,中断返回后会切换到任务3;同样,接收到键盘的功能设置代码后,会切换到任务4;由于入侵检测是由主板CPU自己负责,因此如果检测到有人入侵需要切换到入侵报警状态时,可以借由键盘中转产生串口中断,即向键盘发送一串口数据并要求键盘回送。这样就实现了各个状态的切换。
T2Qb.q/b KB"W"~ R44606
:f-D h)B W5y.xAm44606 实现任务调度需要解决3个关键问题:电子园51单片机学习网N| `"I*P(j'`To1oyZ
电子园51单片机学习网.N[+JM}+R[crgm
① 获取任务入口点的程序地址。由于使用C语言不能直接获取和修改程序计数器PC的值,而在调用函数时会将PC值入栈,利用这个特点在任务入口处之前调用 Get_Next_PC函数即可从堆栈中获得入口地址。Get_Next_PC中,SP为堆栈指针,得到的PC值要加4才是任务入口地址,因为查看反汇编窗口可知,将函数返回值传给全局变量PC_Value需要两条2字节长的mov指令。电子园51单片机学习网p$D3O:`.D
电子园51单片机学习网 vShpt0|h/fa1YQ
② 修改中断返回地址。修改中断返回地址的操作与获取PC值类似,都是通过修改堆栈中的内容实现。但是由于编译器自身的特点,在进入中断时,编译器除了把返回地址入栈外,还会计算自身及它所调用的函数对寄存器ACC、 B、 DPH、 DPL、 PSW、 R0 ~ R7的改变,并将它认为被改变了的寄存器也入栈保护。如果堆栈结构会随中断函数内容改变而变化,就没办法计算中断返回地址堆栈中的位置。解决方法是,在中断函数定义时加上关键字using 0 告诉编译器中断函数及其调用的函数将使用寄存器组0,这样工作寄存器R0~R7将不会被保存。ACC、PSW、DPH、DPL在对PC_Value操作时已经用到,在中断函数开头定义两个变量a1、b1并令它们相乘,使B寄存器也被入栈,这样堆栈的结构就是固定的了。电子园51单片机学习网@m*f k*M)QcB
电子园51单片机学习网3WZ@rG,UX"l
③防止堆栈溢出。由于在调用函数时编译器会将当前地址入栈,返回时再出栈,当任务切换即中断多次发生在函数调用过程中时,堆栈会因为只入不出而最终导致溢出。这是不能容许的。因此,应在主函数开头初始化后立刻将SP值保存,再在每次任务切换后都将SP恢复为初值,这可以有效防止堆栈溢出。
.C2f{L \#Ji44606结语电子园51单片机学习网_k g5t.E-n/mC?L
根据以上的比较与分析可以看出这种实现多任务机制的方法具有如下优点:与采用单任务机制的程序相比,其结构简单清晰,易于控制;利用中断和堆栈实现任务切换时的长跳转,完全不需使用汇编语言,可移植性强;增加的代码量极小,实时性好,节省程序开发时间。电子园51单片机学习网{V1Xi;L M
电子园51单片机学习网bY] A b&x5DR
以上介绍的方法已经通过测试并应用于几个实际项目中,包括智能小区安防系统、汽车CAN总线控制系统等,取得了良好效果。只要根据具体的硬件与编译环境稍作修改,亦可应用于其他的单片机系统中。电子园51单片机学习网G!oe]o#C2r O(j
+P ~t&[Dx6h)H44606参考文献
'g9I3t.smV4q446061. 张培仁. 基于C语言编程MCS-51单片机原理与应用. 北京:清华大学出版社, 2003.1.
JY)D:W1_*L!b1G!Q446062. 胡大可等. 基于单片机8051的嵌入式开发指南. 北京:电子工业出版社, 2003.1电子园51单片机学习网)]7cxI9C@
文章评论(0条评论)
登录后参与讨论