μC/OS-II 移植筆記 2(FreeScale 68HCS12 核微控制器)
本文最初寫於 2012-04-20 於 sohu 部落格,這次部落格搬家一起搬到這裡來。
版權所有,轉載請註明出處。
2.2 OS_CPU_A.S
首先是函式和全域性變數的宣告。
;*************************************************************************** ; PUBLIC DECLARATIONS ;*************************************************************************** xdef OSCPUSaveSR xdef OSCPURestoreSR xdef OSStartHighRdy xdef OSCtxSw xdef OSIntCtxSw xdef OSTickISR ;*************************************************************************** ; EXTERNAL DECLARATIONS ;*************************************************************************** xref OSIntExit xref OSIntNesting xref OSPrioCur xref OSPrioHighRdy xref OSRunning xref OSTaskSwHook xref OSTCBCur xref OSTCBHighRdy xref OSTimeTick
然後是臨界區的相關程式碼。
OSCPUSaveSR: tfr ccr,b ; It's assumed that 8-bit return value is in register B sei ; Disable interrupts rts ; Return to caller with B containing the previous CCR OSCPURestoreSR: tfr b, ccr ; B contains the CCR value to restore, move to CCR rts
下面的程式碼是重點部分,首先是 OSStartHighRdy 函式,雖然這個函式只在 OSStart 函式中被呼叫一次,在之後的程式生命週期中就再也用不到了,但這次呼叫至關重要,決定了使用者任務是否能被排程起來。因此程式碼中給出的註釋儘可能的詳細,我想看過註釋後就不需要我解釋什麼了。
;*********************************************************************** ; START HIGHEST PRIORITY TASK READY-TO-RUN ; ; Description : This function is called by OSStart() to start ; the highest priority task that was created by your ; application before calling OSStart(). ; ; Arguments : none ; ; Note(s) : 1) The stack frame is assumed to look as follows: ; ; OSTCBHighRdy->OSTCBStkPtr + 0 CCR ; + 1 B ; + 2 A ; + 3 X (H) ; + 4 X (L) ; + 5 Y (H) ; + 6 Y (L) ; + 7 PC(H) ; + 8 PC(L) ; ; 2) OSStartHighRdy() MUST: ; a) Call OSTaskSwHook() then, ; b) Set OSRunning to TRUE, ; c) Switch to the highest priority task by loading ; the stack pointer of the highest priority task ; into the SP register and execute an ; RTI instruction. ;************************************************************************ OSStartHighRdy: jsr OSTaskSwHook ; 4~, 呼叫 OSTaskSwHook ldab #$01 ; 2~, OSRunning = 1 stab OSRunning ; 4~ ldx OSTCBHighRdy ; 3~, 將 OSTCBHighRdy 的地址放到暫存器 X lds 0, x ; 3~, 將 OSTCBHighRdy->OSTCBStkPtr 放到 SP nop rti ; 4~, Run task
其實上面的程式碼也可以放到OS_CPU_C.C 中,下面是個示例:
#pragma CODE_SEG NON_BANKED
#pragma TRAP_PROC SAVE_NO_REGS
void OSStartHighRdy(void)
{
__asm jsr OSTaskSwHook ; //OSTaskSwHook();
__asm ldab #$01;
__asm stab OSRunning; // OSRunning = TRUE;
__asm
{
ldx OSTCBHighRdy
lds 0, x
nop
}
}
上面程式碼中 #pragma TRAP_PROC SAVE_NO_REGS 表示這是個中斷處理函式,編譯器不為之儲存任何暫存器內容。 雖然 OSStartHighRdy 並不是個真正的中斷處理函式,但它模擬卻模擬了中斷處理函式的行為,函式結束時呼叫 rti 而不是 rts 命令。
下面是任務切換的程式碼,註釋已經足夠的詳細了,如果有不明白的地方建議將 Jean J.Labrosse 的書再仔細讀讀。
OSCtxSw:
ldy OSTCBCur ; 3~, OSTCBCur->OSTCBStkPtr = Stack Pointer
sts 0, y
OSIntCtxSw:
jsr OSTaskSwHook ; 4~, Call user task switch hook
ldx OSTCBHighRdy ; 3~, OSTCBCur = OSTCBHighRdy
stx OSTCBCur
ldab OSPrioHighRdy ; 3~, OSPrioCur = OSPrioHighRdy
stab OSPrioCur
lds 0, x ; 3~, Load SP into 68HC12
nop
rti ; 8~, Run task
可以看到,上面兩個函式公用了大部分的函式體。上面的程式碼也可以直接寫到 OS_CPU_C.C 中,不過寫成 C 函式後就不能公用函式體了。
最後一部分是時鐘中斷程式,我使用RTI中斷作為週期性時鐘源。
OSTickISR:
inc OSIntNesting ; 4~, Notify uC/OS-II about ISR
ldab OSIntNesting ; 4~, if (OSIntNesting == 1) {
cmpb #$01
bne OSTickISR1
ldy OSTCBCur ; OSTCBCur->OSTCBStkPtr = Stack Pointer
sts 0, y ; }
OSTickISR1:
BSET $37, #128 ; CRGFLG_RTIF = 1, 這句是反彙編出來的,應該沒錯
jsr OSTimeTick
jsr OSIntExit ; 6~+, Notify uC/OS-II about end of ISR
rti ; 12~, Return from interrupt, no higher priority tasks ready.
中斷程式的大部分程式碼都比較簡答,只有下面這句我下了番功夫才寫出來:
BSET $37, #128
與這行程式碼功能相同的 C 程式碼是:CRGFLG_RTIF = 1
我將 C程式碼直接生成彙編程式碼的結果是:BSET _CRGFLG,#128
可是直接拿到彙編檔案中卻無法編譯,提示說 _CRGFLG 沒有定義。一番查詢才確定了_CRGFLG = 0x37。
2.3 OS_CPU_C.C
由於大部分的移植程式碼都放到了彙編檔案中,OS_CPU_C.C 中的工作就很少了。OS_CPU_C.C 最重要的工作是 OSTaskStkInit 函式,並且網上流傳的大多數 68HC12 核心的移植程式碼的這部分都或多或少的有問題。下面先給出我的程式碼:
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt)
{
INT16U *wstk;
INT8U *bstk;
(void) opt; /* 'opt' is not used, prevent warning */
ptos--; /* 需要這麼調整一下棧頂地址,否則存的第一個int16 的低 Byte 會溢位堆疊 */
wstk = (INT16U *)ptos; /* Load stack pointer */
*wstk-- = (INT16U)task; /* Return address. Format: PCH:PCL */
*wstk-- = (INT16U)task; /* Return address. Format: PCH:PCL */
*wstk-- = (INT16U)0x2222; /* Y Register */
*wstk-- = (INT16U)0x1111; /* X Register */
*wstk = (INT16U)p_arg; /* Simulate call to function with argument (In D Register) */
bstk = (INT8U *)wstk; /* Convert WORD ptr to BYTE ptr to set CCR */
bstk--;
*bstk = (0xC0); /* CCR Register (Disable STOP instruction and XIRQ) */
return ((OS_STK *)bstk); /* Return pointer to new top-of-stack */
}
其中有幾點需要特別注意:
(1)68HC12 核心與 68HC11 核心一個大的區別就是 SP 指向的是實棧頂。老的68HC11的移植程式碼都是 *--wstk = XXXX。移植到68HC12 核心就要改為*wstk-- = XXXX。否則會浪費掉堆疊的前兩個位元組。
(2)先要執行 ptos--;否則第一個雙位元組會有一半溢位堆疊空間。
(3)任務的引數傳遞是通過暫存器 D 的,而不是堆疊。網上程式碼多數是:
*wstk-- = (INT16U)p_arg;
*wstk-- = (INT16U)task;
這樣引數是傳遞不進來的,只有像我的程式碼中這樣寫才是正確的。
(4)程式碼中 *wstk-- = (INT16U)task; 重複了兩遍,千萬別以為這是我的筆誤。堆疊中先存的(INT16U)task實際上是 task 函式的返回地址。雖然 μC/OS-II 要求任務不能返回,但是作為 C 語言的呼叫約定,在呼叫一個 C 函式之前要將 C 函式的返回地址先入棧。因此我將 task 的地址重複了兩次,實際上第一的地址是什麼都不重要,因為程式執行中覺得不會用到。甚至不要這行也行,還能節省堆疊中兩個位元組的空間。不過我還是選擇了保留這行,使其看起來更加符合 C 語言的呼叫規範。
除此之外,OS_CPU_C.C 還包括一系列的 Hook 函式:
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
void OSInitHookBegin (void)
{
#if OS_TMR_EN > 0
OSTmrCtr = 0;
#endif
}
void OSInitHookEnd (void)
{
}
#endif
#if OS_CPU_HOOKS_EN > 0
void OSTaskCreateHook (OS_TCB *ptcb)
{
#if OS_APP_HOOKS_EN > 0
App_TaskCreateHook(ptcb);
#else
(void)ptcb;
#endif
}
void OSTaskDelHook (OS_TCB *ptcb)
{
#if OS_APP_HOOKS_EN > 0
App_TaskDelHook(ptcb);
#else
(void)ptcb;
#endif
}
void OSTaskStatHook (void)
{
#if OS_APP_HOOKS_EN > 0
App_TaskStatHook();
#endif
}
void OSTaskSwHook (void)
{
#if OS_APP_HOOKS_EN > 0
App_TaskSwHook();
#endif
}
#endif
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION >= 251
void OSTaskIdleHook (void)
{
#if OS_APP_HOOKS_EN > 0
App_TaskIdleHook();
#endif
}
#endif
#if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203
void OSTCBInitHook (OS_TCB *ptcb)
{
#if OS_APP_HOOKS_EN > 0
App_TCBInitHook(ptcb);
#else
(void)ptcb; /* Prevent compiler warning */
#endif
}
#endif
#if OS_CPU_HOOKS_EN > 0
void OSTimeTickHook (void)
{
#if OS_APP_HOOKS_EN > 0
App_TimeTickHook();
#endif
#if OS_TMR_EN > 0
OSTmrCtr++;
if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC))
{
OSTmrCtr = 0;
OSTmrSignal();
}
#endif
}
#endif
程式碼中 OS_APP_HOOKS_EN 和 OS_TMR_EN 在v2.52 版本中還沒出現,我在這裡這樣寫是為了移植到後面版本時更輕鬆。
至此,移植程式碼就基本完成了。不過這樣還不能執行,因為兩個中斷處理函式(OSCtxSw和OSTickISR)還沒有和對應的中斷產生關聯。將這二者關聯起來的方法有幾種,比如直接在 PRM 檔案中制定,我用了種比較笨的辦法,從網上找了個 vector.c 檔案,雖然看起來不是很優雅,但確實是正確的程式碼。
/*******************************************************************
*
* Freescale MC9S12DP256 ISR Vector Definitions
*
* File Name : vectors.c
* Version : 1.0
* Date : Jun/22/2004
* Programmer : Eric Shufro
********************************************************************/
/********************************************************************
* EXTERNAL ISR FUNCTION PROTOTYPES
*********************************************************************/
extern void near _Startup(void); /* Startup Routine. */
extern void near OSTickISR(void); /* OS Time Tick Routine. */
extern void near OSCtxSw(void); /* OS Contect Switch Routine. */
extern void near SCI1_ISR(void); /* SCI1 Routine. */
extern void near SCI0_ISR(void); /* SCI0 Routine. */
/*
************************************************************************
* DUMMY INTERRUPT SERVICE ROUTINES
*
* Description : When a spurious interrupt occurs, the processor will
* jump to the dedicated default handler and stay there
* so that the source interrupt may be identified and
* debugged.
*
* Notes : Do Not Modify
************************************************************************
*/
#pragma CODE_SEG __NEAR_SEG NON_BANKED
__interrupt void software_trap64 (void) {for(;;);}
__interrupt void software_trap63 (void) {for(;;);}
__interrupt void software_trap62 (void) {for(;;);}
__interrupt void software_trap61 (void) {for(;;);}
__interrupt void software_trap60 (void) {for(;;);}
__interrupt void software_trap59 (void) {for(;;);}
__interrupt void software_trap58 (void) {for(;;);}
__interrupt void software_trap57 (void) {for(;;);}
__interrupt void software_trap56 (void) {for(;;);}
__interrupt void software_trap55 (void) {for(;;);}
__interrupt void software_trap54 (void) {for(;;);}
__interrupt void software_trap53 (void) {for(;;);}
__interrupt void software_trap52 (void) {for(;;);}
__interrupt void software_trap51 (void) {for(;;);}
__interrupt void software_trap50 (void) {for(;;);}
__interrupt void software_trap49 (void) {for(;;);}
__interrupt void software_trap48 (void) {for(;;);}
__interrupt void software_trap47 (void) {for(;;);}
__interrupt void software_trap46 (void) {for(;;);}
__interrupt void software_trap45 (void) {for(;;);}
__interrupt void software_trap44 (void) {for(;;);}
__interrupt void software_trap43 (void) {for(;;);}
__interrupt void software_trap42 (void) {for(;;);}
__interrupt void software_trap41 (void) {for(;;);}
__interrupt void software_trap40 (void) {for(;;);}
__interrupt void software_trap39 (void) {for(;;);}
__interrupt void software_trap38 (void) {for(;;);}
__interrupt void software_trap37 (void) {for(;;);}
__interrupt void software_trap36 (void) {for(;;);}
__interrupt void software_trap35 (void) {for(;;);}
__interrupt void software_trap34 (void) {for(;;);}
__interrupt void software_trap33 (void) {for(;;);}
__interrupt void software_trap32 (void) {for(;;);}
__interrupt void software_trap31 (void) {for(;;);}
__interrupt void software_trap30 (void) {for(;;);}
__interrupt void software_trap29 (void) {for(;;);}
__interrupt void software_trap28 (void) {for(;;);}
__interrupt void software_trap27 (void) {for(;;);}
__interrupt void software_trap26 (void) {for(;;);}
__interrupt void software_trap25 (void) {for(;;);}
__interrupt void software_trap24 (void) {for(;;);}
__interrupt void software_trap23 (void) {for(;;);}
__interrupt void software_trap22 (void) {for(;;);}
__interrupt void software_trap21 (void) {for(;;);}
__interrupt void software_trap20 (void) {for(;;);}
__interrupt void software_trap19 (void) {for(;;);}
__interrupt void software_trap18 (void) {for(;;);}
__interrupt void software_trap17 (void) {for(;;);}
__interrupt void software_trap16 (void) {for(;;);}
__interrupt void software_trap15 (void) {for(;;);}
__interrupt void software_trap14 (void) {for(;;);}
__interrupt void software_trap13 (void) {for(;;);}
__interrupt void software_trap12 (void) {for(;;);}
__interrupt void software_trap11 (void) {for(;;);}
__interrupt void software_trap10 (void) {for(;;);}
__interrupt void software_trap09 (void) {for(;;);}
__interrupt void software_trap08 (void) {for(;;);}
__interrupt void software_trap07 (void) {for(;;);}
__interrupt void software_trap06 (void) {for(;;);}
__interrupt void software_trap05 (void) {for(;;);}
__interrupt void software_trap04 (void) {for(;;);}
__interrupt void software_trap03 (void) {for(;;);}
__interrupt void software_trap02 (void) {for(;;);}
__interrupt void software_trap01 (void) {for(;;);}
#pragma CODE_SEG DEFAULT
/***********************************************************************
* INTERRUPT VECTORS
***********************************************************************/
typedef void (*near tIsrFunc)(void);
const tIsrFunc _vect[] @0xFF80 = { /* Interrupt table */
software_trap63, /* 63 RESERVED */
software_trap62, /* 62 RESERVED */
software_trap61, /* 61 RESERVED */
software_trap60, /* 60 RESERVED */
software_trap59, /* 59 RESERVED */
software_trap58, /* 58 RESERVED */
software_trap57, /* 57 PWM Emergency Shutdown */
software_trap56, /* 56 Port P Interrupt */
software_trap55, /* 55 CAN4 transmit */
software_trap54, /* 54 CAN4 receive */
software_trap53, /* 53 CAN4 errors */
software_trap52, /* 52 CAN4 wake-up */
software_trap51, /* 51 CAN3 transmit */
software_trap50, /* 50 CAN3 receive */
software_trap49, /* 49 CAN3 errors */
software_trap48, /* 48 CAN3 wake-up */
software_trap47, /* 47 CAN2 transmit */
software_trap46, /* 46 CAN2 receive */
software_trap45, /* 45 CAN2 errors */
software_trap44, /* 44 CAN2 wake-up */
software_trap43, /* 43 CAN1 transmit */
software_trap42, /* 42 CAN1 receive */
software_trap41, /* 41 CAN1 errors */
software_trap40, /* 40 CAN1 wake-up */
software_trap39, /* 39 CAN0 transmit */
software_trap38, /* 38 CAN0 receive */
software_trap37, /* 37 CAN0 errors */
software_trap36, /* 36 CAN0 wake-up */
software_trap35, /* 35 FLASH */
software_trap34, /* 34 EEPROM */
software_trap33, /* 33 SPI2 */
software_trap32, /* 32 SPI1 */
software_trap31, /* 31 IIC Bus */
software_trap30, /* 30 BDLC */
software_trap29, /* 29 CRG Self Clock Mode */
software_trap28, /* 28 CRG PLL lock */
software_trap27, /* 27 Pulse Accumulator B Overflow */
software_trap26, /* 26 Modulus Down Counter underflow */
software_trap25, /* 25 Port H */
software_trap24, /* 24 Port J */
software_trap23, /* 23 ATD1 */
software_trap22, /* 22 ATD0 */
SCI1_ISR, /* 21 SC11 */
SCI0_ISR, /* 20 SCI0 */
software_trap19, /* 19 SPI0 */
software_trap18, /* 18 Pulse accumulator input edge */
software_trap17, /* 17 Pulse accumulator A overflow */
software_trap16, /* 16 Enhanced Capture Timer Overflow */
software_trap15, /* 15 Enhanced Capture Timer channel 7 */
software_trap14, /* 14 Enhanced Capture Timer channel 6 */
software_trap13, /* 13 Enhanced Capture Timer channel 5 */
software_trap12, /* 12 Enhanced Capture Timer channel 4 */
software_trap11, /* 11 Enhanced Capture Timer channel 3 */
software_trap10, /* 10 Enhanced Capture Timer channel 2 */
software_trap09, /* 09 Enhanced Capture Timer channel 1 */
software_trap08, /* 08 Enhanced Capture Timer channel 0 */
OSTickISR, /* 07 Real Time Interrupt */
software_trap06, /* 06 IRQ */
software_trap05, /* 05 XIRQ */
OSCtxSw, /* 04 SWI - Breakpoint on HCS12 Serial Mon. */
software_trap03, /* 03 Unimplemented instruction trap */
software_trap02, /* 02 COP failure reset */
software_trap01//, /* 01 Clock monitor fail reset */
//_Startup /* 00 Reset vector */
};
後記:
當我完成全部移植工作並測試通過後,我又重新審視了一遍整個移植過程,發現走了許多彎路。這些彎路基本都是因為我對C編譯器的特性,尤其是內聯彙編的處理不熟悉造成的。比如中斷處理函式,其實可以直接寫到 OS_CPU_C.C 中。就可以省略了 vector.c 檔案了。其實我一開始也是這樣做的,但是最初的中斷處理函式混合了C 語句和彙編語句,產生了各種莫名奇妙的錯誤。比如下面的RTI中斷處理函式程式碼:
interrupt VectorNumber_Vrti void OSTickISR (void)
{
OSIntNesting++; // 4~, Notify uC/OS-II about ISR
if (OSIntNesting == 1)
{
__asm
{
ldx OSTCBCur // 3~, OSTCBCur->OSTCBStkPtr = Stack Pointer
sts 0, x // 3~,}
}
}
CRGFLG_RTIF = 1; // clear interrupt flag.
OSTimeTick(); // 6~+, Call uC/OS-II's tick updating function
OSIntExit (); // 6~+, Notify uC/OS-II about end of ISR
}
對比後來的彙編程式碼,其實已經離成功很近了,只要將其中的C 語句全部用匯編寫成來大功告成了:
interrupt VectorNumber_Vrti void OSTickISR (void)
{
__asm
{
inc OSIntNesting
ldab OSIntNesting
cmpb #$01
bne OSTickISR1
ldx OSTCBCur
sts 0, x
OSTickISR1:
BSET _CRGFLG, #128
jsr OSTimeTick
jsr OSIntExit
}
}
其他的程式碼也一樣,都這樣改寫後就完全不需要 vector.c 檔案了。但這裡還是將這些本可以省略掉的程式碼保留下來了,是想記錄下一條真實的探索路程。