小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

干貨 | FreeRTOS 學(xué)習(xí)筆記——FreeRTOS的軟件結(jié)構(gòu)

 知芯世界 2020-10-28

EEWorld

電子資訊 犀利解讀 

技術(shù)干貨 每日更新

     

 

我是從 FreeRTOS 官方的文檔《Mastering the FreeRTOS Real Time Kernel》開(kāi)始學(xué)習(xí)它的,代碼和參考手冊(cè)都用的 9.0.0 版本。我還沒(méi)有用過(guò)其它的 RTOS, 所以也無(wú)意評(píng)價(jià)它的優(yōu)缺點(diǎn)。當(dāng)然,它無(wú)疑是一個(gè)優(yōu)秀而且很流行的嵌入式 RTOS. 要上手也很快,本篇我就記錄一下如何將 FreeRTOS 的代碼加到已有的工程里面,作為一個(gè)備忘參考(網(wǎng)上也能隨便搜到很多關(guān)于怎么使用 FreeRTOS, 怎么創(chuàng)建任務(wù)等等的文章。在我的學(xué)習(xí)筆記系列里面這部分內(nèi)容倒不是首要的,因?yàn)槲蚁敕窒淼氖菑奈覍?duì) FreeRTOS 代碼的分析和實(shí)踐了解到它是怎么工作的,帶來(lái)了什么好處)。

文件組成

 

從 freertos.org 網(wǎng)站上可以找到下載源代碼包的鏈接。以我用的 v9.0.0 代碼為參考,它的根目錄下有6個(gè)C源文件:


croutine.c
event_groups.c
list.c
queue.c
tasks.c
timers.c
  

這些文件是 FreeRTOS 的核心代碼,有的還是可選的。然后是兩個(gè)子目錄:include 和 portable. include 目錄下的頭文件包含了系統(tǒng)核心用到的宏定義,以及編程用到的 API 數(shù)據(jù)結(jié)構(gòu)、函數(shù)原型等。在 portable 目錄下的文件提供一些會(huì)被 FreeRTOS 核心代碼調(diào)用的函數(shù),這些函數(shù)的實(shí)現(xiàn)與運(yùn)行環(huán)境有關(guān)系,或者是存在多種實(shí)現(xiàn)方式。比如,因?yàn)?FreeRTOS 支持多種硬件平臺(tái),與平臺(tái)實(shí)現(xiàn)密切方式相關(guān)的代碼(例如匯編語(yǔ)言編寫(xiě)的函數(shù))就放到編譯器、CPU類(lèi)型對(duì)應(yīng)的子目錄下。portable 目錄下的文件不是系統(tǒng)核心,除了 FreeRTOS 代碼包里提供的這些文件,用戶(hù)還可以根據(jù)自己的環(huán)境編寫(xiě)函數(shù)。


 

生成目標(biāo)模塊的文件里面,按照能運(yùn)行起來(lái)的最小需求,tasks.c 和 list.c 必不可少(因?yàn)?FreeRTOS 的內(nèi)部數(shù)據(jù)結(jié)構(gòu)用了鏈表,所以需要 list.c)。此外還必須有一個(gè)硬件實(shí)現(xiàn)相關(guān)的模塊,例如我用于 STM32 就選用了 /portable/GCC/ARM_CM3/port.c

在自己寫(xiě)的程序源文件里面,至少需要包含兩個(gè)頭文件:FreeRTOS.h 和 task.h . 如果用了其它可選的功能 API,就要將相應(yīng)的頭文件包含進(jìn)來(lái),具體看手冊(cè)說(shuō)明。如果編譯時(shí)出現(xiàn)警告或錯(cuò)誤說(shuō)什么什么沒(méi)有定義,就先檢查一下是否缺了頭文件。

配置 (可選項(xiàng))

FreeRTOS 的靈活性很大,許多功能不需要?jiǎng)t可裁減掉。為了減少對(duì)內(nèi)存的浪費(fèi),某些數(shù)據(jù)結(jié)構(gòu)長(zhǎng)度、數(shù)據(jù)位寬也是可以指定的。因此 FreeRTOS 要求用戶(hù)編寫(xiě)一個(gè) FreeRTOSConfig.h 頭文件,定義若干參數(shù),對(duì)系統(tǒng)的功能和特性進(jìn)行配置。
  

初次使用 FreeRTOS, 也不必自己書(shū)寫(xiě)這個(gè)配置文件,只須從代碼包的 Demo 目錄下找一個(gè)和自己的環(huán)境相近的工程,將其中的 FreeRTOSConfig.h 拷貝出來(lái),根據(jù)需要稍微調(diào)整一下就可以了。如果用的是 STM32, 還可以從 ST 官方的(比如 CubeF4 里面) FreeRTOS 例子工程里面借用一個(gè)。

有幾個(gè)與系統(tǒng)資源關(guān)系密切的宏定義要留意一下:

#define configCPU_CLOCK_HZ               ( SystemCoreClock )
#define configTICK_RATE_HZ                ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES            ( 5 )
#define configMINIMAL_STACK_SIZE   ( ( unsigned short ) 130 )
#define configTOTAL_HEAP_SIZE          ( ( size_t ) ( 75 * 1024 ) )
  

configCPU_CLOCK_HZ 是 CPU 的時(shí)鐘頻率,F(xiàn)reeRTOS 需要知道它才能正確配置硬件定時(shí)器。configTICK_RATE_HZ 是指定 FreeRTOS 用的時(shí)鐘中斷頻率,也就是決定所用時(shí)間間隔的單位。configMAX_PRIORITIES 指定存在多少種任務(wù)優(yōu)先級(jí),在夠用的前提下應(yīng)盡量少。configMINIMAL_STACK_SIZE 是給任務(wù)分配堆棧的最小值,F(xiàn)reeRTOS 會(huì)按這個(gè)值給 Idle 任務(wù)分配堆棧。configTOTAL_HEAP_SIZE 是決定分配多少內(nèi)存給 FreeRTOS 自己管理,在動(dòng)態(tài)創(chuàng)建任務(wù)和其它一些數(shù)據(jù)結(jié)構(gòu)的時(shí)候使用,見(jiàn)下一節(jié)說(shuō)明。
  

務(wù)必注意決定任務(wù)調(diào)度特點(diǎn)的兩個(gè)宏定義:
  

configUSE_PREEMPITON 指定是否使用搶占式任務(wù)調(diào)度——即是否運(yùn)行中的任務(wù)不主動(dòng)交出控制權(quán)也允許被調(diào)度。
  

configUSE_TIME_SLICING 指定是否針對(duì)同一優(yōu)先級(jí)下的任務(wù)使用時(shí)間片調(diào)度(在搶占式調(diào)度前提下)。
  

有些可選的功能需要通過(guò)宏指定開(kāi)啟,例如 configUSE_QUEUE_SETS, configUSE_TIMERS, configUSE_COUNTING_SEMAPHORES, configUSE_MUTEXES,

configUSE_TASK_NOTIFICATIONS 等。沒(méi)有指定則 FreeRTOS 會(huì)使用默認(rèn)值,需要留意手冊(cè)中的說(shuō)明。
  

也可以直接察看 FreeRTOS.h 中的條件編譯語(yǔ)句對(duì)未定義 configXXX_XXXX 宏的處理,大多數(shù)是默認(rèn)定義為0, 也有少數(shù)定義為1的。另有幾不提供默認(rèn)值的宏若沒(méi)有在 FreeRTOSConfig.h 中定義,就直接報(bào)錯(cuò)了。

內(nèi)存管理

前面我提到過(guò),F(xiàn)reeRTOS 的每個(gè)任務(wù)都需要分配內(nèi)存作為堆棧和TCB數(shù)據(jù)結(jié)構(gòu)。有兩種內(nèi)存分配方式——靜態(tài)分配,在編譯時(shí)決定;和動(dòng)態(tài)分配,在運(yùn)行時(shí)決定。通常我們會(huì)使用動(dòng)態(tài)分配內(nèi)存,這時(shí)候要讓 FreeRTOS 核心能夠分次申請(qǐng)一定大小的內(nèi)存,類(lèi)似于提供C語(yǔ)言的 malloc() free() 機(jī)制。
  

那么為什么 FreeRTOS 不直接使用C標(biāo)準(zhǔn)庫(kù)里面的 malloc() 和 free() 函數(shù)呢?文檔里面提到幾方面的考慮:(1)多任務(wù)調(diào)用, C庫(kù)函數(shù)是否可重入的問(wèn)題。(2)實(shí)現(xiàn)的復(fù)雜度和代碼效率。(3)增加程序鏈接、調(diào)試的復(fù)雜度。
  

FreeRTOS 使用的內(nèi)存分配和釋放函數(shù)是 pvPortMalloc() 及 vPortFree(). 在 /portable/MemMang 目錄下面有幾個(gè)版本的文件,各自實(shí)現(xiàn)了這兩個(gè)函數(shù):


  heap_1.c 只分配內(nèi)存,不可釋放。用在只創(chuàng)建而不銷(xiāo)毀的場(chǎng)合,不存在內(nèi)存碎片問(wèn)題。
  heap_2.c 可以釋放分配的內(nèi)存,使用最適匹配原則。
  heap_3.c 借助標(biāo)準(zhǔn)庫(kù)的 malloc() 和 free() 函數(shù)管理內(nèi)存,額外增加代碼來(lái)避免重入。
  heap_4.c 是在 heap_2 基礎(chǔ)上的改進(jìn),可以將鄰接的空閑內(nèi)存塊合并。
  heap_5.c 可以管理地址不連續(xù)的幾塊SRAM區(qū)域。
  

對(duì)于 heap_1.c, heap_2.c, heap_4.c 三種實(shí)現(xiàn)都需要配置文件中定義好 configTOTAL_HEAP_SIZE 這個(gè)宏,即在編譯時(shí)分配一大塊固定位置的內(nèi)存,給 FreeRTOS 作為堆(Heap)使用。所有任務(wù)申請(qǐng)的內(nèi)存都出自這一塊預(yù)定的內(nèi)存里面。heap_3.c 是由 C 庫(kù)函數(shù)負(fù)責(zé)內(nèi)存管理,就不需要這個(gè)宏定義了。而針對(duì) heap_5.c 顯然這一描述不夠,需要提供每塊 SRAM 區(qū)域分別的地址和大小的信息,所以它要求用戶(hù)調(diào)用 vPortDefineHeapRegions() 函數(shù)來(lái)指定內(nèi)存詳情。


 
如果對(duì)這幾個(gè)還不滿(mǎn)意,當(dāng)然也可以自己寫(xiě)內(nèi)存管理的函數(shù)了(這是 portable 的部分,結(jié)合具體需求定制是它的初衷)。

其實(shí)對(duì)于小的很確定的應(yīng)用,比如就創(chuàng)建一兩個(gè)任務(wù)來(lái)跑,動(dòng)態(tài)分配內(nèi)存都不是必須的。只要在創(chuàng)建任務(wù)的時(shí)候麻煩一點(diǎn),手動(dòng)指定作為堆棧和TCB的內(nèi)存地址,就一樣能工作。這便是 FreeRTOS 支持的靜態(tài)創(chuàng)建方式。從 9.0.0 版本開(kāi)始,已經(jīng)可以完全去掉堆內(nèi)存管理的代碼。要在配置文件中定義 configSUPPORT_STATIC_ALLOCATION 宏值為1才能支持使用靜態(tài)分配的內(nèi)存創(chuàng)建對(duì)象。同樣還有一個(gè)宏 configSUPPORT_DYNAMIC_ALLOCATION (默認(rèn)值為1)是決定是否支持動(dòng)態(tài)分配內(nèi)存的。
  

用靜態(tài)分配內(nèi)存創(chuàng)建對(duì)象,要調(diào)用函數(shù)名以 Static 結(jié)尾的 API. 例如 xQueueCreateStatic(), 其原型如下:


QueueHandle_t xQueueCreateStatic(
                              UBaseType_t uxQueueLength,
                              UBaseType_t uxItemSize,
                              uint8_t *pucQueueStorageBuffer,
                              StaticQueue_t *pxQueueBuffer
                          );
  

而使用動(dòng)態(tài)分配內(nèi)存的常規(guī)版本 API 函數(shù)是 xQueueCreate():


QueueHandle_t xQueueCreate(
                              UBaseType_t uxQueueLength,
                              UBaseType_t uxItemSize
                          );
  

可見(jiàn)靜態(tài)內(nèi)存方式需要用戶(hù)以參數(shù)形式提供對(duì)象使用的內(nèi)存地址,可能還不止一塊。使用動(dòng)態(tài)內(nèi)存就直接按需要從堆中分配了,程序?qū)懫饋?lái)簡(jiǎn)單。
  

此外,若支持靜態(tài)內(nèi)存的選項(xiàng)打開(kāi),F(xiàn)reeRTOS 還要求用戶(hù)提供一個(gè) vApplicationGetIdleTaskMemory() 函數(shù),用來(lái)給系統(tǒng)獲取 Idle 任務(wù)的堆棧內(nèi)存地址、TCB內(nèi)存地址和堆棧大小。因?yàn)橄到y(tǒng)會(huì)用 xTaskCreateStatic() 來(lái)創(chuàng)建 Idle 任務(wù)。同樣的原因,在使用 Timer 功能時(shí),要提供 vApplicationGetTimerTaskMemory() 函數(shù)。

系統(tǒng)自帶任務(wù)

FreeRTOS 總有一個(gè)任務(wù)在運(yùn)行狀態(tài),若當(dāng)前無(wú)事可做,那么就讓一個(gè)代表“系統(tǒng)空閑”的任務(wù)運(yùn)行——這就是 Idle 任務(wù)。當(dāng) FreeRTOS 調(diào)度器啟動(dòng)時(shí),會(huì)隱含地創(chuàng)建這個(gè)任務(wù)。Idle 任務(wù)具有最低的優(yōu)先級(jí),它必須要讓位于任何更重要的任務(wù),除非它們都阻塞或掛起了。
  

Idle 任務(wù)的一個(gè)用途是管理硬件的低功耗模式,我將另開(kāi)一篇來(lái)討論。
  

除了 Idle 任務(wù),F(xiàn)reeRTOS 還帶有一個(gè) Timer 任務(wù),也就是 Daemon Task, 用來(lái)輔助完成一些功能,例如軟件定時(shí)器。這個(gè)任務(wù)是可選的,在配置選項(xiàng) configUSE_TIMERS 為1時(shí)才打開(kāi)。它的代碼在 timers.c 中。Timer 任務(wù)的作用是處理跟時(shí)間有關(guān)的事務(wù),但這些事務(wù)又不能放到時(shí)鐘中斷 ISR 里去處理(并非響應(yīng)硬件請(qǐng)求,沒(méi)有那么高實(shí)時(shí)性要求), 還要接受調(diào)度器管理。
  

例如,創(chuàng)建軟件定時(shí)器的時(shí)候指定一個(gè)回調(diào)函數(shù):


TimerHandle_t xTimerCreate(const char * const pcTimerName,
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction
                        );


在定的時(shí)間到以后,pxCallbackFunction() 將被 Timer 任務(wù)調(diào)用,即是在 Timer 任務(wù)的上下文中執(zhí)行,而不是創(chuàng)建這個(gè)軟件定時(shí)器的任務(wù)上下文中執(zhí)行。
  

Timer 任務(wù)另一功能是執(zhí)行 xTimerPendFunctionCall(), xTimerPendFunctionCallFromISR() 指定的函數(shù)調(diào)用,如我在第(5)篇筆記中寫(xiě)的。這些被指定的函數(shù),連同軟件定時(shí)器的回調(diào)函數(shù),是一個(gè)個(gè)順序執(zhí)行的,都使用 Timer 任務(wù)的堆棧。

系統(tǒng)使用的中斷

為了實(shí)現(xiàn)與時(shí)間有關(guān)的功能,硬件定時(shí)器是必須要用到的。FreeRTOS 有一個(gè)函數(shù) xTaskIncrementTick() 是給硬件定時(shí)器的 ISR 調(diào)用的。因?yàn)楹诵拇a與硬件平臺(tái)無(wú)關(guān),無(wú)法寫(xiě)成中斷形式,故采用這種方式。實(shí)際上也相當(dāng)于系統(tǒng)使用了一個(gè)定時(shí)器中斷,不過(guò) ISR 屬于 portable 層面,用戶(hù)可以自由設(shè)計(jì),這個(gè)中斷也不一定 FreeRTOS 獨(dú)占,例如可以再做一些硬件方面的操作,只要定期及時(shí)調(diào)用 xTaskIncrementTick() 就可以了。為了加深理解,可以看看不同硬件平臺(tái)的 port.c 中如何處理。

FreeRTOS 在 ARM Cortex-m0/m3/m4/m7 平臺(tái)的實(shí)現(xiàn)中,還使用了 PendSV 中斷和 SVC 中斷,這是軟件產(chǎn)生的中斷,其 ISR 是調(diào)度器的一部分。但是在其它硬件平臺(tái)的實(shí)現(xiàn)中,未必有類(lèi)似的軟中斷可用。

初始化和任務(wù)創(chuàng)建

在一個(gè)用 FreeRTOS 的工程里面,幾乎必然用到的是創(chuàng)建任務(wù),哪怕只有一個(gè)任務(wù)。比如可以在 main() 里面創(chuàng)建首先要運(yùn)行的任務(wù),使用 xTaskCreate() 這個(gè) API 函數(shù):


BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
                        const char * const pcName,
                        const uint16_t usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask);

第一個(gè)參數(shù) pxTaskCode 是任務(wù)的主函數(shù)(入口地址),然后 pcName 是代表任務(wù)名稱(chēng)的一個(gè)字符串。usStackDepth 很重要,是決定給任務(wù)分配多少內(nèi)存作為堆棧。pvParameters 是向任務(wù)函數(shù)傳遞參數(shù)用的,uxPriority 是初始的任務(wù)優(yōu)先級(jí),pxCreatedTask 用來(lái)保存任務(wù) handle, 也就是TCB的地址。當(dāng)創(chuàng)建成功,這個(gè)函數(shù)返回 pdTRUE, 然后任務(wù)處于 ready 狀態(tài),隨時(shí)可以運(yùn)行。
  

實(shí)際上要等到調(diào)度器起用以后,前面所創(chuàng)建的任務(wù)才能運(yùn)行。當(dāng)主程序完成了系統(tǒng)的初始化工作,比如配置片上設(shè)備,創(chuàng)建好必需的任務(wù)、各種通信對(duì)象,就可以調(diào)用 vTaskStartScheduler() 來(lái)起用調(diào)度器。一般來(lái)說(shuō)這個(gè)函數(shù)并不返回,因此余下運(yùn)行的就是各個(gè)任務(wù)的函數(shù)了。vTaskStartScheduler() 還會(huì)自動(dòng)創(chuàng)建 Idle 任務(wù)和 Timer 任務(wù),然后選擇最高優(yōu)先級(jí)的一個(gè)任務(wù)開(kāi)始運(yùn)行。

幾個(gè)簡(jiǎn)單常用的 API

在一個(gè)新平臺(tái)上使用 FreeRTOS 功能的時(shí)候,可以用一些 API 實(shí)現(xiàn)簡(jiǎn)單的多任務(wù)運(yùn)行,下面列舉幾個(gè),都不涉及任務(wù)間通信。

vTaskDelay() 最常用來(lái)產(chǎn)生延時(shí)。調(diào)用它的任務(wù)立即被阻塞,等到經(jīng)過(guò)要求的若干 Timer Tick 過(guò)后再恢復(fù)到就緒狀態(tài)。注意,下一次 Tick 的時(shí)刻(也就是硬件定時(shí)器中斷發(fā)生時(shí)刻)和調(diào)用 vTaskDelay() 的時(shí)刻有一段不確定的間隔,因此要求精確的延遲時(shí)要考慮這個(gè)函數(shù)是否滿(mǎn)足需求。
  

vTaskDelayUntil() 功能類(lèi)似,只不過(guò)它是指定一個(gè)絕對(duì)時(shí)間,而 vTaskDelay() 是從調(diào)用時(shí)刻計(jì)的相對(duì)時(shí)間。
  

xTaskGetTickCount() 返回調(diào)度器已運(yùn)行的 Tick 數(shù),也就是總執(zhí)行時(shí)間。
  

vTaskSuspend() 和 vTaskResume(), 這兩個(gè)函數(shù)可以將某一任務(wù)掛起,以及使掛起的任務(wù)恢復(fù)。
  

vTaskSuspendAll() 和 vTaskResumeAll(): 這兩個(gè)表面上看起來(lái)是上兩個(gè) API 功能的擴(kuò)充,實(shí)際上原理完全不同。vTaskSuspendAll() 將調(diào)度器禁止,當(dāng)前任務(wù)繼續(xù)執(zhí)行,中斷也是允許的。限制是此時(shí)不能調(diào)用其它的 API, 直到用 vTaskResumeAll() 恢復(fù)調(diào)度器之后。和關(guān)鍵區(qū)域(critical section)的用法不同,關(guān)鍵區(qū)域是硬件上屏蔽中斷保證不會(huì)發(fā)生任務(wù)切換,API 還是可以使用的。
  

vTaskList() 接收一個(gè)字符緩沖區(qū),生成文本格式的所有任務(wù)的列表,包括狀態(tài)信息。
  

小結(jié):FreeRTOS入門(mén)運(yùn)用不難,只需要把幾個(gè)文件添加進(jìn)現(xiàn)有的工程里面,就能改成多任務(wù)的。必要的步驟是決定配置選項(xiàng),編寫(xiě) FreeRTOSConfig.h 文件,以及決定內(nèi)存管理方式。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多