新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > μC/OS II針對TMS320C32的移植

μC/OS II針對TMS320C32的移植

作者:錢勇 時間:2004-07-23 來源: 收藏
摘  要: OS-mC/OS-II是為數(shù)不多的幾個源代碼公開的操作系統(tǒng)之一,對它的學(xué)習(xí)、分析能夠給我們帶來很多概念上的了解和深入。另外mC/OS-II可以移植到很多CPU芯片,本文介紹了把mC/OS-II移值到TI公司浮點DSP芯片的過程的一些細節(jié)問題。
關(guān)鍵詞: 操作系統(tǒng);mC/OS-II; DSP;移植

嵌入式操作系統(tǒng)mC/OS II是一個可移植可裁剪、占先式多任務(wù)OS。大部分源代碼用ANSI C語言編寫,只有很少的一部分用匯編語言編寫,使移植工作簡化。
下面介紹如何將mC/OS-II移植到TI的浮點DSP芯片TMS320C32上。

TMS320C32 DSP芯片介紹
TMS320C3x是TI公司的浮點DSP芯片。TMS320C32是其中的一款,能工作在60MHz的時鐘頻率下,指令運行速度達到60 MFLOPS,是性價比很高的浮點處理器,有著廣泛的應(yīng)用。
TMS320C32芯片的組成:浮點CPU、512字節(jié)RAM、2個DMA通道、1個串口、2個定時器、固化引導(dǎo)程序,另外還有如下的通用寄存器:8個40bit的寄存器(R0~R7),可以用來存放32bit的整數(shù),同時也可以用來存放40bit的擴展精度的浮點數(shù);8個32bit的輔助寄存器(AR0~AR7),它們的主要作用是存儲地址、參與各種模式的尋址等,當(dāng)然也可以作為一般的寄存器來使用;狀態(tài)寄存器ST(含全局中斷使能位)、堆棧寄存器SP、中斷標(biāo)志寄存器IF、中斷使能寄存器IE、I/O標(biāo)志寄存器IOF、數(shù)據(jù)頁指針寄存器DP(每頁容量為64K)、索引寄存器IR0、IR1、塊寄存器BK、重復(fù)執(zhí)行寄存器RS(塊起始地址)、RE(塊終止地址)、RC(重復(fù)次數(shù))。
要實現(xiàn)mC/OS-II向TMS320C32的移植,需要C3x的C編譯器支持,否則無從下手。我們使用的是TI公司的C編譯器Coder Composer V4.10.36。這個C編譯器允許嵌入行匯編,另外還具有強大的優(yōu)化C編譯的功能。

移植中所需修改的文件
和CPU相關(guān)的文件主要有四個:C語言文件OS_CPU_C32.C、INCLUDES.H頭文件、頭文件OS_CPU_C32.H和匯編文件OS_CPU_C32.ASM,我們的主要移植工作就是針對這些文件做一些變動。
OS_CPU_C32.H
OS_CPU_C32.H包括typedef、#define定義的CPU相關(guān)信息如下:
#ifndef __OS_CPU_H__
#define __OS_CPU_H__         
typedef  unsigned  char   BOOLEAN;/*布爾量*/
typedef  unsigned  char    INT8U;/* 8位無符號數(shù)*/
typedef  signed  char  INT8S;    /* 8位有符號數(shù)*/
typedef  unsigned  int  INT16U;/* 16位無符號數(shù)*/
typedef  signed  int  INT16S; /* 16位有符號數(shù)*/
typedef  unsigned  long  INT32U;/* 32位無符號數(shù)*/
typedef  signed  long  INT32S; /* 32位有符號數(shù)*/
typedef  float  FP32;/* 32位單精度浮點數(shù) */
typedef  long  double  FP40; /*40位擴展精度浮點數(shù)*/
typedef  unsigned  int  OS_STK; /*堆棧入口寬度位32位*/
#define  OS_STK_GROWTH        0 /*堆棧由低地址向高地址增長*/
#define  OS_CRITICAL_METHOD  1
#if  OS_CRITICAL_METHOD == 1 /*方法一*/
#define  OS_ENTER_CRITICAL() asm ("  AND 0DFFFH,ST ") /*關(guān)全局中斷,進入臨界區(qū)*/
#define OS_EXIT_CRITICAL()  asm  ("  OR  02000H,ST ") /*開全局中斷,退出臨界區(qū)*/
#endif
#if  OS_CRITICAL_METHOD == 2  /*方法二*/
/*保存中斷禁止?fàn)顟B(tài)到堆棧,關(guān)全局中斷,進入臨界區(qū)*/
#define  OS_ENTER_CRITICAL() { 
asm(" PUSH ST"); 
asm(" AND 0DFFFH,ST");   }
#define  OS_EXIT_CRITICAL()  asm("POP ST")  /* 恢復(fù)中斷禁止?fàn)顟B(tài)*/
#endif
#define  OS_TASK_SW()    asm("TRAP 27")  /*用于任務(wù)切換的軟中斷*/
數(shù)據(jù)類型
由于不同的處理器有不同的字長,所以mC/OS-II的移植包括了一系列的數(shù)據(jù)類型的定義,以確保其可移植性。這里我們定義一些C32以及Code Composer都能識別、處理的數(shù)據(jù)類型。
C32本質(zhì)上只有4種數(shù)據(jù)類型:32位的無符號整數(shù):0_4294967295;32位的有符號整數(shù):-2147483648_2147483647;32位的浮點單精度浮點數(shù):5.877472e-39_3.4028235e38;40位的擴展進度浮點數(shù)5.87747175e-39_3.4028236684e38;我們上面定義的8、16位數(shù)實際上都是32位的。另外C32中,堆棧都是按32位數(shù)據(jù)類型進行操作的,所以堆棧數(shù)據(jù)類型OS_STK申明為32位無符號整數(shù);
代碼的臨界區(qū)
mC/OS-II在進入系統(tǒng)臨界代碼區(qū)之前要關(guān)中斷,避免臨界區(qū)代碼受多任務(wù)或中斷服務(wù)程序的破壞,等到臨界區(qū)代碼執(zhí)行完畢之后,該怎么處理呢?有兩種方案可以供選擇:1)不管關(guān)中斷前中斷使能情況是什么樣子,一律開中斷;2)恢復(fù)關(guān)中斷前中斷使能情況,從一定程度上保證任務(wù)執(zhí)行環(huán)境的完整性。
C32中,狀態(tài)寄存器ST的第13位是全局中斷使能位GIE把該位置0,那么不管什么中斷都不去被響應(yīng),直到臨界區(qū)代碼執(zhí)行完畢為止。(注:C32沒有不可屏蔽的中斷NMI,對于別的芯片來說,如果有NMI的話,處理辦法就是在這個中斷服務(wù)程序ISR中對ST中的GIE位進行判斷,如果置0,那么這個ISR簡單響應(yīng)一下這個中斷,大部分處理工作放到GIE置1后馬上去執(zhí)行)。宏OS_ENTER_CRITICAL()把GIE位置0而關(guān)閉所有中斷。
堆棧增長方向
C32處理器的堆棧是由低地址向高地址遞增,所以O(shè)S_STK_GROWTH應(yīng)該設(shè)置為1;
進入任務(wù)切換函數(shù)OS_TASK_SW()的定義
mC/OS-II中,進入任務(wù)切換是用函數(shù)OS_TASK_SW()來實現(xiàn)的。這個函數(shù)通過軟中斷模擬了一次中斷過程,在這個中斷服務(wù)程序ISR中實現(xiàn)任務(wù)的切換,切換的具體實現(xiàn)在介紹任務(wù)切換函數(shù)OSCtxSw()時詳細闡述。C32共有28個軟中斷可供使用,通過執(zhí)行匯編指令 TRAP 0、TRAP 1……TRAP27來產(chǎn)生軟中斷,也稱為TRAP陷阱調(diào)用。這里,我們選擇編號為27的軟中斷作為進入任務(wù)切換的中斷:
#define  OS_TASK_SW()    asm("TRAP 27")
還要注意的一點是這個中斷服務(wù)程序的入口必須指向函數(shù)OSCtxSw()。
INCLUDES.H文件
INCLUDES.H是主要的頭文件,在大多數(shù).C文件的開始都包含INCLUDES.H文件。不同處理器、不同編譯器、不同庫文件,需要修改INCLUDES.H文件,刪除不使用的頭文件,添加自己的頭文件。而且,頭文件之間有包含關(guān)系、條件編譯的,一定要排好他們之間的先后順序。,INCLUDES.H文件修改如下:
#ifndef __INCLUDES_H__
#define __INCLUDES_H__       
#include    "OS_CFG.H"
#include    "OS_CPU.H"
#include    "mCOS_II.H"
#include    "C32.H"
#endif
其中C32.H文件包含了4個頭文件:
#include "Timerdef.H"
#include "SerialPort.H"
#include "Dma.H"
#include "Bus.H"
分別對C32的定時器、串口、DMA通道、總線編程用到的數(shù)據(jù)結(jié)構(gòu)進行定義。
OS_CPU_C32.ASM文件
本來,這個匯編文件里面要實現(xiàn)4個函數(shù):多任務(wù)啟動函數(shù)中調(diào)用的OSStartHighRdy()、中斷任務(wù)切換函數(shù)OSIntCtxSw()、任務(wù)切換函數(shù)OSCtxSw()、時鐘節(jié)拍服務(wù)函數(shù)OSTickISR();但是這里只實現(xiàn)后兩個函數(shù)。前兩個函數(shù)在OS_CPU_C32.C中實現(xiàn)。
任務(wù)切換函數(shù):OSCtxSw()
該函數(shù)由任務(wù)切換函數(shù)OS_TASK_SW()進入,與中斷程序中調(diào)用的OSIntCtxSw()不同。mC/OS-II中,如果任務(wù)執(zhí)行了某個函數(shù),其結(jié)果改變了當(dāng)前任務(wù)的狀態(tài)(如OSTaskSuspend()、OSTimeDly())、或者是改變了別的任務(wù)的狀態(tài)(OSTaskResume()、OSTimeDlyResume())都要引起新的任務(wù)調(diào)度:OSSched();在任務(wù)調(diào)度函數(shù)找出新任務(wù)將其控制塊地址放到OSTCBHigRdy后,執(zhí)行OS_TASK_SW()。任務(wù)切換流程:1)硬件進入中斷處理:全局中斷使能位置0、返回地址壓棧。2)寄存器值壓入當(dāng)前任務(wù)堆棧;3)修改當(dāng)前任務(wù)控制塊指針OSTCBCur和當(dāng)前任務(wù)優(yōu)先級OSPrioCur;4)恢復(fù)任務(wù)堆棧中的值到寄存器中;5)執(zhí)行當(dāng)前任務(wù),由RETI指令完成。
時鐘節(jié)拍函數(shù):OSTickISR()
mC/OS-II中,時鐘節(jié)拍中斷是一個非常重要的中斷,因為整個操作系統(tǒng)的活動都受到它的激勵。
OSTickISR()的執(zhí)行流程:1)硬件進入中斷處理,同上;2)保護上下文環(huán)境;3)調(diào)用OSIntEnter(),記錄中斷嵌套層數(shù);4)調(diào)用OSTimeTick(),檢查處理各個任務(wù)的延時,并根據(jù)情況修改就緒任務(wù)表;5)調(diào)用OSIntExit(),檢查就緒任務(wù)表,看是否有比當(dāng)前任務(wù)優(yōu)先級更高的任務(wù)就緒,如果有,則進行調(diào)度;如果沒有,OSIntExit()返回并恢復(fù)2)所保存的上下文環(huán)境,并執(zhí)行RETI回到被中斷的那個任務(wù)里繼續(xù)運行;如果有,那么OSIntExit()就不返回到這里,具體的情況后面介紹OSIntExit()時具體闡述。
OS_CPU_C32.C文件
這個文件里,主要實現(xiàn)3個函數(shù):堆棧初始化函數(shù)OSTaskInit()、中斷任務(wù)切換函數(shù)OSIntCtxSw()、多任務(wù)啟動函數(shù)中調(diào)用的OSStartHighRdy(),另外還有5個擴展外掛函數(shù):
void    OSTaskCreateHook(OS_TCB ptcb){} /*任務(wù)創(chuàng)建擴展外掛函數(shù)*/
void    OSTaskSwHook(void){} /*任務(wù)切換擴展外掛函數(shù)*/
void    OSTaskDelHook(OS_TCB *ptcb){}  /*任務(wù)刪除擴展外掛函數(shù)*/
void    OSTaskStatHook(void){} /*統(tǒng)計任務(wù)擴展外掛函數(shù)*/
void    OSTimeTickHook(void){}  /*時鐘節(jié)拍創(chuàng)建擴展外掛函數(shù)*/
這幾個函數(shù)我們這里都處理為空函數(shù),而且還可以通過在文件OS_CFG.H中設(shè)置OS_CPU_HOOKS_EN為0而不使用這些函數(shù)。我們主要來討論前三個函數(shù):
堆棧初始化函數(shù)OSTaskInit()
堆棧初始化函數(shù)OSTaskInit()是由任務(wù)創(chuàng)建函數(shù)OSTaskCreate()或OSTaskCreateExt()來調(diào)用,用來初始化任務(wù)堆棧。初始化后的堆棧保存著任務(wù)第一次執(zhí)行時的上下文環(huán)境,它和中斷后的堆棧神似!這個函數(shù)最關(guān)鍵的兩個參數(shù)就是任務(wù)的起始地址void(* task)(void *pd)和任務(wù)使用堆棧的棧頂指針void *ptos;需要注意的是,任務(wù)第一次執(zhí)行時的某些全局寄存器的值有特殊要求:1)狀態(tài)寄存器ST的初始值必須保證中斷全局使能位GIE為1,從而保證時鐘節(jié)拍中斷不會長時間被屏蔽,這里我選擇初值為0x2000;2)頁指針寄存器DP的初始值:如果你選擇Small-Memory模式進行編譯時,那么它的初始值應(yīng)該和建立C環(huán)境時對DP的初始化值一樣;否則就不要對這個DP寄存器進行任何保護處理,這樣也可以的,不過這樣的話,別的函數(shù)也就要做相應(yīng)的改動了;如果選擇了Large-Memory模式的話,那么這個值的初始化就可以不進行了,因為編譯系統(tǒng)在編譯時會自動插入更新DP的指令的;
中斷任務(wù)級切換函數(shù)OSIntCtxSw()
mC/OS-II中,中斷的產(chǎn)生可能會引起任務(wù)的切換,在中斷服務(wù)程序的最后會調(diào)用OSIntExit()檢查任務(wù)就緒狀態(tài)。如果需要進行任務(wù)切換,將調(diào)用OSIntCtxSw()。所以O(shè)SIntCtxSw()又稱為中斷級的任務(wù)切換函數(shù)。需要注意的是,任何中斷服務(wù)程序ISR前面都要像時鐘節(jié)拍函數(shù):OSTickISR()流程的第2步那樣保存上下文環(huán)境。OSIntCtxSw()和OSCtxSw()的后半部分幾乎相同,不同之處是:對當(dāng)前任務(wù)的堆棧指針進行調(diào)整!其代碼如下:
asm(" SUBI 5,SP ");
asm(" LDI @_OSTCBCur,AR0 ");
asm(" STI SP,*AR0  ");
這里我們把堆棧指針SP減去5,就是調(diào)整的結(jié)果。
下面我們來分析一下這個“5”是怎么得來的(我們針對時鐘節(jié)拍中斷ISR來進行說明):
任務(wù)調(diào)用函數(shù)OSIntExit()之前時,當(dāng)前任務(wù)堆棧的情況如圖1所示;調(diào)用OSIntExit函數(shù)后,當(dāng)前任務(wù)堆棧的情況如圖2所示,為什么會這樣呢?我們可以看看OSIntExit()函數(shù)編譯后的匯編文件就明白了,這個函數(shù)入口的地方有如下幾條語句:
_OSIntExit:
        push      fp
        ldiu      sp,fp
        push      ar4
然后該函數(shù)又調(diào)用中斷切換函數(shù)OSIntCtxSw(),這時當(dāng)前任務(wù)堆棧的的情況如圖3所示;OSIntCtxSw()編譯后的匯編文件入口的地方有如下幾條語句:
_OSIntCtxSw:
        push      fp
由此可見,當(dāng)別的任務(wù)需要調(diào)度時,當(dāng)前任務(wù)需要把自己的堆棧指針SP調(diào)整到調(diào)用OSIntExit()之前的值,從圖上可以看出只要把當(dāng)前任務(wù)的堆棧指針的值減去“5”便可。
多任務(wù)啟動函數(shù)中調(diào)用的OSStartHighRdy()
這個函數(shù)只在多任務(wù)啟動函數(shù)中調(diào)用一次,主要用來把多任務(wù)啟動時優(yōu)先級最高的就緒任務(wù)的上下文環(huán)境從堆棧恢復(fù)過來。mC/OS-II系統(tǒng)啟動時至少創(chuàng)建了一個任務(wù)__空閑任務(wù),實際應(yīng)用當(dāng)然還要創(chuàng)建別的用戶任務(wù)。OSStart()首先從這些任務(wù)中查找出優(yōu)先級最高的就緒任務(wù),并把它的任務(wù)控制塊的地址賦給OSTCBHighRdy,然后調(diào)用OSStartHighRdy()來運行OSTCBHighRdy指向的那個任務(wù)控制塊所對應(yīng)的任務(wù)。其流程是:1)該函數(shù)的返回地址壓入堆棧,注意,這里提到的堆棧是mC/OS-II系統(tǒng)使用的堆棧,而與其他任何任務(wù)使用的堆棧沒有任何關(guān)系,而且這個地址壓不壓棧意義已經(jīng)不大,因為不可能再從返回這里返回回去;2)變量OSRunning賦值為True,標(biāo)志多任務(wù)已經(jīng)啟動;3)從任務(wù)初始化過的堆棧中恢復(fù)上下文環(huán)境,代碼和上面的雷同;4)執(zhí)行RETI指令運行這個任務(wù);

兩點補充說明
編譯器的編譯選項
在移植過程中,除了要熟悉mC/OS-II內(nèi)核原理和目標(biāo)芯片之外,還要熟悉C編譯器提供編譯選項的使用。使用不當(dāng),會給移植帶來大麻煩。上面在介紹堆棧初始化函數(shù)也提及到有關(guān)DP寄存器的處理問題上,就是涉及到編譯選項的選擇:是選用Small-Memory還是Large-Memory模式,這個取決于編譯選項-mb的打開和關(guān)閉。
中斷函數(shù)的編寫
在分析函數(shù)OSIntCtxSw()時,我們提到分析的結(jié)果是針對時鐘中斷服務(wù)函數(shù)得出的。大家應(yīng)該注意到這個函數(shù)是匯編語言寫的,那么我們寫別的中斷處理程序時,如果用C語言來寫的話,要不要注意些什么呢?回答是肯定的。因為編譯器在處理C語言寫的中斷服務(wù)函數(shù)時會作出一些特殊的處理:在這個ISR入口處插入壓棧指令,把部分全局寄存器的值壓入堆棧,具體哪些,因函數(shù)的不同而有所不同。這就干擾了我們保存上下文環(huán)境的工作,如果不進行處理,任務(wù)調(diào)度時會出現(xiàn)難以想象的問題。解決這個問題的辦法就是要讓C編譯器認為這個中斷服務(wù)函數(shù)是一般的函數(shù),那么它就不會在函數(shù)入口處插入一系列的壓棧指令。C編譯器Code Composer規(guī)定,凡是函數(shù)名為c_intnm(其中n、m是小于9正整數(shù))的函數(shù)都是中斷函數(shù),在編譯這些函數(shù)時都作出特殊的處理,為此,我們避免為中斷處理函數(shù)取這樣的名字就可以了。
但是,編譯在處理一般函數(shù)時還是要做一定的處理的,譬如:
        push      fp
        ldiu      sp,fp
        push      ar4
為此,我們仔細分析編譯器編譯生成的匯編代碼,并對堆棧作相應(yīng)的調(diào)整。然后在函數(shù)結(jié)束的地方嵌入asm(" RETI");語句結(jié)束。這樣中斷程序就可以正確執(zhí)行。
它的框架如下:
void Int0ISR(void)
{
asm(" SUBI N,SP") /*這個N的值要看具體的程序來定,一般等于1或2*/

asm(" RETI")   /*用自己的返回指令返回*/
}
結(jié)語
在移植和運行的mC/OS-II過程中,也許還會有新的問題出現(xiàn),遇到問題時要仔細分析,分析堆棧的使用、中斷的影響,分析編譯器生成的匯編代碼,就可以解決這些問題,從而實現(xiàn)mC/OS-II的可靠運行?!?

參考文獻
1. ‘mC/OS-II——源碼公開的實時嵌入式操作系統(tǒng)’,邵貝貝譯,中國電力出版社 2001.
2. ‘TMS320C3x/4x Optimizing C Compiler User’s Guide’ TEXAS INSTRUMENTS.
3. ‘單片機與嵌入式系統(tǒng)應(yīng)用’雜志,2001  NO.12.
linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)


關(guān)鍵詞: 嵌入式

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉