新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > μC/OS-II 移植筆記 1(FreeScale 68HCS12 核單片機)

        μC/OS-II 移植筆記 1(FreeScale 68HCS12 核單片機)

        作者: 時間:2016-11-20 來源:網絡 收藏
        μC/OS-II移植筆記 1(移植到FreeScale 68HCS12 核單片機,Small Memory Model)


        最近閑暇下來,花了些時間研究了如何將 μC/OS-II 移植到 FreeScale 68HCS12 內核的單片機。其實這個工作前年做過一次,當時是在網上找的相近的移植代碼(68HC11核,Bank Memory Model,METROWERKS 編譯器)自己做了些修改,內核已經跑起來了,但是在跑串口測試程序(ESBB書上的那個串口模塊)時,程序運行一段時間就會跑飛。當時調試了許久也沒有找到問題。這次就是接著上次的工作繼續深入的往下做,消除錯誤。其實前年調試時就已經隱約的想到了錯誤可能的地方,只是當年對 68HCS12 內核還有 CodeWarrior IDE 附帶的 C 編譯器、匯編器了解的有限,尤其是對編譯器的 C Calling Convention、還有 C 編譯器對代碼中內嵌匯編的處理幾乎完全不知,這種水平下調試不出錯誤也是理所當然的。
        這次由于有了以前的基礎,這次著眼點直接就放在了編譯器對我寫的移植代碼的處理上,在匯編語言的層面上對移植代碼進行了剖析,很快(其實也不算快了,花了我整整 2 天時間)就找到了問題所在,并給出了一個初步的解決方案。現在的移植代碼談不上完美,但至少是正確的。
        1. μC/OS-II 版本的選擇
        這次的移植代碼主要針對 μC/OS-II V2.52,2.52 之后的版本對移植代碼的要求大體相同,因此這個移植代碼稍加修改就應該能在新版本上運行,并且我相信修改的難度應該很小。 μC/OS-III還沒有研究過,因此移植代碼是否適合 μC/OS-III 我就不得而知了。
        之所以選擇μC/OS-II V2.52這個版本主要基于兩個方面的考慮。首先,Jean J.Labrosse寫的那本大名鼎鼎的《MicroC/OS-II The Real-Time Kernel Second Edition》就是根據這個版本寫成的,移植過程中遇到問題至少可以翻翻書。另外,這個版本確實也稱得上經典,每一行代碼都經過經得起推敲。因此,我選擇了這個版本。
        2. 移植代碼詳解
        μC/OS-II 移植主要需要重寫 3 個代碼文件:
        OS_CPU.H
        OS_CPU_C.C
        OS_CPU_A.S
        下面就對移植代碼進行詳細的說明。開發環境采用:CodeWarrior Development Studio V5.9.0
        2.1 OS_CPU.H
        OS_CPU.H 中的代碼主要有兩部分,第一部分 typedef 了一系列的基本數據類型和幾個宏定義。具體代碼如下:

        本文引用地址:http://www.104case.com/article/201611/318813.htm
        1. /*
        2. ******************************************************************************
        3. *DATATYPES
        4. ******************************************************************************
        5. */
        6. typedefunsignedcharBOOLEAN;
        7. typedefunsignedcharINT8U;/*Unsigned8bitquantity*/
        8. typedefsignedcharINT8S;/*Signed8bitquantity*/
        9. typedefunsignedintINT16U;/*Unsigned16bitquantity*/
        10. typedefsignedintINT16S;/*Signed16bitquantity*/
        11. typedefunsignedlongINT32U;/*Unsigned32bitquantity*/
        12. typedefsignedlongINT32S;/*Signed32bitquantity*/
        13. typedeffloatFP32;/*Singleprecisionfloatingpoint*/
        14. typedefdoubleFP64;/*Doubleprecisionfloatingpoint*/
        15. #defineBYTEINT8S/*Definedatatypesforbackwardcompatibility..*/
        16. typedefcharSBYTE;
        17. #defineUBYTEINT8U/*...touC/OSV1.xx*/
        18. #defineWORDINT16S
        19. #defineUWORDINT16U
        20. #defineLONGINT32S
        21. #defineULONGINT32U
        22. typedefunsignedcharOS_STK;/*Eachstackentryis8-bitwide*/
        23. typedefunsignedcharOS_CPU_SR;/*DefinesizeofCPUstatusregister(CCR=8bits)*/
        24. /*
        25. ******************************************************************************
        26. *CONSTANTS
        27. ******************************************************************************
        28. */
        29. #ifndefFALSE
        30. #defineFALSE0
        31. #endif
        32. #ifndefTRUE
        33. #defineTRUE1
        34. #endif


        基本數據類型的長度查編譯器手冊都有詳細的說明。
        有點難度的是堆棧和狀態寄存器,需要查68HCS12內核手冊,雖然68HCS12內核是16位的內核,但是堆棧是以字節為單位的,所以有如下代碼:
        typedef unsigned char OS_STK;

        68HCS12內核的狀態寄存器稱為 CCR,也是8位了,因此:
        typedef unsigned char OS_CPU_SR;

        OS_CPU.H 中還包含對臨界區的處理:


        1. #defineOS_CRITICAL_METHOD3
        2. #ifOS_CRITICAL_METHOD==3
        3. OS_CPU_SROSCPUSaveSR(void);
        4. voidOSCPURestoreSR(OS_CPU_SRos_cpu_sr);
        5. #endif
        6. #ifOS_CRITICAL_METHOD==1
        7. #defineOS_ENTER_CRITICAL()__asmsei;
        8. #defineOS_EXIT_CRITICAL()__asmcli;
        9. #endif
        10. #ifOS_CRITICAL_METHOD==2
        11. #defineOS_ENTER_CRITICAL()__asmpshc;__asmsei;
        12. #defineOS_EXIT_CRITICAL()__asmpulc;
        13. #endif
        14. #ifOS_CRITICAL_METHOD==3
        15. #defineOS_ENTER_CRITICAL()(cpu_sr=OSCPUSaveSR())/*Disableinterrupts*/
        16. #defineOS_EXIT_CRITICAL()(OSCPURestoreSR(cpu_sr))/*Enableinterrupts*/


        上述代碼中雖然列出了三種對臨界區的不同處理方法,但實際只有第三種是正確的。
        第一種方法直接開關中斷,這種方法的問題在于退出臨界區后中斷就被強制性的打開了,及時在進入臨界區前中斷是關著的。在任務級代碼中這樣做沒有太大的問題,因為在執行任務代碼是中斷基本都是打開的,中斷幾乎只有在臨界區中才是關閉的。但是在中斷處理函數中情況就不是這樣的了,68HCS12 內核在進入中斷處理函數后中斷是關閉的,中斷處理函數中要調用 OSIntExit(),OSIntExit()中是有臨界區的,一退出臨界區就會導致中斷打開,CPU 處理新的中斷,也就是形成所謂的中斷嵌套。而中斷嵌套是我們不希望發生的,因為允許中斷嵌套并不能顯著提升系統的性能,還會導致各個任務的堆棧使用量加大,,對于內存緊張的單片機來說,絕對弊大于利。因此,在我的移植代碼中不允許中斷嵌套,也就否掉了第一種臨界區的處理方式。
        第二種方法看似很好,進入臨界區時先將 CCR 的值存入堆棧然后關閉中斷,退出臨界區時直接將堆棧的內容恢復到 CCR。看似很完美的解決方法,實際上卻行不通。C 編譯器要利用堆棧指針的地址來尋址局部變量,而匯編語句 pshc 卻改變了堆棧指針的指向,導致對局部變量的訪問產生了錯位。
        要想說明這個問題還要從 C 編譯器對局部變量的處理方式說起,我使用的 C 編譯器將局部變量放到堆棧上,利用堆棧指針(SP)間接尋址局部變量。如果堆棧指針指向的地址變了,訪問局部變量時就要出問題。下面用個例子來說明:

        volatile char a = 1;
        volatile char b = 2;

        __asm pshc; __asm sei;

        a = 3;
        b = 4;

        __asm pulc;

        將其轉化為匯編代碼后如下:

        PSHD
        volatile char a = 1;
        LDAB #1
        STAB 1,SP
        volatile char b = 2;
        LSLB
        STAB 0,SP

        __asm pshc; __asm sei;
        PSHC
        SEI
        a = 3;
        INCB
        STAB 1,SP
        b = 4;
        INCB
        STAB 0,SP
        __asm pulc;
        PULC

        PSHD 指令調整堆棧指針,在堆棧上空出 2 個字節存放 a 和 b 的值。a 的地址為[SP+1],b 的地址為[SP],然后給 a 和 b 賦初值。PSHC 首先調整堆棧指針(SP=SP-1),然后將 CCR 寄存器的值存入堆棧,這里需要注意的是68HCS12核的堆棧是倒生堆棧,實棧頂(也就是說SP指向的是有數據的地方,而不是個空位),而老的68HC11內核是虛棧頂的(SP指向的是個空位)。這時 [SP] 中存的是 CCR 的值, [SP+1]指向 b,[SP+2]指向a。因此, STAB, 0,SP 將原本存的 CCR 的值改成了 4, b 的值卻沒有任何改變,說明這款編譯器無法感知堆棧的變動生成正確的代碼。因此第二種方法會導致錯誤的結果,不能采用。
        這里多說兩句,熟悉 x86 體系的讀者可以將 68HCS12 核與 x86 內核做個對比。這兩個核都是馮諾依曼結構體系,復雜指令集,從某種意義上可以說這兩種內核具有某種神似。在 x86 內核上大多數編譯器也是將局部變量存放到堆棧上,但是不同的是訪問局部變量時用的是 BSP 寄存器而不直接使用 ESP 寄存器,因此在 x86 內核上用第二種方法處理臨界區是沒有問題的,并且可以認為是一種相當好的方式。從這也可以看出來寄存器多一些確實是有好處的。

        第三種方式是實現兩個函數 OSCPUSaveSR 和 OSCPURestoreSR。雖然這樣會多些函數調用產生的額外開銷,卻沒有了前兩種方法的問題。這兩個函數具體的實現可以放到OS_CPU_A.S 中,也可以在 OS_CPU_C.C。

        如果用 C 語言實現,可以如下:

        1. OS_CPU_SROSCPUSaveSR(void)
        2. {
        3. __asm
        4. {
        5. tfrccr,b//copythevalueofCCRtotheregisterB
        6. sei//Disableinterrupts
        7. }
        8. }
        9. voidOSCPURestoreSR(OS_CPU_SRos_cpu_sr)
        10. {
        11. __asm
        12. {
        13. tfrb,ccr//BcontainstheCCRvaluetorestore,movetoCCR
        14. }
        15. }


        想要理解上面代碼,除了要知道匯編指令 tfr 和 sei 的含義。還要知道所謂的 C 語言調用約定,對于當前代碼來說需要知道 C 語言編譯器使用何種方式傳遞函數的參數和返回值。 這些知識在 編譯器附帶的文檔《S12(X)Build Tools Reference Manual》可以查到。我這里只介紹直接相關的幾條,其余的請自己查閱手冊。

        我所采用的編譯器對參數固定的 C 函數采用 PASCAL 調用約定,參數采用堆棧傳遞,傳遞順序為從左到右依次入棧,如果最后一個參數小于 4 個字節則最后一個參數采用寄存器傳遞。1 字節的參數存放到寄存器 B 中,OSCPURestoreSR 就是這種情況。函數的返回值如果也是 1 個字節,那么也通過 寄存器 B 傳出來,OSCPUSaveSR 就是這種情況。關于這兩個函數我就說這么多了。

        如果想直接寫匯編代碼的話也很簡單,下面是例子:

        xdef OSCPUSaveSR
        xdef OSCPURestoreSR
        OSCPUSaveSR:
        tfr ccr,b ; Its assumed that 8-bit return value is in register B
        sei ; Disable interrupts
        rts ; Return to caller with D containing the previous CCR

        OSCPURestoreSR:
        tfr b, ccr ; B contains the CCR value to restore, move to CCR
        rts

        和上面 C 函數的代碼幾乎一樣,沒什么可介紹的了。

        另外多說一句,大家可能覺得上面的代碼可以直接寫成內聯匯編語句,就不用函數調用了。比如下面的代碼:

        #define OS_ENTER_CRITICAL() asm ("tpa; staa cpu_sr; sei")
        #define OS_EXIT_CRITICAL() asm ("ldaa cpu_sr; tap")

        上面的代碼看似很好,進入臨界區時將 CCR 的值放到 寄存器 a 中,然后存到 cpu_sr 中,再關中斷。退出臨界區是恢復 CCR。問題在于 CodeWarrior Development Studio 中帶的 C 編譯器太弱智了,無法感知 寄存器 a 被改變了,更無法添加相應的處理代碼。自己保存寄存器 a 的內容又很麻煩,不能直接放到堆棧中,否則會影響局部變量的訪問,只能存在 C 編譯器可以感知的地方。比如用下面的方法:
        char tmp_a;
        asm ("staa tmp_a, tpa; staa cpu_sr; sei; ldaa tmp_a")
        asm ("staa tmp_a, tpa; ldaa cpu_sr; tap; ldaa tmp_a")

        這樣的開銷也不小,不如直接寫兩個函數方便。

        OS_CPU.H 還有幾行代碼:

        1. #defineOS_TASK_SW()__asmswi;
        2. #defineOS_STK_GROWTH/*Definestackgrowth:1=Down,0=Up*/
        3. #pragmaCODE_SEGNON_BANKED
        4. voidOSStartHighRdy(void);
        5. voidOSIntCtxSw(void);
        6. voidOSCtxSw(void);
        7. #pragmaCODE_SEGDEFAULT


        都比較簡單,OS_TASK_SW()將操作系統使用的中斷指定為 SWI 中斷,也就是 software interrupt。
        堆棧生長方向為向下生長。

        至此,OS_CPU.H 就寫完了。



        評論


        相關推薦

        技術專區

        關閉
        主站蜘蛛池模板: 鲁甸县| 嘉黎县| 伊通| 仁布县| 宜君县| 桃园县| 裕民县| 临高县| 仪征市| 吉首市| 金阳县| 册亨县| 芮城县| 确山县| 马关县| 平罗县| 榆社县| 舞钢市| 光山县| 商丘市| 永寿县| 岢岚县| 双鸭山市| 庆云县| 宣城市| 昌图县| 奎屯市| 怀柔区| 府谷县| 桂阳县| 南安市| 无为县| 沙湾县| 德兴市| 五家渠市| 霞浦县| 鲁甸县| 河津市| 武夷山市| 柯坪县| 阿尔山市|