當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 嵌入式大雜燴
[導(dǎo)讀]嵌入式系統(tǒng)不只是ARM+Linux,不是只有安卓,凡是電子產(chǎn)品都可稱(chēng)為嵌入式系統(tǒng)。物聯(lián)網(wǎng)行業(yè)的興起,也提升了FreeRTOS市場(chǎng)占有率。本文就是介紹FreeRTOS基礎(chǔ)及其應(yīng)用,只是個(gè)人整理,可能存在問(wèn)題,其目的只是簡(jiǎn)要介紹系統(tǒng)的基礎(chǔ),只能作為入門(mén)資料。



嵌入式系統(tǒng)不只是ARM+Linux,不是只有安卓,凡是電子產(chǎn)品都可稱(chēng)為嵌入式系統(tǒng)。物聯(lián)網(wǎng)行業(yè)的興起,也提升了FreeRTOS市場(chǎng)占有率。本文就是介紹FreeRTOS基礎(chǔ)及其應(yīng)用,只是個(gè)人整理,可能存在問(wèn)題,其目的只是簡(jiǎn)要介紹系統(tǒng)的基礎(chǔ),只能作為入門(mén)資料。

目錄

一、 為什么要學(xué)習(xí)RTOS?

二、 操作系統(tǒng)基礎(chǔ)

三、 初識(shí) FreeRTOS

四、 任務(wù)

五、 隊(duì)列?

六、 軟件定時(shí)器

七、 信號(hào)量

八、 事件?

九、 任務(wù)通知

十、 內(nèi)存管理

十一、 通用接口

一、 為什么要學(xué)習(xí) RTOS

進(jìn)入嵌入式這個(gè)領(lǐng)域,入門(mén)首先接觸的是單片機(jī)編程,尤其是C51 單片機(jī)來(lái),基礎(chǔ)的單片機(jī)編程通常都是指裸機(jī)編程,即不加入任何 RTOS(Real Time Operating System 實(shí)時(shí)操作系統(tǒng))。常用的有國(guó)外的FreeRTOS、μC/OS、RTX 和國(guó)內(nèi)的 RT-thread、Huawei LiteOS 和 AliOS-Things 等,其中開(kāi)源且免費(fèi)的 FreeRTOS 的市場(chǎng)占有率較高。

1.1 前后臺(tái)系統(tǒng)

在裸機(jī)系統(tǒng)中,所有的操作都是在一個(gè)無(wú)限的大循環(huán)里面實(shí)現(xiàn),支持中斷檢測(cè)。外部中斷緊急事件在中斷里面標(biāo)記或者響應(yīng),中斷服務(wù)稱(chēng)為前臺(tái),main 函數(shù)里面的while(1)無(wú)限循環(huán)稱(chēng)為后臺(tái),按順序處理業(yè)務(wù)功能,以及中斷標(biāo)記的可執(zhí)行的事件。小型的電子產(chǎn)品用的都是裸機(jī)系統(tǒng),而且也能夠滿足需求。


1.2 多任務(wù)系統(tǒng)

多任務(wù)系統(tǒng)的事件響應(yīng)也是在中斷中完成的,但是事件的處理是在任務(wù)中完成的。如果事件對(duì)應(yīng)的任務(wù)的優(yōu)先級(jí)足夠高,中斷對(duì)應(yīng)的事件會(huì)立刻執(zhí)行。相比前后臺(tái)系統(tǒng),多任務(wù)系統(tǒng)的實(shí)時(shí)性又被提高了。

在多任務(wù)系統(tǒng)中,根據(jù)程序的功能,把這個(gè)程序主體分割成一個(gè)個(gè)獨(dú)立的,無(wú)限循環(huán)且不能返回的子程序,稱(chēng)之為任務(wù)。每個(gè)任務(wù)都是獨(dú)立的,互不干擾的,且具備自身的優(yōu)先級(jí),它由操作系統(tǒng)調(diào)度管理。加入操作系統(tǒng)后,開(kāi)發(fā)人員不需要關(guān)注每個(gè)功能模塊之間的沖突,重心放在子程序的實(shí)現(xiàn)。缺點(diǎn)是整個(gè)系統(tǒng)隨之帶來(lái)的額外RAM開(kāi)銷(xiāo),但對(duì)目前的單片機(jī)的來(lái)影響不大。


1.3 學(xué)習(xí)RTOS的意義

學(xué)習(xí) RTOS,一是項(xiàng)目需要,隨著產(chǎn)品要實(shí)現(xiàn)的功能越來(lái)越多,單純的裸機(jī)系統(tǒng)已經(jīng)不能完美地解決問(wèn)題,反而會(huì)使編程變得更加復(fù)雜,如果想降低編程的難度,就必須引入 RTOS實(shí)現(xiàn)多任務(wù)管理。二是技能需要,掌握操作系統(tǒng),和基于RTOS的編程,實(shí)現(xiàn)更好的職業(yè)規(guī)劃,對(duì)個(gè)人發(fā)展尤其是錢(qián)途是必不可少的。

以前一直覺(jué)得學(xué)操作系統(tǒng)就必須是linux,實(shí)際每個(gè)系統(tǒng)都有其應(yīng)用場(chǎng)景,對(duì)于物聯(lián)網(wǎng)行業(yè),殺雞焉用牛刀,小而美,且應(yīng)用廣泛的FreeRTOS 是首選。有一個(gè)操作系統(tǒng)的基礎(chǔ),即使后續(xù)基于其他系統(tǒng)開(kāi)發(fā)軟件,也可觸類(lèi)旁通,對(duì)新技術(shù)快速入門(mén)。目前接觸的幾款芯片都是基于FreeRTOS。

如何學(xué)習(xí)RTOS?最簡(jiǎn)單的就是在別人移植好的系統(tǒng)之上,看看 RTOS 里面的 API 使用說(shuō)明,然后調(diào)用這些 API 實(shí)現(xiàn)自己想要的功能即可。完全不用關(guān)心底層的移植,這是最簡(jiǎn)單快速的入門(mén)方法。這種學(xué)習(xí)方式,如果是做產(chǎn)品,可以快速的實(shí)現(xiàn)功能,弊端是當(dāng)程序出現(xiàn)問(wèn)題的時(shí)候,如果對(duì)RTOS不夠了解,會(huì)導(dǎo)致調(diào)試?yán)щy,無(wú)從下手。

各種RTOS內(nèi)核實(shí)現(xiàn)方式都差不多,我們只需要深入學(xué)習(xí)其中一款就行。萬(wàn)變不離其宗,正如掌握了C51基礎(chǔ),后續(xù)換其他型號(hào)或者更高級(jí)的ARM單片機(jī),在原理和方法上,都是有借鑒意義,可以比較快的熟悉并掌握新單片機(jī)的使用。

二、 操作系統(tǒng)基礎(chǔ)

2.1 鏈表

鏈表作為 C 語(yǔ)言中一種基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu),在平時(shí)寫(xiě)程序的時(shí)候用的并不多,但在操作系統(tǒng)里面使用的非常多。FreeRTOS 中存在著大量的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)鏈表和鏈表項(xiàng)的操作(list 和 list item)。FreeRTOS 中與鏈表相關(guān)的操作均在 list.h 和 list.c 這兩個(gè)文件中實(shí)現(xiàn)。



鏈表比數(shù)組,最大優(yōu)勢(shì)是占用的內(nèi)存空間可以隨著需求擴(kuò)大或縮小,動(dòng)態(tài)調(diào)整。實(shí)際FreeRTOS中各種任務(wù)的記錄都是依靠鏈表動(dòng)態(tài)管理,具體的可以參考源碼的任務(wù)控制塊tskTCB。任務(wù)切換狀態(tài),就是將對(duì)應(yīng)的鏈表進(jìn)行操作,鏈表操作涉及創(chuàng)建和插入、刪除和查找。

2.2 隊(duì)列

隊(duì)列是一種只允許在表的前端(front)進(jìn)行刪除操作,而在表的后端(rear)進(jìn)行插入操作。隊(duì)尾放入數(shù)據(jù),對(duì)頭擠出。先進(jìn)先出,稱(chēng)為FIFO。


2.3 任務(wù)

在裸機(jī)系統(tǒng)中,系統(tǒng)的主體就是 main 函數(shù)里面順序執(zhí)行的無(wú)限循環(huán),這個(gè)無(wú)限循環(huán)里面 CPU 按照順序完成各種事情。在多任務(wù)系統(tǒng)中,根據(jù)功能的不同,把整個(gè)系統(tǒng)分割成一個(gè)個(gè)獨(dú)立的且無(wú)法返回的函數(shù),這個(gè)函數(shù)我們稱(chēng)為任務(wù)。系統(tǒng)中的每一任務(wù)都有多種運(yùn)行狀態(tài)。系統(tǒng)初始化完成后,創(chuàng)建的任務(wù)就可以在系統(tǒng)中競(jìng)爭(zhēng)一定的資源,由內(nèi)核進(jìn)行調(diào)度。


?

就緒(Ready):該任務(wù)在就緒列表中,就緒的任務(wù)已經(jīng)具備執(zhí)行的能力,只等待調(diào)度器進(jìn)行調(diào)度,新創(chuàng)建的任務(wù)會(huì)初始化為就緒態(tài)。?

? 運(yùn)行(Running):該狀態(tài)表明任務(wù)正在執(zhí)行,此時(shí)它占用處理器,調(diào)度器選擇運(yùn)行的永遠(yuǎn)是處于最高優(yōu)先級(jí)的就緒態(tài)任務(wù)。?

? 阻塞(Blocked):任務(wù)當(dāng)前正在等待某個(gè)事件,比如信號(hào)量或外部中斷。?

? 掛起態(tài)(Suspended):處于掛起態(tài)的任務(wù)對(duì)調(diào)度器而言是不可見(jiàn)的。

掛起態(tài)與阻塞態(tài)的區(qū)別,當(dāng)任務(wù)有較長(zhǎng)的時(shí)間不允許運(yùn)行的時(shí)候,我們可以掛起任務(wù),這樣子調(diào)度器就不會(huì)管這個(gè)任務(wù)的任何信息,直到調(diào)用恢復(fù)任務(wù)的 接口;而任務(wù)處于阻塞態(tài)的時(shí)候,系統(tǒng)還需要判斷阻塞態(tài)的任務(wù)是否超時(shí),是否可以解除阻塞。

各任務(wù)運(yùn)行時(shí)使用消息、信號(hào)量等方式進(jìn)行通信,不能是全局變量。任務(wù)通常會(huì)運(yùn)行在一個(gè)死循環(huán)中,不會(huì)退出,如果不再需要,可以調(diào)用刪除任務(wù)。

2.4 臨界區(qū)

臨界區(qū)就是一段在執(zhí)行的時(shí)候不能被中斷的代碼段。在多任務(wù)操作系統(tǒng)里面,對(duì)全局變量的操作不能被打斷,不能執(zhí)行到一半就被其他任務(wù)再次操作。一般被打斷,原因就是系統(tǒng)調(diào)度或外部中斷。對(duì)臨界區(qū)的保護(hù)控制,歸根到底就是對(duì)系統(tǒng)中斷的使能控制。在使用臨界區(qū)時(shí),關(guān)閉中斷響應(yīng),對(duì)部分優(yōu)先級(jí)的中斷進(jìn)行屏蔽,因此臨界區(qū)不允許運(yùn)行時(shí)間過(guò)長(zhǎng)。為了對(duì)臨界區(qū)進(jìn)行控制,就需要使用信號(hào)量通信,實(shí)現(xiàn)同步或互斥操作。

三、 初識(shí) FreeRTOS

3.1 FreeRTOS源碼

FreeRTOS 由美國(guó)的 Richard Barry 于 2003 年發(fā)布, 2018 年被亞馬遜收購(gòu),改名為 AWS FreeRTOS,版本號(hào)升級(jí)為 V10,支持MIT開(kāi)源協(xié)議,亞馬遜收購(gòu) FreeRTOS 也是為了進(jìn)入物聯(lián)網(wǎng)和人工智能,新版本增加了物聯(lián)網(wǎng)行業(yè)的網(wǎng)絡(luò)協(xié)議等功能。

FreeRTOS 是開(kāi)源免費(fèi)的,可從官網(wǎng) www.freertos.org 下載源碼和說(shuō)明手冊(cè)。例如展銳的UIS8910使用的是V10。以FreeRTOSv10.4.1為例,包含 Demo 例程,Source內(nèi)核的源碼,License許可文件。

3.1.1 Source 文件夾

FreeRTOS/ Source 文件夾下的文件:

包括FreeRTOS 的通用的頭文件include和 C 文件,包括任務(wù)、隊(duì)列、定時(shí)器等,適用于各種編譯器和處理器,是通用的。

需要特殊處理適配的在portblle文件夾,其下內(nèi)容與編譯器和處理器相關(guān), FreeRTOS 要想運(yùn)行在一個(gè)單片機(jī)上面,它們就必須關(guān)聯(lián)在一起,通常由匯編和 C 聯(lián)合編寫(xiě)。通常難度比較高,不過(guò)一般芯片原廠提供移植好的接口文件。這里不介紹移植的方法,因?yàn)樽约阂膊幻靼住?/p>

Portblle/MemMang 文件夾下存放的是跟內(nèi)存管理相關(guān)的,總共有五個(gè) heap 文件,有5種內(nèi)存動(dòng)態(tài)分配方式,一般物聯(lián)網(wǎng)產(chǎn)品選用 heap4.c 。

3.1.2 Demo 文件夾

里面包含了 FreeRTOS 官方為各個(gè)單片機(jī)移植好的工程代碼,F(xiàn)reeRTOS 為了推廣自己,會(huì)給針對(duì)不同半導(dǎo)體廠商的評(píng)估板實(shí)現(xiàn)基礎(chǔ)功能范例, Demo下就是參考范例。

3.1.3 FreeRTOSConfig.h配置

FreeRTOSConfig.h頭文件對(duì)FreeRTOS 所需的功能的宏均做了定義,需要根據(jù)應(yīng)用情況配置合適的參數(shù),其作用類(lèi)似MTK功能機(jī)平臺(tái)的主mak文件,部分定義如下:

1.?#define?configUSE_PREEMPTION????????????1??
2.?#define?configUSE_IDLE_HOOK?????????????0??
3.?#define?configUSE_TICK_HOOK?????????????0??
4.?#define?configCPU_CLOCK_HZ??????????????(?SystemCoreClock?)??
5.?#define?configTICK_RATE_HZ??????????????(?(?TickType_t?)?1000?)??

例如系統(tǒng)時(shí)鐘tick等參數(shù)在就這個(gè)文件配置,具體作用可以看注釋。一般情況下使用SDK不需要改動(dòng),特殊情況下咨詢(xún)?cè)瓘S再調(diào)整。

3.2 FreeRTOS 編碼規(guī)范

接觸一個(gè)新平臺(tái)或者SDK,明白它的編碼規(guī)范,文件作用,可以提高源碼閱讀效率,快速熟悉其內(nèi)部實(shí)現(xiàn)。

3.2.1 數(shù)據(jù)類(lèi)型

FreeRTOS針對(duì)不同的處理器,對(duì)標(biāo)準(zhǔn)C的數(shù)據(jù)類(lèi)型進(jìn)行了重定義。

1.?#define?portCHAR????????char??
2.?#define?portFLOAT???????float??
3.?#define?portDOUBLE??????double??
4.?#define?portLONG????????long??
5.?#define?portSHORT???????short??
6.?#define?portSTACK_TYPE??uint32_t??
7.?#define?portBASE_TYPE???long??

應(yīng)用編碼中,推薦使用的是下面這種風(fēng)格。

1.?typedef?int?int32_t;??
2.?typedef?short?int16_t;??
3.?typedef?char?int8_t;??
4.?typedef?unsigned?int?uint32_t;??
5.?typedef?unsigned?short?uint16_t;??
6.?typedef?unsigned?char?uint8_t;

3.2.2 變量名

FreeRTOS 中,定義變量的時(shí)候往往會(huì)把變量的類(lèi)型當(dāng)作前綴,好處看到就知道其類(lèi)型。?

char 型變量的前綴是 c?

short 型變量的前綴是 s?

long 型變量的前綴是 l?

復(fù)雜的結(jié)構(gòu)體,句柄等定義的變量名的前綴是 x?

變量是無(wú)符號(hào)型的再加前綴 u,是指針變量則加前綴 p

3.2.3 函數(shù)名

函數(shù)名包含了函數(shù)返回值的類(lèi)型、函數(shù)所在的文件名和函數(shù)的功能,如果是私有的函數(shù)則會(huì)加一個(gè) prv(private)的前綴。?

例如vTaskPrioritySet()函數(shù)的返回值為 void 型,在 task.c 這個(gè)文件中定義。

3.2.4 宏

宏內(nèi)容是由大寫(xiě)字母表示,前綴是小寫(xiě)字母,表示該宏在哪個(gè)頭文件定義,如:

1.?#define?taskYIELD()?????????????????portYIELD()??

表示該宏是在task.h。

3.2.5 個(gè)人解讀

1、編碼不缺編碼規(guī)范,但是實(shí)際使用中很難完全依照標(biāo)準(zhǔn)執(zhí)行,即使freeRTOS源碼也是如此。?

2、關(guān)于函數(shù)或者宏定義中帶文件名的作用,使用Source Insight 編輯代碼,該前綴的意義不大。?

3、規(guī)則是活的,只要所有人都按一個(gè)規(guī)則執(zhí)行,它就是標(biāo)準(zhǔn)。

3.3 FreeRTOS應(yīng)用開(kāi)發(fā)

關(guān)于freeRTOS的應(yīng)用開(kāi)發(fā),主要是任務(wù)的創(chuàng)建和調(diào)度,任務(wù)間的通信與同步,涉及隊(duì)列、信號(hào)量等操作系統(tǒng)通用接口。結(jié)合應(yīng)用需求,涉及定時(shí)器、延時(shí)、中斷控制等接口。

特別說(shuō)明,有些功能的實(shí)現(xiàn)方式有多種形式,只針對(duì)常用方式進(jìn)行說(shuō)明,例如task的創(chuàng)建,只說(shuō)明動(dòng)態(tài)創(chuàng)建方式,因?yàn)楹苌偈褂渺o態(tài)方式。

四、 任務(wù)

4.1 創(chuàng)建任務(wù)

xTaskCreate()使用動(dòng)態(tài)內(nèi)存的方式創(chuàng)建一個(gè)任務(wù)。

1.?ret?=?xTaskCreate((TaskFunction_t)?master_task_main,??/*?任務(wù)入口函數(shù)?*/(1)
2.???????????????????“MASTER”,???/*?任務(wù)名字?*/(2)
3.???????????????????64*1024,???/*?任務(wù)棧大小?*/(3)
4.???????????????????NULL,????,/*?任務(wù)入口函數(shù)參數(shù)?*/(4)
5.???????????????????TASK_PRIORITY_NORMAL,??/*?任務(wù)的優(yōu)先級(jí)?*/(5)
6.???????????????????&task_master_handler);??/*?任務(wù)控制塊指針?*/(6)

創(chuàng)建任務(wù)就是軟件運(yùn)行時(shí)的一個(gè)while(1)的入口,一般閱讀其他代碼,找到這個(gè)函數(shù),再跟蹤到任務(wù)入口函數(shù),學(xué)習(xí)基于freeRTOS系統(tǒng)的代碼,首先就是找到main和這個(gè)接口。

(1):任務(wù)入口函數(shù),即任務(wù)函數(shù)的名稱(chēng),需要我們自己定義并且實(shí)現(xiàn)。

?(2):任務(wù)名字,字符串形式,最大長(zhǎng)度由 FreeRTOSConfig.h 中定義的 configMAX_TASK_NAME_LEN 宏指定,多余部分會(huì)被自動(dòng)截掉,只是方便調(diào)試。

(3):任務(wù)堆棧大小,單位為字, 4 個(gè)字節(jié),這個(gè)要注意,否則系統(tǒng)內(nèi)存緊缺。

(4):任務(wù)入口函數(shù)形參,不用的時(shí)候配置為 0 或者NULL 即可。

(5) :任務(wù)的優(yōu)先級(jí),在 FreeRTOS 中,數(shù)值越大優(yōu)先級(jí)越高,0 代表最低優(yōu)先級(jí)。基于其SDK開(kāi)發(fā),可將自定義的所有業(yè)務(wù)功能task設(shè)為同一個(gè)優(yōu)先級(jí),按時(shí)間片輪詢(xún)調(diào)度。

(6):任務(wù)控制塊指針,使用動(dòng)態(tài)內(nèi)存的時(shí)候,任務(wù)創(chuàng)建函數(shù) xTaskCreate()會(huì)返回一個(gè)指針指向任務(wù)控制塊,也可以設(shè)為NULL,因?yàn)槿蝿?wù)句柄后期可以不使用。

4.2 開(kāi)啟調(diào)度

當(dāng)任務(wù)創(chuàng)建成功后處于就緒狀態(tài)(Ready),在就緒態(tài)的任務(wù)可以參與操作系統(tǒng)的調(diào)度。操作系統(tǒng)任務(wù)調(diào)度器只啟動(dòng)一次,之后就不會(huì)再次執(zhí)行了,F(xiàn)reeRTOS 中啟動(dòng)任務(wù)調(diào)度器的函數(shù)是 vTaskStartScheduler(),并且啟動(dòng)任務(wù)調(diào)度器的時(shí)候就不會(huì)返回,從此任務(wù)管理都由FreeRTOS 管理,此時(shí)才是真正進(jìn)入實(shí)時(shí)操作系統(tǒng)中的第一步。

vTaskStartScheduler開(kāi)啟調(diào)度時(shí),順便會(huì)創(chuàng)建空閑任務(wù)和定時(shí)器任務(wù)。

FreeRTOS 為了任務(wù)啟動(dòng)和任務(wù)切換使用了三個(gè)異常:SVC、PendSV 和SysTick。

SVC(系統(tǒng)服務(wù)調(diào)用,亦簡(jiǎn)稱(chēng)系統(tǒng)調(diào)用)用于任務(wù)啟動(dòng)。

PendSV(可掛起系統(tǒng)調(diào)用)用于完成任務(wù)切換,它是可以像普通的中斷一樣被掛起的,它的最大特性是如果當(dāng)前有優(yōu)先級(jí)比它高的中斷在運(yùn)行,PendSV會(huì)延遲執(zhí)行,直到高優(yōu)先級(jí)中斷執(zhí)行完畢,這樣產(chǎn)生的PendSV 中斷就不會(huì)打斷其他中斷的運(yùn)行。

SysTick 用于產(chǎn)生系統(tǒng)節(jié)拍時(shí)鐘,提供一個(gè)時(shí)間片,如果多個(gè)任務(wù)共享同一個(gè)優(yōu)先級(jí),則每次 SysTick 中斷,下一個(gè)任務(wù)將獲得一個(gè)時(shí)間片。

FreeRTOS 中的任務(wù)是搶占式調(diào)度機(jī)制,高優(yōu)先級(jí)的任務(wù)可打斷低優(yōu)先級(jí)任務(wù),低優(yōu)先級(jí)任務(wù)必須在高優(yōu)先級(jí)任務(wù)阻塞或結(jié)束后才能得到調(diào)度。相同優(yōu)先級(jí)的任務(wù)采用時(shí)間片輪轉(zhuǎn)方式進(jìn)行調(diào)度(也就是分時(shí)調(diào)度),時(shí)間片輪轉(zhuǎn)調(diào)度僅在當(dāng)前系統(tǒng)中無(wú)更高優(yōu)先級(jí)就緒任務(wù)存在的情況下才有效。

4.3 啟動(dòng)方式

FreeRTOS有兩種啟動(dòng)方式,效果一樣,看個(gè)人喜好。

第一種:main 函數(shù)中將硬件初始化, RTOS 系統(tǒng)初始化,所有任務(wù)的創(chuàng)建完成,最后一步開(kāi)啟調(diào)度。目前看到的幾個(gè)芯片SDK都是這種方式。

第二種:main 函數(shù)中將硬件和 RTOS 系統(tǒng)先初始化好,只創(chuàng)建一個(gè)任務(wù)后就啟動(dòng)調(diào)度器,然后在這個(gè)任務(wù)里面創(chuàng)建其它應(yīng)用任務(wù),當(dāng)所有任務(wù)都創(chuàng)建成功后,啟動(dòng)任務(wù)再把自己刪除。

4.4 任務(wù)創(chuàng)建源碼分析

xTaskCreate()創(chuàng)建任務(wù)。

1.?BaseType_t?xTaskCreate(?TaskFunction_t?pxTaskCode,??
2.?????????????????????????const?char?*?const?pcName,?/*lint?!e971?Unqualified?char?types?are?allowed?for?strings?and?single?characters?only.?*/??
3.?????????????????????????const?configSTACK_DEPTH_TYPE?usStackDepth,??
4.?????????????????????????void?*?const?pvParameters,??
5.?????????????????????????UBaseType_t?uxPriority,??
6.?????????????????????????TaskHandle_t?*?const?pxCreatedTask?)
??
7.?
{??
8.?????TCB_t?*?pxNewTCB;??
9.?????BaseType_t?xReturn;??
10.???
11.?????/*?If?the?stack?grows?down?then?allocate?the?stack?then?the?TCB?so?the?stack?
12.??????*?does?not?grow?into?the?TCB.??Likewise?if?the?stack?grows?up?then?allocate?
13.??????*?the?TCB?then?the?stack.?*/
??
14.?????#if?(?portSTACK_GROWTH?>?0?)??
15.?????????{??
16.?????????????/**/
17.?????????}??
18.?????#else?/*?portSTACK_GROWTH?*/??
19.?????????{??
20.?????????????StackType_t?*?pxStack;??
21.???
22.?????????????/*?Allocate?space?for?the?stack?used?by?the?task?being?created.?*/??
23.?????????????pxStack?=?pvPortMalloc(?(?(?(?size_t?)?usStackDepth?)?*?sizeof(?StackType_t?)?)?);?/*lint?!e9079?All?values?returned?by?pvPortMalloc()?have?at?least?the?alignment?required?by?the?MCU's?stack?and?this?allocation?is?the?stack.?*/??
24.???
25.?????????????if(?pxStack?!=?NULL?)??
26.?????????????{??
27.?????????????????/*?Allocate?space?for?the?TCB.?*/??
28.?????????????????pxNewTCB?=?(?TCB_t?*?)?pvPortMalloc(?sizeof(?TCB_t?)?);?/*lint?!e9087?!e9079?All?values?returned?by?pvPortMalloc()?have?at?least?the?alignment?required?by?the?MCU's?stack,?and?the?first?member?of?TCB_t?is?always?a?pointer?to?the?task's?stack.?*/??
29.???
30.?????????????????if(?pxNewTCB?!=?NULL?)??
31.?????????????????{??
32.?????????????????????/*?Store?the?stack?location?in?the?TCB.?*/??
33.?????????????????????pxNewTCB->pxStack?=?pxStack;??
34.?????????????????}??
35.?????????????????else??
36.?????????????????{??
37.?????????????????????/*?The?stack?cannot?be?used?as?the?TCB?was?not?created.??Free?
38.??????????????????????*?it?again.?*/
??
39.?????????????????????vPortFree(?pxStack?);??
40.?????????????????}??
41.?????????????}??
42.?????????????else??
43.?????????????{??
44.?????????????????pxNewTCB?=?NULL;??
45.?????????????}??
46.?????????}??
47.?????#endif?/*?portSTACK_GROWTH?*/??
48.???
49.?????if(?pxNewTCB?!=?NULL?)??
50.?????{??
51.?????????#if?(?tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE?!=?0?)?/*lint?!e9029?!e731?Macro?has?been?consolidated?for?readability?reasons.?*/??
52.?????????????{??
53.?????????????????/*?Tasks?can?be?created?statically?or?dynamically,?so?note?this?
54.??????????????????*?task?was?created?dynamically?in?case?it?is?later?deleted.?*/
??
55.?????????????????pxNewTCB->ucStaticallyAllocated?=?tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;??
56.?????????????}??
57.?????????#endif?/*?tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE?*/??
58.???
59.?????????prvInitialiseNewTask(?pxTaskCode,?pcName,?(?uint32_t?)?usStackDepth,?pvParameters,?uxPriority,?pxCreatedTask,?pxNewTCB,?NULL?);??
60.?????????prvAddNewTaskToReadyList(?pxNewTCB?);?//將新任務(wù)加入到就緒鏈表候著
61.?????????xReturn?=?pdPASS;??
62.?????}??
63.?????else??
64.?????{??
65.?????????xReturn?=?errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;??
66.?????}??
67.???
68.?????return?xReturn;??
69.?}

申請(qǐng)任務(wù)控制塊內(nèi)存,檢查配置參數(shù),初始化,將任務(wù)信息加入到就緒鏈表,等待調(diào)度。前面鏈表部分提到,freeRTOS的任務(wù)信息都是使用鏈表記錄,在task.c有

1.?PRIVILEGED_DATA?static?List_t?pxReadyTasksLists[configMAX_PRIORITIES];//就緒
2.?PRIVILEGED_DATA?static?List_t?xDelayedTaskList1;????//延時(shí)
3.?PRIVILEGED_DATA?static?List_t?xDelayedTaskList2;?
4.?PRIVILEGED_DATA?static?List_t?xPendingReadyList;??//掛起
5.?PRIVILEGED_DATA?static?List_t?xSuspendedTaskList;???//阻塞

分別記錄就緒態(tài)、阻塞態(tài)和掛起的任務(wù),其中阻塞態(tài)有2個(gè),是因?yàn)樘厥饪紤],時(shí)間溢出 的問(wèn)題,實(shí)際開(kāi)發(fā)單片機(jī)項(xiàng)目計(jì)時(shí)超過(guò)24h的可以借鑒。其中pxReadyTasksLists鏈表數(shù)組,其下標(biāo)就是任務(wù)的優(yōu)先級(jí)。



4.5 任務(wù)調(diào)度源碼分析

創(chuàng)建完任務(wù)的時(shí)候,vTaskStartScheduler開(kāi)啟調(diào)度器,空閑任務(wù)、定時(shí)器任務(wù)也是在開(kāi)啟調(diào)度函數(shù)中實(shí)現(xiàn)的。

為什么要空閑任務(wù)?因?yàn)?FreeRTOS一旦啟動(dòng),就必須要保證系統(tǒng)中每時(shí)每刻都有一個(gè)任務(wù)處于運(yùn)行態(tài)(Runing),并且空閑任務(wù)不可以被掛起與刪除,空閑任務(wù)的優(yōu)先級(jí)是最低的,以便系統(tǒng)中其他任務(wù)能隨時(shí)搶占空閑任務(wù)的 CPU 使用權(quán)。這些都是系統(tǒng)必要的東西,也無(wú)需自己實(shí)現(xiàn)。

1.?void?vTaskStartScheduler(?void?)??
2.?
{??
3.?????BaseType_t?xReturn;??
4.???
5.?????/*?Add?the?idle?task?at?the?lowest?priority.?*/??
6.?????#if?(?configSUPPORT_STATIC_ALLOCATION?==?1?)??
7.?????????{??
8.??????/***/
9.?????????}??
10.?????#else?/*?if?(?configSUPPORT_STATIC_ALLOCATION?==?1?)?*/??
11.?????????{??
12.?????????????/*創(chuàng)建空閑任務(wù)*/??
13.?????????????xReturn?=?xTaskCreate(?prvIdleTask,??
14.????????????????????????????????????configIDLE_TASK_NAME,??
15.????????????????????????????????????configMINIMAL_STACK_SIZE,??
16.????????????????????????????????????(?void?*?)?NULL,??
17.????????????????????????????????????portPRIVILEGE_BIT,??//優(yōu)先級(jí)為0
18.????????????????????????????????????&xIdleTaskHandle?);??
19.?????????}??
20.?????#endif?/*?configSUPPORT_STATIC_ALLOCATION?*/??
21.???
22.?????#if?(?configUSE_TIMERS?==?1?)??
23.?????????{??
24.?????????????if(?xReturn?==?pdPASS?)??
25.?????????????{??
26.?????????????????//創(chuàng)建定時(shí)器task,接收開(kāi)始、結(jié)束定時(shí)器等命令
27.?????????????????xReturn?=?xTimerCreateTimerTask();?
28.?????????????}??
29.?????????????else??
30.?????????????{??
31.?????????????????mtCOVERAGE_TEST_MARKER();??
32.?????????????}??
33.?????????}??
34.?????#endif?/*?configUSE_TIMERS?*/??
35.???
36.?????if(?xReturn?==?pdPASS?)??
37.?????{??
38.?????????/*?freertos_tasks_c_additions_init()?should?only?be?called?if?the?user?
39.??????????*?definable?macro?FREERTOS_TASKS_C_ADDITIONS_INIT()?is?defined,?as?that?is?
40.??????????*?the?only?macro?called?by?the?function.?*/
??
41.?????????#ifdef?FREERTOS_TASKS_C_ADDITIONS_INIT??
42.?????????????{??
43.?????????????????freertos_tasks_c_additions_init();??
44.?????????????}??
45.?????????#endif??
46.???
47.?????????portDISABLE_INTERRUPTS();??
48.???
49.?????????#if?(?configUSE_NEWLIB_REENTRANT?==?1?)??
50.?????????????{??
51.?????????????????_impure_ptr?=?&(?pxCurrentTCB->xNewLib_reent?);??
52.?????????????}??
53.?????????#endif?/*?configUSE_NEWLIB_REENTRANT?*/??
54.???
55.?????????xNextTaskUnblockTime?=?portMAX_DELAY;??
56.?????????xSchedulerRunning?=?pdTRUE;??
57.?????????xTickCount?=?(?TickType_t?)?configINITIAL_TICK_COUNT;??
58.???
59.?????????portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();??
60.???
61.?????????traceTASK_SWITCHED_IN();??
62.???
63.?????????/*?Setting?up?the?timer?tick?is?hardware?specific?and?thus?in?the?
64.??????????*?portable?interface.?*/
??
65.?????????if(?xPortStartScheduler()?!=?pdFALSE?)??
66.?????????{??
67.?????????????/*?系統(tǒng)開(kāi)始運(yùn)行?*/??
68.?????????}??
69.?????????else??
70.?????????{??
71.?????????????/*?Should?only?reach?here?if?a?task?calls?xTaskEndScheduler().?*/??
72.?????????}??
73.?????}??
74.?????else??
75.?????{??
76.????????/*****/
77.?}?

4.6 任務(wù)狀態(tài)切換

FreeRTOS 系統(tǒng)中的每一個(gè)任務(wù)都有多種運(yùn)行狀態(tài),具體如下:


?

任務(wù)掛起函數(shù)

vTaskSuspend()

掛起指定任務(wù),被掛起的任務(wù)絕不會(huì)得到 CPU 的使用權(quán)

vTaskSuspendAll()

將所有的任務(wù)都掛起任務(wù)恢復(fù)函數(shù)

vTaskResume()
vTaskResume()
xTaskResumeFromISR()

任務(wù)恢復(fù)就是讓掛起的任務(wù)重新進(jìn)入就緒狀態(tài),恢復(fù)的任務(wù)會(huì)保留掛起前的狀態(tài)信息,在恢復(fù)的時(shí)候根據(jù)掛起時(shí)的狀態(tài)繼續(xù)運(yùn)行。xTaskResumeFromISR() 專(zhuān)門(mén)用在中斷服務(wù)程序中。無(wú)論通過(guò)調(diào)用一次或多次vTaskSuspend()函數(shù)而被掛起的任務(wù),也只需調(diào)用一次恢復(fù)即可解掛 。

? 任務(wù)刪除函數(shù) vTaskDelete()用于刪除任務(wù)。當(dāng)一個(gè)任務(wù)可以刪除另外一個(gè)任務(wù),形參為要?jiǎng)h除任 務(wù)創(chuàng)建時(shí)返回的任務(wù)句柄,如果是刪除自身, 則形參為 NULL。

4.7 任務(wù)使用注意點(diǎn)

1、中斷服務(wù)函數(shù)是不允許調(diào)用任何會(huì)阻塞運(yùn)行的接口。一般在中斷服務(wù)函數(shù)中只做標(biāo)記事件的發(fā)生,然后通知任務(wù),讓對(duì)應(yīng)任務(wù)去執(zhí)行相關(guān)處理 。

2、將緊急的處理事件的任務(wù)優(yōu)先級(jí)設(shè)置偏高一些。?

3、空閑任務(wù)(idle 任務(wù))是 FreeRTOS 系統(tǒng)中沒(méi)有其他工作進(jìn)行時(shí)自動(dòng)進(jìn)入的系統(tǒng)任務(wù),永遠(yuǎn)不會(huì)掛起空閑任務(wù),不應(yīng)該陷入死循環(huán)。

4、創(chuàng)建任務(wù)使用的內(nèi)存不要過(guò)多,按需申請(qǐng)。如果浪費(fèi)太多,后續(xù)應(yīng)用申請(qǐng)大空間可能提示內(nèi)存不足。

五、 隊(duì)列

5.1 隊(duì)列的概念

隊(duì)列用于任務(wù)間通信的數(shù)據(jù)結(jié)構(gòu),通過(guò)消息隊(duì)列服務(wù),任務(wù)或中斷服務(wù)將消息放入消息隊(duì)列中。其他任務(wù)或者自身從消息隊(duì)列中獲得消息。實(shí)現(xiàn)隊(duì)列可以在任務(wù)與任務(wù)間、中斷和任務(wù)間傳遞信息。隊(duì)列操作支持阻塞等待,向已經(jīng)填滿的隊(duì)列發(fā)送數(shù)據(jù)或者從空隊(duì)列讀出數(shù)據(jù),都會(huì)導(dǎo)致阻塞,時(shí)間自定義。消息隊(duì)列的運(yùn)作過(guò)程具如下:



5.2 隊(duì)列創(chuàng)建

xQueueCreate()用于創(chuàng)建一個(gè)新的隊(duì)列并返回可用于訪問(wèn)這個(gè)隊(duì)列的句柄。隊(duì)列句柄其實(shí)就是一個(gè)指向隊(duì)列數(shù)據(jù)結(jié)構(gòu)類(lèi)型的指針。

1.?master_queue?=?xQueueCreate(50,?sizeof(task_message_struct_t));??

創(chuàng)建隊(duì)列,占用50個(gè)單元,每個(gè)單元為sizeof(task_message_struct_t)字節(jié),和 malloc比較類(lèi)似。其最終使用的函數(shù)是 xQueueGenericCreate(),后續(xù)信號(hào)量等也是使用它創(chuàng)建,只是最后的隊(duì)列類(lèi)型不同。

申請(qǐng)內(nèi)存后,xQueueGenericReset再對(duì)其進(jìn)行初始化,隊(duì)列的結(jié)構(gòu)體xQUEUE成員:

1.?typedef?struct?QueueDefinition?/*?The?old?naming?convention?is?used?to?prevent?breaking?kernel?aware?debuggers.?*/??
2.?{
??
3.?????int8_t?*?pcHead;???????????/*??
4.?????int8_t?*?pcWriteTo;????????/*??
5.?????//類(lèi)型
6.?????union??
7.?????{??
8.?????????QueuePointers_t?xQueue;?????/*??
9.?????????SemaphoreData_t?xSemaphore;?/*??
10.?????}?u;??
11.???
12.?????//當(dāng)前向隊(duì)列寫(xiě)數(shù)據(jù)阻塞的任務(wù)列表或者從隊(duì)列取數(shù)阻塞的鏈表
13.?????List_t?xTasksWaitingToSend;??
14.?????List_t?xTasksWaitingToReceive;???
15.???
16.?????//隊(duì)列里有多少個(gè)單元被占用,應(yīng)用中需要
17.?????volatile?UBaseType_t?uxMessagesWaiting;?
18.?
19.?????UBaseType_t?uxLength;???????????????????/*??
20.?????UBaseType_t?uxItemSize;?????????????????/*??
21.???
22.??/******/
23.?}?xQUEUE;??

5.3 隊(duì)列刪除

隊(duì)列刪除函數(shù) vQueueDelete()需傳入要?jiǎng)h除的消息隊(duì)列的句柄即可,刪除之后這個(gè)消息隊(duì)列的所有信息都會(huì)被系統(tǒng)回收清空,而且不能再次使用這個(gè)消息隊(duì)列了。實(shí)際應(yīng)用中很少使用。

5.4 向隊(duì)列發(fā)送消息

任務(wù)或者中斷服務(wù)程序都可以給消息隊(duì)列發(fā)送消息,當(dāng)發(fā)送消息時(shí),如果隊(duì)列未滿或者允許覆蓋入隊(duì),F(xiàn)reeRTOS 會(huì)將消息拷貝到消息隊(duì)列隊(duì)尾,否則,會(huì)根據(jù)用戶指定的超時(shí)時(shí)間進(jìn)行阻塞,消息發(fā)送接口很多,最簡(jiǎn)單的是 xQueueSend(),用于向隊(duì)列尾部發(fā)送一個(gè)隊(duì)列消息。消息以拷貝的形式入隊(duì),該函數(shù)絕對(duì)不能在中斷服務(wù)程序里面被調(diào)用,中斷中必須使用帶有中斷保護(hù)功能的 xQueueSendFromISR()來(lái)代替。

BaseType_t?xQueueSend(QueueHandle_t?xQueue,const?void*?pvItemToQueue,?TickType_t?xTicksToWait);

用于向隊(duì)列尾部發(fā)送一個(gè)隊(duì)列消息。

參數(shù)

xQueue 隊(duì)列句柄

pvItemToQueue 指針,指向要發(fā)送到隊(duì)列尾部的隊(duì)列消息。?

xTicksToWait 隊(duì)列滿時(shí),等待隊(duì)列空閑的最大超時(shí)時(shí)間。如果隊(duì)列滿并且xTicksToWait 被設(shè)置成 0,函數(shù)立刻返回。超時(shí)時(shí)間的單位為系統(tǒng)節(jié)拍周期 tick,延時(shí)為 portMAX_DELAY 將導(dǎo)致任務(wù)掛起(沒(méi)有超時(shí))。?

返回值

消息發(fā)送成功成功返回 pdTRUE,否則返回 errQUEUE_FULL。

xQueueSendToBack與xQueueSend完全相同, xQueueSendFromISR()與 xQueueSendToBackFromISR(),帶FromISR表示只能在中斷中使用,freeRTOS所以帶這個(gè)后綴的都是這個(gè)含義。xQueueSendToFront()和QueueSendToFrontFromISR()用于向隊(duì)列隊(duì)首發(fā)送一個(gè)消息。這些在任務(wù)中發(fā)送消息的函數(shù)都是 xQueueGenericSend()展開(kāi)的宏定義。

1.?BaseType_t?xQueueGenericSend(?QueueHandle_t?xQueue,???
2.??????????????????const?void?*?const?pvItemToQueue,???
3.??????????????????????????TickType_t?xTicksToWait,???
4.??????????????????const?BaseType_t?xCopyPosition?)
??//發(fā)送數(shù)據(jù)到消息隊(duì)列的位置

一般使用xQueueSend和xQueueSendFromISR,如不確定當(dāng)前運(yùn)行的是系統(tǒng)服務(wù),還是中斷服務(wù),一般ARM都支持查詢(xún)中斷狀態(tài)寄存器判斷,可以封裝一層接口,只管發(fā)消息,內(nèi)部判斷是否使用支持中斷嵌套的版本,UIS8910就是如此。特殊情況下,如發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包未收到服務(wù)器響應(yīng),期望立刻入隊(duì)再次發(fā)送它,可以xQueueSendToFront向隊(duì)頭發(fā)消息。

5.5 從隊(duì)列讀取消息

當(dāng)任務(wù)試圖讀隊(duì)列中的消息時(shí),可以指定一個(gè)阻塞超時(shí)時(shí)間,當(dāng)且僅當(dāng)消息隊(duì)列中有消息的時(shí)候,任務(wù)才能讀取到消息。如果隊(duì)列為空,該任務(wù)將保持阻塞狀態(tài)以等待隊(duì)列數(shù)據(jù)有效。當(dāng)其它任務(wù)或中斷服務(wù)程序往其等待的隊(duì)列中寫(xiě)入了數(shù)據(jù),該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。當(dāng)任務(wù)等待的時(shí)間超過(guò)了指定的阻塞時(shí)間,即使隊(duì)列中尚無(wú)有效數(shù)據(jù),任務(wù)也會(huì)自動(dòng)從阻塞態(tài)轉(zhuǎn)移為就緒態(tài)。所有的task主入口while循環(huán)體都是按這個(gè)執(zhí)行。例如:

1.?static?void?track_master_task_main()??
2.?
{??
3.?????track_task_message_struct_t?queue_item?=?{0};
4.?????/****/
5.???
6.?????while(1)??
7.?????{??
8.?????????if(xQueueReceive(master_queue,?&queue_item,?portMAX_DELAY))//阻塞等待
9.?????????{??
10.?????????????track_master_task_msg_handler(&queue_item);??
11.?????????}??
12.?????}??
13.?}??

xQueueReceive()用于從一個(gè)隊(duì)列中接收消息并把消息從隊(duì)列中刪除。如果不想刪除消息的話,就調(diào)用 xQueuePeek()函數(shù)。xQueueReceiveFromISR()與xQueuePeekFromISR()是中斷版本,用于在中斷服務(wù)程序中接收一個(gè)隊(duì)列消息并把消息。這兩個(gè)函數(shù)只能用于中斷,是不帶有阻塞機(jī)制的,實(shí)際項(xiàng)目沒(méi)有使用。

5.6 查詢(xún)隊(duì)列使用情況

uxQueueMessagesWaiting()查詢(xún)隊(duì)列中存儲(chǔ)的信息數(shù)目,具有中斷保護(hù)的版本為uxQueueMessagesWaitingFromISR()。查詢(xún)隊(duì)列的空閑數(shù)目uxQueueSpacesAvailable()。

5.7 隊(duì)列使用注意點(diǎn)

使用隊(duì)列函數(shù)需要注意以下幾點(diǎn):

1、中斷中必須使用帶FromISR后綴的接口;?

2、發(fā)送或者是接收消息都是以拷貝的方式進(jìn)行,如果消息內(nèi)容過(guò)于龐大,可以將消息的地址作為消息進(jìn)行發(fā)送、接收。

1.?typedef?struct????
2.?{
????
3.?????TaskHandle_t?src_mod_id;????
4.?????int?message_id;????
5.?????int32_t?param;????
6.?????union????
7.?????{????
8.?????????int32_t?result;????
9.?????????int32_t?socket_id;????
10.?????};????
11.?????void*?pvdata;??//大數(shù)據(jù)使用動(dòng)態(tài)申請(qǐng)內(nèi)存保存,隊(duì)列只傳遞指針??
12.?}?track_task_message_struct_t;???

3、隊(duì)列并不屬于任何任務(wù),所有任務(wù)都可以向同一隊(duì)列寫(xiě)入和讀出,一個(gè)隊(duì)列可以由多任務(wù)或中斷讀寫(xiě)。?

4、隊(duì)列的深度要結(jié)合實(shí)際,可以多申請(qǐng)點(diǎn),前提是每個(gè)隊(duì)列單元盡可能小。?

5、隊(duì)列存在一定限制,在隊(duì)頭沒(méi)有取出來(lái)之前,是無(wú)法取出第二個(gè),和STL鏈表存在差異。

六、 軟件定時(shí)器

6.1 軟件定時(shí)器的概念

定時(shí)器有硬件定時(shí)器和軟件定時(shí)器之分,硬件定時(shí)器是芯片本身提供的定時(shí)功能精度高,并且是中斷觸發(fā)方式。軟件定時(shí)器是由操作系統(tǒng)封裝的接口,它構(gòu)建在硬件定時(shí)器基礎(chǔ)之上,使系統(tǒng)能夠提供不受硬件定時(shí)器資源限制,其實(shí)現(xiàn)的功能與硬件定時(shí)器也是類(lèi)似的。

在操作系統(tǒng)中,通常軟件定時(shí)器以系統(tǒng)節(jié)拍周期為計(jì)時(shí)單位。系統(tǒng)節(jié)拍配置為configTICK_RATE_HZ,該宏在 FreeRTOSConfig.h 中,一般是100或者1000。根據(jù)實(shí)際系統(tǒng) CPU 的處理能力和實(shí)時(shí)性需求設(shè)置合適的數(shù)值,系統(tǒng)節(jié)拍周期的值越小,精度越高,但是系統(tǒng)開(kāi)銷(xiāo)也將越大,因?yàn)檫@代表在 1 秒中系統(tǒng)進(jìn)入時(shí)鐘中斷的次數(shù)也就越多。

6.2 軟件定時(shí)器創(chuàng)建

軟件定時(shí)器需先創(chuàng)建才允許使用,動(dòng)態(tài)創(chuàng)建方式是xTimerCreate(),返回一個(gè)句柄。軟件定時(shí)器在創(chuàng)建成功后是處于休眠狀態(tài)的,沒(méi)有開(kāi)始計(jì)時(shí)運(yùn)行。FreeRTOS的軟件定時(shí)器支持單次模式和周期模式。

單次模式:當(dāng)用戶創(chuàng)建了定時(shí)器并啟動(dòng)了定時(shí)器后,定時(shí)時(shí)間到了,只執(zhí)行一次回調(diào)函數(shù),之后不再執(zhí)行。周期模式:定時(shí)器會(huì)按照設(shè)置的定時(shí)時(shí)間循環(huán)執(zhí)行回調(diào)函數(shù),直到用戶將定時(shí)器停止或刪除。



實(shí)際項(xiàng)目中使用這種模式對(duì)單片機(jī)喂狗就比較省事。

1.?TimerHandle_t?xTimerCreate(?const?char?*?const?pcTimerName,?//定時(shí)器名稱(chēng)
2.?????????????????????????????const?TickType_t?xTimerPeriodInTicks,??//定時(shí)時(shí)間
3.?????????????????????????????const?UBaseType_t?uxAutoReload,??//是否自動(dòng)重載
4.?????????????????????????????void?*?const?pvTimerID,??//回調(diào)函數(shù)的參數(shù)
5.?????????????????????????????TimerCallbackFunction_t?pxCallbackFunction?)
??//回調(diào)函數(shù)

6.3 軟件定時(shí)器開(kāi)啟

新創(chuàng)建的定時(shí)器沒(méi)有開(kāi)始計(jì)時(shí)啟動(dòng),可以使用

xTimerStart()、
xTimerReset()、
xTimerStartFromISR()?、xTimerResetFromISR()?
xTimerChangePeriod()、xTimerChangePeriodFromISR()

這些函數(shù)將其狀態(tài)轉(zhuǎn)換為活躍態(tài),開(kāi)始運(yùn)行。區(qū)別:如果定時(shí)器設(shè)定60秒間隔,已經(jīng)運(yùn)行了30秒,reset是將定時(shí)器重置為原來(lái)設(shè)定的時(shí)間間隔,也就是重新開(kāi)始延時(shí)60秒。ChangePeriod重新設(shè)置計(jì)時(shí)周期。



6.4 軟件定時(shí)器停止

xTimerStop() 用于停止一個(gè)已經(jīng)啟動(dòng)的軟件定時(shí)器,xTimerStopFromISR()是中斷版本。

6.5 軟件定時(shí)器刪除

xTimerDelete()用于刪除一個(gè)已經(jīng)被創(chuàng)建成功的軟件定時(shí)器,釋放資源,刪除之后不能再使用。實(shí)際項(xiàng)目中,任務(wù)和隊(duì)列都是按需創(chuàng)建,一直使用,但是定時(shí)器不使用的就應(yīng)該刪除,并且刪除后一定要將句柄置為NULL。

6.6 軟件定時(shí)器源碼分析

軟件定時(shí)器任務(wù)是在系統(tǒng)開(kāi)始調(diào)度的時(shí)候就被創(chuàng)建:vTaskStartScheduler()—xTimerCreateTimerTask。

1.?BaseType_t?xTimerCreateTimerTask(?void?)??
2.?
{??
3.?????BaseType_t?xReturn?=?pdFAIL;??
4.???
5.?????prvCheckForValidListAndQueue();??//創(chuàng)建定時(shí)器任務(wù)的隊(duì)列
6.???
7.?????if(?xTimerQueue?!=?NULL?)??
8.?????{??
9.?????????#if?(?configSUPPORT_STATIC_ALLOCATION?==?1?)??
10.?????????????{??
11.???????????????????????/**/
12.?????????????}??
13.?????????#else?/*?if?(?configSUPPORT_STATIC_ALLOCATION?==?1?)?*/??
14.?????????????{??
15.??????????????????//創(chuàng)建定時(shí)器任務(wù)
16.?????????????????xReturn?=?xTaskCreate(?prvTimerTask,??
17.????????????????????????????????????????configTIMER_SERVICE_TASK_NAME,??
18.????????????????????????????????????????configTIMER_TASK_STACK_DEPTH,??
19.????????????????????????????????????????NULL,??
20.????????????????????????????????????????(?(?UBaseType_t?)?configTIMER_TASK_PRIORITY?)?|?portPRIVILEGE_BIT,??
21.????????????????????????????????????????&xTimerTaskHandle?);??
22.?????????????}??
23.?????????#endif?/*?configSUPPORT_STATIC_ALLOCATION?*/??
24.?????}??
25.??????/**/
26.?????return?xReturn;??
27.?}??

任務(wù)創(chuàng)建后,等候命令執(zhí)行

1.static?portTASK_FUNCTION(?prvTimerTask,?pvParameters?)??
2.?
{??
3.??????/**/
4.???
5.?????for(?;?;?)??
6.?????{??
7.?????????//最近即將超時(shí)的定時(shí)器還有多長(zhǎng)時(shí)間溢出
8.?????????xNextExpireTime?=?prvGetNextExpireTime(?&xListWasEmpty?);??
9.???
10.?????????//阻塞等待,定時(shí)器溢出或受到命令,進(jìn)入下一步(原因不明)
11.?????????prvProcessTimerOrBlockTask(?xNextExpireTime,?xListWasEmpty?);??
12.???
13.?????????//接收命令并處理,見(jiàn)下面
14.?????????prvProcessReceivedCommands();??
15.?????}??
16.?}??

所有定時(shí)器接口,都是使用xTimerGenericCommand向隊(duì)列發(fā)送控制命令,命令如下:

1.?#define?tmrCOMMAND_START_DONT_TRACE?????????????(?(?BaseType_t?)?0?)??
2.?#define?tmrCOMMAND_START????????????????????????(?(?BaseType_t?)?1?)??
3.?#define?tmrCOMMAND_RESET????????????????????????(?(?BaseType_t?)?2?)??
4.?#define?tmrCOMMAND_STOP?????????????????????????(?(?BaseType_t?)?3?)??
5.?#define?tmrCOMMAND_CHANGE_PERIOD????????????????(?(?BaseType_t?)?4?)??
6.?#define?tmrCOMMAND_DELETE???????????????????????(?(?BaseType_t?)?5?)??

6.7 軟件定時(shí)器使用注意點(diǎn)

1、查看其他開(kāi)源代碼,對(duì)定時(shí)器的使用并不多,但實(shí)際項(xiàng)目中過(guò)多依賴(lài)定時(shí)器,導(dǎo)致應(yīng)用邏輯混亂。?

2、freeRTOS 的定時(shí)器不是無(wú)限制的,其根源是接收定時(shí)器控制命令消息的隊(duì)列,默認(rèn)只有10個(gè)單元。

1.?xTimerQueue?=?xQueueCreate(?(?UBaseType_t?)?configTIMER_QUEUE_LENGTH,?sizeof(?DaemonTaskMessage_t?)?);??

定時(shí)器過(guò)多,可能出現(xiàn)發(fā)起定時(shí)器命令失敗,原因是隊(duì)列已滿。可以將默認(rèn)的10擴(kuò)大為15,后續(xù)盡量使用信號(hào)量來(lái)優(yōu)化代碼。?

4、軟件定時(shí)器的回調(diào)函數(shù)要快進(jìn)快出,而且不能有任何阻塞任務(wù)運(yùn)行的情況,不能有vTaskDelay() 以及其它能阻塞任務(wù)運(yùn)行的函數(shù)。特別說(shuō)明,其回調(diào)函數(shù)是在定時(shí)器任務(wù)執(zhí)行的,并不是開(kāi)啟定時(shí)器的任務(wù)。

七、 信號(hào)量

7.1 信號(hào)量的概念

信號(hào)量(Semaphore)是一種實(shí)現(xiàn)任務(wù)間通信的機(jī)制,可以實(shí)現(xiàn)任務(wù)之間同步或臨界資源的互斥訪問(wèn),常用于協(xié)助一組相互競(jìng)爭(zhēng)的任務(wù)來(lái)訪問(wèn)臨界資源。在多任務(wù)系統(tǒng)中,各任務(wù)之間需要同步或互斥實(shí)現(xiàn)臨界資源的保護(hù),信號(hào)量功能可以為用戶提供這方面的支持??梢院?jiǎn)單認(rèn)為是為支持多任務(wù)同時(shí)操作的全局變量(個(gè)人理解)。

7.1.1 二值信號(hào)量

比如有一個(gè)停車(chē)位,多個(gè)人都想占用停車(chē),這種情況就可以使用一個(gè)變量標(biāo)記車(chē)位狀態(tài),它只有兩種情況,被占用或者沒(méi)被占用。在多任務(wù)中使用二值信號(hào)量表示,用于任務(wù)與任務(wù)、任務(wù)與中斷的同步。在freeRTOS中,二值信號(hào)量看作只有一個(gè)消息的隊(duì)列,因此這個(gè)隊(duì)列只能為空或滿。

7.1.2 計(jì)數(shù)信號(hào)量

如果有100個(gè)停車(chē)位,可以停100輛車(chē),每進(jìn)去一輛車(chē),車(chē)位的數(shù)量就要減一,當(dāng)停車(chē)場(chǎng)停滿了 100 輛車(chē)的時(shí)候,再來(lái)的車(chē)就不能停進(jìn)去了。這種場(chǎng)景就需要計(jì)數(shù)信號(hào)量來(lái)表示多個(gè)狀態(tài)。二進(jìn)制信號(hào)量可以被認(rèn)為是長(zhǎng)度為 1 的隊(duì)列,而計(jì)數(shù)信號(hào)量則可以被認(rèn)為長(zhǎng)度大于 1 的隊(duì)列,信號(hào)量使用者依然不必關(guān)心存儲(chǔ)在隊(duì)列中的消息,只需關(guān)心隊(duì)列是否有消息即可。

7.1.3 互斥信號(hào)量

還是前面車(chē)位問(wèn)題,只剩一個(gè)空車(chē)位,雖然員工車(chē)離得近,但是領(lǐng)導(dǎo)車(chē)來(lái)了,要優(yōu)先安排給領(lǐng)導(dǎo)使用,這就是由地位決定。互斥信號(hào)量其實(shí)是特殊的二值信號(hào)量,由于其特有的優(yōu)先級(jí)繼承機(jī)制從而使它更適用于簡(jiǎn)單互鎖,也就是保護(hù)臨界資源。

優(yōu)先級(jí)翻轉(zhuǎn)問(wèn)題:假設(shè)有任務(wù)H,任務(wù)M和任務(wù)L三個(gè)任務(wù),優(yōu)先級(jí)逐次降低。低優(yōu)先級(jí)的任務(wù)L搶先占有資源,導(dǎo)致高優(yōu)先級(jí)的任務(wù)H阻塞等待,此時(shí)再有中等優(yōu)先級(jí)的任務(wù)M,它不需要該資源,且優(yōu)先級(jí)高于任務(wù)L,它優(yōu)先執(zhí)行;之后再執(zhí)行任務(wù)L,最后才執(zhí)行任務(wù)H。看起來(lái)就是高優(yōu)先級(jí)的任務(wù)反而不如低優(yōu)先級(jí)的任務(wù),即優(yōu)先級(jí)翻轉(zhuǎn)。



改進(jìn)型的互斥信號(hào)量具有優(yōu)先級(jí)繼承機(jī)制,操作系統(tǒng)對(duì)獲取到臨界資源的任務(wù)提高其優(yōu)先級(jí)為所有等待該資源的任務(wù)中的最高優(yōu)先級(jí)。一旦任務(wù)釋放了該資源,就恢復(fù)到原來(lái)的優(yōu)先級(jí)。

任務(wù)L先占用資源,任務(wù)H申請(qǐng)不到資源會(huì)進(jìn)入阻塞態(tài),同時(shí)系統(tǒng)就會(huì)把當(dāng)前正在使用資源的任務(wù)L的優(yōu)先級(jí)臨時(shí)提高到與任務(wù)H優(yōu)先級(jí)相同,即使任務(wù)M被喚醒了,因?yàn)樗膬?yōu)先級(jí)比任務(wù)H低,所以無(wú)法打斷任務(wù)L,因?yàn)槿蝿?wù)L的優(yōu)先級(jí)被臨時(shí)提升到 H;任務(wù)L使用完該資源,任務(wù)H優(yōu)先級(jí)最高,將接著搶占 CPU 的使用權(quán),這樣保證任務(wù)H在任務(wù)M前優(yōu)先執(zhí)行。



上面的這些就是為了說(shuō)明,二值信號(hào)量因?yàn)閮?yōu)先級(jí)翻轉(zhuǎn),不能用于對(duì)臨界區(qū)的訪問(wèn)。

7.1.4 遞歸互斥信號(hào)量

信號(hào)量是每獲取一次,可用信號(hào)量個(gè)數(shù)就會(huì)減少一個(gè),釋放一次就增加一個(gè)。但是遞歸信號(hào)量則不同。對(duì)于已經(jīng)獲取遞歸互斥量的任務(wù)可以重復(fù)獲取該遞歸互斥量,該任務(wù)擁有遞歸信號(hào)量的所有權(quán)。任務(wù)成功獲取幾次遞歸互斥量,就要返還幾次,在此之前遞歸互斥量都處于無(wú)效狀態(tài),其他任務(wù)無(wú)法獲取,只有持有遞歸信號(hào)量的任務(wù)才能獲取與釋放。類(lèi)似棧的效果。

7.2 二值信號(hào)量的應(yīng)用

二值信號(hào)量是任務(wù)與任務(wù)間、任務(wù)與中斷間同步的重要手段。例如,任務(wù)A使用串口發(fā)出AT數(shù)據(jù)后,獲取二值信號(hào)量無(wú)效進(jìn)入阻塞;



某個(gè)時(shí)間后,任務(wù)B中串口收到正確的回復(fù),釋放二值信號(hào)量。



任務(wù)A就立即從阻塞態(tài)中解除,進(jìn)入就緒態(tài),等待運(yùn)行。這種機(jī)制用在模塊AT交互很合適。

7.3 計(jì)數(shù)信號(hào)量的應(yīng)用

計(jì)數(shù)信號(hào)量可以用于資源管理,允許多個(gè)任務(wù)獲取信號(hào)量訪問(wèn)共享資源。例如有公共資源車(chē)位3個(gè),但是有多個(gè)任務(wù)要使用,這種場(chǎng)景就必須使用計(jì)數(shù)信號(hào)量。三個(gè)資源最多支持 3 個(gè)任務(wù)訪問(wèn),那么第 4 個(gè)任務(wù)訪問(wèn)的時(shí)候,會(huì)因?yàn)楂@取不到信號(hào)量而進(jìn)入阻塞。也就是第4個(gè)人無(wú)法占用車(chē)位,必須前面有車(chē)離開(kāi)。等到其中一個(gè)有任務(wù)(比如任務(wù) 1) 釋放掉該資源的時(shí)候,第 4 個(gè)任務(wù)才能獲取到信號(hào)量從而進(jìn)行資源的訪問(wèn)。其運(yùn)作的機(jī)制類(lèi)似下圖。



7.4 互斥信號(hào)量的應(yīng)用

多任務(wù)環(huán)境下往往存在多個(gè)任務(wù)競(jìng)爭(zhēng)同一臨界資源的應(yīng)用場(chǎng)景,互斥量可被用于對(duì)臨界資源的保護(hù)從而實(shí)現(xiàn)獨(dú)占式訪問(wèn)?;コ饬靠梢越档托盘?hào)量存在的優(yōu)先級(jí)翻轉(zhuǎn)問(wèn)題帶來(lái)的影響。

比如有兩個(gè)任務(wù)需要對(duì)串口進(jìn)行發(fā)送數(shù)據(jù),其硬件資源只有一個(gè),那么兩個(gè)任務(wù)肯定不能同時(shí)發(fā)送,不然導(dǎo)致數(shù)據(jù)錯(cuò)誤,那么就可以用互斥量對(duì)串口資源進(jìn)行保護(hù),當(dāng)一個(gè)任務(wù)正在使用串口的時(shí)候,另一個(gè)任務(wù)則無(wú)法使用串口,等到前一個(gè)任務(wù)使用串口完成后, 另外一個(gè)任務(wù)才能獲得串口的使用權(quán)。

另外需要注意的是互斥量不能在中斷服務(wù)函數(shù)中使用,因?yàn)槠涮赜械膬?yōu)先級(jí)繼承機(jī)制只在任務(wù)起作用,在中斷的上下文環(huán)境毫無(wú)意義。

互斥信號(hào)量可以在多個(gè)任務(wù)之間進(jìn)行資源保護(hù),而臨界區(qū)只能是在同一個(gè)任務(wù)進(jìn)行,但是其速度快。(個(gè)人理解)

7.5 信號(hào)量接口

所有信號(hào)量semaphore使用套路相近,都是創(chuàng)建creat、刪除delete、釋放give和獲取take四種;釋放和獲取支持任務(wù)級(jí)和中斷級(jí)FromISR,其中互斥量和遞歸互斥量不支持中斷。使用對(duì)應(yīng)的信號(hào)量,需要在FreeRTOSConfig.h開(kāi)啟對(duì)應(yīng)的功能。

7.5.1 信號(hào)量創(chuàng)建

xSemaphoreCreateBinary()用于創(chuàng)建一個(gè)二值信號(hào)量,并返回一個(gè)句柄,默認(rèn)二值信號(hào)量為空,在使用函數(shù) xSemaphoreTake()獲取之前必須 先 調(diào) 用 函 數(shù) xSemaphoreGive() 釋放后才可以獲取。

xSemaphoreCreateCounting()創(chuàng)建計(jì)數(shù)信號(hào)量。

1.?#define?xSemaphoreCreateCounting(?uxMaxCount,?uxInitialCount?)???

uxMaxCount 計(jì)數(shù)信號(hào)量的最大值,當(dāng)達(dá)到這個(gè)值的時(shí)候,信號(hào)量不能再被釋放。uxInitialCount 創(chuàng)建計(jì)數(shù)信號(hào)量的初始值。

xSemaphoreCreateMutex()用于創(chuàng)建一個(gè)互斥量,并返回一個(gè)互斥量句柄,只能被同一個(gè)任務(wù)獲取一次,如果同一個(gè)任務(wù)想再次獲取則會(huì)失敗。

xSemaphoreCreateRecursiveMutex()用于創(chuàng)建一個(gè)遞歸互斥量,遞歸信號(hào)量可以被同一個(gè)任務(wù)獲取很多次,獲取多少次就需要釋放多少次。遞歸信號(hào)量與互斥量一樣,都實(shí)現(xiàn)了優(yōu)先級(jí)繼承機(jī)制,可以減少優(yōu)先級(jí)反轉(zhuǎn)的反生。

7.5.2 信號(hào)量刪除

vSemaphoreDelete()用于刪除一個(gè)信號(hào)量,包括二值信號(hào)量,計(jì)數(shù)信號(hào)量,互斥量和遞 歸互斥量。如果有任務(wù)阻塞在該信號(hào)量上,暫時(shí)不要?jiǎng)h除該信號(hào)量。傳入的參數(shù)為創(chuàng)建時(shí)返回的句柄。

7.5.3 信號(hào)量釋放

當(dāng)信號(hào)量有效的時(shí)候,任務(wù)才能獲取信號(hào)量,信號(hào)量變得有效就是釋放信號(hào)量。每調(diào)用一次該函數(shù)就釋放一個(gè)信號(hào)量,注意釋放的次數(shù),尤其是計(jì)數(shù)信號(hào)量。

xSemaphoreGive()是任務(wù)中釋放信號(hào)量的宏,可以用于二值信號(hào)量、計(jì)數(shù)信號(hào)量、互斥量的釋放,但不能釋放由函數(shù)xSemaphoreCreateRecursiveMutex()創(chuàng)建的遞歸互斥量,遞歸互斥信號(hào)量用xSemaphoreGiveRecursive()釋放。xSemaphoreGiveFromISR()帶中斷保護(hù)釋放一個(gè)信號(hào)量,被釋放的信號(hào)量可以是二值信號(hào)量和計(jì)數(shù)信號(hào)量,不能釋放互斥量和遞歸互斥量,因?yàn)榛コ饬亢瓦f歸互斥量不可在中斷中使用,互斥量的優(yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用。

7.5.4 信號(hào)量獲取

與釋放信號(hào)量對(duì)應(yīng)的是獲取信號(hào)量,當(dāng)信號(hào)量有效的時(shí)候,任務(wù)才能獲取信號(hào)量,當(dāng)任務(wù)獲取了某個(gè)信號(hào)量的時(shí)候,該信號(hào)量的可用個(gè)數(shù)就減一,當(dāng)它減到0 的時(shí)候,任務(wù)就無(wú)法再獲取了,并且獲取的任務(wù)會(huì)進(jìn)入阻塞態(tài)(如果設(shè)定了阻塞超時(shí)時(shí)間)。

xSemaphoreTake()函數(shù)用于獲取信號(hào)量,不帶中斷保護(hù)。獲取的信號(hào)量對(duì)象可以是二值信號(hào)量、計(jì)數(shù)信號(hào)量和互斥量,但是遞歸互斥量并不能使用它。

1.?#define?xSemaphoreTake(?xSemaphore,?xBlockTime?)??

xSemaphore 信號(hào)量句柄?

xBlockTime 等待信號(hào)量可用的最大超時(shí)時(shí)間,單位為 tick?

獲取 成 功 則 返 回 pdTRUE ,在 指定的 超時(shí) 時(shí)間 中 沒(méi) 有 獲 取 成 功 則 返 回errQUEUE_EMPTY。

使用xSemaphoreTakeRecursive()獲取遞歸互斥量。xSemaphoreTakeFromISR()是獲取信號(hào)量的中斷版本,是一個(gè)不帶阻塞機(jī)制獲取信號(hào)量的函數(shù),獲取對(duì)象必須由是已經(jīng)創(chuàng)建的信號(hào)量,信號(hào)量類(lèi)型可以是二值信號(hào)量和計(jì)數(shù)信號(hào)量,它與 xSemaphoreTake()函數(shù)不同,它不能用于獲取互斥量,因?yàn)榛コ饬坎豢梢栽谥袛嘀惺褂?,并且互斥量特有的?yōu)先級(jí)繼承機(jī)制只能在任務(wù)中起作用,而在中斷中毫無(wú)意義。

7.6 信號(hào)量使用注意點(diǎn)

1、建議合理使用信號(hào)量進(jìn)行事件同步處理,減少對(duì)定時(shí)器的依賴(lài)。

2、使用前合理設(shè)定超時(shí)時(shí)間和依賴(lài)關(guān)系,避免多個(gè)任務(wù)互相等待對(duì)方釋放的信號(hào)量而死鎖。

八、 事件

8.1 事件的概念

信號(hào)量用于單個(gè)任務(wù)與任務(wù)或任務(wù)與中斷之間的同步,但有些任務(wù)可能與多個(gè)任務(wù)由關(guān)聯(lián),此時(shí)信號(hào)量實(shí)現(xiàn)就比較麻煩,可以使用事件機(jī)制。

事件是一種實(shí)現(xiàn)任務(wù)間通信的機(jī)制,多任務(wù)環(huán)境下,任務(wù)、中斷之間往往需要同步操作,一個(gè)事件發(fā)生會(huì)告知等待中的任務(wù),即形成一個(gè)任務(wù)與任務(wù)、中斷與任務(wù)間的同步。事件可以提供一對(duì)多、多對(duì)多的同步操作。一對(duì)多同步模型:一個(gè)任務(wù)等待多個(gè)事件的觸發(fā),這種情況是比較常見(jiàn)的。

任務(wù)可以通過(guò)設(shè)置事件位來(lái)實(shí)現(xiàn)事件的觸發(fā)和等待操作。FreeRTOS 的事件僅用于同步,不提供數(shù)據(jù)傳輸功能。

8.2 事件的應(yīng)用

在某些場(chǎng)合,可能需要多個(gè)事件發(fā)生了才能進(jìn)行下一步操作。各個(gè)事件可分別發(fā)送或一起操作事件標(biāo)志組,而任務(wù)可以等待多個(gè)事件,任務(wù)僅對(duì)感興趣的事件進(jìn)行關(guān)注。當(dāng)有感興趣的事件發(fā)生時(shí)并且符合感興趣的條件,任務(wù)將被喚醒并進(jìn)行后續(xù)的處理動(dòng)作。

其機(jī)制類(lèi)似一個(gè)全局變量,子任務(wù)使用特殊的接口函數(shù)對(duì)指定的位進(jìn)行寫(xiě)1或者清零,主任務(wù)阻塞等待該變量滿足設(shè)定的規(guī)則,則返回運(yùn)行。

例如項(xiàng)目中的喂狗機(jī)制,多個(gè)任務(wù),只要有一個(gè)任務(wù)發(fā)生異常,則主任務(wù)停止喂狗,等待被重啟。



不使用事件機(jī)制,則3個(gè)任務(wù)定時(shí)向主master task發(fā)送消息,表明自身任務(wù)運(yùn)行正常;同時(shí)master task定時(shí)查詢(xún),是否收到3個(gè)任務(wù)的消息,如果全都收到表示正常,清除進(jìn)入下一個(gè)定時(shí)檢查周期;如果其中一個(gè)未收到則表示對(duì)應(yīng)任務(wù)異常,故意停止喂狗等待被重啟。

使用事件機(jī)制,則相對(duì)容易,3個(gè)任務(wù)定時(shí)設(shè)置對(duì)應(yīng)的標(biāo)志位,master task只需要等待指定的事件位,超時(shí)就表示異常;不需要自身定時(shí)查詢(xún),也省去了定時(shí)發(fā)消息。當(dāng)然缺點(diǎn)是master task只能阻塞等待事件不能執(zhí)行其他業(yè)務(wù)邏輯。

8.3 事件接口

xEventGroupCreate()用于創(chuàng)建一個(gè)事件組,vEventGroupDelete()刪除事件對(duì)象控制塊來(lái)釋放系統(tǒng)資源。

事件組置位,任務(wù)中使用 xEventGroupSetBits(),中斷中使用xEventGroupSetBitsFromISR();

xEventGroup 事件句柄。uxBitsToSet 指定事件中的事件標(biāo)志位。如設(shè)置 uxBitsToSet 為 0x09 則位 3和位 0 都需要被置位。返回調(diào)用 xEventGroupSetBits() 時(shí)事件組中的值。

事件組清除位,任務(wù)中使用xEventGroupClearBits(),中斷中使用 xEventGroupClearBitsFromISR(),都是用于清除事件組指定的位,如果在獲取事件的時(shí)候沒(méi)有將對(duì)應(yīng)的標(biāo)志位清除,那么就需要用這個(gè)函數(shù)來(lái)進(jìn)行顯式清除。

xEventGroup 事件句柄。uxBitsToClear 指定事件組中的哪個(gè)位需要清除。如設(shè)置 uxBitsToSet 為 0x09則位 3和位 0 都需要被清除。

讀取事件標(biāo)志,任務(wù)中使用 xEventGroupGetBits(),中斷中使用xEventGroupGetBitsFromISR()。

重點(diǎn)是等待事件函數(shù) xEventGroupWaitBits(),獲取任務(wù)感興趣的事件且支持等待超時(shí)機(jī)制,當(dāng)且僅當(dāng)任務(wù)等待的事件發(fā)生時(shí),任務(wù)才能獲取到事件信息。否則任務(wù)將保持阻塞狀態(tài)以等待事件發(fā)生。當(dāng)其它任務(wù)或中斷服務(wù)程序往其等待的事件設(shè)置對(duì)應(yīng)的標(biāo)志位,該任務(wù)將自動(dòng)由阻塞態(tài)轉(zhuǎn)為就緒態(tài)。

EventGroupWaitBits()用于獲取事件組中的一個(gè)或多個(gè)事件發(fā)生標(biāo)志,當(dāng)要讀取的事件標(biāo)志位沒(méi)有被置位時(shí),任務(wù)將進(jìn)入阻塞等待狀態(tài)。要想使用該函數(shù)必 須 把FreeRTOS/source/event_groups.c 這個(gè) C 文件添加到工程中。

1.?EventBits_t?xEventGroupWaitBits(?EventGroupHandle_t?xEventGroup,??
2.??????????????????????????????????const?EventBits_t?uxBitsToWaitFor,??
3.??????????????????????????????????const?BaseType_t?xClearOnExit,??
4.??????????????????????????????????const?BaseType_t?xWaitForAllBits,??
5.??????????????????????????????????TickType_t?xTicksToWait?)
??

參數(shù)

xEventGroup 事件句柄。?

? uxBitsToWaitFor 一個(gè)按位或的值,指定需要等待事件組中的哪些位置1。如需要等待 bits 0 and/or bit 1 and/or bit 2則 uxBitsToWaitFor 配置為 0x07(0111b)。

xClearOnExit pdTRUE:xEventGroupWaitBits() 等待到滿足任務(wù)喚醒的事件時(shí),系統(tǒng)將清除由形參 uxBitsToWaitFor 指定的事件標(biāo)志位。pdFALSE:不會(huì)清除由形參 uxBitsToWaitFor 指定的事件標(biāo)志位。

xWaitForAllBits pdTRUE :當(dāng)形參 uxBitsToWaitFor 指定的位都置位的時(shí)候,xEventGroupWaitBits()才滿足任務(wù)喚醒的條件,這也是“邏輯與”等待事件,并且在沒(méi)有超時(shí)的情況下返回對(duì)應(yīng)的事件標(biāo)志位的值。pdFALSE:當(dāng)形參 uxBitsToWaitFor 指定的位有其中任意一個(gè)置位的時(shí)候,這也是常說(shuō)的“邏輯或”等待事件,在沒(méi)有超時(shí)的情況下 函數(shù)返回對(duì)應(yīng)的事件標(biāo)志位的值。xTicksToWait 最大超時(shí)時(shí)間,單位為系統(tǒng)節(jié)拍周期

返回值

返回事件中的哪些事件標(biāo)志位被置位,返回值很可能并不是用戶指定的事件位,需要對(duì)返回值進(jìn)行 判斷再處理 。

其應(yīng)用類(lèi)似某個(gè)全局變量,等待事件的任務(wù)在設(shè)定的時(shí)間內(nèi),監(jiān)控該變量某些位的值;該值由其他任務(wù)或中斷修改。

九、 任務(wù)通知

FreeRTOS 從 V8.2.0 版本開(kāi)始提供任務(wù)通知這個(gè)功能,可以在一定場(chǎng)合下替代 FreeRTOS 的信號(hào)量,隊(duì)列、事件組等,但是使用也有局限性。將宏定義 configUSE_TASK_NOTIFICATIONS 設(shè)置為 1才能開(kāi)啟開(kāi)功能。但該功能并不常用。

十、 內(nèi)存管理

10.1 內(nèi)存管理的概念

FreeRTOS 內(nèi)存管理模塊管理用于系統(tǒng)中內(nèi)存資源,它是操作系統(tǒng)的核心模塊之一。主要包括內(nèi)存的初始化、分配以及釋放。一般不同的平臺(tái)移植代碼,內(nèi)存的動(dòng)態(tài)申請(qǐng)和釋放接口需要替換。嵌入式實(shí)時(shí)操作系統(tǒng)中,一般不支持標(biāo)準(zhǔn)C庫(kù)中的 malloc()和 free(),其內(nèi)存有限,隨著內(nèi)存不斷被分配和釋放,整個(gè)系統(tǒng)內(nèi)存區(qū)域會(huì)產(chǎn)生越來(lái)越多的碎片。

FreeRTOS提供了 5 種內(nèi)存管理算法,源文件在Source\portable\MemMang 路徑下,使用的時(shí)候選擇其中一個(gè)。heap_1.c、heap_2.c 和 heap_4.c 這三種內(nèi)存管理方案,內(nèi)存堆實(shí)際上是一個(gè)很大的 數(shù) 組ucHeap。

heap_1.c內(nèi)存管理方案簡(jiǎn)單,它只能申請(qǐng)內(nèi)存而不能進(jìn)行內(nèi)存釋放。有些嵌入式系統(tǒng)并不會(huì)經(jīng)常動(dòng)態(tài)申請(qǐng)與釋放內(nèi)存,一般都是在系統(tǒng)啟動(dòng)后就一直使用下去,永不刪除,適合這種方式。

heap_2.c 方案支持釋放申請(qǐng)的內(nèi)存,但是它不能把相鄰的兩個(gè)小的內(nèi)存塊合成一個(gè)大的內(nèi)存塊,對(duì)于每次申請(qǐng)內(nèi)存大小都比較固定的;但每次申請(qǐng)并不是固定內(nèi)存大小的則會(huì)造成內(nèi)存碎片。如下圖,隨著不斷的申請(qǐng)釋放,空閑空間會(huì)變成很多小片段。



heap_3.c 方案只是封裝了標(biāo)準(zhǔn) C 庫(kù)中的 malloc()和 free()函數(shù),由編譯器提供,需要通過(guò)編譯器或者啟動(dòng)文件設(shè)置堆空間。

heap_4.c 方案是在heap_2.c 基礎(chǔ)上,對(duì)內(nèi)存碎片進(jìn)行了改進(jìn),能把相鄰的空閑的內(nèi)存塊合并成一個(gè)更大的塊,這樣可以減少內(nèi)存碎片。

heap_5.c 方案在實(shí)現(xiàn)動(dòng)態(tài)內(nèi)存分配時(shí)與 heap4.c 方案一樣,采用最佳匹配算法和合并算法,并且允許內(nèi)存堆跨越多個(gè)非連續(xù)的內(nèi)存區(qū),也就是允許在不連續(xù)的內(nèi)存堆中實(shí)現(xiàn)內(nèi)存分配,比如做圖形顯示,可能芯片內(nèi)部的 RAM 不足,額外擴(kuò)展SDRAM,那這種內(nèi)存管理方案則比較合適。

一般物聯(lián)網(wǎng)平臺(tái)使用的是heap_4.c。

10.2 內(nèi)存管理接口

不管其內(nèi)部的管理如何實(shí)現(xiàn)的,對(duì)上層應(yīng)用層的接口都是一樣的。

1.?void?*pvPortMalloc(?size_t?xSize?);?//內(nèi)存申請(qǐng)函數(shù)???
2.?void?vPortFree(?void?*pv?);??????????//內(nèi)存釋放函數(shù)???
3.?void?vPortInitialiseBlocks(?void?);?//初始化內(nèi)存堆函數(shù)???
4.?size_t?xPortGetFreeHeapSize(?void?);????//獲取當(dāng)前未分配的內(nèi)存堆大小???
5.?size_t?xPortGetMinimumEverFreeHeapSize(?void?);?//獲取未分配的內(nèi)存堆歷史最小值??

一般主要是使用內(nèi)存申請(qǐng)和釋放兩個(gè)接口,用法和注意事項(xiàng)同malloc/free一樣,成對(duì)使用。內(nèi)存釋放后盡量將指針設(shè)為NULL。

十一、 通用接口

一些常用接口進(jìn)行說(shuō)明。

11.1 臨界段

進(jìn)入和退出臨界段的宏在 task.h 中定義,進(jìn)入和退出臨界段的宏分中斷保護(hù)版本和非中斷版本,但最終都是通過(guò)開(kāi)/關(guān)中斷來(lái)實(shí)現(xiàn)。主要用于對(duì)全局變量的控制,系統(tǒng)使用非常多,但實(shí)際項(xiàng)目中沒(méi)使用,因?yàn)槿肿兞康漠惓TL問(wèn)時(shí)小概率問(wèn)題,只是測(cè)試沒(méi)發(fā)現(xiàn),理論上是存在問(wèn)題的。

1.?/*?在中斷場(chǎng)合*/??{???
2.?????uint32_t?ulReturn;???
3.?????
4.?????ulReturn?=?taskENTER_CRITICAL_FROM_ISR();?/*?進(jìn)入臨界段,臨界段可以嵌套?*/???
5.?????
6.?????/*?臨界段代碼?*/??????
7.????????
8.?????taskEXIT_CRITICAL_FROM_ISR(?ulReturn?);??}???/*?退出臨界段?*/
1.??/*?在非中斷場(chǎng)合?*/??{???
2.???????
3.?????taskENTER_CRITICAL();?????/*?進(jìn)入臨界段?*/?
4.?
5.?????/*?臨界段代碼?*/????
6.?????
7.?????taskEXIT_CRITICAL();??}???/*?退出臨界段*/??

11.2 任務(wù)阻塞延時(shí)

vTaskDelay ()阻塞延時(shí),任務(wù)調(diào)用該延時(shí)函數(shù)后會(huì)被剝離 CPU 使用權(quán),進(jìn)入阻塞狀態(tài),直到延時(shí)結(jié)束。但是該函數(shù)不能用在中斷服務(wù)和定時(shí)回調(diào)函數(shù)。延時(shí)單位是tick。

11.3 獲取系統(tǒng)時(shí)鐘計(jì)數(shù)值

1.?TickType_t?xTaskGetTickCount(?void?)??
2.?TickType_t?xTaskGetTickCountFromISR(?void?)??

注意該接口分任務(wù)版和中斷版,該接口獲取的是tick計(jì)數(shù)值,需要結(jié)合系統(tǒng)時(shí)鐘頻率轉(zhuǎn)換成時(shí)間。

11.4 中斷回調(diào)函數(shù)

和其它平臺(tái)不同,中斷回調(diào)中釋放中斷標(biāo)記即可,freeRTOS中,中斷觸發(fā)后,可能某些阻塞的任務(wù)獲取了相關(guān)信號(hào),需要立刻執(zhí)行,因此中斷服務(wù)發(fā)送消息后,需要主動(dòng)查詢(xún)阻塞任務(wù)的情況,執(zhí)行任務(wù)切換動(dòng)作。

1.?static?uint32_t?ulExampleInterruptHandler(?void?)??
2.?
{??
3.?????BaseType_t?xHigherPriorityTaskWoken;??
4.?
5.?????xQueueSendToBackFromISR?(xQueueRx,&cChar,&xHigherPriorityTaskWoken);??
6.?????portYIELD_FROM_ISR(xHigherPriorityTaskWoken);??
7.?}??


猜你喜歡

基于RT-Thread的智慧路燈案例實(shí)驗(yàn)分享

【RT-Thread筆記】BH1750軟件包的使用


1024G 嵌入式資源大放送!包括但不限于C/C++、單片機(jī)、Linux等。在公眾號(hào)聊天界面回復(fù)1024,即可免費(fèi)獲??!

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車(chē)的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車(chē)技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車(chē)工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車(chē)。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車(chē) 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱(chēng),數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱(chēng)"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉