前言
之前大學接觸過uc/os,大學開了ARM這門課程,用的是周立功的教材,好像是大四開的,你懂的,實驗只帶手不帶腦,複製–編譯–下載–嘿,燈亮了–走人。
最近在接觸EFM32系列MCU,就想彌補一下大學的遺憾。爲時間4天的準備和移植工作終於完成,還是,嘿燈亮了。
硬件:EFM32G890F128
IDE : IAR
準備工作
兩本書:Cortex-M3權威指南,uc/OSII操作系統原理(邵貝貝)。
因爲內核之前已看過了,就琢磨了下uc,大學的時候接觸過點點還有點印象,所以看起來不費什麼勁,確實挺小的一個核。裏面關於移植的過程也挺詳細的。不過那裏關於移植的是ARM7內核。大同小異。原理性的東西是一樣的。
其餘,關於移植部分,各大芯片廠商都已經很完善了,對於求知的同學,這是一個理解CPU,操作系統的好地方。
建立模板
官網下載源代碼
http://micrium.com/ ->downloads -> Application Notes ->
壓縮包下載下來解壓最主要的就是這幾個文件夾了。
當然還有一個PDF文檔,這個文檔主要是教你怎樣使用它們的官方工程模板。英文好的可以照着上面的步驟一步一步來,沒什麼說的,當然你要有耐心看英語。我試了下:確實規範,一目瞭然。
個人覺得學習的話還是自己建一個模板吧,這樣有利於理解各個模塊之間的關係。所以我就以自己的習慣加參考官方的自己操作了。
其實我們自己移植的話就只要uc/OSII的內核源碼和三個移植文件啦。最難的移植部分人家幫我們做好了,那就是那三個移植文件了,即os_cpu.h,os_cpu_a.asm, os_cpu.c當然我們還是要看懂的。接下來就是建工程,這是我自己的工程:
代碼分析
這裏的代碼分析就是與處理器相關的代碼了,最接近處理器的代碼:彙編。
主要乾了兩個個事情:
- 任務級和中斷級調度,這兩個函數的內容是一樣的,真正的調度則放到了PENDSVHandler。
- 系統開始啓動時,啓動最高優先級任務。
- PENDVHandler保存上下文切換環境。
下面分析整個OS_CPU_A.ASM文件
EXTERN OSRunning ; External references,外部標號
EXTERN OSPrioCur
EXTERN OSPrioHighRdy
EXTERN OSTCBCur
EXTERN OSTCBHighRdy
EXTERN OSIntExit
EXTERN OSTaskSwHook
EXTERN OS_CPU_ExceptStkBase
PUBLIC OS_CPU_SR_Save ; Functions declared in this file,全局函數,可被其他模塊引用
PUBLIC OS_CPU_SR_Restore
PUBLIC OSStartHighRdy
PUBLIC OSCtxSw
PUBLIC OSIntCtxSw
PUBLIC PendSV_Handler
NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register. 中斷控制寄存器
NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).優先級寄存器
NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest). PENDSV優先級(最低)
NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception. 觸發PENDSV的值
;**********************************************************************************************
; 代碼部分
;**********************************************************************************************
RSEG CODE:CODE:NOROOT(2)
THUMB
;RSEG 是段選擇指令。RSEG CODE:選擇段 code。第二個 CODE 表示代碼段的意思,只讀。
;NOROOT 表示:如果這段中的代碼沒調用,則允許連接器丟棄這段.(2)表示:4 字節對齊。假如是(n),則表示 2^n 對
;齊,更多關於IAR編譯指令參考IAR編譯手冊。
;THUMB則爲代碼爲THUMB指令,M3爲THUMB指令集。
OS_CPU_SR_Save
MRS R0, PRIMASK ; 保存PRIMASK值至R0,OS_CPU_SR_Save返回時,R0中值送入
; cpu_sr.
CPSID I ; PRIMASK =1 ,關中斷.
BX LR
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;把R0的值加載到PRIMASK,R0爲調用OS_CPU_SR_Restore的函
;數的參數cpu_sr.
BX LR
/*-----啓動最高優先級任務--操作系統啓動第一個任務時------*/
OSStartHighRdy
LDR R0, =NVIC_SYSPRI14 ; 設置PENDSV爲最低優先級
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 ; 把從堆棧置0,在調度器調度時通過判斷PSP是否
MSR PSP, R0 ; 0來判斷系統是否爲首次調度。
STRB R1, [R0]
LDR R0, =OS_CPU_ExceptStkBase ; 初始化主堆棧
LDR R1, [R0]
MSR MSP, R1
LDR R0, =OSRunning ; OSRunning = TRUE
MOVS R1, #1
STRB R1, [R0]
LDR R0, =NVIC_INT_CTRL ; 觸發一次PENDSV
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
CPSIE I ; 打開中斷
OSStartHang
B OSStartHang ; 程序到這裏就崩了。
;************************************************************************************************
任務級切換與中斷級切換
;************************************************************************************************
OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; 觸發一次PENDSV
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
;********************************************************************************************************
; void OS_CPU_PendSVHandler(void)
;
; a) 獲取SP, 通過判斷sp是否爲第一次任務切換,如果不是則跳過如果是則跳轉到
OS_CPU_PendSVHandler_nosave,第一次任務不需要進行現場保護。
; b) 如果不是第一次任務切換,需保存R4-R11的值至PSP;
; c) 將上一任務的SP保存在OSTCBCur->OSTCBStkPtr = SP;
; d) 調用鉤子函數 OSTaskSwHook();
; e) 獲取最高優先級任務, OSPrioCur = OSPrioHighRdy;
; f) 獲得最高優先級任務控制塊, OSTCBCur = OSTCBHighRdy;
; g) 從任務快做獲取其SP堆棧指針, SP = OSTCBHighRdy->OSTCBStkPtr;
; h) 從起任務堆棧SP中恢復R4-R11;
; i) 啓動異常返回序列xPSR, PC, LR, R12, R0-R3
;
; 3) PendSV handler中斷髮生時:
; a) xPSR, PC, LR, R12, R0-R3自動入棧進程堆棧PSP
; b) 處理器模式從線程模式轉換到異常模式
; c) 此時堆棧使用主堆棧MSP
; d) OSTCBCur 指針指向的爲被掛起的任務
; OSTCBHighRdy 指針指向將被調度的任務
;
; 4) 因爲PendSV爲最低優先級,所以只有當沒有任何異常中斷的時候PendSV纔會相應。
;
;
;************************************************************************************************
PendSV_Handler
CPSID I ; 關中斷
MRS R0, PSP
CBZ R0, OS_CPU_PendSVHandler_nosave ; 第一次任務開始時,PSP爲0,故跳過對R4-R11的保存
SUBS R0, R0, #0x20 ; 總共需要保存8個寄存器的值,即 8個字*4=32字節=0x20,將
;r4-r11存儲到psp中
STM R0, {R4-R11}
LDR R1, =OSTCBCur ; 保存被中斷任務的堆棧
LDR R1, [R1] ; 獲取堆棧指針,任務堆棧指針在TCB的頂部。
STR R0, [R1] ```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] ; 獲得最高優先級任務堆棧指針;
LDM R0, {R4-R11} ; 從PSP從恢復r4-11
ADDS R0, R0, #0x20
MSR PSP, R0 ;更新PSP,已啓動正確的返回序列
ORR LR, LR, #0x04 ;確保異常返回後使用PSP(詳見M3異常返回)
CPSIE I ;打開中斷
BX LR ;啓動中斷返回序列,獲取PC到任務斷點處。
代碼修改
這裏的代碼修改主要是開發板上的啓動代碼和OS內核的掛接了。因爲我們下載下來的代碼本身是IAR下的代碼,所以很多關於編譯器的代碼我們就可以省了,至於什麼是與編譯器相關的代碼,那隻能翻書了。
ARM芯片都有一個啓動代碼.S文件,就是放置向量表的地方。
1、函數名稱的對應
在OS內核os_cpu_a_asm中PENDSV中斷的名稱爲OS_CPU_PendSvHandler.。而開發板啓動代碼的PENDSV中斷標號爲PendSV_Handler,保證兩者標號一直即可,我這裏將OS_CPU_PendSvHandler.改爲PendSV_Handler。
Os_cpu_a_asm:
因爲我自己開發板工程目錄下core_cm3.h下有關於SysTick的函數:所以我們得把os_cpu_c.c關於SystemTick的函數和宏定義都註釋掉:
自己工程下的SysTick_Config()
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) return (1);
SysTick->LOAD = ticks - 1;
NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
SysTick->VAL = 0;
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return (0);
}
將os_cpu_c.c關於SystemTick的函數和宏定義都註釋掉:
#if 0 //-------註釋掉
void OS_CPU_SysTickInit (INT32U cnts)
{
OS_CPU_CM3_NVIC_ST_RELOAD = cnts - 1u;
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;
OS_CPU_CM3_NVIC_ST_CTRL |= OS_CPU_CM3_NVIC_ST_CTRL_INTEN;
}
#endif
#if 0 //-------註釋掉
#define OS_CPU_CM3_NVIC_ST_CTRL (*((volatile INT32U *)0xE000E010uL))
#define OS_CPU_CM3_NVIC_ST_RELOAD (*((volatile INT32U *)0xE000E014uL))
#define OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018uL))
#define OS_CPU_CM3_NVIC_ST_CAL (*((volatile INT32U *)0xE000E01CuL))
#define OS_CPU_CM3_NVIC_PRIO_ST (*((volatile INT8U *)0xE000ED23uL))
#define OS_CPU_CM3_NVIC_ST_CTRL_COUNT 0x00010000uL
#define OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC 0x00000004uL
#define OS_CPU_CM3_NVIC_ST_CTRL_INTEN 0x00000002uL
#define OS_CPU_CM3_NVIC_ST_CTRL_ENABLE 0x00000001uL
#define OS_CPU_CM3_NVIC_PRIO_MIN 0xFFu
#endif
總結
1、光知道移植這個操作肯定是不行的,還要知道爲什麼這樣移植。這就要參考很多資料了,我是讀了邵貝貝的那本書,其實也不難,當然,我是懷着一股好奇的態度去閱讀的,我就想搞清楚,操作系統到底是個啥玩意。
2、瞭解Cortex-M3內核,只要接觸過單片機,不管是大學裏微機原理中的8086還是C51,只要仔細研究過單片機的工作原理,其實也無非就是指令集,中斷,內存映射。。。至於外設操作大同小異。對於M3應該是很簡單的。
3、此次移植的收穫很大,人家的編程方式,人家的代碼一看就舒服,人家的文件structure。當然以前許多C語言教程微微提到過的東西,也終於見到他的影子了,總之,學習一個操作系統的源碼,對自己的編程能力和見識是很有幫助的。
4、英文真的很重要,其實向這種移植過程micrim的網站上都有其應用文檔。自己能看懂,就不必去羨慕那些輔導機構的人爲什麼知道的那麼多了哦。。。他們也是從國外的網站上借鑑過來的。做技術的,英語無處不在啊!
5、中國的科技道路還有很長的路要走啊。
最後,說一下,關於ucos方面的移植,網上的文章講爛了都,但捫心自問一下,自己是否真正徹底弄明白過!