轉載於:httpzq2007.blog.hexun.com9534277_d.html
UC/OS-II 的移植步驟分析
zqcumt 07-4-15
關於UC/OS-II 的移植網上介紹的已經很多了,比較流行的幾款處理器(例如ARM )在網上都可以直接下載移植好的代碼。由於最近選修了一門嵌入式系統的課,用的處理器是EPSON 公司的S1C33 系列,做實驗的時候要進行操作系統的移植,這個週末花了一天半的時間學習了一下,因爲畢業設計的時候做過ARM 上的移植,於是將兩者比較了一下,給出一般的移植要點。由於將來實驗還要設計到GUI 的移植以及文件系統的移植和網絡協議的移植,我會將自己的學習筆記都記錄下來。
大家下載到源碼後,針對Intel 80x86 的代碼在uCOS-II/Ix86L 目錄下。代碼是80x86 實模式,且在編譯器大模式下編譯的。移植部分的代碼可在下述文件中找到:OS_CPU.H , OS_CPU_C.C , 和 OS_CPU_A.ASM 。大家可以參考這個例子,對它進行修改。
INCLUDES.H 是主頭文件,在所有後綴名爲.C 的文件的開始都包含INCLUDES.H 文件。使用INCLUDES.H 的好處是所有的.C 文件都只包含一個頭文件,程序簡潔,可讀性強。缺點是.C 文件可能會包含一些它並不需要的頭文件,額外的增加編譯時間。與優點相比,多一些編譯時間還是可以接受的。用戶可以改寫INCLUDES.H 文件,增加自己的頭文件,但必須加在文件末尾。
///////////////////////////////////////////////////////////////////////////////
一、(1)OS_CPU.H 文件的移植 ( 針對S1C33209)
//////////////////////////////////////////////////////////////////////////
OS_CPU.H 文件中包含與處理器相關的常量,宏和結構體的定義。
#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT // 全局變量
#else
#define OS_CPU_EXT extern
#endif
///////////////////////////////////////////////////////////////////////////////
由於不同的處理器有不同的字長,µC/OS-II 的移植需要重新定義一系列的數據結構。這部分是和處理器相關的.
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
// 因爲沒有浮點運算所以刪掉
typedef unsigned int OS_STK;// 定義 堆棧的 寬度爲 16 位
typedef unsigned int OS_CPU_SR;// 定義 狀態寄存器的寬度爲16 位
///////////////////////////////////////////////////////////////////////////////
下面的部分主要是爲了和UC/OS 第一版的兼容
#define BYTE INT8S
#define UBYTE INT8U
#define WORD INT16S
#define UWORD INT16U
#define LONG INT32S
#define ULONG INT32U
///////////////////////////////////////////////////////////////////////////////
與其他實時系統一樣,µC/OS-II 在進入系統臨界代碼區之前要關閉中斷,等到退出臨界區後再打開。從而保護核心數據不被多任務環境下的其他任務或中斷破壞。Borland C/C++ 支持嵌入彙編語句,所以加入關閉/ 打開中斷的語句是很方便的。µC/OS-II 定義了兩個宏用來關閉/ 打開中斷:OS_ENTER_CRITICAL() 和OS_EXIT_CRITICAL() 。下面定義了三種方法, 具體的可以查閱相關書籍.
//////////////////////////////////////////////////////////////
#define OS_CRITICAL_METHOD 2 // 使用第二種方法
///////////////////////////////////////////////////////////////////////////////
#if OS_CRITICAL_METHOD == 1 // 第一種方法, 由於沒有用到, 我們不用去修改, 可以註釋掉
#define OS_ENTER_CRITICAL() asm CLI
#define OS_EXIT_CRITICAL() asm STI
#endif
///////////////////////////////////////////////////////////////////////////////
#if OS_CRITICAL_METHOD == 2 // 第二種方法, 這個是我們用到的, 要修改, 一般用匯編寫, 根據各個處理器的不同而不同, 下面是S1C33 系列的彙編
#define OS_ENTER_CRITICAL()
asm(" ld.w %r4, %psr");
asm(" xld.w %r5, 0xffffffef");
asm(" and %r4, %r5");// 關中斷, 保持狀態寄存器的其它狀態不變
asm(" ld.w %psr, %r4");
#define OS_EXIT_CRITICAL()
asm(" ld.w %r4, %psr");
asm(" or %r4, 0b10000");
asm(" ld.w %psr, %r4"); // 開中斷, 保持狀態寄存器的其它狀態不變
#endif
///////////////////////////////////////////////////////////////////////////////
#if OS_CRITICAL_METHOD == 3 // 第三種方法, 由於沒有用到, 我們不用去修改, 可以直接注視掉
#define OS_ENTER_CRITICAL()
(cpu_sr = OSCPUSaveSR())
#define OS_EXIT_CRITICAL()
(OSCPURestoreSR(cpu_sr))
#endif
///////////////////////////////////////////////////////////////////////////
#define OS_STK_GROWTH 1 // 堆棧的增長方向, 由高相低, 這個也是和處理器相關的, 有的處理器堆棧是由低向高變, 只要定義爲零即可
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
在 µC/OS-II 中, 就緒任務的堆棧初始化應該模擬一次中斷髮生後的樣子,堆棧中應該按進棧次序設置好各個寄存器的內容。OS_TASK_SW() 函數模擬一次中斷過程,在中斷返回的時候進行任務切換。 中斷服務程序(ISR )(也稱爲例外處理過程)的入口點必須指向彙編函數OSCtxSw(), 參看文件OS_CPU_A.ASM. 在中斷向量表vector.c 的代碼中修改向量表如下
(unsigned long) OSCtxSw, // 48 12 software exception 0
///////////////////////////////////////////////////////////////////////
#define uCOS 0
#define OS_TASK_SW() asm(" int 0"); / / 使用零號中斷來進行任務切換
///////////////////////////////////////////////////////////////////////////////
可以註釋掉, 主要是用於在PC 機上模擬時鐘節拍
OS_CPU_EXT INT8U OSTickDOSCtr; // 全局變量
////////////////////////////////////////////////////////////////////////////
可以註釋掉
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR OSCPUSaveSR(void);
void OSCPURestoreSR(OS_CPU_SR cpu_sr);
#endif
///////////////////////////////////////////////////////////////////////////////
(2)OS_CPU.H 文件的移植 ( 針對ARM 核的S3C44BOX )
///////////////////////////////////////////////////////////////////////////////
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U; /*8
位無符號整數*/
typedef signed char INT8S; /*8
位有符號整數*/
typedef unsigned short INT16U; /*16
位有符號整數*/
typedef signed short INT16S; /*16
位無符號整數*/
typedef unsigned long INT32U; /*32
位無符號整數*/
typedef signed long INT32S; /*32
位有符號整數*/
typedef float FP32; /*
單精度浮點數*/
typedef double FP64; /*
雙精度浮點數*/
///////////////////////////////////////////////////////////////////////////
typedef unsigned int OS_STK;/*
堆棧入口寬度爲16
位*/
與ARM
處理器相關的代碼:
///////////////////////////////////////////////////////////////////////////////
具體的實現見第二步,
在OS_CPU_A.ASM
中實現
#define OS_ENTER_CRITICAL () ARMDisableInt() /*
關中斷在
OS_CPU.A.S
中定義,可以參
考下面的程序
*/
#define OS_EXIT_CRITICAL () ARMEnableInt() /*
開啓中斷*/
#define OS_STK_GROWTH 1 /*
堆棧由高地址向低地址增長*/
///////////////////////////////////////////////////////////////////////////////
定義宏OS_TASK_SW (),這個宏實際上被定義爲os_CPU_a.s 中的函數OSCtxSw ()。由此可以瞭解OSCtxSw ()的任務:保存當前任務上下文,裝入新任務上下文。這裏並沒有用到模擬軟中斷
#define OS_TASK_SW OSCtxSw
///////////////////////////////////////////////////////////////////////////////
// Definitions specific to ARM/uHAL
#define SVC32MODE 0x13
// 定義空閒任務堆棧的大小,可以不用定義這部分
#define SEMIHOSTED_STACK_NEEDS 1024
// idle task stack size (words)
#ifdef SEMIHOSTED
#define OS_IDLE_STK_SIZE (32+SEMIHOSTED_STACK_NEEDS)
#else
#define OS_IDLE_STK_SIZE 32
#endif
// defined in os_cpu_a.s 聲明這些函數,在後面都有所定義
extern void OSCtxSw(void); // task switch routine
extern void OSIntCtxSw(void); // interrupt context switch
extern void ARMDisableInt(void); // disable global interrupts
extern void ARMEnableInt(void); // enable global interrupts
extern void OSTickISR(void); // timer interrupt routine
///////////////////////////////////////////////////////////////////////////////
二、(1)OS_CPU.A.S 文件的移植 ( 針對S1C33209)
///////////////////////////////////////////////////////////////////////////////
µC/OS-II 的移植需要用戶改寫OS_CPU_A.ASM 中的四個函數:
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()
////////////////////////////////////////////////////////////////
該函數由SStart() 函數調用,功能是運行優先級最高的就緒任務,在調用OSStart() 之前,用戶必須先調用OSInit() ,並且已經至少創建了一個任務(請參考OSTaskCreate() 和OSTaskCreateExt() 函數)。OSStartHighRdy() 默認指針OSTCBHighRdy 指向優先級最高就緒任務的任務控制塊(OS_TCB )(在這之前OSTCBHighRdy 已由OSStart() 設置好了)。OSTCBHighRdy->OSTCBStkPtr 指向的是任務堆棧的頂端
OSStartHighRdy:
xcall OSTaskSwHook // 調用OSTaskSwHook ,此時OSRunning 爲FALSE
ld.w %r4, 0x1
xld.w %r5, OSRunning
xld.b [%r5], %r4 // 使OSRunning 的狀態爲TRUE ,以後調用OSTaskSwHook 時會先保存寄存器再恢復
xld.w %r5, [OSTCBHighRdy];
ld.w %sp, %r5;// 得到最高優先級任務的堆棧指針
xld.w %r4, [%sp+0x0];
ld.w %sp, %r4;// 偏移量爲0
popn %r15 // 恢復r15-r0, 這個是S1C33209 的彙編語句
reti; // 返回,此命名執行時,處理器會自動恢復PC 和狀態寄存器的值,至此新任務
///////////////////////////////////////////////////////////////////////////////
OSCtxSw() 是一個任務級的任務切換函數(在任務中調用,區別於在中斷程序中調用的OSIntCtxSw() )。 它通過執行一條軟中斷的指令來實現任務切換。軟中斷向量指向OSCtxSw() 。在 µC/OS-II 中,如果任務調用了某個函數,而該函數的執行結果可能造成系統任務重新調度(例如試圖喚醒了一個優先級更高的任務),則在函數的末尾會調用OSSched() , 如果OSSched() 判斷需要進行任務調度,會找到該任務控制塊OS_TCB 的地址,並將該地址拷貝到OSTCBHighRdy ,然後通過宏OS_TASK_SW() 執行軟中斷進行任務切換。。注意到在此過程中,變量OSTCBCur 始終包含一個指向當前運行任務OS_TCB 的指針。大部分解釋同上,只是多了寄存器的保存這一段。
OSCtxSw:
xcall OSTaskSwHook // 中斷時,PC 和寄存器的值S1C33209 處理器已經自動保存了
pushn %r15;// Save current task's context
xld.w %r4, [OSTCBCur];// 指向當前的運行任務
ld.w %r5, %sp; Save the SP to R5
ld.w %sp, %r4;// 保存當前任務的堆棧指針
ld.w [%sp+0x0], %r5 ; //Save the SP to OSTCBCur
xld.w %r4, [OSTCBHighRdy] ; //OSTCBCur = OSTCBHighRdy
xld.w %r5, OSTCBCur;
ld.w [%r5], %r4 ;
xld.w %r4, [OSPrioHighRdy]; //OSPrioCur = OSPrioHighRdy ,把任務優先級也保存
xld.w %r5, OSPrioCur ;
ld.b [%r5], %r4
xld.w %r4, [OSTCBCur];// 載入新的任務
ld.w %sp, %r4
ld.w %r5, [%sp+0x0];// 恢復新任務的堆棧
ld.w %sp, %r5
popn %r15 ;
reti ; // 運行新的任務
///////////////////////////////////////////////////////////////////////////////
在 µC/OS-II 中,由於中斷的產生可能會引起任務切換,在中斷服務程序的最後會調用OSIntExit() 函數檢查任務就緒狀態,如果需要進行任務切換,將調用OSIntCtxSw() 。所以OSIntCtxSw() 又稱爲中斷級的任務切換函數。由於在調用OSIntCtxSw() 之前已經發生了中斷,OSIntCtxSw() 將默認CPU 寄存器已經保存在被中斷任務的堆棧中了。因此在中斷服務程序中要保存寄存器,PC 和狀態寄存器的值已經被處理器自動保存。OSIntCtxSw() 大部分程序和OSCtxSw ()相同只是不用保存寄存器,它也可直接用OSCtxSw ()來實現
OSIntCtxSw:
xcall OSTaskSwHook ; //Call user defined task switch hook
xld.w %r4, [OSTCBHighRdy] ;// OSTCBCur = OSTCBHighRdy
xld.w %r5, OSTCBCur ;
ld.w [%r5], %r4 ;
xld.w %r4, [OSPrioHighRdy] ; //OSPrioCur = OSPrioHighRdy ,把任務優先級也保存
xld.w %r5, OSPrioCur ;
ld.b [%r5], %r4
xld.w %r4, [OSTCBCur] // 載入新的任務
ld.w %sp, %r4
ld.w %r5, [%sp+0x0]
ld.w %sp, %r5
popn %r15 ;
reti //Return to new task
///////////////////////////////////////////////////////////////////////////////
和 µC/OS-II 中的其他中斷服務程序一樣,OSTickISR() 首先在被中斷任務堆棧中保存CPU 寄存器的值,然後調用OSIntEnter() 。 µC/OS-II 要求在中斷服務程序開頭調用OSIntEnter() ,其作用是將記錄中斷嵌套層數的全局變量OSIntNesting 加1 。如果不調用OSIntEnter() ,直接將OSIntNesting 加1 也是允許的。OSTickISR() 調用OSTimeTick() ,檢查所有處於延時等待狀態的任務,判斷是否有延時結束就緒的任務。 在OSTickISR() 的最後調用OSIntExit() ,如果在中斷中(或其他嵌套的中斷)有更高優先級的任務就緒,並且當前中斷爲中斷嵌套的最後一層。OSIntExit() 將進行任務調度。注意如果進行了任務調度,OSIntExit() 將不再返回調用者,而是用新任務的堆棧中的寄存器數值恢復CPU 現場,然後用IRET 實現任務切換。如果當前中斷不是中斷嵌套的最後一層,或中斷中沒有改變任務的就緒狀態,OSIntExit() 將返回調用者OSTickISR() ,最後OSTickISR() 返回被中斷的任務。如果編譯器支持C 語言和彙編的混合編程,則這段代碼可以放到 OS_CPU_C.C 中,針對S1C33209 的移植這部分放在OS_CPU_C.C 中。爲了連續性就在這裏順便寫吧。
void OSTickISR()
{ asm( " pushn %r15");// 保存中斷的任務的寄存器
///////////////////////////////////////////////////////////////////////////////
在這個移植中以8 位定時器TIME2 作爲時鐘節拍,2MS 發生一次中斷,在中斷向量表vector.c 中在timer2 的入口地址處放入(unsigned long)OSTickISR, 發生中斷後程學將會跳到此程序處執行。
*(volatile unsigned char*)0x40285 |= 0x04; // 清除timer2 的中斷標誌位
OSIntEnter();// 處理中斷嵌套曾數的增加也可以直接 給OSIntNesting 加一
if (OSIntNesting == 1) {
asm(" ld.w %r4, %sp");// 如果嵌套層數爲1 則在當前的任務控制塊中保存堆棧指針
asm(" xld.w %r10, [OSTCBCur]");
asm(" ld.w %sp, %r10");
asm(" ld.w [%sp+0x0], %r4");
asm(" ld.w %sp, %r4");
}
OSTimeTick(); // 給等待延遲時間的任務的參數減1
OSIntExit(); // 調用這個函數,如果ISR 使更高優先級的任務進入就緒態或者ISR 脫離
// 了中斷嵌套,則此函數不會返回,而是由進行中斷級任務切換,否
// 此函數返回OSTickISR ,然後恢復寄存器
asm(" popn %r15");// 恢復寄存器
asm(" reti");// 返回中斷的任務繼續運行
}
///////////////////////////////////////////////////////////////////////////////
爲了更清楚一點這裏面的過程,順便付上這裏用到TIMER2 的程序,最好有個感性的認識
這部分程序應該在驅動程序裏或者放在初始化程序裏。始終節拍中斷的啓動(定時器2 的啓動)應該放在OSStart() 運行之後,但是OSStart() 不會返回,所以應該放在OSStart() 之前建立的任務中的優先級最高的任務中啓動,如果放在 OSInit() 和OSStart() 之間啓動,程序容易崩潰。
/* Prototype */
void init_timer(void);
void Start_Timer(void);
/////////////////////////////////////////////////////////////////////////////
定時器2 的初始化,完成定時時間等一下設置,每隔2ms 發生一次的中斷
void init_timer(void)
{
*(volatile unsigned char *)0x4014E |= 0x0F;
*(volatile unsigned char *)0x40169 = 0x92;/
*(volatile unsigned char *)0x40168 |= 0x02;
*(volatile unsigned char *)0x40285 &= 0xFB;
*(volatile unsigned char *)0x40275 |= 0x04;
}
///////////////////////////////////////////////////////////////////////////////
啓動定時器2
void Start_Timer(void)
{
*(volatile unsigned char*)0x40168 |= 0x01;
}
//////////////////////////////////////////////////////////////////////////////
(2)OS_CPU.A.S 文件的移植 ( 針對ARM 核的S3C44BOX )
/////////////////////////////////////////////////////////////////////////////
µC/OS-II 的移植需要用戶改寫OS_CPU_A.ASM 中的四個函數:
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()
同時對於ARM 的開關中斷( ARMEnableInt , ARMDisableInt ) 的定義也是放在這個文件下的
關於ARM 的程序就不用解釋那麼清楚了,相信英文大家都能看懂,也可以參考上面的程序實現的功能都是相同的
///////////////////////////////////////////////////////////////////////////////
EXPORT OSStartHighRdy
IMPORT OSTaskSwHook
IMPORT OSTCBHighRdy
IMPORT OSRunning
OSStartHighRdy
BL OSTaskSwHook //Call user-defined hook function
LDR r4,=OSRunning // Indicate that multitasking has started
MOV r5, #1
STRB r5, [r4] // OSRunning = true
LDR r4, =OSTCBHighRdy // Get highest priority task TCB address
LDR r4, [r4] // get stack pointer
LDR sp, [r4] // switch to the new stack
LDMFD sp!, {r4} ;// CPSR 特殊 , 只能用 MRS 或 MSR 在寄存器間操作
MSR cpsr_cxsf, r4 // 從r4 中恢復cpsr
///////////////////////////////////////////////////////////////////////////////SVC 模式下ARM 處理器不會自動保存PC 的所以需要自己保存和恢復
LDMFD sp!, {r0-r12,lr,pc} ; pop new task s r0-r12,lr & pc
///////////////////////////////////////////////////////////////////////////////
EXPORT OSCtxSw // 這個函數別的文件要用
IMPORT OSPrioCur // 這是在別的文件定義的變量 , 當前任務優先級
IMPORT OSPrioHighRdy // 將要恢復執行的任務的優先級
IMPORT OSTCBCur // 當前任務的 TCB 的指針
IMPORT OSTaskSwHook // 調用用戶定義 HOOK
IMPORT OSTCBHighRdy // 將要恢復執行的任務的 TCB 指針
OSCtxSw
STMFD sp!, {lr} // push pc (lr is actually be pushed in place of PC) 因爲是從 OS_Sched() BL 到這裏的
STMFD sp!, {r0-r12,lr} // push lr & register file
MRS r4, cpsr // CPSR 特殊 , 只能用 MRS 或 MSR 在寄存器間操作
STMFD sp!, {r4} // push current psr
LDR r4, =OSTCBCur // Get current task TCB address
LDR r5, [r4]
STR sp, [r5] // store sp in preempted tasks s TCB
/////////////////////////////////////////////////////////////////////////////
以下程序段和 OSIntCtxSw 相同,可以共用一段
BL OSTaskSwHook // call Task Switch Hook
LDR r5, =OSTCBHighRdy // 得到就緒任務中的最高優先級的任務
LDR r5, [r5]
STR r5, [r4] // 使當前任務指針指向最高優先級的任務
OSTCBCur = OSTCBHighRdy
LDR r6, =OSPrioHighRdy
LDRB r6, [r6]
LDR r4, =OSPrioCur
STRB r6, [r4] // 保存優先級到當前的優先級
LDR sp, [r5] //get new task s stack pointer
LDMFD sp!, {r4} //pop new task cpsr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} // 切換到新的任務
///////////////////////////////////////////////////////////////////////////////
關 於OSIntCtxSw()就是上面那下半截。這是因爲:ARM硬件的中斷時並不自動壓棧任何寄存器,所以免去了恢復堆棧指針的麻煩;另外,我們最好在進 入ISR保存當前任務現場時一同保存好TCB中的堆棧指針,而不是在OSIntCtxSw()中保存。具體的解釋也可以參考上面這裏只是用的寄存器不同而 已。
IMPORT OSTaskSwHook
OSIntCtxSw
BL OSTaskSwHook // 調用OSTaskSwHook 函數
LDR r4, =OSTCBHighRdy // 得到當前最高優先級就緒的任務
LDR r4, [r4]
LDR r5, =OSTCBCur
STR r4, [r5] // OSTCBCur = OSTCBHighRdy
LDR r6, =OSPrioHighRdy
LDRB r6, [r6]
LDR r5, =OSPrioCur
STRB r6, [r5] // OSPrioCur = OSPrioHighRdy
LDR sp, [r4] // 得到新任務的堆棧指針
LDMFD sp!, {r4} // pop new task cpsr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} // 切換到新的任務
///////////////////////////////////////////////////////////////////////////////
這 是 UCOS-II 搶佔式調度ISR的一個標本。當一個優先級高的任務放棄CPU使用權,例如要休眠 10 個 Tick,系統調度一個低優先級的任務執行之。OSTickISR()爲休眠的任務計時,每次執行,就把休眠任務剩餘的睡覺時間減去一個Tick數。如果 發現一個任務睡夠了,就順便恢復它爲READY態。做完該做的一切,一個對OSIntExit()的調用,使調度發生了。
EXPORT OSTickISR
IMPORT OSIntEnter
IMPORT OSTimeTick
IMPORT tick_hook
IMPORT OSIntExit
///////////////////////////////////////////////////////////////////////////////
注意 ARM 的 IRQ 中斷髮生後的 PC 保存(處理器自動保存 LR=PC+4 ),而不是前面的 PC = LR 。另外,我們保存的是 SVC 模式下的現場,中斷後處理器進入 IRQ 模式,訪問不到 SVC 模式下的 R13(sp) ,於是在 IRQ 模式下,只好先另存 SPSR 和 LR ,然後儘快退回到 SVC 模式,這時的 R13 纔是任務的堆棧指針。在此模式下再將 SPSR 和 LR 保存到堆棧中,立即保存所有寄存器。任務是在 SVC 模式下運行。關於時鐘節拍怎麼實現的(如果不是很懂就看下一篇文章關於 ARM 中斷處理的詳細分析)。
LINK_SAVE DCD 0 // 申請一個字單元用0 來初始化這個字
PSR_SAVE DCD 0 // 地址爲LINK_SAVE+4
OSTickISR
STMFD sp!, {r4} // 這裏的sp 是IRQ 模式下的,將r4 壓入堆棧
///////////////////////////////////////////////////////////////////////////
另存 IRQ 模式下的 SPSR 和 LR ,以便在 SVC 模式下也能訪問,相當於一箇中介作用
LDR r4, =LINK_SAVE
STR lr, [r4] //LINK_SAVE = lr ,保存lr, 此lr 爲IRQ 模式下
MRS lr, spsr //lr=spsr
STR lr, [r4, #4] // PSR_SAVE = spsr_irq, 保存spsr
////////////////////////////////////////////////////////////////////////////
LDMFD sp!, {r4} // 恢復r4 中的內容
ORR lr, lr, #0x80 // Mask irq for context switching before
MSR cpsr_cxsf , lr // 從IRQ 模式恢復到SVC 模式
////////////////////////////////////////////////////////////////////////////
SUB sp, sp, #4 // Space for 給 PC 保留位置
STMFD sp!, {r0-r12, lr} // 保存寄存器和lr
LDR r4, =LINK_SAVE //r4= lr_irq
LDR lr, [r4, #0] //lr=lr_irq
SUB lr, lr, #4 // PC = LINK_SAVE - 4, 這個一定要正確
///////////////////////////////////////////////////////////////////////////////
將PC= LR - 4 存回到堆棧中,剛纔跳過了 PC 4 字節的空間 (R1 到 R12 再加 lr 共佔了 14 個字 )
STR lr, [sp, #(14*4)] //sp=sp+14*4, 因爲堆棧是從高地址向低地址遞減
PC=LR -4
LDR r4, [r4, #4] // r4 = PSR_SAVE,
STMFD sp!, {r4} // save CPSR of the task
LDR r4, =OSTCBCur // 將sp 保存到當前的任務中
LDR r4, [r4]
STR sp, [r4] // OSTCBCur -> stkptr = sp
BL OSIntEnter / / 處理中斷嵌套曾數的增加也可以直接 給OSIntNesting 加一
BL OSTimeTick // 調用ostimetick()
BL tick_hook // 我們在 Tick_hook() 裏清除 S3C44B0x 的 Tick_Int_Pend 位 函數在 main.c 裏,是另加的
BL OSIntExit // 決定是否進行任務調度
/////////////////////////////////////////////////////////////////////////////
如果返回則繼續運行此任務
LDMFD sp!, {r4} //pop new task cpsr
MSR cpsr_cxsf, r4
LDMFD sp!, {r0-r12,lr,pc} // pop new task r0-r12,lr & pc
///////////////////////////////////////////////////////////////////////////////
定義關中斷,主要是爲了安全訪問臨界區的資源
EXPORT ARMDisableInt
ARMDisableInt
MRS r0, cpsr
STMFD sp!, {r0} // push current PSR
ORR r0, r0, #0xC0
MSR cpsr_c, r0 //disable IRQ Int s
MOV pc, lr // 返回
///////////////////////////////////////////////////////////////////////////////
定義開中斷
EXPORT ARMEnableInt
ARMEnableInt
LDMFD sp!, {r0} // pop current PSR
MSR cpsr_c, r0 //restore original cpsr
MOV pc, lr // 返回
///////////////////////////////////////////////////////////////////////////////
三、(1)OS_CPU.C.C 文件的移植 ( 針對S1C33209)
///////////////////////////////////////////////////////////////////////////////
µC/OS-II 的移植需要用戶改寫OS_CPU_C.C 中的六個函數:
OSTaskStkInit()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskSwHook()
OSTaskStatHook()
OSTimeTickHook()
實際需要修改的只有 OSTaskStkInit() 函數,其他五個函數需要聲明,但不一定有實際內容。這五個函數都是用戶定義的,所以OS_CPU_C.C 中沒有給出代碼。如果用戶需要使用這些函數,請將文件OS_CFG.H 中的#define constant OS_CPU_HOOKS_EN 設爲1 ,設爲0 表示不使用這些函數。
///////////////////////////////////////////////////////////////////////////////
這個函數是很重要的 , 該函數由OSTaskCreate() 或OSTaskCreateExt() 調用,用來初始化任務的堆棧。初始狀態的堆棧模擬發生一次中斷後的堆棧結構。 當調用OSTaskCreate() 或OSTaskCreateExt() 創建一個新任務時,需要傳遞的參數是:任務代碼的起使地址,參數指針(pdata ) ,任務堆棧頂端的地址,任務的優先級。OSTaskCreateExt() 還需要一些其他參數,但與OSTaskStkInit() 沒有關係。OSTaskStkInit() 只需要以上提到的3 個參數(task , pdata , 和ptos )。在這個堆棧初始化函數中要清楚堆棧中都要保存哪些東西,要留多大的空間,這些都很重要,否則會發生很嚴重的錯誤。
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
INT32U *stk; // 定義一個指針
opt = opt; /* 這個參數沒有用,但是爲了防止編譯錯誤*/
stk = (INT32U*)ptos; // 載入堆棧指針
///////////////////////////////////////////////////////////////////////////
S1c33 處理器是在入棧時,先變化sp ,再向當前的sp 指向的地址寫入數據。出棧時是先彈出數據,再變化sp
*stk-- = (INT32U)task; // 存放PC 的地址,s1c33209 的處理器會自動保存
////////////////////////////////////////////////////////////////////////////
存放狀態寄存器,同樣也會被自動保存,設置爲中斷開啓 參考其PSR 每位的作用。 如果選擇任務啓動後允許中斷髮生,則所有的任務運行期間中斷都允許;同樣,如果選擇任務啓動後禁止中斷,則所有的任務都禁止中斷髮生,而不能有所選擇。知道爲什麼嗎?因爲啓動的時候,OSStart ()調用的是 OSStartHighRdy ,即從堆棧中恢復PC 和SPR 以及寄存器中的內容,因此第一次堆棧中的放的值決定了spr ,其它寄存器的值到沒有什麼關係。
*stk-- = (INT32U)0x00000010;
*stk-- = (INT32U)0; // 存R15 中的值
*stk-- = (INT32U)0; //--R14
*stk-- = (INT32U)0; //--R13
*stk-- = (INT32U)0; //--R12
*stk-- = (INT32U)0; //--R11
*stk-- = (INT32U)0; //--R10
*stk-- = (INT32U)0; //--R9
*stk-- = (INT32U)0; //--R8
*stk-- = (INT32U)0; //--R7
*stk-- = (INT32U)0; //--R6
*stk-- = (INT32U)0; //--R5
*stk-- = (INT32U)0; //--R4
*stk-- = (INT32U)0; //--R3
*stk-- = (INT32U)0; //--R2
*stk-- = (INT32U)0; //--R1
*stk = (INT32U)0; //--R0
return ((OS_STK *)stk); // 返回堆棧指針所指向的地址,恢復寄存器時候要用
}
///////////////////////////////////////////////////////////////////////////////