新聞中心
嵌入式系統不只是ARM+Linux,不是只有安卓,凡是電子產品都可稱為嵌入式系統。物聯網行業(yè)的興起,也提升了FreeRTOS市場占有率。本文就是介紹FreeRTOS基礎及其應用,只是個人整理,可能存在問題,其目的只是簡要介紹系統的基礎,只能作為入門資料。

目錄
- 一、 為什么要學習RTOS
- 二、 操作系統基礎
- 三、 初識 FreeRTOS
- 四、 任務
- 五、 隊列
- 六、 軟件定時器
- 七、 信號量
- 八、 事件
- 九、 任務通知
- 十、 內存管理
- 十一、 通用接口
一、 為什么要學習 RTOS
進入嵌入式這個領域,入門首先接觸的是單片機編程,尤其是C51 單片機來,基礎的單片機編程通常都是指裸機編程,即不加入任何 RTOS(Real Time Operating System 實時操作系統)。常用的有國外的FreeRTOS、μC/OS、RTX 和國內的 RT-thread、Huawei LiteOS 和 AliOS-Things 等,其中開源且免費的 FreeRTOS 的市場占有率較高。
1.1 前后臺系統
在裸機系統中,所有的操作都是在一個無限的大循環(huán)里面實現,支持中斷檢測。外部中斷緊急事件在中斷里面標記或者響應,中斷服務稱為前臺,main 函數里面的while(1)無限循環(huán)稱為后臺,按順序處理業(yè)務功能,以及中斷標記的可執(zhí)行的事件。小型的電子產品用的都是裸機系統,而且也能夠滿足需求。
1.2 多任務系統
多任務系統的事件響應也是在中斷中完成的,但是事件的處理是在任務中完成的。如果事件對應的任務的優(yōu)先級足夠高,中斷對應的事件會立刻執(zhí)行。相比前后臺系統,多任務系統的實時性又被提高了。
在多任務系統中,根據程序的功能,把這個程序主體分割成一個個獨立的,無限循環(huán)且不能返回的子程序,稱之為任務。每個任務都是獨立的,互不干擾的,且具備自身的優(yōu)先級,它由操作系統調度管理。加入操作系統后,開發(fā)人員不需要關注每個功能模塊之間的沖突,重心放在子程序的實現。缺點是整個系統隨之帶來的額外RAM開銷,但對目前的單片機的來影響不大。
1.3 學習RTOS的意義
學習 RTOS,一是項目需要,隨著產品要實現的功能越來越多,單純的裸機系統已經不能完美地解決問題,反而會使編程變得更加復雜,如果想降低編程的難度,就必須引入 RTOS實現多任務管理。二是技能需要,掌握操作系統,和基于RTOS的編程,實現更好的職業(yè)規(guī)劃,對個人發(fā)展尤其是錢途是必不可少的。
以前一直覺得學操作系統就必須是linux,實際每個系統都有其應用場景,對于物聯網行業(yè),殺雞焉用牛刀,小而美,且應用廣泛的FreeRTOS 是首選。有一個操作系統的基礎,即使后續(xù)基于其他系統開發(fā)軟件,也可觸類旁通,對新技術快速入門。目前接觸的幾款芯片都是基于FreeRTOS。
如何學習RTOS?最簡單的就是在別人移植好的系統之上,看看 RTOS 里面的 API 使用說明,然后調用這些 API 實現自己想要的功能即可。完全不用關心底層的移植,這是最簡單快速的入門方法。這種學習方式,如果是做產品,可以快速的實現功能,弊端是當程序出現問題的時候,如果對RTOS不夠了解,會導致調試困難,無從下手。
各種RTOS內核實現方式都差不多,我們只需要深入學習其中一款就行。萬變不離其宗,正如掌握了C51基礎,后續(xù)換其他型號或者更高級的ARM單片機,在原理和方法上,都是有借鑒意義,可以比較快的熟悉并掌握新單片機的使用。
二、 操作系統基礎
2.1 鏈表
鏈表作為 C 語言中一種基礎的數據結構,在平時寫程序的時候用的并不多,但在操作系統里面使用的非常多。FreeRTOS 中存在著大量的基礎數據結構鏈表和鏈表項的操作(list 和 list item)。FreeRTOS 中與鏈表相關的操作均在 list.h 和 list.c 這兩個文件中實現。
鏈表比數組,最大優(yōu)勢是占用的內存空間可以隨著需求擴大或縮小,動態(tài)調整。實際FreeRTOS中各種任務的記錄都是依靠鏈表動態(tài)管理,具體的可以參考源碼的任務控制塊tskTCB。任務切換狀態(tài),就是將對應的鏈表進行操作,鏈表操作涉及創(chuàng)建和插入、刪除和查找。
2.2 隊列
隊列是一種只允許在表的前端(front)進行刪除操作,而在表的后端(rear)進行插入操作。隊尾放入數據,對頭擠出。先進先出,稱為FIFO
2.3 任務
在裸機系統中,系統的主體就是 main 函數里面順序執(zhí)行的無限循環(huán),這個無限循環(huán)里面 CPU 按照順序完成各種事情。在多任務系統中,根據功能的不同,把整個系統分割成一個個獨立的且無法返回的函數,這個函數我們稱為任務。系統中的每一任務都有多種運行狀態(tài)。系統初始化完成后,創(chuàng)建的任務就可以在系統中競爭一定的資源,由內核進行調度。
- ? 就緒(Ready):該任務在就緒列表中,就緒的任務已經具備執(zhí)行的能力,只等待調度器進行調度,新創(chuàng)建的任務會初始化為就緒態(tài)。
- ? 運行(Running):該狀態(tài)表明任務正在執(zhí)行,此時它占用處理器,調度器選擇運行的永遠是處于最高優(yōu)先級的就緒態(tài)任務。
- ? 阻塞(Blocked):任務當前正在等待某個事件,比如信號量或外部中斷。
- ? 掛起態(tài)(Suspended):處于掛起態(tài)的任務對調度器而言是不可見的。
掛起態(tài)與阻塞態(tài)的區(qū)別,當任務有較長的時間不允許運行的時候,我們可以掛起任務,這樣子調度器就不會管這個任務的任何信息,直到調用恢復任務的 接口;而任務處于阻塞態(tài)的時候,系統還需要判斷阻塞態(tài)的任務是否超時,是否可以解除阻塞。
各任務運行時使用消息、信號量等方式進行通信,不能是全局變量。任務通常會運行在一個死循環(huán)中,不會退出,如果不再需要,可以調用刪除任務。
2.4 臨界區(qū)
臨界區(qū)就是一段在執(zhí)行的時候不能被中斷的代碼段。在多任務操作系統里面,對全局變量的操作不能被打斷,不能執(zhí)行到一半就被其他任務再次操作。一般被打斷,原因就是系統調度或外部中斷。對臨界區(qū)的保護控制,歸根到底就是對系統中斷的使能控制。在使用臨界區(qū)時,關閉中斷響應,對部分優(yōu)先級的中斷進行屏蔽,因此臨界區(qū)不允許運行時間過長。為了對臨界區(qū)進行控制,就需要使用信號量通信,實現同步或互斥操作。
三、 初識 FreeRTOS
3.1 FreeRTOS源碼
FreeRTOS 由美國的 Richard Barry 于 2003 年發(fā)布, 2018 年被亞馬遜收購,改名為 AWS FreeRTOS,版本號升級為 V10,支持MIT開源協議,亞馬遜收購 FreeRTOS 也是為了進入物聯網和人工智能,新版本增加了物聯網行業(yè)的網絡協議等功能。
FreeRTOS 是開源免費的,可從官網 www.freertos.org 下載源碼和說明手冊。例如展銳的UIS8910使用的是V10。以FreeRTOSv10.4.1為例,包含 Demo 例程,Source內核的源碼,License許可文件。
3.1.1 Source 文件夾
FreeRTOS/ Source 文件夾下的文件:
包括FreeRTOS 的通用的頭文件include和 C 文件,包括任務、隊列、定時器等,適用于各種編譯器和處理器,是通用的。
需要特殊處理適配的在portblle文件夾,其下內容與編譯器和處理器相關, FreeRTOS 要想運行在一個單片機上面,它們就必須關聯在一起,通常由匯編和 C 聯合編寫。通常難度比較高,不過一般芯片原廠提供移植好的接口文件。這里不介紹移植的方法,因為自己也不明白。
Portblle/MemMang 文件夾下存放的是跟內存管理相關的,總共有五個 heap 文件,有5種內存動態(tài)分配方式,一般物聯網產品選用 heap4.c 。
3.1.2 Demo 文件夾
里面包含了 FreeRTOS 官方為各個單片機移植好的工程代碼,FreeRTOS 為了推廣自己,會給針對不同半導體廠商的評估板實現基礎功能范例, Demo下就是參考范例。
3.1.3 FreeRTOSConfig.h配置
FreeRTOSConfig.h頭文件對FreeRTOS 所需的功能的宏均做了定義,需要根據應用情況配置合適的參數,其作用類似MTK功能機平臺的主mak文件,部分定義如下:
- #define configUSE_PREEMPTION 1
- #define configUSE_IDLE_HOOK 0
- #define configUSE_TICK_HOOK 0
- #define configCPU_CLOCK_HZ ( SystemCoreClock )
- #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
例如系統時鐘tick等參數在就這個文件配置,具體作用可以看注釋。一般情況下使用SDK不需要改動,特殊情況下咨詢原廠再調整。
3.2 FreeRTOS 編碼規(guī)范
接觸一個新平臺或者SDK,明白它的編碼規(guī)范,文件作用,可以提高源碼閱讀效率,快速熟悉其內部實現。
3.2.1 數據類型
FreeRTOS針對不同的處理器,對標準C的數據類型進行了重定義。
- #define portCHAR char
- #define portFLOAT float
- #define portDOUBLE double
- #define portLONG long
- #define portSHORT short
- #define portSTACK_TYPE uint32_t
- #define portBASE_TYPE long
應用編碼中,推薦使用的是下面這種風格。
- typedef int int32_t;
- typedef short int16_t;
- typedef char int8_t;
- typedef unsigned int uint32_t;
- typedef unsigned short uint16_t;
- typedef unsigned char uint8_t;
3.2.2 變量名
FreeRTOS 中,定義變量的時候往往會把變量的類型當作前綴,好處看到就知道其類型。
char 型變量的前綴是 c
short 型變量的前綴是 s
long 型變量的前綴是 l
復雜的結構體,句柄等定義的變量名的前綴是 x
變量是無符號型的再加前綴 u,是指針變量則加前綴 p
3.2.3 函數名
函數名包含了函數返回值的類型、函數所在的文件名和函數的功能,如果是私有的函數則會加一個 prv(private)的前綴。
例如vTaskPrioritySet()函數的返回值為 void 型,在 task.c 這個文件中定義。
3.2.4 宏
宏內容是由大寫字母表示,前綴是小寫字母,表示該宏在哪個頭文件定義,如:
- #define taskYIELD() portYIELD()
表示該宏是在task.h。
3.2.5 個人解讀
1、編碼不缺編碼規(guī)范,但是實際使用中很難完全依照標準執(zhí)行,即使freeRTOS源碼也是如此。
2、關于函數或者宏定義中帶文件名的作用,使用Source Insight 編輯代碼,該前綴的意義不大。
3、規(guī)則是活的,只要所有人都按一個規(guī)則執(zhí)行,它就是標準。
3.3 FreeRTOS應用開發(fā)
關于freeRTOS的應用開發(fā),主要是任務的創(chuàng)建和調度,任務間的通信與同步,涉及隊列、信號量等操作系統通用接口。結合應用需求,涉及定時器、延時、中斷控制等接口。
特別說明,有些功能的實現方式有多種形式,只針對常用方式進行說明,例如task的創(chuàng)建,只說明動態(tài)創(chuàng)建方式,因為很少使用靜態(tài)方式。
四、 任務
4.1 創(chuàng)建任務
xTaskCreate()使用動態(tài)內存的方式創(chuàng)建一個任務。
- ret = xTaskCreate((TaskFunction_t) master_task_main, /* 任務入口函數 */(1)
- “MASTER”, /* 任務名字 */(2)
- 64*1024, /* 任務棧大小 */(3)
- NULL, ,/* 任務入口函數參數 */(4)
- TASK_PRIORITY_NORMAL, /* 任務的優(yōu)先級 */(5)
- &task_master_handler); /* 任務控制塊指針 */(6)
創(chuàng)建任務就是軟件運行時的一個while(1)的入口,一般閱讀其他代碼,找到這個函數,再跟蹤到任務入口函數,學習基于freeRTOS系統的代碼,首先就是找到main和這個接口。
(1):任務入口函數,即任務函數的名稱,需要我們自己定義并且實現。
(2):任務名字,字符串形式,最大長度由 FreeRTOSConfig.h 中定義的 configMAX_TASK_NAME_LEN 宏指定,多余部分會被自動截掉,只是方便調試。
(3):任務堆棧大小,單位為字, 4 個字節(jié),這個要注意,否則系統內存緊缺。
(4):任務入口函數形參,不用的時候配置為 0 或者NULL 即可。
(5) :任務的優(yōu)先級,在 FreeRTOS 中,數值越大優(yōu)先級越高,0 代表最低優(yōu)先級?;谄銼DK開發(fā),可將自定義的所有業(yè)務功能task設為同一個優(yōu)先級,按時間片輪詢調度。
(6):任務控制塊指針,使用動態(tài)內存的時候,任務創(chuàng)建函數 xTaskCreate()會返回一個指針指向任務控制塊,也可以設為NULL,因為任務句柄后期可以不使用。
4.2 開啟調度
當任務創(chuàng)建成功后處于就緒狀態(tài)(Ready),在就緒態(tài)的任務可以參與操作系統的調度。操作系統任務調度器只啟動一次,之后就不會再次執(zhí)行了,FreeRTOS 中啟動任務調度器的函數是 vTaskStartScheduler(),并且啟動任務調度器的時候就不會返回,從此任務管理都由FreeRTOS 管理,此時才是真正進入實時操作系統中的第一步。
vTaskStartScheduler開啟調度時,順便會創(chuàng)建空閑任務和定時器任務。
FreeRTOS 為了任務啟動和任務切換使用了三個異常:SVC、PendSV 和SysTick。
SVC(系統服務調用,亦簡稱系統調用)用于任務啟動。
PendSV(可掛起系統調用)用于完成任務切換,它是可以像普通的中斷一樣被掛起的,它的最大特性是如果當前有優(yōu)先級比它高的中斷在運行,PendSV會延遲執(zhí)行,直到高優(yōu)先級中斷執(zhí)行完畢,這樣產生的PendSV 中斷就不會打斷其他中斷的運行。
SysTick 用于產生系統節(jié)拍時鐘,提供一個時間片,如果多個任務共享同一個優(yōu)先級,則每次 SysTick 中斷,下一個任務將獲得一個時間片。
FreeRTOS 中的任務是搶占式調度機制,高優(yōu)先級的任務可打斷低優(yōu)先級任務,低優(yōu)先級任務必須在高優(yōu)先級任務阻塞或結束后才能得到調度。相同優(yōu)先級的任務采用時間片輪轉方式進行調度(也就是分時調度),時間片輪轉調度僅在當前系統中無更高優(yōu)先級就緒任務存在的情況下才有效。
4.3 啟動方式
FreeRTOS有兩種啟動方式,效果一樣,看個人喜好。
第一種:main 函數中將硬件初始化, RTOS 系統初始化,所有任務的創(chuàng)建完成,最后一步開啟調度。目前看到的幾個芯片SDK都是這種方式。
第二種:main 函數中將硬件和 RTOS 系統先初始化好,只創(chuàng)建一個任務后就啟動調度器,然后在這個任務里面創(chuàng)建其它應用任務,當所有任務都創(chuàng)建成功后,啟動任務再把自己刪除。
4.4 任務創(chuàng)建源碼分析
xTaskCreate()創(chuàng)建任務。
- BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
- const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
- const configSTACK_DEPTH_TYPE usStackDepth,
- void * const pvParameters,
- UBaseType_t uxPriority,
- TaskHandle_t * const pxCreatedTask )
- {
- TCB_t * pxNewTCB;
- BaseType_t xReturn;
- /* If the stack grows down then allocate the stack then the TCB so the stack
- * does not grow into the TCB. Likewise if the stack grows up then allocate
- * the TCB then the stack. */
- #if ( portSTACK_GROWTH > 0 )
- {
- /**/
- }
- #else /* portSTACK_GROWTH */
- {
- StackType_t * pxStack;
- /* Allocate space for the stack used by the task being created. */
- 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. */
- if( pxStack != NULL )
- {
- /* Allocate space for the TCB. */
- 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. */
- if( pxNewTCB != NULL )
- {
- /* Store the stack location in the TCB. */
- pxNewTCB->pxStack = pxStack;
- }
- else
- {
- /* The stack cannot be used as the TCB was not created. Free
- * it again. */
- vPortFree( pxStack );
- }
- }
- else
- {
- pxNewTCB = NULL;
- }
- }
- #endif /* portSTACK_GROWTH */
- if( pxNewTCB != NULL )
- {
- #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
- {
- /* Tasks can be created statically or dynamically, so note this
- * task was created dynamically in case it is later deleted. */
- pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
- }
- #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
- prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
- prvAddNewTaskToReadyList( pxNewTCB ); //將新任務加入到就緒鏈表候著
- xReturn = pdPASS;
- }
- else
- {
- xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
- }
- return xReturn;
- }
申請任務控制塊內存,檢查配置參數,初始化,將任務信息加入到就緒鏈表,等待調度。前面鏈表部分提到,freeRTOS的任務信息都是使用鏈表記錄,在task.c有
- PRIVILEGED_DATA static List_t pxReadyTasksLists[configMAX_PRIORITIES];//就緒
- PRIVILEGED_DATA static List_t xDelayedTaskList1; //延時
- PRIVILEGED_DATA static List_t xDelayedTaskList2;
- PRIVILEGED_DATA static List_t xPendingReadyList; //掛起
- PRIVILEGED_DATA static List_t xSuspendedTaskList; //阻塞
分別記錄就緒態(tài)、阻塞態(tài)和掛起的任務,其中阻塞態(tài)有2個,是因為特殊考慮,時間溢出 的問題,實際開發(fā)單片機項目計時超過24h的可以借鑒。其中pxReadyTasksLists鏈表數組,其下標就是任務的優(yōu)先級。
4.5 任務調度源碼分析
創(chuàng)建完任務的時候,vTaskStartScheduler開啟調度器,空閑任務、定時器任務也是在開啟調度函數中實現的。
為什么要空閑任務?因為 FreeRTOS一旦啟動,就必須要保證系統中每時每刻都有一個任務處于運行態(tài)(Runing),并且空閑任務不可以被掛起與刪除,空閑任務的優(yōu)先級是最低的,以便系統中其他任務能隨時搶占空閑任務的 CPU 使用權。這些都是系統必要的東西,也無需自己實現。
- void vTaskStartScheduler( void )
- {
- BaseType_t xReturn;
- /* Add the idle task at the lowest priority. */
- #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
- {
- /***/
- }
- #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */
- {
- /*創(chuàng)建空閑任務*/
- xReturn = xTaskCreate( prvIdleTask,
- configIDLE_TASK_NAME,
- configMINIMAL_STACK_SIZE,
- ( void * ) NULL,
- portPRIVILEGE_BIT, //優(yōu)先級為0
- &xIdleTaskHandle );
- }
- #endif /* configSUPPORT_STATIC_ALLOCATION */
- #if ( configUSE_TIMERS == 1 )
- {
- if( xReturn == pdPASS )
- {
- //創(chuàng)建定時器task,接收開始、結束定時器等命令
- xReturn = xTimerCreateTimerTask();
- }
- else
- {
- mtCOVERAGE_TEST_MARKER();
- }
- }
- #endif /* configUSE_TIMERS */
- if( xReturn == pdPASS )
- {
- /* freertos_tasks_c_additions_init() should only be called if the user
- * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
- * the only macro called by the function. */
- #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
- {
- freertos_tasks_c_additions_init();
- }
- #endif
- portDISABLE_INTERRUPTS();
- #if ( configUSE_NEWLIB_REENTRANT == 1 )
- {
- _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
- }
- #endif /* configUSE_NEWLIB_REENTRANT */
- xNextTaskUnblockTime = portMAX_DELAY;
- xSchedulerRunning = pdTRUE;
- xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
- portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
- traceTASK_SWITCHED_IN();
- /* Setting up the timer tick is hardware specific and thus in the
- * portable interface. */
- if( xPortStartScheduler() != pdFALSE )
- {
- /* 系統開始運行 */
- }
- else
- {
- /* Should only reach here if a task calls xTaskEndScheduler(). */
- }
- }
- else
- {
- /*****/
- }
4.6 任務狀態(tài)切換
FreeRTOS 系統中的每一個任務都有多種運行狀態(tài),具體如下:
任務掛起函數
- vTaskSuspend()
掛起指定任務,被掛起的任務絕不會得到 CPU 的使用權
- vTaskSuspendAll()
將所有的任務都掛起 ? 任務恢復函數
- vTaskResume()
- vTaskResume()
- xTaskResumeFromISR()
任務恢復就是讓掛起的任務重新進入就緒狀態(tài),恢復的任務會保留掛起前的狀態(tài)信息,在恢復的時候根據掛起時的狀態(tài)繼續(xù)運行。xTaskResumeFromISR() 專門用在中斷服務程序中。無論通過調用一次或多次vTaskSuspend()函數而被掛起的任務,也只需調用一次恢復即可解掛 。
? 任務刪除函數 vTaskDelete()用于刪除任務。當一個任務可以刪除另外一個任務,形參為要刪除任 務創(chuàng)建時返回的任務句柄,如果是刪除自身, 則形參為 NULL。
4.7 任務使用注意點
1、中斷服務函數是不允許調用任何會阻塞運行的接口。一般在中斷服務函數中只做標記事件的發(fā)生,然后通知任務,讓對應任務去執(zhí)行相關處理 。
2、將緊急的處理事件的任務優(yōu)先級設置偏高一些。
3、空閑任務(idle 任務)是 FreeRTOS 系統中沒有其他工作進行時自動進入的系統任務,永遠不會掛起空閑任務,不應該陷入死循環(huán)。
4、創(chuàng)建任務使用的內存不要過多,按需申請。如果浪費太多,后續(xù)應用申請大空間可能提示內存不足。
五、 隊列
5.1 隊列的概念
隊列用于任務間通信的數據結構,通過消息隊列服務,任務或中斷服務將消息放入消息隊列中。其他任務或者自身從消息隊列中獲得消息。實現隊列可以在任務與任務間、中斷和任務間傳遞信息。隊列操作支持阻塞等待,向已經填滿的隊列發(fā)送數據或者從空隊列讀出數據,都會導致阻塞,時間自定義。消息隊列的運作過程具如下:
5.2 隊列創(chuàng)建
xQueueCreate()用于創(chuàng)建一個新的隊列并返回可用于訪問這個隊列的句柄。隊列句柄其實就是一個指向隊列數據結構類型的指針。
- master_queue = xQueueCreate(50, sizeof(task_message_struct_t));
創(chuàng)建隊列,占用50個單元,每個單元為sizeof(task_message_struct_t)字節(jié),和 malloc比較類似。其最終使用的函數是 xQueueGenericCreate(),后續(xù)信號量等也是使用它創(chuàng)建,只是最后的隊列類型不同。
申請內存后,xQueueGenericReset再對其進行初始化,隊列的結構體xQUEUE成員:
- typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
- {
- int8_t * pcHead; /*< Points to the beginning of the queue storage area. */
- int8_t * pcWriteTo; /*< Points to the free next place in the storage area. */
- //類型
- union
- {
- QueuePointers_t xQueue; /*< Data required exclusively when this structure is used as a queue. */
- SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */
- } u;
- //當前向隊列寫數據阻塞的任務列表或者從隊列取數阻塞的鏈表
- List_t xTasksWaitingToSend;
- List_t xTasksWaitingToReceive;
- //隊列里有多少個單元被占用,應用中需要
- volatile UBaseType_t uxMessagesWaiting;
- UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
- UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */
- /******/
- } xQUEUE;
5.3 隊列刪除
隊列刪除函數 vQueueDelete()需傳入要刪除的消息隊列的句柄即可,刪除之后這個消息隊列的所有信息都會被系統回收清空,而且不能再次使用這個消息隊列了。實際應用中很少使用。
5.4 向隊列發(fā)送消息
任務或者中斷服務程序都可以給消息隊列發(fā)送消息,當發(fā)送消息時,如果隊列未滿或者允許覆蓋入隊,FreeRTOS 會將消息拷貝到消息隊列隊尾,否則,會根據用戶指定的超時時間進行阻塞,消息發(fā)送接口很多,最簡單的是 xQueueSend(),用于向隊列尾部發(fā)送一個隊列消息。消息以拷貝的形式入隊,該函數絕對不能在中斷服務程序里面被調用,中斷中必須使用帶有中斷保護功能的 xQueueSendFromISR()來代替。
- BaseType_t xQueueSend(QueueHandle_t xQueue,const void* pvItemToQueue, TickType_t xTicksToWait);
用于向隊列尾部發(fā)送一個隊列消息。
參數
xQueue 隊列句柄
pvItemToQueue 指針,指向要發(fā)送到隊列尾部的隊列消息。
xTicksToWait 隊列滿時,等待隊列空閑的最大超時時間。如果隊列滿并且xTicksToWait 被設置成 0,函數立刻返回。超時時間的單位為系統節(jié)拍周期 tick,延時為 portMAX_DELAY 將導致任務掛起(沒有超時)。
返回值
消息發(fā)送成功成功返回 pdTRUE,否則返回 errQUEUE_FULL。
- BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
- const void * const pvItemToQueue,
- TickType_t xTicksToWait,
- const BaseType_t xCopyPosition ) //發(fā)送數據到消息隊列的位置
1. BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 2. const void * const pvItemToQueue, 3. TickType_t xTicksToWait, 4. const BaseType_t xCopyPosition ) //發(fā)送數據到消息隊列的位置
一般使用xQueueSend和xQueueSendFromISR,如不確定當前運行的是系統服務,還是中斷服務,一般ARM都支持查詢中斷狀態(tài)寄存器判斷,可以封裝一層接口,只管發(fā)消息,內部判斷是否使用支持中斷嵌套的版本,UIS8910就是如此。特殊情況下,如發(fā)送網絡數據包未收到服務器響應,期望立刻入隊再次發(fā)送它,可以xQueueSendToFront向隊頭發(fā)消息。
5.5 從隊列讀取消息
當任務試圖讀隊列中的消息時,可以指定一個阻塞超時時間,當且僅當消息隊列中有消息的時候,任務才能讀取到消息。如果隊列為空,該任務將保持阻塞狀態(tài)以等待隊列數據有效。當其它任務或中斷服務程序往其等待的隊列中寫入了數據,該任務將自動由阻塞態(tài)轉為就緒態(tài)。當任務等待的時間超過了指定的阻塞時間,即使隊列中尚無有效數據,任務也會自動從阻塞態(tài)轉移為就緒態(tài)。所有的task主入口while循環(huán)體都是按這個執(zhí)行。例如:
- static void track_master_task_main()
- {
- track_task_message_struct_t queue_item = {0};
- /****/
- while(1)
- {
- if(xQueueReceive(master_queue, &queue_item, portMAX_DELAY))//阻塞等待
- {
- track_master_task_msg_handler(&queue_item);
- }
- }
- }
xQueueReceive()用于從一個隊列中接收消息并把消息從隊列中刪除。如果不想刪除消息的話,就調用 xQueuePeek()函數。xQueueReceiveFromISR()與xQueuePeekFromISR()是中斷版本,用于在中斷服務程序中接收一個隊列消息并把消息。這兩個函數只能用于中斷,是不帶有阻塞機制的,實際項目沒有使用。
5.6 查詢隊列使用情況
uxQueueMessagesWaiting()查詢隊列中存儲的信息數目,具有中斷保護的版本為uxQueueMessagesWaitingFromISR()。查詢隊列的空閑數目uxQueueSpacesAvailable()。
5.7 隊列使用注意點
使用隊列函數需要注意以下幾點:
1、中斷中必須使用帶FromISR后綴的接口;
2、發(fā)送或者是接收消息都是以拷貝的方式進行,如果消息內容過于龐大,可以將消息的地址作為消息進行發(fā)送、接收。
- typedef struct
- {
- TaskHandle_t src_mod_id;
- int message_id;
- int32_t param;
- union
- {
- int32_t result;
- int32_t socket_id;
- };
- void* pvdata; //大數據使用動態(tài)申請內存保存,隊列只傳遞指針
- } track_task_message_struct_t;
3、隊列并不屬于任何任務,所有任務都可以向同一隊列寫入和讀出,一個隊列可以由多任務或中斷讀寫。
4、隊列的深度要結合實際,可以多申請點,前提是每個隊列單元盡可能小。
5、隊列存在一定限制,在隊頭沒有取出來之前,是無法取出第二個,和STL鏈表存在差異。
六、 軟件定時器
6.1 軟件定時器的概念
定時器有硬件定時器和軟件定時器之分,硬件定時器是芯片本身提供的定時功能精度高,并且是中斷觸發(fā)方式。軟件定時器是由操作系統封裝的接口,它構建在硬件定時器基礎之上,使系統能夠提供不受硬件定時器資源限制,其實現的功能與硬件定時器也是類似的。
在操作系統中,通常軟件定時器以系統節(jié)拍周期為計時單位。系統節(jié)拍配置為configTICK_RATE_HZ,該宏在 FreeRTOSConfig.h 中,一般是100或者1000。根據實際系統 CPU 的處理能力和實時性需求設置合適的數值,系統節(jié)拍周期的值越小,精度越高,但是系統開銷也將越大,因為這代表在 1 秒中系統進入時鐘中斷的次數也就越多。
6.2 軟件定時器創(chuàng)建
軟件定時器需先創(chuàng)建才允許使用,動態(tài)創(chuàng)建方式是xTimerCreate(),返回一個句柄。軟件定時器在創(chuàng)建成功后是處于休眠狀態(tài)的,沒有開始計時運行。FreeRTOS的軟件定時器支持單次模式和周期模式。
單次模式:當用戶創(chuàng)建了定時器并啟動了定時器后,定時時間到了,只執(zhí)行一次回調函數,之后不再執(zhí)行。周期模式:定時器會按照設置的定時時間循環(huán)執(zhí)行回調函數,直到用戶將定時器停止或刪除。
實際項目中使用這種模式對單片機喂狗就比較省事。
- TimerHandle_t xTimerCreate( const char * const pcTimerName, //定時器名稱
- const TickType_t xTimerPeriodInTicks, //定時時間
- const UBaseType_t uxAutoReload, //是否自動重載
- void * const pvTimerID, //回調函數的參數
- TimerCallbackFunction_t pxCallbackFunction ) //回調函數
6.3 軟件定時器開啟
新創(chuàng)建的定時器沒有開始計時啟動,可以使用
- xTimerStart()、
- xTimerReset()、
- xTimerStartFromISR() 、xTimerResetFromISR()
- xTimerChangePeriod()、xTimerChangePeriodFromISR()
這些函數將其狀態(tài)轉換為活躍態(tài),開始運行。區(qū)別:如果定時器設定60秒間隔,已經運行了30秒,reset是將定時器重置為原來設定的時間間隔,也就是重新開始延時60秒。ChangePeriod重新設置計時周期。
6.4 軟件定時器停止
xTimerStop() 用于停止一個已經啟動的軟件定時器,xTimerStopFromISR()是中斷版本。
6.5 軟件定時器刪除
xTimerDelete()用于刪除一個已經被創(chuàng)建成功的軟件定時器,釋放資源,刪除之后不能再使用。實際項目中,任務和隊列都是按需創(chuàng)建,一直使用,但是定時器不使用的就應該刪除,并且刪除后一定要將句柄置為NULL。
6.6 軟件定時器源碼分析
軟件定時器任務是在系統開始調度的時候就被創(chuàng)建:vTaskStartScheduler()—xTimerCreateTimerTask。
- BaseType_t xTimerCreateTimerTask( void )
- { 
本文標題:FreeRTOS及其應用,萬字長文,基礎入門
URL標題:http://www.5511xx.com/article/djsgejo.html


咨詢
建站咨詢
