1.事件組
事件組結構體:
事件組 “不關中斷” 的核心邏輯
事件組操作時,優先選擇 “關調度器” 而非 “關中斷” ,原因和實現如下:
關調度器(而非關中斷)
FreeRTOS 提供 taskENTER_CRITICAL()(關調度器 + 按需關中斷,取決于 configKERNEL_INTERRUPT_PRIORITY 配置 )或 vTaskSuspendAll()(僅關調度器 ),來保護事件組的任務級操作(如任務調用 xEventGroupSetBits )。
關調度器后,中斷仍可正常觸發(硬件中斷不受影響),但任務無法切換,保證事件組操作的原子性。
中斷里的事件組操作
中斷函數中不能直接修改事件組(因為中斷設置事件組的時候有可能喚醒多個滿足條件的任務導致中斷時間不確定),而是通過 xEventGroupSetBitsFromISR 觸發 “守護任務”(daemon task) 來異步處理:
中斷里向守護任務的隊列發消息,請求設置事件位;
守護任務(任務上下文)真正執行 xEventGroupSetBits,此時用關調度器保證安全。
事件組創建流程:
開始↓
調用 xEventGroupCreate() 函數↓
動態分配事件組內存(若支持靜態分配,則使用 xEventGroupCreateStatic())↓
成功? ——→ 是 ——→ 初始化事件組狀態(默認所有位為0)↓ ↓否 返回事件組句柄↓ ↓
返回 NULL 結束
設置事件位流程:
開始↓
[上下文判斷] → 任務中調用 xEventGroupSetBits() / 中斷中調用 xEventGroupSetBitsFromISR()↓
設置指定的事件位(按位或操作更新組值)(操作EventGroup_t 結構體)↓
檢查是否有任務因等待這些位被阻塞?↓
是 ——→ 解除阻塞符合條件的任務↓ ↓否 觸發上下文切換(若在任務中)或標記延遲調度(若在中斷中)↓ ↓
結束 ←←←←←←
等待事件位流程:
開始↓
調用 xEventGroupWaitBits(),傳入等待的位掩碼、清除標志、阻塞時間等參數↓
檢查當前事件位是否滿足條件:是否所有指定位被置位?(邏輯AND)或任意指定位被置位?(邏輯OR)↓
滿足? ——→ 是 ——→ 根據參數清除事件位↓ ↓否 返回當前事件位值↓ ↓
進入阻塞狀態(若阻塞時間 > 0),釋放CPU↓ ↓
等待事件被設置或超時 →→→ 超時? ——→ 是 ——→ 返回超時狀態↓ ↓否(事件觸發) ↓↓ ↓
返回觸發后的事件位值 結束
2.任務通知
FreeRTOS 任務通知是一種輕量級任務間通信機制,用于任務或中斷向指定任務發送事件或數據:
一、基本概念
核心原理:每個任務的控制塊(TCB)中內置通知相關成員(如 ulNotifiedValue 存儲通知值 ),無需額外創建通信結構體,可直接向目標任務發送 “通知”。
啟用條件:需在 FreeRTOSConfig.h 中定義 configUSE_TASK_NOTIFICATIONS = 1 開啟功能。
二、優勢與局限
優勢
高效性:無需額外結構體,操作直接,比隊列、信號量、事件組更快,節省內存(每個任務僅額外占 8 字節存儲通知狀態和值 )。
靈活更新:支持多種通知值更新方式(如覆蓋、保留原值、置位、遞增等 ),適配不同場景。
局限
單播特性:僅能指定一個任務接收通知,無法廣播給多任務。
數據緩存限制:任務控制塊只有一個通知值,無法緩存多個數據,發送方也不能因發送受阻進入阻塞。
中斷交互限制:可從中斷發通知給任務,但無法給中斷發通知(中斷無任務結構體 )。
發通知流程:
開始↓
關閉中斷 (taskENTER_CRITICAL)↓
目標任務存在?——否——→ 開啟中斷 ——→ 返回失敗 ——→ 結束↓是
目標任務在等待通知?↓
是 ——→ 設置通知值↓從延遲列表移除目標任務↓將目標任務設為就緒狀態↓目標任務優先級更高?——是——→ 開啟中斷 ——→ 設置需要上下文切換標志↓否 ↓開啟中斷 ←————————————————————————— 觸發任務調度 (portYIELD)↓ ↓返回成功 ←————————————————————————— 返回成功↓ ↓結束 結束↓否
設置通知值(累加或覆蓋)↓
開啟中斷 (taskEXIT_CRITICAL)↓
返回成功↓
結束
等待通知流程:
開始↓
關閉中斷 (taskENTER_CRITICAL)↓
檢查是否有待處理的通知↓
有通知? ——是——→ 清除/遞減通知值 ——→ 開啟中斷 ——→ 返回通知值 ——→ 結束↓否
等待時間為0? ——是——→ 開啟中斷 ——→ 返回0 ——→ 結束↓否
將任務添加到延遲列表↓
設置任務狀態為阻塞↓
開啟中斷 (taskEXIT_CRITICAL)↓
觸發任務調度 (portYIELD)↓
其他任務運行...↓
收到通知或超時?↓
關閉中斷 (taskENTER_CRITICAL)↓
從延遲列表移除任務↓
設置任務為就緒狀態↓
開啟中斷 (taskEXIT_CRITICAL)↓
返回通知值(成功)或0(超時)↓
結束
3.軟件定時器
軟件定時器的核心機制就是時間到達預設值后自動觸發回調函數執行。
FreeRTOS 軟件定時器的核心是基于系統 tick 的有序鏈表管理和低優先級檢測任務,通過回調函數實現定時事件處理。
內部實現:
一、核心數據結構
1. 定時器控制塊(Timer_t
)
typedef struct tmrTimerControl {const char *pcTimerName; // 定時器名稱(調試用)ListItem_t xTimerListItem; // 用于鏈表管理的節點TickType_t xTimerPeriodInTicks; // 定時周期(tick數)UBaseType_t uxAutoReload; // 是否自動重載(周期/單次模式)void (*pxCallbackFunction)(TimerHandle_t xTimer); // 回調函數指針void *pvTimerID; // 定時器ID(用戶數據)TickType_t xExpireTime; // 下次超時的絕對tick時間// ...其他內部字段
} Timer_t;
2. 定時器列表(按超時時間排序)
static List_t xActiveTimerList1; // 活躍定時器列表1
static List_t xActiveTimerList2; // 活躍定時器列表2(用于溢出處理)
二、實現機制
1.定時器任務(prvTimerTask
)
- 核心職責:
- 優先級通常設為較低值(如
configTIMER_TASK_PRIORITY
),避免影響關鍵任務。 - 周期性檢查定時器列表,處理超時定時器。
- 優先級通常設為較低值(如
執行流程:
void prvTimerTask(void *pvParameters) {for (;;) {// 1. 進入臨界區,防止任務調度干擾taskENTER_CRITICAL();// 2. 獲取當前系統tickTickType_t xTimeNow = xTaskGetTickCount();// 3. 檢查活躍列表頭部定時器是否超時while (listLIST_IS_EMPTY(&xActiveTimerList1) == pdFALSE) {Timer_t *pxTimer = (Timer_t *)listGET_OWNER_OF_HEAD_ENTRY(&xActiveTimerList1);if (pxTimer->xExpireTime <= xTimeNow) {// 4. 移除超時定時器(void)uxListRemove(&pxTimer->xTimerListItem);// 5. 退出臨界區,執行回調(避免長時間持有鎖)taskEXIT_CRITICAL();pxTimer->pxCallbackFunction((TimerHandle_t)pxTimer);taskENTER_CRITICAL();// 6. 若為周期模式,重新計算超時時間并加入列表if (pxTimer->uxAutoReload == pdTRUE) {pxTimer->xExpireTime += pxTimer->xTimerPeriodInTicks;vListInsertInOrder(&xActiveTimerList1, &pxTimer->xTimerListItem);}} else {break; // 后續定時器未超時,退出循環}}// 7. 退出臨界區,進入阻塞狀態直到下一個定時器超時或被喚醒taskEXIT_CRITICAL();vTaskDelayUntil(&xTimeNow, pdMS_TO_TICKS(10)); // 10ms檢查一次}
}
2. 定時器添加 / 刪除機制
添加定時器(xTimerStart()):
計算超時時間(當前tick + 定時周期)。
將定時器按超時時間插入有序鏈表(保證頭部為最近超時的定時器)。
若新定時器成為鏈表頭部,喚醒定時器任務重新計算阻塞時間。
刪除定時器(xTimerDelete()):
從活躍鏈表中移除定時器節點。
標記定時器為無效狀態,防止重復操作。
4.兩套API
FreeRTOS 設計兩套 API 的核心目標是保證中斷處理的實時性和系統穩定性:
任務上下文 API:面向普通任務場景,允許阻塞和直接調度,簡化編程。
中斷安全 API:面向中斷場景,避免長時間關中斷,通過參數間接控制調度,確保中斷快速響應
對比維度 | 任務上下文 API(常規版本) | 中斷安全 API(FromISR 版本) |
---|---|---|
命名規則 | 無特殊后綴(如?xQueueSend 、vTaskDelay ) | 后綴為?FromISR (如?xQueueSendFromISR 、vTaskDelayFromISR ) |
調用場景 | 僅能在任務函數中調用,禁止在中斷服務程序(ISR)中使用 | 專門用于 ISR 或異常處理程序,可在中斷環境中安全調用 |
中斷狀態處理 | 無需顯式處理中斷狀態(任務上下文默認開中斷) | 需保存并恢復中斷狀態(如?portSET_INTERRUPT_MASK_FROM_ISR ),避免破壞 ISR 上下文 |
上下文切換觸發 | 直接調用內核調度器(如?taskYIELD ),主動觸發切換 | 通過?pxHigherPriorityTaskWoken ?參數標記是否需切換,由 ISR 決定是否調用?portYIELD_FROM_ISR |
關鍵參數差異 | 常規參數(如隊列句柄、數據指針、超時時間) | 部分函數增加?BaseType_t* pxHigherPriorityTaskWoken ?參數,用于標記高優先級任務是否被喚醒 |
臨界區保護機制 | 使用?taskENTER_CRITICAL() ?和?taskEXIT_CRITICAL() | 使用?portSET_INTERRUPT_MASK_FROM_ISR() ?和?portCLEAR_INTERRUPT_MASK_FROM_ISR() ,適配中斷環境 |
返回值含義 | 直接返回操作結果(如?pdPASS 、pdFAIL 、pdTRUE ) | 除返回操作結果外,需通過?pxHigherPriorityTaskWoken ?間接傳遞調度需求 |
阻塞特性 | 支持阻塞(如等待隊列或信號量時可指定超時時間) | 不支持阻塞(中斷場景需快速返回,超時參數無效或被忽略) |
典型函數示例 | xQueueSend 、xSemaphoreTake 、vTaskDelay 、vTaskSuspend | xQueueSendFromISR 、xSemaphoreTakeFromISR 、vTaskDelayFromISR 、vTaskSuspendFromISR |
內核調度介入方式 | 函數內部主動觸發調度(如任務切換) | 需由 ISR 根據函數返回結果決定是否觸發調度 |
對系統實時性的影響 | 可能因阻塞操作降低實時性(任務上下文允許) | 無阻塞操作,中斷處理更高效,保證系統實時響應 |
FreeRTOS 在中斷中檢測到高優先級任務就緒時,會立即請求切換,但實際切換發生在中斷返回后的安全時機(不是立即切換)。這一設計在保證中斷處理完整性的同時,確保高優先級任務以最小延遲獲得 CPU 資源
中斷API切換高優先級任務流程:
5.FreeRTOS里的兩類中斷
特性 | 系統中斷 | 用戶中斷 |
---|---|---|
優先級范圍 | 高優先級(通常 0~4) | 低優先級(通常 5~15,取決于配置) |
是否可調度 | 否(不可搶占其他系統中斷) | 是(可被系統中斷搶占) |
能否調用 FreeRTOS API | 僅能調用帶FromISR 后綴的 API | 可調用所有 API(需注意上下文) |
對任務調度的影響 | 可能直接觸發任務切換(如 PendSV、SysTick) | 通過pxHigherPriorityTaskWoken 標記間接觸發切換 |
典型示例 | PendSV、SysTick、HardFault、NMI | 外設中斷(如 UART、GPIO、定時器) |
顯然在 FreeRTOS 中,任務調度器依賴的定時器中斷(通常是 SysTick 中斷 )和 PendSV 中斷,優先級配置是刻意設計為低優先級,當我們關閉中斷時,任務也不再調度