永遠不要小看不起眼的東西,哪怕是短短的一行代碼!編程
(某些圖片分辨率過大顯示不清楚,保存到本地或拖動可放大)。api
不少例程將vTaskStartScheduler函數做爲main執行的最後一行代碼,由於執行了vTaskStartScheduler後主權便交給了freertos調度器,程序永遠不會在這個函數中獲得返回。數組
vTaskStartScheduler主要動做有:建立IDLE任務,初始化systick等重要寄存器,手動觸發中斷從新配置MSP並返回到handle模式執行第一個任務。如圖展現源碼主要流程:函數
(調度器啓動流程)this
流程分析:spa
vTaskStartScheduler函數中建立了名爲IDLE的任務,這個任務的功能先無論它,先饒它不死等對源碼逐個擊破後它就會不攻自破。設計
某些不可重入代碼段或定義的一些全局變量,在使用它們以前每每先進入臨界區保護下,以避免突發事件產生時破壞當前正要處理的數據。好比一些全局變量,若是在中斷裏或多個任務中都被使用到,當任務正在處理某個全局數組的數據時,此時中斷髮生並更新了數組中的數據,這樣中斷返回後就會產生一些不可預料的結果。調試
記得在實習那會寫過很多這樣的錯誤代碼,沒有考慮代碼能不能重入,結果只能害人害己啊~。code
在task.c中有一些全局變量,freertos爲了保護這些變量數據在處理時不被異常破壞而使用了中斷屏蔽,不過這種操做並無將全部可屏蔽中斷一股腦所有殺死:freertos提供了「可管理的中斷範圍」。以某個中斷優先級爲閾值,優先級低於(或等於)此分界線的中斷將所有屏蔽,較高的中斷則不受影響。固然不會白白讓「高等」優先級執行的,代價即是freertos不會讓高優先級的中斷參與任何api的調用。因此「高等」優先級是否觸發都不會影響freertos的正常運行。事件
事實上可以有如此方便的屏蔽操做還要歸功於CM3/CM4中NVIC的強大。寫到這裏,終於引出今天的主角了~。NVIC :嵌套向量中斷控制器,是CM3/CM4內核水平上搭載了一個異常響應系統,支持爲數衆多的系統異常和外部中斷。不只如此,NVIC擁有者更多精巧的設計,BASEPRI寄存器即是其中之一,屏蔽中斷所使用的閾值就存儲在其中,屏蔽時將須要屏蔽中斷的最高優先級值填入寄存器便能達到效果。若是向BASEPRI寫0則意味着中止掩蔽任何中斷。
在配置頭文件中能夠看到閾值的身影:configMAX_SYSCALL_INTERRUPT_PRIORITY
freertos執行中斷屏蔽的源碼:
mov r1, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r1
CM3中使用了8位寄存器來表達中斷優先級,因此原則上,CM3支持256級可編程優先級和3個固定優先級(復位,NMI ,硬 fault)。
可是,絕大多數 CM3 芯片都會精簡設計,對優先級有效位數進行裁剪以達到減小優先級數的目的,不過不管怎樣裁剪,有效位都是在高位開始算起。
注:CM3 容許的最少使用位數爲 3 個位,亦即至少要支持 8 級優先級。增長位數將增長更多成本和功耗。
優先級的數值越小,則優先級越高。
舉個例子,使用了3位有效位表達優先級的寄存器:
(使用了3位有效位表達優先級)
對有效位寫1能讀回1,無效位讀回0,上圖寄存器中,若對它寫入0xFF則讀回0xE0,可使用讀回的方法判斷芯片支持多少級中斷。
對比下3位和4位優先級情況:
(3-4優先級對比)
上圖仍是來自寶典,有圖截取真爽。很明顯4位要比3位表示的優先級範圍更細緻些,固然這是廢話,不過爲何必定要將有效位放在高位,而不放在低位更直觀。這也徹底是爲了開發者考慮,高位能夠簡化程序的跨器件移植:
假設一個程序要從4位向3位上移植(3位向4位上移植確定是沒問題的,由於4位範圍更廣),在沒有刻意更改優先級的狀況下如圖:
(MSB減小有效位情況)
移植後優先級有效位減小,原來相鄰的兩個優先級被置爲一個,消失的中斷優先級被拉高,不過這些改變將不會帶來致命錯誤。
若是使用低位來表明有效位的話將不會這樣幸運了:
(LSB減小有效位情況)
紅色區域出現移植後超過7的優先級反而升高,這對於以前的程序將會帶來不可預測的錯誤。
最後一個函數xPortStartScheduler中有一段斷言宏打開時執行的代碼,調試階段確定要將斷言配置打開,這樣便於程序在調試階段及時發現異常。程序段以下:
乍一看徹底不知道執行這段程想要達到的目的,想要弄清楚以前首先要搞懂優先級分組的原則。
關於優先級分組的信息,引用寶典的原話:「爲了使搶佔機能變得更可控,CM3 還把 256 級優先級按位分紅高低兩段,分別是搶佔優先級和亞優先級。 NVIC 中有一個寄存器是「應用程序中斷及復位控制寄存器」,它裏面有一個位段名爲「優先級組」。該位段的值對每個優先級可配置的異常都有影響——把其優先級分爲個位段:MSB 所在的位段(左邊的)對應搶佔優先級,而 LSB 所在的位段(右邊的)對應亞優先級」。
(分組位與分組優先級對應表)
搶佔優先級決定了搶佔行爲:當系統正在響應某異常 L 時,若是來了搶佔優先級更高的異常 H,則 H 能夠搶佔 L。亞優先級則處理「內務」:當搶佔優先級相同的異常有不止一個懸起時,就優先響應亞優先級最高的異常。這種優先級分組規定:亞優先級至少是 1 個位。所以搶佔優先級最可能是 7 個位,形成了最多隻有 128 級搶佔的現象。可是 CM3 容許從比特 7 處分組,此時全部的位都表達亞優先級,沒有任何位表達搶佔優先級,於是全部優先級可編程的異常之間就不會發生搶佔——至關於在它們之中除能了CM3 的中斷嵌套機制。
注:分組位置能夠在無效位,在任意無效位的效果都同樣,它們都捨去了亞優先級。
那麼瞭解優先級分組以後,再去分析上面斷言宏的主要動做,先向某個外部中斷優先級配置寄存器寫0xFF再讀回,主要判斷芯片有多少優先級有效位,而後根據有效位左移依次將最大分組數7減1,這樣便獲得了可以劃分搶佔優先級爲最大數的分組最大位置,例如開發板優先級有效位爲3(優先級寄存器7,6,5位),優先級最多能被分爲8組,可是可以將其分爲8組的分組位置爲4,3,2,1,0。這段程序只取最大值因此ulMaxPRIGROUPValue值獲得爲4。
ulMaxPRIGROUPValue值將會在freertos提供的中斷api中被使用到,這些api中都會首先執行vPortValidateInterruptPriority()函數。這個函數中有兩個斷言宏,第一個檢查了當前的中斷優先級是否低於可屏蔽的中斷(上文提到高於可屏蔽的優先級不容許調用freertos的中斷api);第二個檢查NVIC中設置的分組值是否大於ulMaxPRIGROUPValue,若是大於則意味着存在着主優先級和亞優先級,freertos不容許存在亞優先級,不然斷言宏伺候,貼下注釋原話:Priority grouping: The interrupt controller (NVIC) allows the bits that define each interrupt's priority to be split between bits that define the interrupt's pre-emption priority bits and bits that define the interrupt's sub-priority. For simplicity all bits must be defined to be pre-emption priority bits. The following assertion will fail if this is not the case (if some bits represent a sub-priority).
按註釋的描述是爲了簡化而不去設置亞優先級。
在調度器啓動以前還進行了:
1. 配置了兩個中斷PendSV 和SysTick的優先級,配置頭文件中的宏
configKERNEL_INTERRUPT_PRIORITY表示它們的中斷級別,configKERNEL_INTERRUPT_PRIORITY數值必定要大於或等於宏configMAX_SYSCALL_INTERRUPT_PRIORITY的值,或者說PendSV 和SysTick中斷優先級必定要在可屏蔽中斷範圍內,若相反的話可就亂成一鍋粥了。
2. 開啓freertos的心臟systick,填入計數值來決定systick中斷頻率,也是任務調度頻率或說是時間片長度。
3. 跳入prvPortStartFirstTask函數:
ldr r0, NVIC_VTABLE_R // NVIC_VTABLE_R: 0xE000ED08 ,向量表偏移量寄存器的地址,須要現將向量表重定向
ldr r0, [r0]
ldr r0, [r0] //取出MSP新地址
msr msp, r0 //從新配置MSP地址
cpsie i //開中斷
dsb
isb
svc #0 //觸發SVC中斷
至此,在進入SVC中斷以前,freertos開啓調度器代碼流程已執行完畢,等待SVC中斷處理完成後freertos便進入正軌。奔跑吧!fucking source code