您的位置 首页 应用

中止多任务+状态机 单片机软件结构设计

mcu由于内部资源的限制,软件设计有其特殊性,程序一般没有复杂的算法以及数据结构,代码量也不大,通常不会使用OS(OperatingSystem),

mcu因为内部资源的约束,软件规划有其特殊性,程序一般没有杂乱的算法以及数据结构,代码量也不大,一般不会运用OS (Operating System),因为关于一个只要若干K ROM,一百多byte RAM的mcu来说,一个简略OS也会吃掉大部分的资源。

关于无os的体系,盛行的规划是主程序(主循环) +(守时)中止,这种结构尽管契合天然主意,不过却有许多晦气之处,首要是中止能够在主程序的任何地方产生,随意打断主程序。其次主程序与中止之间的耦合性(关联度)较大,这种做法使得主程序与中止环绕在一起,有必要细心处理以防不测。

那么换一种思路,假如把主程序悉数放入(守时)中止中会怎么样?这么做至少能够当即看到几个优点:体系能够处于低功耗的休眠状况,将由中止唤醒进入主程序;假如程序跑飞,则中止能够拉回;没有了主从之分(其他中止另计),程序易于模块化。

(题外话:这种办法就不会有何处喂狗的说法,也没有中止是否应该尽可能的简略的争辩了)

为了把主程序悉数放入(守时)中止中,有必要把程序化分红一个个的模块,即使命,每个使命完结一个特定的功用,例如扫描键盘并检测按键。设定一个合理的时基(tick),例如5, 10或20 ms,每次守时中止,把一切使命履行一遍,为削减杂乱性,一般不做动态调度(最多运用固定数组以简化规划,做动态调度就挨近os了),这实际上是一种无优先级时刻片轮循的变种。来看看主程序的构成:

void main()

{

….// Initialize

while (true) {

IDLE;//sleep

}

}

这儿的IDLE是一条sleep指令,让mcu进入低功耗形式。中止程序的构成

void Timer_Interrupt()

{

SetTimer();

ResetStack();

Enable_Timer_Interrupt;

….

进入中止后,首要重置Timer,这首要针对8051, 8051主动重装分频器只要8-bit,难以做到长时刻守时;复位stack,即把stack指针赋值为栈顶或栈底(关于pic,TI DSP等运用循环栈的mcu来说,则无此必要),用以表明与曩昔分裂,并且不准备回来到中止点,确保不会保存程序在跑飞时stack中的遗体。Enable_Timer_Interrupt也首要是针对8051。8051因为中止操控较弱,只要两级中止优先级,并且运用了假如中止程序不必reti回来,则不能呼应同级中止这种偷闲办法,所以关于8051,有必要调用一次reti来敞开中止:

_Enable_Timer_Interrupt:

acall_reti

_reti:reti

下面便是使命的履行了,这儿有几种办法。榜首种是选用固定次序,因为mcu程序杂乱度不高,大都情况下能够选用这种办法:

Enable_Timer_Interrupt;

ProcessKey();

RunTask2();

RunTaskN();

while (1) IDLE;

能够看到中止把一切使命调用一遍,至于使命是否需求运转,由程序员自己操控。另一种做法是经过函数指针数组:

#define CountOfArray(x) (sizeof(x)/sizeof(x[0]))

typedef void (*FUNCTIONPTR)();

const FUNCTIONPTR[] tasks = {

ProcessKey,

RunTask2,

RunTaskN

};

void Timer_Interrupt()

{

SetTimer();

ResetStack();

Enable_Timer_Interrupt;

for (i=0; i

(*tasks[i])();

while (1) IDLE;

}

运用const是让数组内容坐落code segment(ROM)而非data segment (RAM)中,8051中运用code作为const的替代品。

(题外话:关于函数指针赋值时是否需求取地址操作符&的问题,与数组名相同,取决于compiler.关于了解汇编的人来说,函数名和数组名都是常数地址,无需也不能取地址。关于不了解汇编的人来说,用&取地址是天经地义的工作。Visual C++ 2005对此两者都支撑)

这种办法在汇编下表现为散转,一个小技巧是使用stack获取跳转表进口:

movA, state

acallMultiJump

ajmpstate0

ajmpstate1

MultiJump:popDPH

popDPL

rlA

jmp@A+DPTR

还有一种办法是把函数指针数组(动态数组,链表更好,不过在mcu中不适用)放在data segment中,便于修正函数指针以运转不同的使命,这现已挨近于动态调度了:

FUNCTIONPTR[COUNTOFTASKS] tasks;

tasks[0] = ProcessKey;

tasks[0] = RunTaskM;

tasks[0] = NULL;

FUNCTIONPTR pFunc;

for (i=0; i< COUNTOFTASKS; i++){

pFunc = tasks[i]);

if (pFunc != NULL)

(*pFunc)();

}

经过上面的手法,一个中止驱动的结构形成了,下面的工作便是确保每个tick内一切使命的运转时刻总和不能超过一个tick的时刻。为了做到这一点,有必要把每个使命切分红一个个的时刻片,每个tick内运转一片。这儿引入了状况机(state machine)来完成切分。关于state machine,许多书中都有介绍,这儿就不多说了。

(题外话:实践提高出理论,理论再作用于实践。我很长时刻不知道我一向沿袭的办法便是state machine,直到学习UML/C++,书中介绍tachniques for identifying dynamic behvior,刚才恍然大悟。功夫在诗外,把握C++,乃至C# JAVA,对了解嵌入式程序规划,会有极大的协助)

状况机的程序完成适当简略,榜首种办法是用swich-case完成:

void RunTaskN()

{

switch (state) {

case 0: state0(); break;

case 1: state1(); break;

case M: stateM(); break;

default:

state = 0;

}

}

另一种办法仍是用更通用简练的函数指针数组:

const FUNCTIONPTR[] states = { state0, state1, …, stateM };

void RunTaskN()

{

(*states[state])();

}

下面是state machine操控的比如:

void state0() { }

void state1() { state++; }//next state;

void state2() { state+=2; }//go to state 4;

void state3() { state–; }//go to previous state;

void state4() { delay = 100; state++; }

void state5() { delay–; if (delay <= 0) state++; }//delay 100*tick

void state6() { state=0; }//go to the first state

一个小技巧是把榜首个状况state0设置为空状况,即:

void state0() { }

这样,state =0能够让整个task中止运转,假如需求投入运转,简略的让state = 1即可。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/yingyong/257006.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部