新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 中斷多任務+狀態機 單片機軟件結構設計

        中斷多任務+狀態機 單片機軟件結構設計

        作者: 時間:2016-11-27 來源:網絡 收藏
        mcu由于內部資源的限制,軟件設計有其特殊性,程序一般沒有復雜的算法以及數據結構,代碼量也不大,通常不會使用OS (Operating System),因為對于一個只有若干K ROM,一百多byte RAM的mcu來說,一個簡單OS也會吃掉大部分的資源。

        本文引用地址:http://www.104case.com/article/201611/322212.htm

        對于無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即可。


        上一頁 1 2 下一頁

        評論


        技術專區

        關閉
        主站蜘蛛池模板: 宁德市| 香港 | 库车县| 庄浪县| 侯马市| 阿瓦提县| 会理县| 衡水市| 湘潭县| 澜沧| 乌兰察布市| 临猗县| 香港 | 卢氏县| 分宜县| 桐梓县| 陕西省| 潮安县| 固始县| 高青县| 建阳市| 游戏| 镇雄县| 浏阳市| 金华市| 托里县| 上林县| 南平市| 唐山市| 沾益县| 新野县| 新平| 金山区| 阳东县| 邯郸市| 瓦房店市| 潼南县| 石阡县| 东兴市| 宁南县| 富平县|