1. 知識準備

要想對ucos-ii的移植有較深的理解,需要兩方面知識:

(1)目標晶片,這裡是lpc17xx系列晶片,它們都是基於ARMv7 Cortex-M3核心,所以這一類晶片的ucos-ii移植幾乎都是一樣的,要想了解Cortex-M3核心,推薦《ARM Cortex-M3權威指南》(宋巖譯);

(2)ucos-ii核心原理,推薦《嵌入式實時作業系統uC/OS-II(第2版)》(邵貝貝譯)。

2. 下載檔案

ucos-ii移植過程主要涉及三個檔案:os_cpu.h, os_cpu_a.asm和os_cpu_c.c

實際上,一般情況下,我們想要移植的目標晶片前輩們都已經移植成功過了,我們需要做的就是下載就可以了。

需要下載兩類檔案:

(1)lpc17xx晶片啟動/初始化程式碼:LPC17xx.h, system_LPC17xx.h, core_cm3.h, core_cm3.c, startup_LPC17xx.s和system_LPC17xx.c,這幾個檔案都可以從lpc官方網站lpc17xx系列晶片的任何一個專案中找到;

(2)ucos-ii移植程式碼:可以在Micrium官方網站中找到uCOS-II在LPC17xx上的移植程式碼(IAR平臺)。

3. 建立工程

(1)建立資料夾UCOS_II_V289,在該目錄下建立子目錄APP, lpc17xx, Output, uC-CPU, UCOS-II,在Output下建立obj和list子目錄,然後將第2步下載的檔案新增進相應的資料夾中,檔案拓撲圖如下:

UCOS_II_V289
├─APP
│      hello.c

├─lpc17xx
│      core_cm3.c
│      core_cm3.h
│      LPC17xx.h
│      startup_LPC17xx.s
│      system_LPC17xx.c
│      system_LPC17xx.h
│      type.h

├─Output
│  ├─list
│  └─obj
├─uC-CPU
│      os_cpu.h
│      os_cpu_a.asm
│      os_cpu_c.c
│      os_dbg.c

└─uCOS-II
        app_cfg.h
        os_cfg.h
        os_core.c
        os_flag.c
        os_mbox.c
        os_mem.c
        os_mutex.c
        os_q.c
        os_sem.c
        os_task.c
        os_time.c
        os_tmr.c
        ucos_ii.h

其中,hello.c中的檔案程式碼如下:

#include <LPC17xx.h>
#include <ucos_ii.h>

#define TASK_STK_SIZE 512

OS_STK TaskStartStk[TASK_STK_SIZE];

void TaskStart(void *data);

int main(void)
{
    OSInit();

    OSTaskCreate(TaskStart, (, &TaskStartStk[TASK_STK_SIZE - ], );

    OSStart();

    ;
}

void  TaskStart(void *data)
{
    data=data;

    OS_CPU_SysTickInit(SystemFrequency/);

    for(;;)
    {
        OSCtxSwCtr = ;
        OSTimeDlyHMSM(,,,);
    }
}

(2)Keil uVision4建立新工程,選擇UCOS_II_V289作為工程目錄,選擇晶片型號,需要注意的是當提示“Copy NXP LPC17xx Startup Code to Project Folder and Add File to Project?”時,選擇“否”,因為我們已經有這個檔案了。建立組,新增相應檔案到組,如下所示:

右擊“UCOS_II_V289”,更改工程設定:

如果勾選“Run to main()”,那麼在模擬的時候,就會跳過啟動程式碼,直接到main函式。

4. 編譯

編譯,會報很多錯誤,下面一個一個改:

(1)將os_cpu_a.asm中的“public”改為“EXPORT”;

(2)將os_cpu_a.asm中的

        RSEG CODE:CODE:NOROOT(2)
        THUMB

改為

        AREA OSKernelschedular,code,READONLY
        THUMB

(3)將os_cfg.h中“OS_APP_HOOKS_EN”、“OS_DEBUG_EN”和“OS_TASK_STAT_EN”設定為0;

(4)將startup_LPC17xx.s中的所有的“PendSVHandler”改為“OS_CPU_PendSVHandler”,所有的“SysTickHandler”改為“OS_CPU_SysTickHandler”。

編譯通過。

5. 軟體模擬除錯

在步驟4中已經設定為軟體模擬除錯,編譯成功後,即可新增斷點進行軟體模擬除錯,檢視程式碼執行是否符合預期。至此,移植結束。

6. 相關說明

(1)啟動檔案與啟動流程

i)啟動檔案

啟動檔案為以下幾個檔案core_cm3.c, core_cm3.h, LPC17xx.h, startup_LPC17xx.s, system_LPC17xx.c 和 system_LPC17xx.h,下面分別說明它們的功能。

startup_LPC17xx.s:該檔案是Cortex-M3的啟動彙編程式碼,閱讀原始碼不難發現,它的作用是:堆和棧的初始化以及向量表的定義。Cortex-M3的向量表其實就是一個32位整數陣列,每個下標對應一個向量,該下標元素的值則是該中斷服務子程式的入口地址。向量表在地址空間中的位置是可以設定的,通過NVIC(向量中斷控制器)中的一個重定位七寸器來指出向量表的地址。復位後,該暫存器的值為0,因此,在地址0處必須包含一張向量表,用於初始時的中斷分配。

中斷型別

表項地址偏移量

中斷向量

0

0x00

MSP的初始值

1

0x04

復位

2

0x08

NMI

其中,向量表中的第一個元素並非中斷向量,而是MSP(主堆疊暫存器)的初始值。

LPC17xx.h:該檔案是CM3(Cortex-M3,下同)核心晶片的標頭檔案,它定義了晶片暫存器的結構體。

core_cm3.h和core_cm3.c:這兩個檔案分別是CM3核心晶片的外圍驅動標頭檔案和原始碼。

system_LPC17xx.h和system_LPC17xx.c:這兩個檔案為我們提供了一個系統初始化函式SystemInit(),CM3的初始化包括時鐘配置、電源管理、功耗管理等。其中時鐘配置比較複雜,因為他包括兩個PLL倍頻電路,一個是主PLL0,主要為系統和USB提供時鐘,另一個是PLL1,專門為USB提供48M時鐘。預設情況下,系統使用12M外部晶振,通過PLL0倍頻到一個較高的頻率,之後可以通過分頻為CPU、外設以及可選的USB子系統提供精確的時鐘。

ii)啟動流程

由Cortex-M3的啟動步驟可知,系統上電後,首先執行復位的5個步驟:
    ①NVIC復位,控制核心;
    ②NVIC從復位中釋放核心;
    ③核心配置堆疊;
    ④從地址0x00000000處取出MSP的初始值,從地址0x00000004處取出PC的初始值——這個值是復位向量;
    ⑤運行復位中斷服務子程式;

其中,復位中斷服務子程式的程式碼如下:

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  SystemInit
                IMPORT  __main
                LDR     R0, =SystemInit
                BLX     R0
                LDR     R0, =__main
                BX      R0
                ENDP

可知,通過復位中斷服務子程式,首先載入程式進入SystemInit()函式,然後進入__main(此__main是C_Library中的函式,非main())。

(2)SysTick定時器、SysTickInit與SysTickHandler

i)SysTick定時器

Cortex-M3核心內部包含了一個簡單的定時器——SysTick定時器。SysTick定時器被捆綁在NVIC中,用於產生SysTick中斷。一般情況下,作業系統以及所有使用了時基的系統,都必須由硬體定時器來產生需要的“滴答”中斷,作為整個系統的時基。SysTick定時器就是用來產生週期性的中斷,以維持作業系統“心跳”節律的。SysTick定時器的時鐘源可以是內部時鐘(FCLK,CM3上的自由執行時鐘),或者是外部時鐘(CM3上的STCLK訊號)。

SysTick定時器能產生中斷,CM3為它專門開出一箇中斷型別,並且在向量表中有它的一席之地——SysTickHandler,它使得作業系統和其它軟體系統在CM3核心的移植變得更加簡單,因為在所有的CM3微處理器上,SysTick的處理方式都是相同的。

有4個暫存器控制SysTick定時器,下面只介紹其中經常用到的三個:

①SysTick控制及狀態暫存器(地址:0xE000E010)

位段

名稱

型別

復位值

描述

16

COUNTFLAG

R

0

如果在上次讀取本暫存器後,SysTick已經計到了0,則該位為1;如果讀取該位,該位自動清0

2

CLKSOURCE

R/W

0

0=外部時鐘源(STCLK)

1=核心時鐘源(FCLK)

1

TICKINT

R/W

0

1=SysTick倒數計數到0時產生SysTick異常請求

0=倒數到0時無動作

0

ENABLE

R/W

0

SysTick定時器的使能位

②SysTick重灌載數值暫存器(地址:0xE000E014)

位段

名稱

型別

復位值

描述

23:0

RELOAD

R/W

0

當倒數計數到0時,將被重灌載的值

③SysTick當前數值暫存器(地址:0xE000E018)

位段

名稱

型別

復位值

描述

23:0

CURRENT

R/Wc

0

讀取時返回當前倒數計數的值,寫它則使之清零,同時還會清除在SysTick控制及狀態暫存器中的COUNTFLAG標誌

ii) SysTickInit()函式

該函式用於初始化SysTick定時器,在本移植例項中,它位於os_cpu_c.c檔案中,其函式名被更改為“OS_CPU_SysTickInit”,原始碼如下:

void  OS_CPU_SysTickInit (INT32U  cnts)
{
    OS_CPU_CM3_NVIC_ST_RELOAD = cnts - 1u;
                                                 /* 設定SysTickHandler中斷優先順序為最低優先順序           */
    OS_CPU_CM3_NVIC_PRIO_ST   = OS_CPU_CM3_NVIC_PRIO_MIN;
                                                 /* 使能定時器                                      */
    OS_CPU_CM3_NVIC_ST_CTRL  |= OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC | OS_CPU_CM3_NVIC_ST_CTRL_ENABLE;
                                                 /* 使能SysTickHandler中斷                            */
    OS_CPU_CM3_NVIC_ST_CTRL  |= OS_CPU_CM3_NVIC_ST_CTRL_INTEN;
}

第一行用於裝載SysTick重灌載數值暫存器,其他幾行都有註釋,在此不再解釋。

iii)SysTickHandler

當SysTick定時器倒數計數到0時,將產生SysTick中斷,該中斷位於向量表15號位置(中斷向量號:15,下同)。在本移植例項中,該中斷服務子程式的名稱被更改為“OS_CPU_SysTickHandler”,其位於os_cpu_c.c檔案中,原始碼如下:

void  OS_CPU_SysTickHandler (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          */
}

(3) OSCtxSw與PendSVHandler

i)PendSV中斷

Cortex-M3核心內建了一個重要的中斷——PendSV中斷(可掛起的系統呼叫)。與SVC中斷(系統服務呼叫,簡稱系統呼叫)不同的是,PendSV可以像普通的中斷一樣被掛起,OS可以利用它“緩期執行”一箇中斷——直到其他重要的任務完成後才執行動作。掛起PendSV的方法是:手動往NVIC的PendSV掛起暫存器中寫1,掛起後,如果該中斷優先順序不夠高,則將緩期等待執行。

PendSV的典型功能是上下文(任務)切換。若在即將做上下文(任務)切換時發現CPU正在響應一箇中斷,這時,OS是不能執行上下文(任務)切換的,否則將使中斷請求被延遲,而這在實時系統中是絕不能容忍的。PendSV可以完美地解決這個問題,PendSV中斷會自動延遲上下文(任務)切換的請求,直到其他的中斷請求都完成後才響應。為實現這個機制,需要把PendSV的優先順序設定為最低,當OS需要做上下文(任務)切換時掛起一個PendSV中斷即可。

PendSV中斷位於向量表14號位置,在本移植例項中,該中斷服務子程式的名稱被更改為“OS_CPU_PendSVHandler”,其位於os_cpu_a.asm檔案中,原始碼如下:

OS_CPU_PendSVHandler
    CPSID   I                                                   ; Prevent interruption during context switch
    MRS     R0, PSP                                             ; PSP is process stack pointer
    CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time

    SUBS    R0, R0, # on process stack
    STM     R0, {R4-R11}

    LDR     R1, =OSTCBCur                                       ; OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                                            ; R0 is SP of process being switched out

                                                                ; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave
    PUSH    {R14}                                               ; Save LR exc_return value
    LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();
    BLX     R0
    POP     {R14}

    LDR     R0, =OSPrioCur                                      ; OSPrioCur = OSPrioHighRdy;
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]

    LDR     R0, =OSTCBCur                                       ; OSTCBCur  = OSTCBHighRdy;
    LDR     R1, =OSTCBHighRdy
    LDR     R2, [R1]
    STR     R2, [R0]

    LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
    LDM     R0, {R4-R11}                                        ; Restore r4- from new process stack
    ADDS    R0, R0, #0x20
    MSR     PSP, R0                                             ; Load PSP with new process SP
    ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack
    CPSIE   I
    BX      LR                                                  ; Exception return will restore remaining context

    END

ii)OSCtxSw

ucos-ii中,OSCtxSw的功能是上下文(任務)切換。而由上面的介紹可知,真正的切換工作是在PendSV中斷中完成的,那麼可想而知,OSCtxSw只需觸發一個PendSV中斷即可完成上下文(任務)切換的工作。OSCtxSw位於os_cpu_a.asm檔案中,原始碼如下:

OSCtxSw
    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

7. 工程原始碼

工程原始碼連結:USCO_II_V289.rar

8. 參考資料

[1] 《ARM Cortex-M3權威指南》(宋巖譯)

[2] 《嵌入式實時作業系統uC/OS-II(第2版)》(邵貝貝譯)

[3] ucosii在stm32上的移植詳解系列