文章目錄
- CubeMX使用FreeRTOS編程指南
- 一、開發前言
- 1.1 軟體準備
- 1.2 開啟FreeRTOS
- 二、配置界面
- 三、系統設定
- 2.1 調度內核設定
- 2.2 記憶體管理設定
- 2.3 鉤子函式配置
- 2.5 任務運行追蹤配置
- 2.6 協程配置
- 2.7 軟體定時器配置
- 2.8 中斷優先級配置
- 三、內核裁剪
- 四、創建任務與佇列
- 4.1 CubeMX 下任務創建與配置
- 4.2 CubeMX 下佇列的創建與配置
- 五、創建定時器和信號量
- 5.1 CubeMX下定時器的創建和配置
- 5.2 CubeMX下信號量的創建和配置
- 六、創建互斥量
- 6.1 CubeMX下互斥量的創建和配置
- 七、創建事件標志組
- 7.1 CubeMX下事件的創建和配置
- 八、用戶常量
- 九、任務通知
- 十、系統內核配置
CubeMX使用FreeRTOS編程指南
一、開發前言
1.1 軟體準備
-
STM32CubeMX 代碼生成軟體
-
MDK 集成代碼開發環境
1.2 開啟FreeRTOS
新建一個 CubeMX 工程,在配置好時鐘后,點擊 Middleware -> 選擇 FreeRTOS -> 下拉框選擇 V2 版本 CMSIS

到此在 CubeMX 中就已經開啟 FreeRTOS 系統了,下面分享 FreeRTOS 的配置:
二、配置界面
開啟 FreeRTOS 之后,可以看到配置項主要分為以下幾個部分

這幾個部分的主要功能如下表:
| 配置項 | 功能 |
|---|---|
| Tasks and Queues | 任務與佇列,用于配置任務體以及訊息佇列; |
| Timers and Semaphores | 軟體定時器與信號量,用于配置內核物件 (軟體定時器和信號量); |
| Mutexes | 互斥量,用于配置內核物件(互斥量) |
| Events | 事件,配置內核物件(事件) |
| FreeRTOS Heap Usage | 查看用戶任務和系統任務的堆占用 |
| Config Parameters | 系統的引數配置 |
| Include Parameters | 系統的功能裁剪 |
| Advanced Settings | CubeMX 生成代碼預配置項 |
| User Constants | 用戶常量定義 |
以上各個功能分的很清晰,我們需要配置什么功能就去對應的選項下進行配置,下面根據各個配置項進行詳細配置介紹
三、系統設定
首先我們先了解一下 Config Parameters,他的配置引數如下

引數功能表:
| 引數 | 功能 |
|---|---|
| API | 顯示 FreeRTOS API 介面版本 |
| Version | 顯示 FreeRTOS 內核版本 顯示 CMSIS 版本 |
| Kernel Setting | FreeRTOS 調度內核設定 |
| Memory management setting | 記憶體管理設定 |
| Hook function related definitions | 鉤子函式有關定義 |
| Run time and task stats gathering related definitions | 系統運行時的引數收集配置 |
| Co-routine related definitions | 協程配置 |
| Software timer definitons | 軟體定時器任務配置 |
| Interrupt nesting behaviour configuration | 中斷優先級配置 |
API 和 Version 不過多解釋,顯示版本資訊
2.1 調度內核設定
Kernel Setting 是 FreeRTOS 的調度內核配置,展開后有下面的配置項,使用時一般保持默認,也可以根據需要修改

- USE_PREEMPTION
USE_PREEMPTION 是 RTOS 的調度方式選擇,為 1 時使用搶占式調度器,為 0 時使用協程,如果使用搶占式調度器的話內核會在每個時鐘節拍中斷中進行任務切換,當使用協程的話會在如下地方進行任務切換
- 一個任務呼叫了函式 taskYIELD(),
- 一個任務呼叫了可以使任務進入阻塞態的 API 函式,
- 應用程式明確定義了在中斷中執行背景關系切換,
- CPU_CLOCK_HZ
CPU_CLOCK_HZ 是 CPU 系統時鐘頻率,默認使用的是晶振通過時鐘樹后獲得的時鐘頻率
- TICK_RATE_HZ
TICK_RATE_HZ 是 RTOS 的心跳時鐘頻率,默認為最大值 1000 ,即心跳時鐘 1ms 跳動一次
- MAX_PRIORITIES
MAX_PRIORITIES 是 RTOS 任務的最高優先級設定,默認56級,一般來說一個優先級表是32位,這里用了兩個,對應64位,其中8位用于系統任務的優先級處理
- MINIMAL_STACK_SIZE
MINIMAL_STACK_SIZE 設定分配給空閑任務的堆疊大小,該值是用字(32位)指定的,而不是位元組,默認為128個字,如果修改過空閑任務,則根據實際情況修改
- MAX_TASK_NAME_LEN
MAX_TASK_NAME_LEN 設定任務名稱的最大字符數,默認16位足夠
- USE_16_BIT_TICKS
USE_16_BIT_TICKS 存放 Tick 周期的計數器的數字位寬,默認為 Disable 即 16 位
- IDLE_SHOULD_YIELD
如果IDLE_SHOULD_YIELD 設定為0,則空閑任務永遠不會讓位于另一個任務,只在被搶占時才會離開運行狀態,如果 IDLE_SHOULD_YIELD 設定為1,那么當有另一個空閑優先級任務處于Ready狀態時,空閑任務將不會執行它定義的功能的不止一次迭代,而不會讓位于另一個任務,這確保當應用程式任務處于空閑狀態時,在空閑任務中花費的時間最少,即同在空閑優先級下,空閑任務優先級更高,不會被搶占,不會以時間片運行
- USE_MUTEXES、USE_RECURSIVE_MUTEXES、USE_COUNTING_SEMAPHORES
為 1 則開啟系統構建程序中的互斥量、遞回互斥量和信號量,該值強制為1(ENABLE)
- QUEUE_REGISTRY_SIZE
佇列注冊表的大小,可以用于管理佇列名稱和佇列物體,方便運行中進行查看與管理,默認為8
- USE_APPLICATION_TASK_TAG
使能時會給任務一個 TAG 標簽,便于用戶進行使用
- ENABLE_BACKWARD_COMPATIBILITY
一個兼容性使能,使能后, FreeRTOS 8.0.0 之后的版本可以通過宏定義使用 8.0.0 版本之前的函式介面,默認使能
- USE_PORT_OPTIMISED_TASK_SELECTION
查找下一個任務方式的選擇,查找下一個就緒任務就是查找優先級表,對優先級表進行導0演算法,分為通用切換或者針對性切換,一般默認不使能,使用通用切換,通用切換使用C撰寫,執行效率低,兼容性高;針對性切換使用處理器自帶的導0指令,使用匯編撰寫,切換效率高,但兼容性差
- USE_TICKLESS_IDLE
使能后會生成的兩個空函式PreSleepProcessing和PostSleepProcessing,用戶可以撰寫代碼進入低功耗模式,生成函式如下圖

- USE_TASK_NOTIFICATIONS
任務通知使能,每個RTOS任務都有一個32位的通知值,RTOS任務通知是一個直接發送給任務的事件,它可以解除接收任務的阻塞,并可選地更新接收任務的通知值,為1開啟,為0關閉,關閉可以為每個任務節省8個位元組的記憶體空間
- RECORD_STACK_HIGH_ADDRESS
記錄任務的堆疊入口地址到TCB,為1使能,為0關閉
2.2 記憶體管理設定
記憶體管理可以看到3個配置引數

- Memory Allocation
記憶體分配方式,此處默認動態和靜態都可以
- TOTAL_HEAP_SIZE
記憶體堆的分配大小,堆本質上就是一個陣列,此處是設定堆陣列的大小,設定時要考慮最小要滿足所有任務的使用要求,最大不要超過系統的分配上限
- Memory Management scheme
記憶體分配方式,有heap_1.c, heap_2.c, heap_3.c, heap_4.c and heap5.c 5種,其中1、2、4、5都是先建立一個堆陣列,從陣列中申請,用完再釋放,與C語言中molloc和free使用鏈表的方式不同,該方式在 MCU 中更安全穩定,此處默認使用的方式4,具體申請釋放方式可以在heap4.c中閱讀到
關于堆和堆疊的區別,可以閱讀我的另外一篇文章進行了解:C語言:記憶體四區
2.3 鉤子函式配置
鉤子函式是一種回呼函式,用于在任務執行一次之后或者某些事件發生后執行的函式,該配置項里面有五個選項,控制5種不同功能的鉤子函式開啟,當然用戶也可以在代碼中自己定義
- USE_IDLE_HOOK
使能后,系統生成一個慷訓呼函式,由用戶撰寫函式主體
void vApplicationIdleHook(void)
每當空閑任務執行一次,鉤子函式都會被執行一次
- USE_TICK_HOOK
使能后,系統生成一個慷訓呼函式,由用戶撰寫函式主體
void vApplicationTickHook(void)
每個TICK周期,鉤子函式都會執行一次
- USE_MALLOC_FAILED_HOOK
使能后,系統生成一個慷訓呼函式,由用戶撰寫函式主體
void vApplicationMallocFailedHook(void)
當申請動態記憶體失敗時,鉤子函式會執行一次
- USE_DAEMON_TASK_STARTUP_HOOK
使能后,系統生成一個慷訓呼函式,由用戶撰寫函式主體
void vApplicationDaemonTaskStartupHook(void).
任務剛啟動時,鉤子函式會執行一次
- CHECK_FOR_STACK_OVERFLOW
使能后,系統生成一個慷訓呼函式,由用戶撰寫函式主體
void vApplicationStackOverflowHook( xTaskHandle xTask, signed char *pcTaskName );
任務堆疊溢位時,鉤子函式會執行一次,傳入任務 TCB 和任務名稱
當我們在 CubeMX 里面開啟對應鉤子函式,生成代碼之后,在FreeRTOS就可以看到自動生成的鉤子函式,我們在里面撰寫相應的功能就行

2.5 任務運行追蹤配置
功能配置項如下:

- GENERATE_RUN_TIME_STATS
開啟時間統計功能,在呼叫 vTaskGetRunTimeStats() 函式時,將任務運行時間資訊保存到可讀串列中
- USE_TRACE_FACILITY
使能后會包含額外的結構成員和函式以幫助執行可視化和跟蹤,默認開啟,方便 MDK 軟體工具除錯使用
- USE_STATS_FORMATTING_FUNCTIONS
使能后會生成 vTaskList() 和 vTaskGetRunTimeStats() 函式用于獲取任務運行狀態
2.6 協程配置
Co-routine related definitions 是協程的配置項,兩個選項用來配置協程是否開啟,以及協程的優先級,開啟后,需要用戶手動創建協程,在協程幾乎很少用到了,是 FreeRTOS目前還沒有把協程移除的計劃,但 FreeRTOS是不會再更新和維護協程了,因此大家解一下就行
協程特點:
- 堆疊使用
所有的協程使用同一個堆疊(如果是任務的話每個任務都有自己的堆疊),這樣就比使用任務消耗更少的 RAM - 調度器和優先級
協程使用合作式的調度器,但是可以在使用搶占式的調度器中使用協程 - 宏實作
協程是通過宏定義來實作的 - 使用限制
為了降低對 RAM 的消耗做了很多的限制
具體 API 介面和調度原理可以參考這篇文章 : FreeRTOS協程
2.7 軟體定時器配置
軟體定時器配置的一些相關項如下:

這四個配置項主要與軟體定時器處理任務有關,軟體定時器任務屬于系統任務(守護執行緒),開啟軟體定時器后用于維護軟體定時器
- USE_TIMERS
默認開啟軟體定時器任務
- TIMER_TASK_PRIORITY
軟體定時器任務優先級
- TIMER_QUEUE_LENGTH
定時器任務佇列長度,FreeRTOS 是通過佇列來發送控制命令給定時器任務,叫做定時器命令佇列,此處設定佇列長度
- TIMER_TASK_STACK_DEPTH
軟體定時器任務堆疊大小
2.8 中斷優先級配置
- LIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用來設定最低優先級,FreeRTOS 使用的4位優先級,對應16位優先級,對應的最低優先級為15
- LIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
設定FreeRTOS 系統可管理的最大優先級,也就是設定閾值優先級,這個大家可以自由設定,這里設定為5,也就是高于5 的優先級(優先級數小于5)不歸 FreeRTOS 管理
三、內核裁剪
Include Parameters 下的選項應用于內核裁剪,裁剪不必要的功能,精簡系統功能,減少資源占用,主要有以下幾個選項:

配置項可裁剪的函式功能如下:
| 選項 | 功能 |
|---|---|
| vTaskPrioritySet | 改變某個任務的任務優先級, |
| uxTaskPriorityGet | 查詢某個任務的優先級, |
| vTaskDelete | 洗掉任務 |
| vTaskCleanUpResources | 回收任務洗掉后的資源如RAM等等 |
| vTaskSuspend | 掛起任務 |
| vTaskDelayUntil | 阻塞延時一段絕對時間(絕對延時去去除程式執行時間,執行更精準) |
| vTaskDelay | 阻塞延時一段相對時間 |
| xTaskGetSchedulerState | 獲取任務調度器的狀態,開啟或未開啟 |
| xTaskResumeFromISR | 在中斷服務函式中恢復一個任務的運行 |
| xQueueGetMutexHolder | 獲取信號量的佇列擁有者,回傳擁有此信號量的佇列 |
| xSemaphoreGetMutexHolder | 查詢擁有互斥鎖的任務,回傳任務控制塊 |
| pcTaskGetTaskName | 獲取任務名稱 |
| uxTaskGetStackHighWaterMark | 獲取任務的堆疊的歷史剩余最小值,FreeRTOS 中叫做“高水位線” |
| xTaskGetCurrentTaskHandle | 此函式用于獲取當前任務的任務句柄,就是獲取當前任務控制塊 |
| eTaskGetState | 此函式用于查詢某個任務的運行壯態,比如:運行態、阻塞態、掛起態、就緒態等 |
| xEventGroupSetBitFromISR | 在中斷服務函式中將指定的事件位清零 |
| xTimerPendFunctionCall | 定時器守護任務的回呼函式(定時器守護任務使用到一個命令佇列,只要向佇列發送信號就可以執行相應代碼,可以實作“中斷推遲處理”功能) |
| xTaskAbortDelay | 中止延時函式,該函式能立即解除任務的阻塞狀態,將任務插入就緒串列中 |
| xTaskGetHandle | 此函式根據任務名字獲取的任務句柄(控制塊) |
四、創建任務與佇列
4.1 CubeMX 下任務創建與配置
任務(執行緒)是作業系統運行的基本單元,也是資源分配的基本單元, CubeMX 任務的創建基本以圖形化進行,配置方式如下
進入Tashs and Queues 配置,點擊 Add 添加新任務

任務配置引數介紹
| 引數 | 功能 |
|---|---|
| Task Name | 任務名稱,保存在 TCB 結構體中,設定時自己起名字 |
| Priority | 任務優先級,任務的調度等級,根據自己創建任務的緊急程度設定 比如通信任務不能被打斷,可以設計較高優先級 |
| Stack Size(Words) | 設定給任務分配的記憶體大小,單位是字,對于32位單片機來說占4個位元組 |
| Entry Function | 任務物體,即任務的運行函式名 |
| Code Generation | 代碼生成模式 As weak: 產生一個用 __weak 修飾的弱定義任務函式,用戶可自己在進行定義; As external: 產生一個外部參考的任務函式,用戶需要自己定義該函式; Default: 產生一個默認格式的任務函式,用戶需要在該函式內實作自己的功能 |
| Parameter: | 傳入的引數,保持默認就行 |
| Allocation: | 記憶體分配方式 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
設定完成后點擊OK,配置就完成了,之后生成代碼,使用 MDK 進一步配置任務的具體資訊
在生成的代碼中,我們打開 freertos.c 檔案可以在代碼中看到任務的配置資訊

在 freertos.c 檔案的末尾部分,我們可以看到生成的任務物體

任務物體本身就是一個死回圈函式,回圈執行程式代碼,但回圈體代碼里面必須要有延時函式,釋放當前任務對 MCU 的控制權,使其他低優先級可以執行,此外,關于任務,CubeMX 提供了一系列的用戶呼叫介面函式,具體如下
| 函式 | 功能 |
|---|---|
| osThreadNew | 創建新任務 |
| *osThreadGetName | 獲取任務名稱 |
| osThreadGetId | 獲取當前任務的控制塊(TCB) |
| osThreadGetState | 獲取當前任務的運行狀態 |
| osThreadGetStackSize | 獲取任務的堆疊大小 |
| osThreadGetStackSpace | 獲取任務剩余的堆疊大小 |
| osThreadSetPriority | 設定任務優先級 |
| osThreadGetPriority | 獲取任務優先級 |
| osThreadYield | 切換控制權給下一個任務 |
| osThreadSuspend | 掛起任務 |
| osThreadResume | 恢復任務(掛起多少次恢復多少次) |
| osThreadDetach | 分離任務,方便任務結束進行回收 |
| osThreadJoin | 等待指定的任務停止 |
| osThreadExit | 停止當前任務 |
| osThreadTerminate | 停止指定任務 |
| osThreadGetCount | 獲取激活的任務數量 |
| osThreadEnumerate | 列舉激活的任務 |
4.2 CubeMX 下佇列的創建與配置
佇列,又稱為訊息佇列,用于任務間的資料通信,傳輸資料,在作業系統里面,直接使用全域變數傳輸資料十分危險,看似正常運行,但不知道啥時候就會因為暫存器或者記憶體等等原因引起崩潰,所以引入訊息,佇列的概念,任務發送資料到佇列,需要接受訊息的任務掛起在佇列的掛起串列,等待訊息的到來,CubeMX 創建佇列的步驟如下:
先點擊 Add 添加佇列

佇列配置引數介紹
| 引數 | 功能 |
|---|---|
| Queue Name | 佇列名稱(自己設定) |
| Queue Size | 訊息佇列大小 |
| Item Size | 佇列傳輸型別,保持默認16 位就行 |
| Allocation | 佇列記憶體的分配方式 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
配置需要的引數后,點擊OK,然后生成代碼
生成代碼后,我們可以在 freertos.c 中系統初始話函式中看到佇列的初始化

初始化函式會在一開始被呼叫,對 FreeRTOS 系統和內核物件進行初始化,初始化后系統就可以進行調度和使用內核物件,CubeMX 生成的代碼自動將創建的內核物件放到初始化函式內,所以我們在任務和中斷中直接使用就可以,佇列的 FreeRTOS API 介面在CubeMX 內再次進行了封裝,使用更加簡單,使用方式如下:
我們使用的 CMSIS 2.0 版本,所以在任務檔案中包含呼叫宣告頭檔案
#include "cmsis_os2.h"
在佇列頭檔案內我們可以在 600 多行的位置找到有關佇列的 API 函式宣告:

下面介紹一下佇列有關介面的函式介面:
| 函式 | 功能 |
|---|---|
| osMessageQueueNew | 創建并初始化一個新的佇列 |
| osMessageQueueGetName | 獲取佇列的名字 |
| osMessageQueuePut | 發送一條訊息到佇列 |
| osMessageQueueGet | 從佇列等待一條訊息 |
| osMessageQueueGetCapacity | 獲取佇列傳輸訊息的峰值 |
| osMessageQueueGetMsgSize | 獲取佇列使用記憶體池的最大峰值 |
| osMessageQueueGetCount | 獲取佇列的訊息數量 |
| osMessageQueueGetSpace | 獲取佇列剩余的可用空槽 |
| osMessageQueueReset | 清空佇列 |
| osMessageQueueDelete | 洗掉佇列 |
以上的API介面有其對應的傳入引數,具體使用方式需要在翻原始碼的注釋,這里我選常用的來介紹一下:
訊息佇列常用的是插入與獲取訊息,初始化系統已經幫助我們完成,在初始化的時候會獲取一個佇列的句柄,之后對佇列的操作都是圍繞這個句柄展開,比如上面的代碼中,句柄就是 myQueue01Handle ,我們發送一個訊息到這個佇列,就是呼叫發送函式,對句柄進行操作,先看一下發送訊息的函式原型
osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout);
引數的功能
| 引數 | 功能 |
|---|---|
| mq_id | 傳入佇列的句柄 |
| *msg_ptr | 指向需要發送的訊息內容的指標 |
| msg_prio | 本次發送訊息的優先級(目前API未加入功能) |
| timeout | 發送訊息的超時時間(設定為0代表一直等待發送成功) |
| osStatus_t(回傳值) | 回傳執行結果 |
回傳值的可能
| 錯誤 | 含義 |
|---|---|
| osOK | 執行正常 |
| osError | 系統錯誤 |
| osErrorTimeout | 執行超時 |
| osErrorResource | 資源不可用 |
| osErrorParameter | 引數無效 |
| osErrorNoMemory | 記憶體不足 |
| osErrorISR | 不允許在中斷呼叫 |
| osStatusReserved | 防止編譯器優化項,不需要管他 |
所以我們發送一個訊息到佇列,函式用法如下:
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
osStatus_t result;
uint8_t dat[]="666\r\n";
/* Infinite loop */
for(;;)
{
result= osMessageQueuePut(&myQueue01Handle,dat,1,0);
if(result == osOK)
{
//發送成功
}else
{
//發送失敗
}
osDelay(1);
}
/* USER CODE END StartTask02 */
}
發送訊息的優先級暫時無用,CubeMX 對 FreeRTOS 的支持還不完善,發送訊息里面的優先級未使用到,并且入隊方式使用的是發送到佇列尾部,沒有從頭部插入的方式,有需求可以 通過包含 queue.h 檔案,呼叫 FreeRTOS 的官方代碼,或者自己修改 生成代碼的 API 介面結合優先級使用佇列的向前插入和向后插入,豐富系統功能!
除了發送訊息到佇列,接受佇列的訊息 API 介面也經常用到,函式原型如下
osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout);
引數的功能
| 引數 | 功能 |
|---|---|
| mq_id | 接受佇列的句柄 |
| *msg_ptr | 用于接受訊息內容的指標 |
| msg_prio | 存放接受訊息的優先級(目前API未加入功能) |
| timeout | 接受訊息的超時時間(設定為10代表,當前任務掛起在掛起串列,直到接收成功時恢復,或者10個TICK等待周期到達然后任務強行恢復,不再等待,為0則是不等待,等待期間任務掛起在內核物件的掛起佇列) |
| osStatus_t(回傳值) | 回傳執行結果 |
函式用法
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
osStatus_t result;
uint8_t dat[10]={};
uint8_t *pro;
/* Infinite loop */
for(;;)
{
result= osMessageQueueGet(&myQueue01Handle,dat,pro,10);
if(result == osOK)
{
//接受成功
}else
{
//接受失敗
}
osDelay(1);
}
/* USER CODE END StartTask02 */
}
注意:FreeRTOS 中獲取和發送訊息的 API 介面函式分為任務中呼叫和中斷中呼叫,CubeMX 代碼介面將兩者整合了,呼叫時自動判斷呼叫環境是在 ISR 還是正常運行環境中
五、創建定時器和信號量
5.1 CubeMX下定時器的創建和配置
軟體定時器本質上就是設定一段時間,當設定的時間到達之后就執行指定的功能函式,呼叫的這個函式叫做回呼函式,回呼函式的兩次執行間隔叫做定時器的定時周期,簡而言之,當定時器的定時周期到了以后就會執行回呼函式,下面介紹一下 CubeMX 中開啟定時器的方法:
在 CubeMX 里面按下面步驟添加定時器

然后配置具體引數,引數的功能如下:
| 引數 | 功能 |
|---|---|
| Timer Name | 設定定時器的名稱 |
| Callback | 設定定時器的回呼函式體 |
| Type | 設定定時器的執行型別 osTimerPeriodic 定時器周期執行回呼函式 osTimerOnce 定時器只執行一次回呼函式 |
| Code Generation Option | 代碼生成模式 As weak: 產生一個用 __weak 修飾的弱定義任務函式,用戶可自己在進行定義; As external: 產生一個外部參考的任務函式,用戶需要自己定義該函式; Default: 產生一個默認格式的任務函式,用戶需要在該函式內實作自己的功能 |
| Parameter | 傳入引數,保持默認NULL就行 |
| Allocation | 軟體定時器記憶體的分配方式,一般使用動態 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
引數配置完成后,生成代碼,我們可以在 freertos.c 檔案里面看到定時器創建后獲得的句柄,以及生成的回呼函式:


有了句柄,我們就可以呼叫 cmsis_os2.c 里面的定時器介面函式對定時器進行操作,先看一下 CubeMX 提供的定時器介面函式及其功能
| 函式 | 功能 |
|---|---|
| osTimerNew | 新建定時器,回傳定時器控制句柄 |
| osTimerGetName | 獲取定時器名稱 |
| osTimerStart | 設定定時器周期,啟動定時器 |
| osTimerStop | 停止定時器 |
| osTimerIsRunning | 檢測定時器是否在運行 |
| osTimerDelete | 洗掉定時器 |
其中常用的介面是定時器的啟動和停止
定時器啟動: osTimerStart,函式原型
osStatus_t osTimerStart (osTimerId_t timer_id, uint32_t ticks);
引數介紹:
| 引數 | 功能 |
|---|---|
| timer_id | 需要啟動的定時器句柄 |
| ticks | 設定定時器的運行周期 |
此處的 ticks 設定的數字是定時器兩次呼叫回呼函式的周期數目,每個 tick 是一個心跳時鐘的長度
使用例程:
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
osStatus_t result;
uint8_t dat[10]={0};
uint8_t *pro;
result= osTimerStart(&myTimer01Handle,10);
if(result == osOK)
{
//啟動成功
}else
{
//啟動失敗
}
/* Infinite loop */
for(;;)
{
osDelay(10);
}
/* USER CODE END StartTask02 */
}
按照例程啟動定時器,定時器會以 10個tick 的周期,呼叫回呼函式
回呼函式不要放阻塞函式,程式盡可能短
定時器啟動: osTimerStop,函式原型
osStatus_t osTimerStop (osTimerId_t timer_id);
引數只有一個,就是定時器的控制句柄,傳入即可停止定時器,例程如下
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
osStatus_t result;
uint8_t dat[10]={0};
uint8_t *pro;
result= osTimerStop(&myTimer01Handle);
if(result == osOK)
{
//停止成功
}else
{
//停止失敗
}
/* Infinite loop */
for(;;)
{
osDelay(10);
}
/* USER CODE END StartTask02 */
}
軟體定時器是由軟體定時器維護任務進行維護,檢測各個定時器的狀態,進行處理,回呼回呼函式,軟體定時器維護任務的引數配置在前面的 Config 就已經提到過
5.2 CubeMX下信號量的創建和配置
信號量是 RTOS 的一個內核物件,該物件有一個隊串列示該信號量擁有的信號數目,任何任務都可以對這個信號數目進行獲取和釋放,獲取時信號-1,釋放時信號+1,為0時不能繼續獲取,此時有任務想要繼續獲取信號量的話,任務會掛起在該內核物件的掛起串列,等到信號可以獲取時進行恢復,根據這個特性,信號量常用于控制對共享資源的訪問和任務同步,下面介紹一下 CubeMX 下信號量的配置:
點開配置頁面,可以看到有兩個信號量添加頁面,其中 Binary Semaphores 是二值信號量,Counting Semaphores 是計數信號量,二進制信號量,僅有一個佇列或者說 token,用于同步一個操作;計數信號量則擁有多個 tokens,可用于同步多個操作,或者管理有限資源

二值信號量創建:
點擊 Add,配置引數

引數介紹
| 引數 | 功能 |
|---|---|
| Semaphore Name | 信號量名稱 |
| Allocation | 記憶體分配方式,一般使用動態 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
計數信號量:
點擊 Add,配置引數

引數介紹
| 引數 | 功能 |
|---|---|
| Semaphore Name | 信號量名稱 |
| Count | 計數信號量的最大數目 |
| Allocation | 記憶體分配方式,一般使用動態 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
配置完成后我們生成代碼,在 freertos.c 的初始化代碼中可以看到信號量被創建,并且回傳了信號量的控制句柄

下面介紹一下 CubeMX 提供的信號量操作函式介面:
| 函式 | 功能 |
|---|---|
| osSemaphoreNew | 創建新的信號量 |
| *osSemaphoreGetName | 獲取信號量的名稱 |
| osSemaphoreAcquire | 獲取信號量 |
| osSemaphoreRelease | 釋放信號量 |
| osSemaphoreGetCount | 獲取當前可用信號量的數目 |
| osSemaphoreDelete | 洗掉信號量 |
其中常用的函式有獲取和釋放信號量,下面介紹一下這兩個函式的引數和使用方式
獲取信號量 osSemaphoreAcquire
函式原型
osStatus_t osSemaphoreAcquire (osSemaphoreId_t semaphore_id, uint32_t timeout);
引數介紹
| 引數 | 功能 |
|---|---|
| semaphore_id | 傳入要獲取信號量的控制句柄 |
| timeout | 獲取等待時間(等待期間任務掛起在內核物件的掛起佇列) |
使用例程
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
osStatus_t result;
/* Infinite loop */
for(;;)
{
result = osSemaphoreAcquire(&myBinarySem01Handle,10);
if(result == osOK)
{
//獲取成功
}else
{
//獲取失敗
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
釋放信號量 osSemaphoreRelease
函式原型
osStatus_t osSemaphoreRelease (osSemaphoreId_t semaphore_id);
| 引數 | 功能 |
|---|---|
| semaphore_id | 傳入要釋放的信號量控制句柄 |
使用例程
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
osStatus_t result;
/* Infinite loop */
for(;;)
{
result = osSemaphoreRelease(&myBinarySem01Handle);
if(result == osOK)
{
//釋放成功
}else
{
//釋放失敗
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
二值信號量和計數信號量的操作基本一致,沒用區別,只是用有的信號佇列最大數目不同而已
同時注意信號量在使用程序中會出現優先級反轉的Bug,使用時需要注意
六、創建互斥量
6.1 CubeMX下互斥量的創建和配置
互斥量其實就是一個擁有優先級繼承的二值信號量,互斥信號量適合用于那些需要互斥訪問的應用中,在互斥訪問中互斥信號量相當于一個鑰匙,當任務想要使用資源的時候就必須先獲得這個鑰匙,當使用完資源以后就必須歸還這個鑰匙,這樣其他的任務就可以拿著這個鑰匙去使用資源,與信號量不同的是,互斥量的釋放必須由獲取他的任務進行釋放,如果不釋放,可能會造成死鎖
死鎖就是兩個任務獲取對方擁有的鎖,各自進入掛起串列,無法釋放互斥鎖
下面介紹一下 CubeMX 下互斥量的配置,在配置界面我們可用看到兩個互斥量配置界面,上面的是普通互斥量,其獲取只能獲取一次,重復獲取是無效的,而第二個則是遞回互斥量,遞回互斥信號量可以獲取多次,但對應的也要釋放多次才能讓出使用權,比如我獲取3次,任務要釋放3次才能釋放該互斥量的使用權
使用互斥量,需要點擊 Add 然后配置引數

引數介紹:
| 引數 | 功能 |
|---|---|
| Mutex Name | 互斥量名稱 |
| Allocation | 記憶體分配方式,一般使用動態 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
遞回互斥信號量的配置方式與其相同,包括配置引數也相同,兩者只是在用法上有些許區別,添加方式如下:

添加配置完成后,點擊生成代碼,在 freertos.c 檔案中我們可以看到互斥量初始化完成,并且生成了對應的控制句柄

CubeMX 提供的 API 介面函式如下
| 函式 | 功能 |
|---|---|
| osMutexNew | 創建互斥量 |
| *osMutexGetName | 獲取互斥量名稱 |
| osMutexAcquire | 任務獲取互斥量 |
| osMutexRelease | 任務釋放互斥量 |
| osMutexGetOwner | 獲取互斥量的擁有任務的任務 TCB |
| osMutexDelete | 洗掉互斥量 |
主要使用到的還是互斥量的獲取與釋放,下面分析一下這兩個函式:
獲取互斥量 osMutexAcquire
函式原型
osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout);
引數介紹:
| 引數 | 功能 |
|---|---|
| mutex_id | 互斥量控制句柄 |
| timeout | 獲取互斥量時的等待時間(等待期間任務掛起在內核物件的掛起佇列) |
使用方式
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
osStatus_t result;
result= osMutexAcquire(&myMutex01Handle,10);
if(result == osOK)
{
//獲取成功
}else
{
//獲取失敗
}
/* Infinite loop */
for(;;)
{
osDelay(10);
}
/* USER CODE END StartTask02 */
}
釋放互斥量 osMutexRelease
函式原型
osStatus_t osMutexRelease (osMutexId_t mutex_id);
引數介紹:
| 引數 | 功能 |
|---|---|
| mutex_id | 互斥量控制句柄 |
使用方式
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
osStatus_t result;
result= osMutexRelease(&myMutex01Handle);
if(result == osOK)
{
//釋放成功
}else
{
//釋放失敗
}
/* Infinite loop */
for(;;)
{
osDelay(10);
}
/* USER CODE END StartTask02 */
}
使用方式和信號量基本相同,因為互斥量本質上就是信號量的一種
七、創建事件標志組
7.1 CubeMX下事件的創建和配置
任務間的同步除了信號量還有時間標志組,信號的同步通常是一對一的同步,有的時候系統需要多對一的同步,比如同時滿足5個按鍵按下時,任務啟動,如果使用信號會很占據資源,所以 RTOS 引入了事件標志組來滿足這一需求,下面我們看一下 CubeMX 內事件標志組的配置方法:
點擊 Add 創建事件標志組

配置介紹
| 引數 | 功能 |
|---|---|
| Event flags Name | 事件標志組名稱 |
| Allocation | 記憶體分配方式,一般使用動態 Static: 靜態方式是直接在RAM占據一個靜態空間 Dynamic:動態方則是在初始配置的記憶體池大小陣列中動態申請、釋放空間 |
配置完成后,生成代碼,在系統初始化內,看有沒有生成事件標志組控制句柄,可以看到句柄創建完成

CubeMX 提供的配置事件標志組的介面 API 如下:
| 函式 | 功能 |
|---|---|
| osEventFlagsNew | 創建事件標志組 |
| *osEventFlagsGetName | 獲取事件標志組名稱 |
| osEventFlagsSet | 設定事件標志組 |
| osEventFlagsClear | 清除事件標志組 |
| osEventFlagsGet | 獲取當前事件組標志資訊 |
| osEventFlagsWait | 等待事件標志組觸發 |
| osEventFlagsDelete | 洗掉事件標志組 |
常用的 API 介面是設定事件標志組以及等待事件標志組的觸發,下面我們分析一下這兩個 API
在了解 API 前我們需要簡單了解一下事件的觸發原理:首先事件標志組的資料型別為 EventGroupHandle_t,事件標志組中的所有事件位都存盤在一個無符號的 EventBits_t 型別的變數中,當 configUSE_16_BIT_TICKS 為 1 的時候事件標志組可以存盤 8 個事件位,當 configUSE_16_BIT_TICKS 為 0 的時候事件標志組存盤 24個事件位,每個事件位其實就是一個0或者1數字,就像下面的24位組成一個事件標志組

我們在使用事件API介面函式前需要先定義我們需要的觸發事件位,比如添加如下的代碼
#define event1 1<<1 //事件1
#define event2 1<<2 //事件2
撰寫好觸發事件后,我們在看如何使用 API 介面
設定事件標志 osEventFlagsSet
函式原型
uint32_t osEventFlagsSet (osEventFlagsId_t ef_id, uint32_t flags);
引數介紹:
| 引數 | 功能 |
|---|---|
| ef_id | 事件標志組控制句柄 |
| flags | 事件位 |
使用方式:設定事件1和事件2
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
osStatus_t result;
/* Infinite loop */
for(;;)
{
result = osEventFlagsSet(&myEvent01Handle,event1);
if(result == osOK)
{
//事件1設定成功
}else
{
//事件1設定失敗
}
result = osEventFlagsSet(&myEvent01Handle,event2);
if(result == osOK)
{
//事件2設定成功
}else
{
//事件2設定失敗
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
等待事件標志 osEventFlagsWait
函式原型
uint32_t osEventFlagsWait (osEventFlagsId_t ef_id, uint32_t flags, uint32_t options, uint32_t timeout);
引數介紹:
| 引數 | 功能 |
|---|---|
| ef_id | 事件標志組控制句柄 |
| flags | 等待的事件位 |
| options | 等待事件位的操作 osFlagsWaitAny :等待的事件位有任意一個等到就恢復任務 osFlagsWaitAll:等待的事件位全部等到才恢復任務 osFlagsNoClear:等待成功后不清楚所等待的標志位(默認清除) |
| timeout | 等待事件組的等待時間(等待期間任務掛起在內核物件的掛起佇列) |
使用例子:同時等待事件1和事件2,且等待到不清除
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
osStatus_t result;
/* Infinite loop */
for(;;)
{
result = osEventFlagsWait(&myEvent01Handle,event1|event2,osFlagsWaitAll|osFlagsNoClear,10);
if(result == osOK)
{
//等待成功
}else
{
//等待失敗
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
八、用戶常量
User Constants 用于添加用戶常量,將不變的量轉化為常量保存,可以節省 RAM 資源空間,因為常量和變數的保存位置不同,詳細了解可以參考這篇文章:C語言:記憶體四區
九、任務通知
FreeRTOS 的每個任務都有一個 32 位的通知值,任務控制塊中的成員變數 ulNotifiedValue 就是這個通知值,任務通知是一個事件,假如某個任務通知的接收任務因為等待任務通知而阻塞的話,向這個接收任務發送任務通知以后就會解除這個任務的阻塞狀態,CubeMX內沒有提供相關的配置項,但在其生成的 FreeRTOS 介面里面有相關函式進行配置,函式位置如下:

介面函式功能:
| 函式 | 功能 |
|---|---|
| osThreadFlagsSet | 設定任務的通知標志 |
| osThreadFlagsClear | 清除任務通知 |
| osThreadFlagsGet | 獲取任務標志 |
| osThreadFlagsWait | 等待特定的任務標志 |
常用的兩個 API 就是設定任務通知和等待任務通知函式
設定通知 osThreadFlagsSet
函式原型
uint32_t osThreadFlagsSet (osThreadId_t thread_id, uint32_t flags);
引數介紹:
| 引數 | 功能 |
|---|---|
| thread_id | 任務控制塊 |
| flags | 設定的標志 |
使用方式
先定義一個事件標志
#define event1 1<<1 //事件1
然后呼叫 API 通知對應任務事件發生
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
osStatus_t result;
/* Infinite loop */
for(;;)
{
result = osThreadFlagsSet(&myTask02Handle,event1);
if(result == osOK)
{
//設定成功
}else
{
//設定失敗
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
等待通知 osThreadFlagsWait
函式原型
uint32_t osThreadFlagsWait (uint32_t flags, uint32_t options, uint32_t timeout);
引數介紹:
| 引數 | 功能 |
|---|---|
| flags | 設定的標志 |
| options | 設定功能 |
| timeout | 超時時間 |
options引數
| 引數 | 功能 |
|---|---|
| osFlagsWaitAny | 等待32位通知值任意一位觸發后恢復任務(默認) |
| osFlagsWaitAll | 等待指定的任務通知值全部觸發后再恢復任務 |
| osFlagsNoClear | 恢復任務后不清除任務標志(默認清除) |
使用方式
呼叫 API 等待對應的任務通知就緒,當其他任務設定到對應的通知后,任務恢復運行
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
osStatus_t result;
/* Infinite loop */
for(;;)
{
result = osThreadFlagsWait(&myTask02Handle,osFlagsWaitAll,event1);
if(result == osOK)
{
//等待成功
}else
{
//等待失敗
}
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
任務通知其實個任務事件標志組使用上沒有多大的區別,但他們兩個的實作原理不同,同時任務通知對資源的占用更少
根據 FreeRTOS 官方的統計,使用任務通知替代二值信號量的時候任務解除阻 塞的時間要快 45%,并且需要的 RAM 也更少
十、系統內核配置
CubeMX 生成的代碼中封裝了一系列內核配置函式,有些函式也經常使用到,比如獲取時間戳和調度器管理的函式,這里不做過多解釋,簡單的介紹一下函式的功能
| 函式 | 功能 |
|---|---|
| osKernelInitialize | 初始化RTOS的內核 |
| osKernelGetInfo | 獲取RTOS的資訊 |
| osKernelGetState | 獲取當前內核的運行狀態 |
| osKernelStart | 啟動內核調度 |
| osKernelLock | 鎖內核調度器 |
| osKernelUnlock | 解鎖內核調度器 |
| osKernelRestoreLock | 恢復RTOS內核調度器鎖狀態 |
| osKernelSuspend | 掛起任務 |
| osKernelResume | 恢復任務 |
| osKernelGetTickCount | 用于獲取系統當前運行的時鐘節拍數 |
| osKernelGetTickFreq | 用于獲取系統當前運行的時鐘節拍的分頻頻率 |
| osKernelGetSysTimerCount | 獲取系統時鐘(SysTick)的計數值 |
| osKernelGetSysTimerFreq | 獲取系統時鐘(SysTick)的頻率 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/330431.html
標籤:其他
