LM3S8962에 RTOS인 uC/OS-II를 포팅해보기 위해 관련 Application note 검토해 보았다.
우선 아래 링크에 가서 자료를 다운받는다.

Link : http://micrium.com/page/support/application_notes#AN-1


위의 링크에서 AN-1018 관련 자료를 다운받는다.

다운로드 된 실행파일을 설치하면
C:\Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3
위의 path에서 AN-1018 관련 Document를 참조할 수 있다.




1.00 Introduction
 ARM은 수 년 동안 Cortex라고 불리는 새로운 아키텍처를 개발해 왔다. 개발하는 동안 , uC/OS-II는 디자인 측면의 어떤것을 유효하게 하기 위해 사용되었고, RTOS를 제공하도록 새로운 가능성을 생성하기 위한 아이디어의 source로서 사용되었다. 다시말해 uC/OS-II는 Cortex에  port된 첫번째 RTOS이다.

 이 어플리케이션 노트는 Cortex-M3 processor에 uC/OS-II를 위한  'official' Micrium port를 설명한다. Figure1-1은 당신의 application ,uC/OS-II, port code, BSP  사이의 관계를 보여주는 block diagram을 보여준다. 이 어플리케이션 노트의 관련된 sections은 그림에서 참조된다.

2.00 The ARM Cortex-M3 programmer’s model
ARM Cortex-M3 processor에서 사용할 수 있는 레지스터는 Figure 2-1에 보여진다. ARM Cortex-M3는 총 20개의 레지스터를 갖는다. 각 레지스터는 32 bits wide이다.

R0-R12
R0 ~ R12 는 general purpose register이다. 이 레지스터들은  pointer 뿐만 아니라 data도 hold 하도록 사용될 수 있다.

R13
R13은 일반적을 stack pointer(SP)로서 사용되지만  산술연산의 결과를 저장할 때도 사용될 수 있다. 실제로 2개의 stack pointer(SP_process, SP_main)가 있지만 단지 하나만이 주어진 시간에 보여지게 된다. SP_process는 task level code 를 위해 사용되고 SP_main은 exception processing을 위해 사용된다.

R14
R14는 Link Register(LR)로 불려지고 Branch와 Link(BL) 명령이 실행될 때 PC의 contents를 저장하기 위해 사용된다. LR은 호출할 곳으로 return하게 해준다.

R15
R15는 Program Counter(PC)로 사용되고 수행되고 있는 현재 명령을 가리키고 있다. 명령이 실행될 때, PC는 명령에 따라 2 나 4 씩 증가하게 된다.

xPSR
CPU의 상태를 hold하기 위해 3개의 독립적인 레지스터가 있다. :  APSR, IPSR, EPSR

APSR은 그림 2-2에서 보여지는 것처럼 application의 상태를 포함한다.

N
비트 31dms 'negative' 비트이다. 최근 ALU 연산이 negative 결과일 때 set된다.
(i.e. the top bit of a 32-bit result was a one).

Z
비트 30은 'zero' 비트이다. 최근 ALU 연산이 zero 결과가 되었을 때, set된다.
(every bit of the 32-bit result was zero).

C
비트 29는 'carry' 비트이다. 최근 ALU 연산이 carry-out을 발생했을 때 set된다.  
ALU에서 산술 연산의 결과로서 또는 시프터로부터 carry-out이 발생했을 때 set된다.

V
비트 28은 'overflow' 비트이다. 최근 산술 ALU 연산이 sign 비트로 overflow를 발생했을 때, set 된다.

Q
비트 27은 sticky saturation flag이다.

Interrupt PSR(iPSR)은 현재 exception  동작 중인 ISR 번호를 포함한다. 그림 2-3에서 보여지고 있다.

Execution PSR(EPSR)은 두 개으 overlapping fields를 포함한다 :
- interrupted load multiple 과 store multiple 명령을 위한 Interruptible-Continuable Instruction(ICI) filed
- If-Then(IT) 명령과 T-bit(Thumb state bit)를 위한 execution state field

exception에 들어갈 때, 프로세서는 3개의 상태 레지스터로부터 stack으로 결합된 정보를 저장한다.


3.00 μC/OS-II Port for the ARM Cortex-M3 processors
우리는 port test를 위해 IAR EWARM V4.40A를 사용했다. EWARM은 editer, C/EC++ 컴파일러, 어셈블러, linker/locator,C-spy 디버거를 포함한다. C-Spy 디버거는 실제로 ARM Cotrex-M3 시뮬레이터를 포함한다.. 그것은 실제 하드웨어에서 run하기 전에 code를 테스트 할 수 있도록 해준다. 우리는 그림 3-1에 보여지는 Luminary Micro DK-LM3Sxxx 개발 보드에서 ARM Cortex-M3 port를 테스트했다.

당신은 다른 컴파일러 기반의 ARM Cortex-M3 에 이 application note에서 제공하는  port를 적용할 수 있다. 명령(i.e. the code)은 동일하고 당신이 해야만 하는 모든 것은 컴파일러 특색에 따라 port를 적용한다. 우리는 다른 파일의 내용을 cover할  때 이것 중 몇몇을 설명할 것이다.

port는 당신이 uC/OS-II V2.83이나 그보다 높은 버전을 사용한다고 가정한다.


3.01 Directories and Files
이 application note을 동반하는 소프트웨어는 아래 디렉토리에 위치된다고 가정한다.
\Micrium\Software\uCOS-II\ARM-Cortex-M3\Generic\IAR

모든 uC/OS-II ports는 유사하게, port를 위한 소스 코드는  아래 파일에서 찾을 수 있다.

OS_CPU.H
            OS_CPU_C.C
            OS_CPU_A.ASM
            OS_DBG.C

테스트 코드와 환경 설정 파일은 적합한 디렉토리에서 찾게 된다.


3.02 OS_CPU.H
OS_CPU.H는 processor- 와 구현을 구체화시킨 #define 상수, macro , typedef을 포함하고 있다.

3.02.01 OS_CPU.H, macro for 'externals'
OS_CPU_GLOBALS와 OS_CPU_EXT는 이 port에 특정 전역 변수를 가능하게 한다.

Listing 3-1, OS_CPU.H, Globals and Externs
#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT extern
#endif


3.02.02 OS_CPU_H, Data Types
Listing 3-2, OS_CPU.H, Data Types
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;          // (1)
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
typedef float FP32;                           // (2)
typedef double FP64;
typedef unsigned int OS_STK;          // (3)
typedef unsigned int OS_CPU_SR;    // (4)

L3-2(1)
만약 당신이 IAR compiler documnetation을 문의한다면 short는 16비트이고 int는 32비트 인 것을 찾을 수 있다. 대부분의 ARM 컴파일러는 같은 정의를 가진다.

L3-2(2)
부동 소수점 데이터 타입은 uC/OS-II는 부동소수점을 사용하지 않는데도 포함되어 있다.

L3-2(3)
ARM 프로세서를 위한 stack entry는 항상 32bit-wide이다. 그래서 OS_STK는 그에 맞게 적절히 정의된다. 모든 task stack은 데이터 타입으로서 OS_STK를 사용하여 정의되어야만 한다.

L3-2(4)
ARM 프로세서에서 상태 레지스터(xPSR)은 32bit-wide이다. OS_CPU_SR 데이터 타입은 OS_CRITICAL_METHOD #3이 사용될 때 사용된다. 사실 이 port는 단지 OS_CRITICAL_METHOD #3 만을 제공한다. 그것은 uC/OS-II port를 위한 우선되는 방법이기 때문이다.

3.02.03 OS_CPU_H, Critical Sections
모든 real-time kernel 들이 그런 것처럼, uC/OS-II는 코드의 critical section에 접근하기 위해 interrupt를 disable 하고 완료되었을 때, re-enable 하는 것이 필요하다. uC/OS-II는 interrupt를 disable하고 enable 하기 위해 두 개의 macro를 정의한다. : OS_ENTER_CRITICAL()과 OS_EXIT_CRITICAL()이 그것이다. uC/OS-II는 interrupt를 disable하기 위해 3가지 방법을 제시하지만, 당신은 interrupt를 disabling 하고 enabling 하기 위해 3가지 방법 중 단지 한가지를 사용하는 것이 필요하다. book(MicroC/OS-II, The Real-Time Kernel)은 이 3가지 방법을 설명한다. 하나를 선택하는 것은 프로세서와 컴파일러에 의존한다. 대부분의 경우, 우선시 되는 방법은 OS_CRITICAL_METHOD #3이다.

OS_CRITICAL_METHOD #3는 변수에 CPU의 상태 레지스터를 저장할 함수를 작성함으로써 OS_ENTER_CRITICAL()을 실행한다. OS_EXIT_CRITICAL()은 변수로부터 상태 레지시트를 restore 하기 위해 다른 함수를 inoke 한다. 책에서 Mr.Labrosse는 당신이 OS_ENTER_CRITICAL()과 OS_EXIT_CRITICAL()에서 기대되는 함수를 호출하도록 요구한다. 바로 OS_CPU_SR_Save()와 OS_CPU_SR_Restore()이다. 이 두함수를 위한 코드는 OS_CPU_A.S에 선언되어 있다.

Listing 3-3, OS_CPU.H, OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL()
#define OS_CRITICAL_METHOD 3
#define OS_ENTER_CRITICAL()  {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL()     {OS_CPU_SR_Restore(cpu_sr);}


만약 당신의 application code가 이 macro를 사용한다면, 당신은 'cpu_sr'이라고 불리는 지역변수를 할당하고 그것을 0으로 초기화 해야 한다. 아래 보여진다 :

OS_CPU_SR cpu_sr = 0;

3.02.04 OS_CPU.H, Stack growth
ARM Cortex-M3에서 stack은 high 메모리로부터 low 메모리로 성장한다. 따라서 OS_STK_GROWTH는 uC/OS-II에 이것을 나타내기 위해 1로 set된다.

Listing 3-4, OS_CPU.H, Stack Growth
#define OS_STK_GROWTH 1


3.02.05 OS_CPU_H, Task Level Context Switch
Task level context switch는 uC/OS-II가 macro OS_TASK_SW()를 invoke 할 때 수행된다. context switcing이 프로세서에 특성화 되어 있기 때문에, OS_TASK_SW()는 어셈블리 언어 함수를 실행하는 것이 필요하다. 이 경우, OSCtxSw()는 OS_CPU_A.ASM에서 선언된다.

Listing 3-5, OS_CPU.H, Task Level Context Switch
#define OS_TASK_SW() OSCtxSw()

3.02.06 OS_CPU.H, Function Prototypes
Listing 3-6에서 prototype은 OS_CRITICAL_METHOD #3를 사용하는 interrupt를 disable하고 re-enable 하기 위해 사용되는 함수를 위한 것이다. 당신은 이러한 prototype이 특별한 키워드인 __arm 으로 prefixed 되었음을 주의해야 한다. 이것은 이 함수들이 ARM 모드에서 동작할 것이라는 것을 나타내는 IAR 키워드 이다.  따라서 호출되었을 때 컴파일러는 적절한 명령을 발생시킬 것이다.

Listing 3-6, OS_CPU.H, Function Prototypes
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR OS_CPU_SR_Save(void);
void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
#endif

V2.77에서 OSCtxSw(), OSIntCtxSw(), OSStartHighRdy()를 위한 prototype은 OS_CPU.H에 위치되는 것이 필요하다. 사실 그것은 이것들이 모두 port specific한 파일이므로 이것을 수행하는 것을 느낄 수 있게 된다.

Listing 3-7, OS_CPU.H, Function Prototypes
void OSCtxSw(void);
void OSIntCtxSw(void);
void OSStartHighRdy(void);
void OSPendSV(void);


3.03 OS_CPU_C.C
uC/OS-II port는 10개의 간단한 C 함수를 작성하는 것을 요구한다.

OSInitHookBegin()
OSInitHookEnd()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskIdleHook()
OSTaskStatHook()OSTaskStkInit()
OSTaskSwHook()
OSTCBInitHook()
OSTimeTickHook()

일반적으로, uC/OS-II는 단지 OSTaskStkInit()을 요구한다. 다른 함수들은 당신 자신의 함수를 통해 OS의 기능을 확장하도록 해준다.  보여지는 함수들은 이 section에서 설명할 것이다.

컴파일러에 이 파일에서 선언된 함수를 사용하는 것을 알려주기 위해 OS_CFG.H에서 #define 상수 OS_CPU_HOOKS_EN을 1로 설정하는 것이 필요하다.

3.03.01 OS_CPU_C.C, OSInitHookBegin()
이 함수는 OSInit()의 시작에서 uC/OS-II의 OSInit()에 의해 호출된다. 그것은 port에 추가적인 초기화 코드를 추가할 수 있도록 해준다. 여기에서는 , 우리는 전역변수(global to OS_CPU_C.C) OSTmrCtr(which is used by the OS_TMR.C module if OS_TMR_EN is set to 1)을 초기화한다.

Listing 3-8, OS_CPU_C.C, OSInitHookEnd()
void OSInitHookBegin (void)
{
#if OS_TMR_EN > 0 OSTmrCtr = 0;
#endif
}

3.03.02 OS_CPU_C.C, OSTaskCreateHook()
이 함수는 task가 생성될 때 , uC/OS-II의 OSTaskCreate(), OSTaskCreateExt()에 의해 호출된다. OSTaskCreateHook() 는 task가 생성될때, port에 특정 코드를 추가할 수 있도록 해준다. 우리의 경우, 우리는 uC/OS-View의 초기화 함수를 호출한다(an optional module available for μC/OS-II which performs task profiling at run-time, See www.micrium.com for details).
 
호출되는 OSView_TaskCreateHook()를 주의해라. uC/OS-View를 위한 target 임시 코드는 당신의 build의 한 부분으로서 included 되야 한다. 여기서는, 당신의 applicaiton의 OS_CFG.H에서 #define OS_VIEW_MODULE 1을 추가하는 것이 필요하다.

만약 OS_VEW_MODULE이 0이라면, 우리는 ptcb는 실제 사용되지 않고 컴파일러 warning을 피하도록 컴파일러에게 알려 주어야 하는것을 주의해라.

Listing 3-9, OS_CPU_C.C, OSInitHookEnd()
void OSTaskCreateHook (OS_TCB *ptcb)
{
#if OS_VIEW_MODULE > 0
        OSView_TaskCreateHook(ptcb);
#else
        (void)ptcb;
#endif
}

3.03.03 OS_CPU_C.C, OSTaskStkInit()
함수의 첫번째 argument를 R0 레지스터로 pass하는 것은 ARM 컴파일러에서는 일반적인 것이다. listing 3-10에서 보여지는 것처럼 task가 선언된 것을 Recall한다.

Listing 3-10, μC/OS-II Task
void MyTask (void *p_arg)
{
    /* Do something with ‘p_arg’, optional */
    while (1) {
        /* Task body */
    }
}

listing 3-11에서 코드는 생성되고 있는 task를 위해 stack  frame을 초기화한다. task는 선택적인 argument 'p_arg'를 수신했다. 그것은 task가 생성될 때 'p_arg'가 R0에서 pass되기 때문이다. 대부분의 CPU 레지스터의 초기값은 그렇게 중요하지 않다, 우리는 레지스터 번호와 연관된 번호로 그것들을 초기화하기로 결정했다. 이것은 RAM에서 stack을 디법깅하고 시험할 때 변하게 해준다. 초기값은 task가 처음 생성될 때 사용할 수 있지만, 물론  레지스터 값은 task code가 실행됨으로서 바뀌게 될 것이다

Listing 3-11, OS_CPU_C.C, OSTaskStkInit()
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *p_arg, OS_STK *ptos, INT16U opt)
{
OS_STK *stk;

(void)opt;                                                        /* 'opt' is not used, prevent warning */
stk = ptos;                                                       /* Load stack pointer */ 

                                                                     /* Registers stacked as if saved on exception */
*(stk) = (INT32U)0x01000000L;                           /* xPSR */
*(--stk) = (INT32U)task;                                   /* Entry Point */
*(--stk) = (INT32U)0xFFFFFFFEL;                      /* R14 (LR) */
*(--stk) = (INT32U)0x12121212L;                        /* R12 */
*(--stk) = (INT32U)0x03030303L;                        /* R3 */
*(--stk) = (INT32U)0x02020202L;                        /* R2 */
*(--stk) = (INT32U)0x01010101L;                        /* R1 */
*(--stk) = (INT32U)p_arg;                                 /* R0 : argument */
 
                                                                     /* Remaining registers saved on process stack */
*(--stk) = (INT32U)0x11111111L;                        /* R11 */
*(--stk) = (INT32U)0x10101010L;                        /* R10 */
*(--stk) = (INT32U)0x09090909L;                        /* R9 */
*(--stk) = (INT32U)0x08080808L;                        /* R8 */
*(--stk) = (INT32U)0x07070707L;                        /* R7 */
*(--stk) = (INT32U)0x06060606L;                        /* R6 */
*(--stk) = (INT32U)0x05050505L;                        /* R5 */
*(--stk) = (INT32U)0x04040404L;                        /* R4 */

return (stk);
}

그림 3-2sms 각 task가 생성될 때 stack frame을  초기화하는 방법을 보여준다.


task가 생성될 때, stk의 마직막 값은 OSTaskStkInit()를 호출하는 uC/OS-II 함수에 의해 task의 OS_TCB에 위치하게 된다(i.e. OSTaskCreate() or OSTaskCreateExt()).
 

3.03.04 OS_CPU_C.C, OSTaskSwHook()
OSTaskSwHook()는 context switch가 발생했을 때 호출된다. 이 함수는 context switch가 발생했을 대, task의 실행 시간을 측정하거나, port pin으로 pulse를 출력하는 것과 같은 작업을 수행하고 확장되기 위한 port code를 가능하게 한다. 여기서는, 우리는 OSView_TaskSwHook()라고 불리는 uC/OS-View task switch hook을 호출한다. 이것은 당신이 build의 한 부분으로 uC/OS-View를 갖고  OS_CFG.H에서 OS_VIEW_MODULE을 1로 설정했다고 가정한다.

Listing 3-12, OS_CPU_C.C, OSTaskSwHook()
void OSTaskSwHook (void)
{
#if OS_VIEW_MODULE > 0
        OSView_TaskSwHook();
#endif
}

3.03.05 OS_CPU_C.C, OSTimeTickHook()
OSTimeTickHook()은 OSTimeTick()의 시작에서 호출된다. 이 함수는 확장되는 port code를 가능하게 한다, 우리의 경우, 우리는 uC/OS-View 함수 OSView_TickHook()을 호출한다. 이것은 uC/OS-View를 build의 한 부분으로 가지고, OS_CFG.H에서 OS_VIEW_MODULE을 1로 설정한다고 가정한다.

OSTimeTickHook()은 또한 uC/OS-II 타이머를 update하는 시간을 결정한다. 이것은 timer task를 signaling함으로써 완료된다.

Listing 3-13, OS_CPU_C.C, OSTimeTickHook()
void OSTimeTickHook (void)
{
#if OS_VIEW_MODULE > 0
    OSView_TickHook();
#endif

#if OS_TMR_EN > 0
    OSTmrCtr++;
    if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) { 
        OSTmrCtr = 0;
        OSTmrSignal();
    }
#endif
}


3.04 OS_CPU_A.ASM
uC/OS-II port는 5개의 간단한 어셈블러 함수를 작성하는 것을 요구한다. 이 함수들은 C 함수로 부터 레지스터를 save/restore 할 수 없기 때문에 필요하게 된다. 이 다섯개의 함수는 :

    OS_CPU_SR_Save()
    OS_CPU_SR_Restore()
    OSStartHighRdy()
    OSCtxSw()
    OSIntCtxSw()

ARM Cortex-M3는context switch를 수행하기 위해 영리한 방법을 사용한다. 이것은 정의되는 것이 필요한 exception handler를 통해 수행된다. 이 handler는 :

    OSPendSV()

3.04.01 OS_CPU_A.ASM, OS_CPU_SR_Save()
listing 3-14에서 코드는 interrupt mask register를 saving하고 OS_CRITICAL_METHOD #3를 실행하기 위해 인터럽트를 disabling한다. 이 함수는 OS_ENTER_CRITICAL() macro에 의해 invoked된다.

이 함수가 return될 때, R0는 interrupt disabling에 앞서 global interrupt mask를  포함하고 있는 PRIMASK 레지스터의 상태를 저장하게 된다.

Listing 3-14, OS_CPU_SR_Save()
OS_CPU_SR_Save
        MRS R0, PRIMASK        ; set prio int mask to mask all (except faults)
        CPSID I
        BX LR

3.04.02 OS_CPU_A.ASM, OS_CPU_SR_Restore()
아래의 listing에서 코드는 OS_ENTER_CRITICAL()을 호출하기에 앞서 original 값으로 interrupt disable mask를 restore하기 위한 함수를 실행한다. 다시 말해, 만약 인터럽트가 OS_ENTER_CRITICAL() 을 호출하기에 앞서 disabled되었다면, 그것들은 OS_EXIT_CRITICAL()을 호출하기에 앞서 disabled(???) 될 것이다.

Listing 3-15, OS_CPU_SR_Restore()
OS_CPU_SR_Restore
        MSR PRIMASK,R0
        BX LR

3.04.03 OS_CPU_A.ASM, OSStartHighRdy()
OSStartHighRdy()는 OSStart()를 호출하기 전에 생성된  가장 높은 우선순위의 task를 running을 시작하기 위해 OSStart()에 의해 호출된다. OSStart()는 가장 높은 우선순위의 task의 OS_TCB를 가리키도록(point) OSTCBHighRdy를 설정한다.

Listing 3-16, OSStartHighRdy()

OSStartHighRdy
        LDR R4, =NVIC_SYSPRI2 ; (1) Set the PendSV exception priority 
        LDR R5, =NVIC_PENDSV_PRI 
        STR R5, [R4] 

        MOV R4, #0 ; (2) Set PSP to 0 for initial context switch call 
        MSR PSP, R4 

        LDR R4, __OS_Running ; (3) OSRunning = TRUE
        MOV R5, #1 
        STRB R5, [R4] 

        LDR R4, =NVIC_INT_CTRL ; (4) Trigger the PendSV exception 
        LDR R5, =NVIC_PENDSVSET 
        STR R5, [R4]

        CPSIE I ; (5) Enable interrupts at processor level

L3-16(1)
ARM Cortex-M3는 context switch 수행하기 위해 특별한 mechanism을 제공한다. 특히 ARM Cortex-M3는 PendSV라고 불리는 특별한 exception handler를 제공한다(Pend Service call). PendSV는 기본적으로 소프트웨어에 호출됨으로써 트리거되는 인터럽트되는 machanism이다.  인터럽트가 enabled될 때까지 인터럽트가 발생하지 않는 것을 제외하고는 소프트웨어 인터럽트와 유사하다. 이 단계는 PendSV 인터럽트의 우선순위를 설정한다. 일반적으로 하드웨어 인터럽트보다는 낮게 설정된다. PendSV는 un-interruptable하게 설정해서 PendSV 호출은 atomically하게 context switch를 수행할 수 있다.

L3-16(2)
여기서 우리는 첫번째 task이지만 PendSV handler에 의해 구동하는 SP_process stack를 설정한다. 이것은 task의 context를 저장하지 않는다고 PendSV Handler에 알려주기 위해 SP_process를 0으로 설정함으로써 완료된다(because there is no task to save the context for since it will be the first task to run).  그것은 OSTCBHighRdy가 시작하기 위해 task의 OS_TCB로 pointer를 포함하고 있다고 가정한다.

L3-16(3)
 여기서 우리는 OSRunning을 TRUE로 설정한다. 이것은 multitasking을 시작할 것이라는 것을 나타내기 위한 것이다.

L3-16(4)
우리는 첫번째 task를 시작하게 될 PendSV handler를 trigger하기 위해 준비를 한다. PendSV handler는 단지 interrupt를 enabled 되었을 때 run할 것이다.

L3-16(5)
인터럽트가 enabled 되었을 때 ARM Cortex-M3 processor는 PendSV handler로 branh할 것이다.

3.04.04 OS_CPU_A.ASM, OSCtxSw()
task가 CPU의 제어권을 포기할 때, OS_TASK_SW() macro는 invoked된다. 이것은 OSCtxSw()로 호출로 바뀌게 된다. 보통, OSCtxSw()는 task level의 context switch를 수행하지만, ARM Cotex-M3는 모든 context switching은 PendSV handler로 연기된다. OSCtxSw()는 그래서 간단하게 PendSV handler를 트리거하고 , 호출한 곳으로 return한다. PendSV handler는 즉시 실행하지는 않는다. 이유는 OS_TASK_SW()는 interrupt disabled되기 때문이다. PendSV handler는 오로지 인터럽트가 re-enabled되었을 때, 실행된다.

OS_TASK_SW()는 항상 OS_Sched()로부터 호출된다(see OS_CORE.C). OS_Sched()의 현재 버전은 Listing 3-17에서 보여진다.

Listing 3-17, OS_Sched()
void OS_Sched (void) {
#if OS_CRITICAL_METHOD == 3
        OS_CPU_SR cpu_sr = 0;
#endif

    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {
         if (OSLockNesting == 0) {
             OS_SchedNew();
             if (OSPrioHighRdy != OSPrioCur) {
                 OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];

#if OS_TASK_PROFILE_EN > 0
                 OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif 
                 OSCtxSwCtr++; 
                 OS_TASK_SW();  -> Tirgger the PendSV handler
            }
        }
    }
    OS_EXIT_CRITICAL();     -> PendSV handler will run when interrupts are re-enabled
}

OSCtxSw()를 위한 코드는 Lising 3-18에서 보여진다. 다시 말하면 ,여기서 하는 모든 것은 PendSV handler를 trigger한다. OS_Sched()는 우리가 switch하기 위한 task의 OS_TCB를 가리키기 위해(point) OSTCBHighRdy를 설정한다.

Listing 3-18, OSCtxSw()
OSCtxSw
        LDR R4, =NVIC_INT_CTRL ; trigger the PendSV exception
        LDR R5, =NVIC_PENDSVSET 
        STR R5, [R4]
        BX LR



3.04.05 OS_CPU_A.ASM, OSIntCtxSw()

ISR이 완료되었을 때, OSIntExit()는 interrupted task보다 더 중요한 task를 결정하기 위해 호출된다. 만약 이런 경우라면, OSIntExit()는 다음에 구동할 task르르 결정하고 OSIntCtxSw()를 호출한다. 그러나 실제로 OSIntCtxSw()가 context switch를 수행하는 다른 uC/OS-II port와 달리, ARM Cortex-M3를 위한 OSIntCtxSw()는 간단히 PendSV handler를 trigger하고 Listing 3-19에서 보여지는 것처럼 return 한다.

Listing 3-19, OSIntCtxSw()
OSCtxSw
        LDR R4, =NVIC_INT_CTRL ; trigger the PendSV exception
        LDR R5, =NVIC_PENDSVSET
        STR R5, [R4]
        BX LR

3.04.06 OS_CPU_A.ASM, OSPendSV()
OSPendSV()는 uC/OS-II를 위한 모든 context switch를 담당하는 PendSV handler이다. 이것은 ARM Cortex-M3를 가지고 Context switch를 수행하기 위한 요구되는 방법이다. 이것은 ARM Cortex-M3 가 any exception에서 processor context의 절반을 auto-save하고, exception으로부터 return시 동일한 레지스터를 restore하기 때문이다. PendSV handler는 단지 R4-R11을 저장하는 것이 필요하고 stack pointer는 고정되어 있다. PendSV exception을 사용하는 것은 그것이 task로부터 초기화되거나 또는 인터럽트나 exception 때문에 발생하더라도 context saving과 restoring이 동일한 방법을 사용한다는 것을 의미한다. 

exception vector table에서 vector location 14번에 OSPendSV()의 Pointer를 위치시켜야 한다(based of the vector table + 4 * 14 or, offset 56).
 
PendSV handler를 위한 pseudo-code는 다음과 같다.

Listing 3-20, OSPendSV()
OSPendSV:
        if (PSP != NULL) {                                              (1)
                Save R4-R11 onto task stack;                      (2)
                OSTCBCur->OSTCBStkPtr = SP;                 (3)
        }
        OSTaskSwHook();                                            (4)
        OSPrioCur = OSPrioHighRdy;                             (5)
        OSTCBCur = OSTCBHighRdy;                           (6)
        PSP = OSTCBHighRdy->OSTCBStkPtr;              (7)
        Restore R4-R11 from new task stack;                (8)
        Return from exception;                                    (9)



L3-20(0)
OSPendSV()가 CPU에 의해 시작될 때, CPU는 자동으로 xPSR, PC, LR, R12, R0-R3레지스터를 task stack으로 저장한다. CPU 레지스터의 절반을 task stack에 저장한 후에, CPU는 handler의 나머지를 처리하기 위해, SP_main을 사용하도록 stack pointer를 switch 한다.

L3-20(1)
여기에서 우리는 만약 이것이 SP_process라면 stack pointer가 NULL 설정되었는지 그렇지 않은지 확인하기 위해 check 한다. OSStartHighRdy()가 task가 처음 시작할 때 task의 context를 저장하는 것을 피하기 위해 SP_process를 NULL로 설정하는 것을 재호출한다.

L3-20(2)
만약 OSPendSV()는 실제로 full task switch를 수행하도록 트리거되면, 그때 우리는 단순히 남아있는 레지스터(R4-R11)를 저장한다.

L3-20(3)
switched out 되고 있는 task의 context가 saved되었을 때, dnflsms ekstnsgl task의 OS_TCB로 task stack pointer(SP_process)를 저장한다.

L3-20(4)
우리는 uC/OS-II context switch hook을 호출한다(see OS_CPU_C.C).

L3-20(5)
모드 uC/OS-II ports에서, 우리는 current priority로 새로운 high priority를 복사하는 것이 필요하다.

L3-20(6)
유사하게 우리는 OSTCBCur로 OSTCBHighRdy를 복사하는 것이 필요하다.

L3-20(7)
우리는 우리가 지금 switch하기 원하는 task의 current top-of-stack pointer를 되찾는다.  top-of-stack pointer가 OSTCBHigRdy->OSTCBStkPtr에서 저장되도록 재호출한다. 편의를 위해, uC/OS-II는 항상 OS_TCB의 시작에 OSTCBStkPrt을 위치시킨다. 그리고 store SP의 offset가 무엇인지 찾을 필요가 없이, 항상 offset은 0이다.

L3-20(8)
우리는 task의 stack frame으로부터 실행하기 위해 task의 context를 restore한다.

L3-20(9)
우리는 exception으로부터 return을 수행한다. 이것은 ARM Cortex-M3을 R3-R0, R12, LR, PC, xPSR레지스터를 task의 stack frame로부터 restore하도록 한다. 이 시점에 우리는 새로운 task를 run한다.


OSPendSV() handler를 위한 실제 코드는 Listing 3-21에서 보여진다. comment에서 참조 번호는 Listing 3-20의 pseudo-code에서 동일 요소와 연관되어 있다 .

그림 3-3는 context switch를 graphically하게 보여준다(again with the corresponding references).

PendSV handler는 context switch가 atomically하게 수행하는 것을 보장하기 위해 non-preemptable하고 non-interruptable해야 한다는 것에 주의해야 한다. 만약 인터럽트가 context-switch가 수행될 때 발생한다면, 그것은 새로운 task가 restored 될 때 서비스될 것이다.

Listing 3-21, OSPendSV()
OSPendSV                                               ; (0) CPU saved xPSR, PC, LR, R12, R0-R3
        MRS R0, PSP                                   ; (1) PSP is process stack pointer
        CBZ R0, OSPendSV_nosave              ; skip register save the first time

        SUB R0, R0, #0x20                            ; (2) save remaining regs r4-11 on process stack
        STM R0, {R4-R11} 

        LDR R4, __OS_TCBCur                      ; (3) OSTCBCur->OSTCBStkPtr = SP; 
        LDR R4, [R4]
        STR R0, [R4]                                   ; R0 is SP of process being switched out

OSPendSV_nosave
        PUSH {R14}                                     ; (4) OSTaskSwHook();
        LDR R0, __OS_TaskSwHook
        BLX R0
        POP {R14}

        LDR R4, __OS_PrioCur                      ; (5) OSPrioCur = OSPrioHighRdy
        LDR R5, __OS_PrioHighRdy
        LDRB R6, [R5]
        STRB R6, [R4] 
       
        LDR R4, __OS_TCBCur                     ; (6) OSTCBCur = OSTCBHighRdy;
        LDR R6, __OS_TCBHighRdy
        LDR R6, [R6]
        STR R6, [R4]

        LDR R0, [R6]                                  ; (7) R0 is new task SP
                                                             ; SP = OSTCBHighRdy->OSTCBStkPtr;

        LDM R0, {R4-R11}                           ; (8) restore R4-R11 from new task stack
        ADD R0, R0, #0x20
        MSR PSP, R0                                  ; load PSP with new task SP
        ORR LR, LR, #0x04                          ; ensure exception return uses process stack
        BX LR                                            ; (9) exception return



3.05 OS_DBG.C
OS_DBG.C는 uC/OS-II와 그것의 환경 설정에 대한 정보를 뽑아내도록 Kernel Aware debugger를 제공하기 위해 V2.62에서 추가된 파일이다. 특기 OS_DGB.C는 debugger가 읽고 표시할 수 있는 ROM 공간에 위치한 여러개의 상수를 포함하고 있다. 이 파일이 필요한 debugger를 사용하지 않을 수도 있기 때문에 build시 제외할 수 도 있다.

IAR의 C-Spy debugger를 위해, Micrium은 이 파일을 사용하게 하는 Windows-based 'Plug-In' module을 소개해왔다. 그래서 C-Spy를 사용한다면 included 하는 것이 필요하다.

4.00 Exception Vector Table
ARM Cortex-M3는 0x00000000번지에서 시작하는 exception vector table(also called the interrupt vector table)을 포함하고 있다. 테이블은 256개의 entry를 포함할 수 있다(can be up to 1 Kbytes since each entry is a 32-bit pointer). 각 entry는 관련 exception 또는 interrupt를 가리키는 pointer이다.

일반적으로 이 테이블에 위치된 명령어는 signed 26-bit destination address를 갖는 branch 명령어이다. 다시말해 ARM은 vector위치로부터 +/- 0x0200000하게 address를 branch할 수 있다. branch하도록 하는 코드는 인터럽트 source를 결정해야 한다. 그것은 ARM이 interrupt할 수 있는 모든 device 중에 단지 하나의 address 만이 거기에 존재하기 때문이다.

ARM Cortex-M3의 exception vector table은 table 4-1에서 보여진다:


uC/OS-II는 context switching을 위해 PendSV handler를 사용하고 system tick을 처리하기 위해 SysTick handler를 사용한다.

PendSV handler는 un-interruptable되기 위해 OSStartHighRdy()에 의해 cofigured 된다. 그래서 PendSV handler는 atomically하게 실행할 수 있다.

ARM Cortex-M3는 RTOS 사용을 위해 특별히 디자인된 built-in 타이머를 가지고 있다. 이 타이머는 any tick rate로 동작할 수 있게 configured 될 수 있다. 어플리케이션의 BSP는 OS_TICKS_PER_SEC로이 타이머를 설정해야만 한다.

어플리케이션 코드가 Exception Vector Table을 setup 할 수 있다. 이 작업을 돕기 위해 APP_VECT.C라고 불리는 파일을 생성했다. 그것은 각각의 프로젝트에서 편집가능하다.


4.01 Exception / Interrupt Handling Sequence
CPU가 exception 또는 interrupt handler를 invoke할 때,  CPU는 자동으로 xPSR, PC, LR, R12, R0-R3 레지스터를 SP_process stack으로 push한다.

그 다음 CPU는 exception/interrupt handler의 address를 뽑아오기 위해 vector table을 읽고 그 address로 PC를 업데이트 한다. old  PC는 original code로 return하기위해 LR에 위치된다.  그 다음 CPU는 SP_main stack pointer를 사용하기 위해 switch한다.

4.02 Interrupt Controllers
ARM Cortex-M3는 또한 integrated Nestable Vectored Interrupt Controller(NVIC)를 가지고 있다.

4.03 Interrupt Service Routines
uC/OS-II 서비스를 사용하기 위해 필요한 ISR은 ARM Cortex-M3를 위해 Listing 4-1처럼 작성되야 한다.

Listing 4-1, Interrupt Service Routines using μC/OS-II services.
void OS_CPU_IRQ_ISR_Handler (void)
{
        OS_CPU_SR cpu_sr = 0;
       
        OS_ENTER_CRITICAL();         /* Tell uC/OS-II that we are starting an ISR */
        OSIntNesting++;
        OS_EXIT_CRITICAL();

        /* Handle the Interrupt … don’t forget to clear the interrupt source */

        OSIntExit();                          /* Tell uC/OS-II that we are leaving the ISR */
}

연산이 atomically하게 수행되는 것을 보장하도록 OSIntNesting을 증가시키기 위해 인터럽트를 disable해야만 한다. 우리는 OS_ENTER_CRITICAL()과 OS_EXIT_CRITICAL() macro를 호출함으로써 이것을 수행한다.

몇몇 ISR은 task에 signal을  줄 필요가 없이 동작하는 것이 가능하다. 이런 경우, ISR은 OSIntNesting을 증가시키고 OSIntExit()를 호출하는 것이 필요하지 않을 것이다.


5.00 Application Code
당신의 어플리케이션 코드는 이 섹션에서 설명하는 것처럼 이 어플르케이션 노트에서 표현된 port를 사용할 수 있다. 그림 5-1은 어플리케이션, uC/OS-II, uC/OS-II port, BSP, ARM Cotrex-M3 CPU, target 하드웨어  사이의 관계를 block diagram으로 보여주고 있다.


5.01 APP.C, APP.H and APP_CFG.H
토론을 위해, 어플리케이션은 APP.C, APP_H, APP_CFG.H라고 불리는 파일들에 위치된다. 물론 어플리케이션은 더 많은 파일을 포함할 수 있다.

APP.C는 main()이 위치되어 있을 것이다. , 물론 어느 곳에 위치하든 상관없다.

APP_VECT.C는 어플리케이션을 위한 exception/interrupt vector table을 포함하고 있다. 당신은 자신만의 인터럽트 handler를 추가하기 위해 이 파일을 편집할 수 있다(or at least pointers to them). vector 14번은 OSPendSV()를 가리키는 포인터가 필요하고(see OS_CPU_A.ASM), vector 15번은 Tmr_TickISR_Handler()를 가리키는 포인터를 위해 필요하다(see BSP.C).

APP_CFG.C는 어플리케이션을 configure하기 위한 #define 상수를 포함하고 있다. 우리는 task stack size, task 우선순위, 다른 #define 들을 이 파일에 위치시켰다. 이것은 한 곳에서 task의 우선순위와 size를 알 수 있게 해준다.

APP.C는 uC/OS-II 예제를 위한 standard test file이다. 두 개의 중요한 함수는 main()과 AppStartTask()이다.


Listing 6-1, main()
void main (void)
{
#if OS_TASK_NAME_SIZE > 13
        INT8U err;
#endif

        BSP_IntDisAll();                                                                                                     (1)
        OSInit();                                                                                                               (2)
        OSTaskCreateExt(AppStartTask,                                                                             (3)
                                  (void *)0,
                                  (OS_STK *)&AppStartTaskStk[APP_TASK_START_STK_SIZE-1],
                                  APP_TASK_START_PRIO,
                                  APP_TASK_START_PRIO,
                                  (OS_STK *)&AppStartTaskStk[0],
                                  APP_TASK_START_STK_SIZE,
                                  (void *)0,
                                  OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);

#if OS_TASK_NAME_SIZE > 11
        OSTaskNameSet(APP_TASK_START_PRIO, "Start Task", &err);                                  (4)
#endif

        OSStart();                                                                                                             (5)
}

L5-1(1)
초기화 과정이 완료될 때까지 인터럽트를 받지 않는 것을 보장하기 위해 인터럽트를 disable하는 것이 필요하다.

L5-1(2)
어플리케이션 기반의 모든 uC/OS-II는 OSInit()를 호출함으로써 uC/OS-II를 호출하는 것이 필요하다.

L5-1(3)
 적어도 하나의 task를 생성하는 것이 필요하다. 이 경우에서 우리는 extended task create call을 사용해서 task를 생성했다. 이것은 uC/OS-II가 task에 대해 더 많은 정보를 갖도록 해준다. 특히 IAR toolchain에서 여분의 정보는 uC/OS-II Kernel Awareness Plug-in을 사용하 때 , C-Spy debugger가 stack 사용 정보를 표시하도록 해준다.

L5-1(4)
우리는 지금 task에게 이름을 줄 수 가 있고 IAR의 C-Spy와 같은 Kernel Aware debugger에 의해 표시될 수 있다.

L5-1(5)
multitasking을 시작하기 위해, OSStart()를 호출하는 것이 필요하다. OSStart()는 이 호출로부터 return하지 않을 것이다.

Listing 5-2, AppStartTask()
static void AppStartTask (void *p_arg)
{
        (void)p_arg;

        BSP_Init();                                                               (1)

#if OS_TASK_STAT_EN > 0
        OSStatInit();                                                             (2)
#endif

#if OS_VIEW_MODULE > 0
        OSView_Init(38400);                                                  (3)
        OSView_TerminalRxSetCallback(AppTerminalRx);
#endif

        AppTaskCreate();                                                     (4)
        while (TRUE) {
                /* Do something ‘useful’ in this task */               (5)
               LED_Toggle(1);                                                 (6)
               OSTimeDly(OS_TICKS_PER_SEC / 20);
        }
}

L5-2(1)
target boart를 위해 BSP를 실핼하기로 결정했다면, 여기에서 그것을 초기화할 것이다.

L5-2(2)
OS_CFG.H에서 OS_TASK_STAT_EN을 1로 셋팅함으로써 startinit task를 enabled 하였다면, 그 때 여기서 그것을 호출하는 것이 필요하다. OSStartInit()가 clock tick이 있다고 가정하기 때문에 uC/OS-II clock tick을 초기화했고 enabled 했다고 확신하는 것이 필요하다. 다시 말해, 만약 tick ISR이 OSStartInit()를 호출했을 때 active되지 않았다면, 어플리케이션은 uC/OS-II의 idel task에서 종료되고, 다른 task를 run하지 못할 것이다.

L5-2(3)
uC/OS-View를 구매했다면, 여기서 그것을 초기화 할 수 있을 것이다. 이것을 발생시키기 위해, uC/OS-View target 임시 파일을 build에 include하는 것이 필요하다.

L5-2(4)
이 시점에, 추가적인 task를 생성할 수 있다. 우리는 AppTaskCreate()라고 불리는 하나의 함수에 모든 task 초기화를 위치시키기로 결정했다.  그러나 당신은  다른 기술을 사용하기를 바란다.

L5-2(5)
이 task를 위해 당신이 원하는 추가적인 함수가 무엇이든지 지금 수행 할 수 있다.

L5-2(6)
우리는 이 task가 running할 때 , 20Hz를 주기로 LED를 토글하기로 결정했다.


5.02 INCLUDES.H
INCLUDE.H는 master include file이고 모든 .C 파일의 맨 위에 위치한다. INCLUDE.H는 header file이 실제 필요한 것에 신경쓰지 않고 작성되도록 당신의 프로젝트에서 모든 .C 파일을 접근할 수 있게 해준다. 단지 master include file이 가지는 약점은 INCLUDE.H가 컴파일된 실제 .C 파일과 관련없는 header file을 include 할수도 있다는 것과 컴파일 과정이 더 길어질 지도 모른다는 것이다. 이러한 불편함은 code의 유동성에 의해 offset 된다. 당신은 자신만의 header file을 추가하기 위해 INCLUDE.H를 편집할 수 있다. 하지만 header file은 list의 끝에 추가되어야만 한다.


6.00 BSP (Board Support Package)

BSP를 생성하는 것은 target 하드웨어를 위해 편리하다. BSP는 아래의 기능들으 encapsulate 할수 있게 해준다.

        Timer initialization
        ISR Handlers
        LED control functions
        Reading switches
        Setting up the interrupt controller
        Setting up communication channels
        Etc.

Micrium BSP는 적어도 2개의 파일로 구성된다. : BSP.C와 BSP.H이다.

각 BSP는 BSP 초기화 함수를 포함하고 있어야만 한다. 우리는 BSP_Init()를 호출했고 어플리케이션 코드에 의해 호출되어야만 한다.


6.01 BSP (Board Support Package) – LED Management
여러개의 evaluation board는 LED를 장착하고 있다. 우리는 아래와 같은 LED control 함수를 생성하기로 결정했다.

        void LED_Init(void);
        void LED_On(CPU_INT08U led_id);
        void LED_Off(CPU_INT08U led_id);
        void LED_Toggle(CPU_INT08U led_id);

이 경우에, LED는 'physically' 대신에 'logically'으로 참조된다. BSP를 작성할 때 , LED가 LED #1인지 LED #2인지 결정한다. LED #1을 켜기를 원할 때 단순히 LED_On(1)을 호출한다. LED #2를 토글하기를 원한다면, LED_Toggle(2)를 호출한다. 사실 #define을 사용해서 LED의 이름을 정의할 수 있다. 그래서 LED_OFF(LED_PM) 같이 명시할 수 있다. 

 
6.02 BSP (Board Support Package) – Clock Tick
우리는 다른 uC/OS-II port와 일관성을 위해 BSP에서 uC/OS-II clock tick handler를 encapsulate 하기로 결정했다. ARM Cortex-M3를 위해 clock tick handler는 uC/OS-II port의 한 부분이 될 수 있어왔다. 모든 ARM Cotex-M3 실행이 Systick를 포함할 것이기 때문이다. clock tick ISR handler는 BSP.C에서 찾게 되고, Tmr_TickISR_Handler()라고 불리게 된다.  clock tick handler의 예시는 Listing 6-1에서 보여진다. ARM Cortex-M3에서는 어셈블리어로 ISR code를 작성할 필요가 없다. ISR은 다른 C 함수이다.

Listing 6-1, Tmr_TickISR_Handler()
void Tmr_TickISR_Handler (void)
{
        OS_CPU_SR cpu_sr;

        OS_ENTER_CRITICAL();         /* Tell uC/OS-II that we are starting an ISR */
        OSIntNesting++;
        OS_EXIT_CRITICAL();

        OSTimeTick();                     /* Call uC/OS-II's OSTimeTick() */

        OSIntExit();                         /* Tell uC/OS-II that we are leaving the ISR */
}

7.00 Conclusion
이 어플리케이션 노트는 ARM Cortex-M3 프로세서를 위한 'generic' port를 설명했다. port는 쉽게 다른 컴파일러에 적용되어야만 한다. 물론 uC/OS-II를 사용하고 실제 하드웨어에 port를 사용한다면, 초기화가 필요할 것이고, 하드웨어 interrupt를 다루는 것이 필요할 것이다.




'[D-03] uC/OS-II' 카테고리의 다른 글

Flow-chart를 통해 분석한 µC/OS-II  (0) 2009.12.04
RTOS, ARM Architecture 개인 사이트  (0) 2009.11.25
uCOS-II_뛰어넘기  (0) 2009.11.25
Posted by eoseontaek

Link : http://blog.naver.com/leechis70/80009895106


약간은 오래된 2.04 버전의 영문 주석을 일일이 한글로 바꾼 한글코멘트판 Source입니다...  
웹 상에서 보기편하도록 Color Syntax를 적용하였습니다...  
Source 분석에 많은 도움이 되시기를... 




 

 

'[D-03] uC/OS-II' 카테고리의 다른 글

[uC/OS-II] uC/OS-II and ARM Cortex-M3 Processors  (0) 2010.01.28
RTOS, ARM Architecture 개인 사이트  (0) 2009.11.25
uCOS-II_뛰어넘기  (0) 2009.11.25
Posted by eoseontaek

유용한 자료가 많다.

Link : http://cgi.chol.com/~hebb/

'[D-03] uC/OS-II' 카테고리의 다른 글

[uC/OS-II] uC/OS-II and ARM Cortex-M3 Processors  (0) 2010.01.28
Flow-chart를 통해 분석한 µC/OS-II  (0) 2009.12.04
uCOS-II_뛰어넘기  (0) 2009.11.25
Posted by eoseontaek
Posted by eoseontaek
이전버튼 1 이전버튼