1 uCOS移植到STM32中,代碼移植的詳細注釋,參考了網絡上的很多資料,我隻是用我自己的思路整理了一下。
有不對的地方請大家多多指教,互相學習
2 需要準備兩個資料用于随時檢視一些重點,《Cortex-M3權威指南》和《Cortex-M3_技術參考手冊》
在代碼的注釋過程中都注明了對應的知識點在哪頁,可以找到對應的地方仔細閱讀,重點需要閱讀的是《Cortex- M3權威指南》
3 系統複位後預設使用的堆棧指針是MSP,複位後的狀态是特權級線程狀态
4 假如在使用者模式下使用的是PSP,那麼寄存器的數值被儲存到任務堆棧的空間,進入中斷程式後就開始使用MSP,
如果還有一個高優先級的中斷那麼就繼續的使用MSP,在程式退出最後一級中斷的時候就用使用者堆棧恢複寄存器
5 uCOS-II的任務使用的程序堆棧指針(PSP),異常使用的是主堆棧指針(MSP)
是以在起始檔案中設定的Stack_Mem SPACE Stack_Size是設定的主堆棧的空間大小
在中斷嵌套不深的時候可以讓主堆棧占用的空間小一些
os_cpu_c.c
隻有OSTaskStkInit函數的注釋,其他的hook函數沒有包含進來
<span style="font-size:18px;">/*
*********************************************************************************************************
* INITIALIZE A TASK'S STACK
*
* Description: This function is called by either OSTaskCreate() or OSTaskCreateExt() to initialize the
* stack frame of the task being created. This function is highly processor specific.
*
* Arguments : task is a pointer to the task code
*
* p_arg is a pointer to a user supplied data area that will be passed to the task
* when the task first executes.
*
* ptos is a pointer to the top of stack. It is assumed that 'ptos' points to
* a 'free' entry on the task stack. If OS_STK_GROWTH is set to 1 then
* 'ptos' will contain the HIGHEST valid address of the stack. Similarly, if
* OS_STK_GROWTH is set to 0, the 'ptos' will contains the LOWEST valid address
* of the stack.
*
* opt specifies options that can be used to alter the behavior of OSTaskStkInit().
* (see uCOS_II.H for OS_TASK_OPT_xxx).
*
* Returns : Always returns the location of the new top-of-stack once the processor registers have
* been placed on the stack in the proper order.
*
* Note(s) : 1) Interrupts are enabled when your task starts executing.
* 2) All tasks run in Thread mode, using process stack.
*********************************************************************************************************
*/
//做出任務好像被中斷過一樣的假象,用于任務的第一次執行的時候來恢複寄存器的内容
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), 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 auto-saved on exception */
*(stk) = (INT32U)0x01000000L; /* xPSR,<Cortex-M3技術參考手冊P21>,當T為(第24位)0時,執行指令會引起INVSTATE異常*/
*(--stk) = (INT32U)task; /* 任務的入口位址 */
*(--stk) = (INT32U)0xFFFFFFFEL; /*任務第一次被排程從堆棧中恢複寄存器的值的時候LR的值會恢複為0xFFFFFFFEL
這個值也沒什麼用,因為在進入中斷之後LR的意義發生改變,此時的值也是由硬體自動設定的*/
/*LR是連接配接寄存器,用于在執行BL等分支指令的時候自動儲存PC的值,用于儲存子程式的傳回位址*/
/* R14 (LR) (init value will cause fault if ever used)*/
/* LR:進入異常時LR的用法将被重新解釋,其值也被更新成一種特殊的值,稱為“EXC_RETURN”,
并且在異常傳回時使用。EXC_RETURN的二進制值除了最低4位外全為1,而其最低4
位則有另外的含義(見表9.3和表9.4)。<權威指南PDF136頁和139頁>
合法的EXC_RETURN值及其功能:
0xFFFF_FFF1 傳回handler模式
0xFFFF_FFF9 傳回線程模式,并使用主堆棧(SP=MSP)
0xFFFF_FFFD 傳回線程模式,并使用線程堆棧(SP=PSP)
如果主程式線上程模式下運作, 并且在使用MSP 時被中斷, 則在服務例程中
LR=0xFFFF_FFF9(主程式被打斷前的LR已被自動入棧)。
如果主程式線上程模式下運作, 并且在使用PSP 時被中斷, 則在服務例程中
LR=0xFFFF_FFFD(主程式被打斷前的LR已被自動入棧)。
如果主程式在Handler模式下運作,則在服務例程中LR=0xFFFF_FFF1(主程式被打斷前的
LR已被自動入棧)。這時的“主程式”,其實更可能是被搶占的服務例程。事實上,在嵌套
時,更深層ISR所看到的LR總是0xFFFF_FFF1,
無效傳回時<權威指南PDF142頁>
如果LR中的EXC_RETURN不是合法的值(合法值見表9.4,包括企圖傳回ARM狀态),則
引起用法fault。如果用法fault被除能,也上訪成硬fault。此時,用法Fault狀态寄存器(UFSR,
位址:0xE000_ED2A)中的INVPC位(位偏移:2),或者是INVSTATE位(位偏移:1)置位。
*/
/*無效的中斷傳回(LR 中包含了無效/錯誤的值)會産生用法fault <權威指南PDF119>*/
*(--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);
}</span>
os_cpu_a.asm
<span style="font-size:18px;">;/*********************** (C) COPYRIGHT 2010 Libraworks *************************
;* File Name : os_cpu_a.asm
;* Author : Librae
;* Version : V1.0
;* Date : 06/10/2010
;* Description : μCOS-II asm port for STM32
;*******************************************************************************/
IMPORT OSRunning ; External references
IMPORT OSPrioCur
IMPORT OSPrioHighRdy
IMPORT OSTCBCur
IMPORT OSTCBHighRdy
IMPORT OSIntNesting
IMPORT OSIntExit
IMPORT OSTaskSwHook
EXPORT OSStartHighRdy
EXPORT OSCtxSw
EXPORT OSIntCtxSw
EXPORT OS_CPU_SR_Save
EXPORT OS_CPU_SR_Restore
EXPORT PendSV_Handler
NVIC_INT_CTRL EQU 0xE000ED04 ; 中斷控制寄存器,<權威指南P131>表8.5
NVIC_SYSPRI2 EQU 0xE000ED20 ; 系統異常優先級寄存器陣列 <權威指南P128> 表8.3B
; 0xE000ED20:調試螢幕的優先級配置寄存器位址
; 0xE000ED21:一個沒有使用的位址
<span style="white-space:pre"> </span> ; 0xE000ED22:PendSV的優先級配置寄存器位址
; 0xE000ED23:SysTick的優先級配置寄存器位址
NVIC_PENDSV_PRI EQU 0xFFFF0000 ; 軟體中斷和系統節拍中斷
; (都為最低,0xff).
NVIC_PENDSVSET EQU 0x10000000 ; 觸發軟體中斷的值.<權威指南P131>表8.5
PRESERVE8
AREA |.text|, CODE, READONLY
THUMB
;********************************************************************************************************
; CRITICAL SECTION METHOD 3 FUNCTIONS
;
; Description: Disable/Enable interrupts by preserving the state of interrupts. Generally speaking you
; would store the state of the interrupt disable flag in the local variable 'cpu_sr' and then
; disable interrupts. 'cpu_sr' is allocated in all of uC/OS-II's functions that need to
; disable interrupts. You would restore the interrupt disable state by copying back 'cpu_sr'
; into the CPU's status register.
;
; Prototypes : OS_CPU_SR OS_CPU_SR_Save(void);
; void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
;
;
; Note(s) : 1) These functions are used in general like this:
;
; void Task (void *p_arg)
; {
; #if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
; OS_CPU_SR cpu_sr;
; #endif
;
; :
; :
; OS_ENTER_CRITICAL(); /* cpu_sr = OS_CPU_SaveSR(); */
; :
; :
; OS_EXIT_CRITICAL(); /* OS_CPU_RestoreSR(cpu_sr); */
; :
; :
; }
;********************************************************************************************************
OS_CPU_SR_Save
MRS R0, PRIMASK ;Set prio int mask to mask all (except faults)
;PRIMASK是隻有一個位的寄存器,置1的時候關閉所有的可屏蔽中斷
;隻有NMI和硬fault可以響應,預設值為0,表示沒有關中斷
;MRS,加載特俗功能寄存器的值到Rn,這裡使 R0=PRIMASK
CPSID I ;快速關閉中斷的指令,将PRIMASK置1
BX LR ;BX reg 轉移到由寄存器reg給出的位址,這個指令不儲存下一條指令的PC值到LR寄存器
;reg 的最低位訓示出在轉移後,将進入的狀态是ARM(LSB=0)還是Thumb(LSB=1)
;CM3隻在Thumb中運作,就必須保證reg 的LSB=1,否則fault<權威指南P107>表7.1系統異常清單
;BL Label; 轉移到Label 處對應的位址,并且把轉移前的下條指令位址儲存到LR
OS_CPU_SR_Restore
MSR PRIMASK, R0 ;MSR,存儲Rn 的值到特殊功能寄存器,這裡使PRIMASK=R0
BX LR
;/**************************************************************************************
;* 函數名稱: OSStartHighRdy
;*
;* 功能描述: 使用排程器運作第一個任務
;*
;* 參 數: None
;*
;* 返 回 值: None
;**************************************************************************************/
OSStartHighRdy
LDR R4, =NVIC_SYSPRI2 ;set the PendSV exception priority
;加載立即數NVIC_SYSPRI2到R4,這裡LDR是僞指令
;<權威指南 P128>
;NVIC_SYSPRI2 EQU 0xE000ED20
;0xE000ED20:調試螢幕的優先級配置寄存器位址
;0xE000ED21:一個沒有使用的位址
;0xE000ED22:PendSV的優先級配置寄存器位址
;0xE000ED23:SysTick的優先級配置寄存器位址
LDR R5, =NVIC_PENDSV_PRI ;
STR R5, [R4] ;設定PendSV和SysTick異常的優先級為FF
;這樣之後,就把PendSV,SysTick優先級設定為了FF
;調試螢幕的優先級就設定為了00
MOV R4, #0 ;set the PSP to 0 for initial context switch call
MSR PSP, R4 ;設定線程堆棧指針為0,PSP=R4
;用于在PendSV_Handler函數中判斷系統是否是進行的第一次任務排程
LDR R4, =OSRunning ;OSRunning = TRUE,表示系統開始運作
MOV R5, #1 ;在ucos-ii.h中OSRunning被定義為 OS_EXT BOOLEAN OSRunning; 就是一個unsigned char 類型
STRB R5, [R4] ;STRB:把一個寄存器的低位元組存儲到存儲器中
;
LDR R4, =NVIC_INT_CTRL ;rigger the PendSV exception (causes context switch)
;NVIC_INT_CTRL EQU 0xE000ED04; 中斷控制寄存器,<權威指南P131>表8.5
LDR R5, =NVIC_PENDSVSET ;NVIC_PENDSVSET EQU 0x10000000; 軟體觸發PendSV中斷,在PendSV中進行任務切換
STR R5, [R4] ;
CPSIE I ;enable interrupts at processor level
;開中斷
OSStartHang ;正常情況下不會執行到下面的死循環代碼
B OSStartHang ;should never get here
;/**************************************************************************************
;* 函數名稱: OSCtxSw
;*
;* 功能描述: 任務級上下文切換
;*
;* 參 數: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSCtxSw
PUSH {R4, R5} ;寄存器的PUSH 和POP 操作永遠都是4 位元組對齊的——也就是說他們的位址必須是
;0x4,0x8,0xc,……。這樣一來,R13 的最低兩位被硬線連接配接到0,并且總是讀出0(Read As Zero
;堆棧指針的最低兩位永遠是0,這意味着堆棧總是4位元組對齊的 <權威指南P27>
;這就是為什麼 定義OS_STK為32位的,typedef unsigned int OS_STK;
;PUSH,POP的批量操作<權威指南P47>圖3.10
;不管在寄存器清單中,寄存器的序号是以什麼順序給出的,彙編器都将把它們升
;序排序。然後PUSH 指令按照從大到小的順序依次入棧,POP 則按從小到大的順序依次出棧
LDR R4, =NVIC_INT_CTRL ;觸發PendSV異常 (causes context switch)
LDR R5, =NVIC_PENDSVSET ;
STR R5, [R4]
POP {R4, R5}
BX LR
;/**************************************************************************************
;* 函數名稱: OSIntCtxSw
;*
;* 功能描述: 中斷級任務切換
;*
;* 參 數: None
;*
;* 返 回 值: None
;***************************************************************************************/
OSIntCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;觸發PendSV異常 (causes context switch)
LDR R5, =NVIC_PENDSVSET ;
STR R5, [R4]
POP {R4, R5}
BX LR
NOP
;/**************************************************************************************
;* 函數名稱: OSPendSV
;*
;* 功能描述: OSPendSV is used to cause a context switch.
;*
;* 參 數: None
;*
;* 返 回 值: None
;***************************************************************************************/
PendSV_Handler
CPSID I ; 關中斷,阻止任務切換被打斷
MRS R0, PSP ; 如果在用PSP堆棧,則可以忽略儲存寄存器,參考CM3權威中的雙堆棧-白菜注
CBZ R0, PendSV_Handler_Nosave ; Skip register save the first time
; CBZ 比較,如果結果為0 就轉移
; PendSV_Handler_Nosave這個函數隻在第一次進行任務排程的時候調用一次
; 我的了解是
; 第一次會跳過執行下面的五句代碼,以後每次任務切換的時候都會執行
; 第一次不執行是因為任務堆棧裡面的寄存器資料都是手動填進去的,不需要再儲存一次
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 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
;
PendSV_Handler_Nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook()
BLX R0 <span style="white-space:pre"> </span>; 調用鈎子函數
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
; OS_EXT INT8U OSPrioCur;
LDR R1, =OSPrioHighRdy ; OS_EXT INT8U OSPrioHighRdy;
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
;
; typedef struct os_tcb {OS_STK *OSTCBStkPtr;...}
; OS_EXT OS_TCB *OSTCBCur;
LDR R1, =OSTCBHighRdy ; OS_EXT OS_TCB *OSTCBHighRdy;
LDR R2, [R1] ; R2是最高優先級任務TCB管理塊所在的位址
STR R2, [R0] ; 使OSTCBCur指向優先級最高的任務的TCB管理塊
LDR R0, [R2] ; R0是新任務的堆棧指針SP,SP = OSTCBHighRdy->OSTCBStkPtr;
; OS_TCB結構體定義中第一個位元組是一個堆棧指針,是以這裡R0中存放的就是最高優先級任務的堆棧指針
LDM R0, {R4-R11} ; 從最高優先級的堆棧中恢複R4-R1寄存器的值,其他的寄存器由硬體自動恢複
; LDM:從一片連續的位址空間中加載多個字到若幹寄存器
ADDS R0, R0, #0x20 ; 修改堆棧指針的指向,這裡恢複了8個寄存器,每個寄存器占用4個位元組,一共32個位元組,也就是0X20個
MSR PSP, R0 ; 更新程序堆棧,用新任務的SP加載PSP
ORR LR, LR, #0x04 ; 確定LR位2為1,傳回後使用程序堆棧,因為在第一次排程任務之前系統預設使用的是MSP且線上程模式下運<span style="white-space:pre"> </span>; 行,第一次進入異常的時候LR=FFFFFFF9
<span style="white-space:pre"> </span>; 為了確定第一次進入任務排程之後能正确的傳回線程模式,并使用線程堆棧(SP=PSP),是以或上#0X04,
<span style="white-space:pre"> </span>; 第一次 0XFFFFFFF9 | 0X04 = 0XFFFFFFFD
; 改變處理器的模式也有其它的方式:在異常傳回時,通過修改LR 的位2,也能實作模式切換 <權威指南P42>
; 通過修改CONTROL寄存器的CONTROL[1]的值也可以選擇堆棧指針,0=主堆棧指針(MSP),1=程序堆棧指針(PSP)
; ORR.W Rd, Rn, #imm12 ; Rd = Rn | imm12 按
;<權威指南P139>
;在進入異常服務程式後,LR的值被自動更新為特殊的EXC_RETURN
;這是一個高28位全為1的值,隻有[3:0]的值有特殊含義,如表9.3所示。當異常服務例程把這個
;值送往PC時,就會啟動處理器的中斷傳回序列。因為LR的值是由CM3自動設定的,是以隻要沒有特殊需求,<span style="white-space:pre"> </span>;就不要改動它
;或上#0X04就是置位位2,表示從程序堆棧中做出棧操作,傳回後使用PSP
CPSIE I
BX LR ; 中斷傳回的時候由硬體自動傳回最高優先級堆棧中的其他寄存器的值
; 這裡才是整個PendSV_Handler函數的傳回點,前面的CBZ R0, PendSV_Handler_Nosave隻是做一下判斷
; 是否執行CBZ R0, PendSV_Handler_Nosave後面的五句代碼
; 中斷傳回時,處理器會自動把R0-R3,R12,LR,PC,PSR 彈出堆棧
end </span>
os_cpu.h
在這個頭檔案就是一些聲明,沒有什麼需要注釋的