中断驱动多任务单片机MCU下的一种软件设计结构Word文档格式.docx
《中断驱动多任务单片机MCU下的一种软件设计结构Word文档格式.docx》由会员分享,可在线阅读,更多相关《中断驱动多任务单片机MCU下的一种软件设计结构Word文档格式.docx(11页珍藏版)》请在冰点文库上搜索。
….
//Initialize
while(true){
IDLE;
//sleep
}
这里的IDLE是一条sleep指令,让mcu进入低功耗模式。
中断程序的构成
voidTimer_Interrupt()
SetTimer();
ResetStack();
Enable_Timer_Interrupt;
….
进入中断后,首先重置Timer,这主要针对8051,8051自动重装分频器只有8-bit,难以做到长时间定时;
复位stack,即把stack指针赋值为栈顶或栈底(对于pic,TIDSP等使用循环栈的mcu来说,则无此必要),用以表示与过去决裂,而且不准备返回到中断点,保证不会保留程序在跑飞时stack中的遗体。
Enable_Timer_Interrupt也主要是针对8051。
8051由于中断控制较弱,只有两级中断优先级,而且使用了如果中断程序不用reti返回,则不能响应同级中断这种偷懒方法,所以对于8051,必须调用一次reti来开放中断:
_Enable_Timer_Interrupt:
acall
_reti
_reti:
reti
下面就是任务的执行了,这里有几种方法。
第一种是采用固定顺序,由于mcu程序复杂度不高,多数情况下可以采用这种方法:
…
ProcessKey();
RunTask2();
RunTaskN();
while
(1)IDLE;
可以看到中断把所有任务调用一遍,至于任务是否需要运行,由程序员自己控制。
另一种做法是通过函数指针数组:
#defineCountOfArray(x)(sizeof(x)/sizeof(x[0]))
typedefvoid(*FUNCTIONPTR)();
constFUNCTIONPTR[]tasks={
ProcessKey,
RunTask2,
RunTaskN
};
for(i=0;
i<
CountOfArray(tasks),i++)
(*tasks[i])();
}使用const是让数组内容位于codesegment(ROM)而非datasegment(RAM)中,8051中使用code作为const的替代品。
关于函数指针赋值时是否需要取地址操作符&
的问题,与数组名一样,取决于compiler.对于熟悉汇编的人来说,函数名和数组名都是常数地址,无需也不能取地址。
对于不熟悉汇编的人来说,用&
取地址是理所当然的事情。
VisualC++2005对此两者都支持)
这种方法在汇编下表现为散转,一个小技巧是利用stack获取跳转表入口:
mov
A,state
MultiJump
ajmp
state0
state1
...
MultiJump:
pop
DPH
DPL
rl
A
jmp
@A+DPTR
还有一种方法是把函数指针数组(动态数组,链表更好,不过在mcu中不适用)放在datasegment中,便于修改函数指针以运行不同的任务,这已经接近于动态调度了:
FUNCTIONPTR[COUNTOFTASKS]tasks;
tasks[0]=ProcessKey;
tasks[0]=RunTaskM;
tasks[0]=NULL;
FUNCTIONPTRpFunc;
COUNTOFTASKS;
i++)
pFunc=tasks[i]);
if(pFunc!
=NULL)
(*pFunc)();
通过上面的手段,一个中断驱动的框架形成了,下面的事情就是保证每个tick内所有任务的运行时间总和不能超过一个tick的时间。
为了做到这一点,必须把每个任务切分成一个个的时间片,每个tick内运行一片。
这里引入了状态机(statemachine)来实现切分。
关于statemachine,
很多书中都有介绍,这里就不多说了。
实践升华出理论,理论再作用于实践。
我很长时间不知道我一直沿用的方法就是statemachine,直到学习UML/C++,书中介绍tachniquesforidentifyingdynamicbehvior,方才豁然开朗。
功夫在诗外,掌握C++,甚至C#JAVA,对理解嵌入式程序设计,会有莫大的帮助)
状态机的程序实现相当简单,第一种方法是用swich-case实现:
voidRunTaskN()
switch(state){
case0:
state0();
break;
case1:
state1();
caseM:
stateM();
default:
state=0;
另一种方法还是用更通用简洁的函数指针数组:
constFUNCTIONPTR[]states={state0,state1,…,stateM};
(*states[state])();
下面是statemachine控制的例子:
voidstate0(){}
voidstate1(){state++;
}
//
nextstate;
voidstate2(){state+=2;
gotostate4;
voidstate3(){state--;
gotopreviousstate;
voidstate4(){delay=100;
state++;
}
voidstate5(){delay--;
if(delay<
=0)state++;
//delay100*tick
voidstate6(){state=0;
gotothefirststate
一个小技巧是把第一个状态state0设置为空状态,即:
voidstate0(){}
这样,state=0可以让整个task停止运行,如果需要投入运行,简单的让state=1即可。
以下是一个键盘扫描的例子,这里假设tick=20ms,ScanKeyboard()函数控制口线的输出扫描,并检测输入转换为键码,利用每个state之间20ms的间隔去抖动。
enumEnumKey{
EnumKey_NoKey=
0,
structStructKey{
int
keyValue;
bool
keyPressed;
};
structStructKeyProcesskey;
voidProcessKey(){(*states[state])();
voidstate1(){key.keyPressed=false;
}
voidstate2(){if(ScanKey()!
=EnumKey_NoKey)state++;
//nextstateifakeypressed
voidstate3()
{
//debouncingstate
key.keyValue=ScanKey();
if(key.keyValue==EnumKey_NoKey)
state--;
else{
key.keyPressed=true;
state++;
}
voidstate4(){
if(ScanKey()==EnumKey_NoKey)state++;
//nextstateifthekeyreleased
voidstate5(){
ScanKey()==EnumKey_NoKey?
state=1:
state--;
上面的键盘处理过程显然比通常使用标志去抖的程序简洁清晰,而且没有软件延时去抖的困扰。
以此类推,各个任务都可以划分成一个个的state,每个state实际上占用不多的处理时间。
某些任务可以划分成若干个子任务,每个子任务再划分成若干个状态。
对于常数类型,建议使用enum分类组织,避免使用大量#define定义常数)
对于一些完全不能分割,必须独占的任务来说,比如我以前一个低成本应用中红外遥控器的软件解码任务,这时只能牺牲其他的任务了。
两种做法:
一种是关闭中断,完全的独占;
Disable_Interrupt;
Enable_Interrupt;
第二种,允许定时中断发生,保证某些时基register得以更新;
UpdateTimingRegisters();
if(watchDogCounter=0){
else
watchDogCounter--;
}
只要watchDogCounter不为0,那么中断正常返回到中断点,继续执行先前被中断的任务,否则,复位stack,重新进行任务循环。
这种状况下,中断处理过程极短,对独占任务的影响也有限。
中断驱动多任务配合状态机的使用,我相信这是mcu下无os系统较好的设计结构。
对于绝大多数mcu程序设计来说,可以极大的减轻程序结构的安排,无需过多的考虑各个任务之间的时间安排,而且可以让程序简洁易懂。
缺点是,程序员必须花费一定的时间考虑如何切分任务。
下面是一段用C改写的CDPlayer中检测disc是否存在的伪代码,用以展示这种结构的设计技巧,原源代码为Z8mcu汇编,基于Sony的DSP,ServoandRF处理芯片,通过送出命令字来控制主轴/滑板/聚焦/寻迹电机,并读取状态以及CD的subQ码。
这个处理任务只是一个大任务下用statemachine切开的一个二级子任务,tick=20ms。
state1(){InitializeMotor();
state2(){
if(innerSwitch!
=ON){
SendCommand(EnumCommand_SlidingMotorBackward);
timeout=MILLISECOND(10000);
//滑板电机向内运动,直至触及最内开关。
else
state+=
2;
state3(){
if((--timeout)==0){
//note:
someCcompliersdonotsupport(--timeout)==
SendCommand(EnumCommand_SlidingMotorStop)
systemErrorCode=EnumErrorCode_InnerSwitch;
//10s超时错误,
if(innerSwitch==ON){
SendCommand(EnumCommand_SlidingMotorStop)
timeout=MILLISECOND(200);
//200ms电机停止时间
state4(){if((--timeout)==0)state++;
//等待电机完全停止
state5(){
SendCommand(EnumCommand_SlidingMotorForward);
timeout=MILLISECOND(2000);
//滑板电机向外运动,脱离innerswitch
state6(){
//2s超时错误,
if(innerSwitch==OFF){