結合野火的<<RT_Thread內核實現與應用開發實戰>> 進行學習從0開始寫RT_Thread的內核,從而達到對實時系統內核的瞭解和對C語言和彙編的提高。
常用的ARM彙編指令如下表格所示
指令名稱 | 作用 |
---|---|
EQU | 用於給數字常量設置一個符號名,相當於C語言中的define |
AREA | 彙編一個新的代碼段或者數據段 |
SPACE | 分配內存空間 |
PRESERVE8 | 當前文件棧需要按照8字節進行對齊 |
EXPORT | 聲明一個標號爲全局屬性,這個標號可以被外部文件使用 |
DCD | 以字爲單位分配內存,要求爲4字節對齊,並且要求初始化這些內存 |
PROC | 定義子程序,與ENDP進行成對使用。ENDP表明該子程序結束 |
WEAK | 弱定義,如果外部文件聲明瞭一個標號,則優先使用外部文件定義好的標號。(WEAK不爲ARM指令,而是編譯器指令) |
IMPORT | 聲明標號來自於外部文件,與C語言中的 extern關鍵字比較相類似 |
B | 轉跳到一個標號 |
ALIGN | (不爲ARM指令,爲編譯器指令) 用於對指令或者數據的存放地址進行對齊,一般需要跟上一個立即數,默認對齊是4字節對齊。 |
END | 到達文件的尾部,文件結束 |
IF, ELSE, ENDIF | 彙編條件分支語句,與C語言中的if else類似 |
MRS | 加載特殊功能寄存器的值到通用寄存器 |
MSR | 存儲在通用寄存器的值加載到特殊功能寄存器 |
CBZ | 比較,結果爲0就進行轉移 |
CBNZ | 比較,結果爲非0則轉移 |
LDR | 從存儲器中加載字到一個寄存器中 |
LDR[僞指令] | 加載一個立即數或者一個地址值到一個寄存器。例如,LDR Rd,=lable,如果lable是立即數,那麼Rd等於立即數;如果lable是一個標識符(比如指針),那麼存入Rd的就是lable標識符的地址。 |
LDRH | 從存儲器中加載半字到一個寄存器中 |
LDRB | 從存儲器中加載字節到一個寄存器中 |
STR | 把一個存儲器按字節存儲到存儲器中 |
STRB | 把一個寄存器的低字節存儲到存儲器中 |
LDMIA | 加載多個字,並且在加載後自增基址寄存器 |
STMIA | 存儲多個字,並且在存儲後自增基址寄存器 |
ORR | 按位或 |
BX | 直接跳轉到由寄存器給定的地址 |
BL | 跳轉到標號對應的地址,並且把跳轉前的下一條指令地址保存到LR |
BLX | 跳轉到由寄存器 REG 給出的的地址,並根據 REG 的 LSB 切換處理器狀態 , 還 要 把 轉 移 前 的 下 條 指 令 地 址 保 存 到 LR 。 ARM(LSB=0) ,Thumb(LSB=1)。 CM3 只在 Thumb 中運行,就必須保證 reg 的 LSB=1,否則會報錯。 |
CPSID I | 關中斷 |
CPSID F | 關異常 |
CPSIE I | 開中斷 |
CPSIE F | 開異常 |
LDMFD |
主要用於中斷對現場環境的恢復。 指令格式 Rn 爲 SP(堆棧指針) 後綴 "!"表明將最後的地址回寫到SP中 |
STMFD | 主要用於中斷對現場環境的保護。STMFD寫在後面的寄存器先入棧 |
;*************************************************************************
; 全局變量
;*************************************************************************
; IMPORT聲明標號來自外部文件 跟C語言中的EXTERN關鍵字類似
; 以下3個變量在C語言文件中定義,此處進行聲明用於彙編的調用
IMPORT rt_thread_switch_interrupt_flag ;PendSV中斷服務函數執行標誌;
IMPORT rt_interrupt_from_thread ;上一個線程的棧的SP的指針
IMPORT rt_interrupt_to_thread ;下一個運行線程的棧的sp的指針
;*************************************************************************
; 常量
;*************************************************************************
;-------------------------------------------------------------------------
;有關內核外設寄存器定義可參考官方文檔:STM32F10xxx Cortex-M3 programming manual
;系統控制塊外設SCB地址範圍:0xE000ED00-0xE000ED3F
;-------------------------------------------------------------------------
;EQU 是給常量取一個名字 相當於C語言中的define;
SCB_VTOR EQU 0xE000ED08 ; 向量表偏移寄存器
NVIC_INT_CTRL EQU 0xE000ED04 ; 中斷控制狀態寄存器
NVIC_SYSPRI2 EQU 0xE000ED20 ; 系統優先級寄存器(2)
NVIC_PENDSV_PRI EQU 0x00FF0000 ; PendSV 優先級值 (lowest)
NVIC_PENDSVSET EQU 0x10000000 ; 觸發PendSV exception的值
;*************************************************************************
; 彙編代碼產生指令
;*************************************************************************
;當新建一個彙編文件寫代碼時,必須包括類似的指令。
;AREA表示彙編一個新的數據段或者代碼段
;.text表明段名,如果段名不是以字母開頭,而是以其他符號開頭,則需在段名兩段加上"|"
;CODE 表示僞代碼; READONLY表示只讀; ALIGN=2表示當前文件指令要按2^2字節對齊
;THUMB 表示THUMB指令代碼;REQUIRE8和PRESERVE8表明當前文件棧按照8字節進行對齊
AREA |.text|, CODE, READONLY, ALIGN=2
THUMB
REQUIRE8
PRESERVE8
;/*
; *-----------------------------------------------------------------------
; * 函數原型:void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * 該函數用於開啓第一次線程切換
; *-----------------------------------------------------------------------
; */
; PROC用於定義子程序 與EDNP成對使用
rt_hw_context_switch_to PROC
; 導出rt_hw_context_switch_to,讓其具有全局屬性,可以在C文件調用此函數
EXPORT rt_hw_context_switch_to
; 設置rt_interrupt_to_thread的值
LDR r1, =rt_interrupt_to_thread ;將rt_interrupt_to_thread的地址加載到r1
STR r0, [r1] ;將r0的值存儲到rt_interrupt_to_thread 即sp
; 設置rt_interrupt_from_thread的值爲0,表示啓動第一次線程切換 並沒有上一個運行線程
LDR r1, =rt_interrupt_from_thread ;將rt_interrupt_from_thread的地址加載到r1
MOV r0, #0x0 ;配置r0等於0
STR r0, [r1] ;將r0的值存儲到rt_interrupt_from_thread
; 設置中斷標誌位rt_thread_switch_interrupt_flag的值爲1 當執行PendSVC_Handlder時 會被清 0
LDR r1, =rt_thread_switch_interrupt_flag ;將rt_thread_switch_interrupt_flag的地址加載到r1
MOV r0, #1 ;配置r0等於1
STR r0, [r1] ;將r0的值存儲到rt_thread_switch_interrupt_flag
; 設置 PendSV 異常的優先級 設置爲最低 實際線程的切換就是在PendSV中斷中進行
LDR r0, =NVIC_SYSPRI2
LDR r1, =NVIC_PENDSV_PRI
LDR.W r2, [r0,#0x00] ; 讀
ORR r1,r1,r2 ; 改
STR r1, [r0] ; 寫
; 觸發 PendSV 異常 (產生上下文切換)
LDR r0, =NVIC_INT_CTRL
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
; 開中斷 CPSIE F 開異常 CPSIE I 開中斷
CPSIE F
CPSIE I
ENDP ;表明一個子程序的結束
;/*
; *-----------------------------------------------------------------------
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; *-----------------------------------------------------------------------
; */
;rt_hw_context_switch_interrupt
;EXPORT rt_hw_context_switch_interrupt
; 用於產生上下文 當一個彙編函數被C文件調用時,如果有兩個形參,則會將形參傳入CPU寄存器 r0 r1中。
rt_hw_context_switch PROC
EXPORT rt_hw_context_switch
; 設置中斷標誌位rt_thread_switch_interrupt_flag爲1
LDR r2, =rt_thread_switch_interrupt_flag ; 加載rt_thread_switch_interrupt_flag的地址到r2
LDR r3, [r2] ; 加載rt_thread_switch_interrupt_flag的值到r3
CMP r3, #1 ; r3與1比較,相等則執行BEQ指令,否則不執行
BEQ _reswitch
MOV r3, #1 ; 設置r3的值爲1
STR r3, [r2] ; 將r3的值存儲到rt_thread_switch_interrupt_flag,即置1
; 設置rt_interrupt_from_thread的值
LDR r2, =rt_interrupt_from_thread ; 加載rt_interrupt_from_thread的地址到r2
STR r0, [r2] ; 存儲r0的值到rt_interrupt_from_thread,即上一個線程棧指針sp的指針
_reswitch
; 設置rt_interrupt_to_thread的值
LDR r2, =rt_interrupt_to_thread ; 加載rt_interrupt_from_thread的地址到r2
STR r1, [r2] ; 存儲r1的值到rt_interrupt_from_thread,即下一個線程棧指針sp的指針
; 觸發PendSV異常,實現上下文切換
LDR r0, =NVIC_INT_CTRL
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
; 子程序返回
BX LR
; 子程序結束
ENDP
;/*
; *-----------------------------------------------------------------------
; * void PendSV_Handler(void);
; * r0 --> switch from thread stack
; * r1 --> switch to thread stack
; * psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
; *-----------------------------------------------------------------------
; */
; 這纔是真正的實現線程上下文切換的函數
PendSV_Handler PROC
EXPORT PendSV_Handler
; 失能中斷,爲了保護上下文切換不被中斷
MRS r2, PRIMASK ;加載特殊功能寄存器的值到通用寄存器 PRIMASK是中斷屏蔽寄存器
CPSID I ;關中斷
; 獲取中斷標誌位,判斷是否爲0 爲0則退出PendSV_Handler函數
LDR r0, =rt_thread_switch_interrupt_flag ; 加載rt_thread_switch_interrupt_flag的地址到r0
LDR r1, [r0] ; 加載rt_thread_switch_interrupt_flag的值到r1
CBZ r1, pendsv_exit ; 判斷r1是否爲0,爲0則跳轉到pendsv_exit
; r1不爲0則清0
MOV r1, #0x00
STR r1, [r0] ; 將r1的值存儲到rt_thread_switch_interrupt_flag,即清0
; 判斷rt_interrupt_from_thread的值是否爲0 用於判斷是否爲第一次線程切換
LDR r0, =rt_interrupt_from_thread ; 加載rt_interrupt_from_thread的地址到r0
LDR r1, [r0] ; 加載rt_interrupt_from_thread的值到r1
CBZ r1, switch_to_thread ; 判斷r1是否爲0,爲0則跳轉到switch_to_thread
; 第一次線程切換時rt_interrupt_from_thread肯定爲0,則跳轉到switch_to_thread
; ========================== 上文保存 ==============================
; 當進入PendSVC Handler時,上一個線程運行的環境即:
; xPSR,PC(線程入口地址),R14,R12,R3,R2,R1,R0(線程的形參)
; 這些CPU寄存器的值會自動保存到線程的棧中,剩下的r4~r11需要手動保存
MRS r1, psp ; 獲取線程棧指針到r1 特殊寄存器的值保存在通用寄存器中
STMFD r1!, {r4 - r11} ;將CPU寄存器r4~r11的值存儲到r1指向的地址(每操作一次地址將遞減一次)
LDR r0, [r0] ; 加載r0指向值到r0,即r0=rt_interrupt_from_thread
STR r1, [r0] ; 將r1的值存儲到r0,即更新線程棧sp
; ========================== 下文切換 ==============================
; 下文切換的實質 是將接下來要運行的線程棧中的內容加載到CPU寄存器 更改PC指針和PSP指針,從而實現程序的轉跳
switch_to_thread
LDR r1, =rt_interrupt_to_thread ; 加載rt_interrupt_to_thread的地址到r1
; rt_interrupt_to_thread是一個全局變量,裏面存的是線程棧指針SP的指針
LDR r1, [r1] ; 加載rt_interrupt_to_thread的值到r1,即sp指針的指針
LDR r1, [r1] ; 加載rt_interrupt_to_thread的值到r1,即sp
LDMFD r1!, {r4 - r11} ;將線程棧指針r1(操作之前先遞減)指向的內容加載到CPU寄存器r4~r11
MSR psp, r1 ;將線程棧指針更新到PSP MSR通用寄存器的值保存到特殊寄存器
pendsv_exit
; 恢復中斷
MSR PRIMASK, r2
ORR lr, lr, #0x04 ; 確保異常返回使用的堆棧指針是PSP,即LR寄存器的位2要爲1
BX lr ; 異常返回,這個時候任務堆棧中的剩下內容將會自動加載到xPSR,PC(任務入口地址),R14,R12,R3,R2,R1,R0(任務的形參)
; 同時PSP的值也將更新,即指向任務堆棧的棧頂。在ARMC3中,堆是由高地址向低地址生長的。
; PendSV_Handler 子程序結束
ENDP
ALIGN 4
END ;彙編文件結束,每一個彙編文件都需要一個END
我服了csdn了。好好的代碼格式貼上去,又亂格式了。