# ThreadX介紹 ## Mutex (互斥鎖) 互斥鎖用於控制執行緒對關鍵資源的訪問,確保同一時間只有一個執行緒進入受保護區域。 --- ### **Mutex的基本操作** 1. **`tx_mutex_create`**:創建互斥鎖,並設定是否開啟優先級繼承。 2. **`tx_mutex_get`**:獲取互斥鎖。如果互斥鎖被其他執行緒擁有,當前執行緒將等待。 3. **`tx_mutex_put`**:釋放互斥鎖,允許其他執行緒獲取。 > **注意** 執行緒執行的 `tx_mutex_put` 次數必須等於先前的 `tx_mutex_get` 次數,才能完全釋放互斥鎖,這確保了對關鍵資源的互斥訪問。 ![image](https://hackmd.io/_uploads/HkYbyBpNyx.png) :::spoiler gif圖 ![228](https://hackmd.io/_uploads/HJf91r641e.gif) ::: --- ### **優先級繼承** #### **什麼是優先級反轉?** 當一個低優先級的執行緒擁有一個資源(例如互斥鎖),而高優先級的執行緒需要等待該資源時,如果低優先級執行緒因更高優先級的執行緒被搶占,這會導致高優先級的執行緒無法運行。這種情況稱為 **優先級反轉(Priority Inversion)**。 ![優先級反轉](https://hackmd.io/_uploads/SyP3J14wyg.png) 任務B先優級比任務A低卻先執行完成 #### **優先級繼承的原理** 在優先級繼承機制下: 1. 系統會臨時提升低優先級執行緒的優先級至等待該互斥鎖的最高優先級。 2. 當互斥鎖被釋放後,優先級會恢復為原始值。 >這樣可以減少高優先級執行緒的等待時間,解決優先級反轉問題。 ```mermaid gantt title 優先級繼承範例//待確認 dateFormat HH:mm axisFormat %H:%M section 高優先級<br>任務 A<br>任務 C(暫時) A C優先級相同無法搶占 :done, a2, 00:00, 00:30 C 持有互斥鎖開始執行 :active,a3 , 00:00, 00:30 C 執行完釋放互斥鎖 :active, c3, 00:30, 01:00 A 執行緒 :active, a3, 01:00, 01:15 section 中優先級<br>任務 B B 執行緒 :active, b1, 01:15, 01:45 section 低優先級<br>任務 C C 優先級恢復 :done, c2, 01:00, 01:15 ``` --- ### **死鎖(Deadlock)** #### **什麼是死鎖?** 死鎖是一種執行緒之間「**相互等待**」的現象,發生於多個執行緒同時競爭多個資源,但由於資源獲取順序不一致,導致彼此無法繼續執行,形成僵局。 #### **死鎖的發生條件** 死鎖發生需滿足以下四個條件: 1. **互斥條件**:資源一次只能被一個執行緒占用。 2. **持有並等待條件**:執行緒持有某個資源,同時等待其他資源。 3. **不可搶占條件**:已獲得的資源不能被強制搶占,需由持有者釋放。 4. **循環等待條件**:存在一組執行緒,它們形成資源循環等待的關係。 --- #### **死鎖示例** ![死鎖](https://hackmd.io/_uploads/ryJQFi8Hye.png =75%x) 例如: - **執行緒1** 獲取 **資源A**,同時等待 **資源B**。 - **執行緒2** 獲取 **資源B**,同時等待 **資源A**。 這樣兩個執行緒無法獲取所需的另一個資源,形成相互等待,導致系統陷入死鎖狀態。 --- #### **如何避免死鎖?** 1. **統一資源獲取順序** - 所有執行緒按照一致的順序獲取資源,避免循環等待。 2. **設定超時機制** - 設定等待超時,防止執行緒無限期阻塞。 3. **避免持有並等待** - 在獲取新資源之前,釋放已持有的資源。 4. **資源分配預先分析** - 在系統設計時分析資源分配,確保不會產生死鎖。 --- ### **Mutex的使用場景** 1. **保護關鍵資源的互斥存取**:避免多執行緒同時修改共享資源。 - 多執行緒讀寫一個共享的資料結構(例如佇列或緩衝區)。 - 保護對硬體外設(如 UART、I2C)的操作,避免多個執行緒同時讀寫導致衝突 3. **解決多執行緒優先級反轉問題**:啟用優先級繼承功能。 4. **單執行緒重入支持**:同一執行緒可多次獲取同一互斥鎖。 5. **操作順序性保障**:確保多執行緒按順序執行。 6. **資源的獨占控制**:確保某執行緒持有的資源不被搶占。 --- ### **Mutex優缺點與注意事項** #### **優點** - 確保資源的互斥存取。 - 支援優先級繼承。 - 支援重入操作。 #### **缺點** - 使用不當可能導致死鎖。 - 無法管理多資源的計數操作。 - 持有時間過長會影響其他執行緒效率。 #### **注意事項** 1. **避免死鎖**:多互斥鎖時需保持一致的獲取順序。 2. **適時釋放**:完成操作後立即釋放互斥鎖。 3. **釋放匹配**:`get` 和 `put` 操作需對應。 4. **啟用優先級繼承**:開啟 `TX_INHERIT` 避免優先級反轉。 --- ### **Mutex API介紹** ```c= // 創建互斥鎖 UINT tx_mutex_create( TX_MUTEX *mutex_ptr, // 指向互斥鎖控制塊的指針 CHAR *name_ptr, // 互斥鎖名稱 UINT priority_inherit); // 是否啟用優先級繼承 (TX_INHERIT / TX_NO_INHERIT) // 獲取互斥鎖 UINT tx_mutex_get( TX_MUTEX *mutex_ptr, // 指向互斥鎖的指針 ULONG wait_option); // 等待時間 (例如 TX_WAIT_FOREVER) // 釋放互斥鎖 UINT tx_mutex_put( TX_MUTEX *mutex_ptr); // 指向互斥鎖的指針 ``` --- ## Semaphore (訊號量) 訊號量用於控制多執行緒間的同步或資源共享,透過計數值來管理可用資源的數量。它通過一個整數計數值來管理可用資源數量,並根據計數值執行「增加」或「減少」操作。 --- ### **Semaphore的基本操作** 1. **`tx_semaphore_create`**:創建訊號量,並設定初始計數值。 2. **`tx_semaphore_get`**:獲取訊號量,成功則減少計數值;若失敗,執行緒進入等待。 3. **`tx_semaphore_put`**:釋放訊號量,增加計數值並喚醒等待的執行緒。 > **注意** 每次 `get` 必須對應一次 `put`,否則可能導致資源使用錯誤或溢出。 ![image](https://hackmd.io/_uploads/B1QVgr64kl.png) :::spoiler gif圖 ![249](https://hackmd.io/_uploads/r1kBxBaNJl.gif) ::: --- ### **Semaphore的類型** 1. **計數型訊號量(Counting Semaphore)** - 計數值大於 1,用於管理多個相同資源訪問,例如限制最多10個執行緒同時訪問某資源。 2. **二元訊號量(Binary Semaphore)** - 計數值只有 0 或 1,功能類似於互斥鎖,用於保護單一資源的訪問。 --- ### **Semaphore的使用場景** 1. **執行緒同步** 確保執行緒 A 在執行緒 B 完成特定操作後再開始執行。 2. **資源共享** 控制多執行緒對有限資源(如緩衝區、硬體設備等)的訪問。 3. **事件通知** 一個執行緒等待某事件,另一執行緒在事件發生後通過訊號量通知。 --- ### **Semaphore的優缺點與注意事項** #### **優點** - 適合管理多資源的計數操作。 - 支援多執行緒間的高效同步。 - 靈活性高,可適用於多種應用場景。 #### **缺點** - 無優先級管理機制,高優先級執行緒可能被低優先級執行緒阻塞。 - 過度釋放可能導致訊號量溢出。 - 不適合用於單一資源的互斥操作。 #### **注意事項** 1. **匹配釋放與獲取**:每次 `get` 必須對應一次 `put`,避免計數錯誤。 2. **設置合理初始值**:根據應用需求設置訊號量的初始值,避免資源不足或浪費。 3. **避免死鎖**:使用多訊號量時需保持一致的操作順序。 --- ### **Semaphore API介紹** ```c= // 創建訊號量 UINT tx_semaphore_create( TX_SEMAPHORE *semaphore_ptr, // 指向訊號量控制塊的指針 CHAR *name_ptr, // 訊號量名稱 ULONG initial_count); // 初始計數值 // 獲取訊號量 UINT tx_semaphore_get( TX_SEMAPHORE *semaphore_ptr, // 指向訊號量控制塊的指針 ULONG wait_option); // 等待時間 (例如 TX_WAIT_FOREVER) // 釋放訊號量 UINT tx_semaphore_put( TX_SEMAPHORE *semaphore_ptr); // 指向訊號量控制塊的指針 ``` --- ## Queue (訊息佇列) 訊息佇列用於執行緒間的數據傳遞,提供先進先出的操作模式,支援多執行緒同時發送和接收訊息。 --- ### **Queue的基本操作** 1. **`tx_queue_create`**:創建訊息佇列,定義名稱、大小和容量等屬性。 2. **`tx_queue_send`**:將訊息發送至佇列。 3. **`tx_queue_receive`**:從佇列中接收訊息。 4. **`tx_queue_flush`**:清空佇列中的所有訊息。 > **注意** 佇列滿時,發送訊息的執行緒可能阻塞;接收訊息的執行緒需處理空佇列的情況。 ![image](https://hackmd.io/_uploads/B1DwJHaE1g.png) :::spoiler gif圖 ![185](https://hackmd.io/_uploads/BkIOySpEkg.gif) ::: --- ### **Queue的使用場景** 1. **執行緒間數據傳遞** 多執行緒生產數據並存入佇列,其他執行緒從佇列中消費數據。 2. **事件通知** 通過佇列傳遞事件訊號或參數(如硬體中斷通知)。 3. **多對多通信** 多個生產者與消費者之間的數據交換。 --- ### **Queue的優缺點與注意事項** #### **優點** - 支援多執行緒並行操作,適合多生產者-多消費者模式。 - 靈活性高,適用於多種數據交換場景。 - 可設置等待時間,支持非阻塞操作。 #### **缺點** - 需合理設置佇列大小,避免過大或頻繁阻塞。 - 若佇列滿或空,可能導致執行緒阻塞或丟失訊息。 #### **注意事項** 1. **設置合理的佇列大小** 根據應用場景設置佇列容量,避免過小導致阻塞或過大浪費記憶體。 2. **清空佇列時需注意同步** 確保在沒有其他執行緒操作佇列時進行 `flush`。 3. **超時管理** 使用合適的等待選項,避免執行緒長時間阻塞。 --- ### **Queue API介紹** ```c= // 創建訊息佇列 UINT tx_queue_create( TX_QUEUE *queue_ptr, // 指向佇列控制塊的指針 CHAR *name_ptr, // 佇列名稱 UINT message_size, // 單條訊息的大小 VOID *queue_start, // 佇列的起始地址 (記憶體空間) ULONG queue_size); // 佇列的容量 (以字元為單位) // 發送訊息到佇列 UINT tx_queue_send( TX_QUEUE *queue_ptr, // 指向佇列控制塊的指針 VOID *source_ptr, // 指向要發送的訊息 ULONG wait_option); // 等待時間 (例如 TX_WAIT_FOREVER) // 從佇列接收訊息 UINT tx_queue_receive( TX_QUEUE *queue_ptr, // 指向佇列控制塊的指針 VOID *destination_ptr, // 接收訊息的緩衝區指針 ULONG wait_option); // 等待時間 (例如 TX_WAIT_FOREVER) // 清空佇列 UINT tx_queue_flush( TX_QUEUE *queue_ptr); // 指向佇列控制塊的指針 ``` --- ## Time & Timer ### **Time(時間)** `Time` 是 **ThreadX** 中的核心功能,用於管理和追蹤系統時鐘節拍(Ticks)。系統時鐘由一個定時中斷(Timer Interrupt)驅動,所有執行緒的延遲與計時功能均基於此運行。 --- ### **Time的功能** 1. **獲取當前系統時間** 使用 `tx_time_get` 獲取當前系統時鐘節拍數。 - 系統時鐘節拍與實際時間的對應關係由「時鐘節拍頻率」決定,例如1秒可對應100tick。 2. **設置系統時間** 使用 `tx_time_set` 修改系統時鐘,用於初始化或校正系統時間。 --- ### **Time API介紹** ```c= // 獲取當前時間 ULONG tx_time_get(void); // 返回系統當前的時鐘節拍數 // 設置系統時間 VOID tx_time_set( ULONG new_time); // 設置新的系統時鐘節拍數 ``` --- ### **Timer(定時器)** `Timer` 是基於系統時鐘的一種高級功能,用於執行週期性或延遲性的任務,適合於定時事件的觸發或執行緒間的同步操作。 預設情況下,應用程式計時器在優先級為 0 的隱藏系統執行緒中執行,該執行緒的優先級通常比任何應用程式執行緒都高。 --- ### **Timer的功能** 1. **單次定時** - 定時器只執行一次,適用於延遲執行的任務。 2. **週期定時** - 定時器可設置為週期性觸發,適合定時檢測或執行週期性任務。 3. **自動啟動與手動控制** - 創建時可選擇自動啟動(`TX_AUTO_ACTIVATE`)或手動啟動(`TX_DONT_START`)。 --- ### **Timer API介紹** ```c= // 創建定時器 UINT tx_timer_create( TX_TIMER *timer_ptr, // 指向定時器控制塊的指針 CHAR *name_ptr, // 定時器名稱 VOID (*expiration_function)(ULONG input), // 定時器到期時執行的函數 ULONG expiration_input, // 傳遞給回調函數的參數 ULONG initial_ticks, // 初次觸發的時鐘節拍數 ULONG reschedule_ticks, // 週期性觸發的時鐘節拍數 UINT auto_activate); // 自動啟動選項 (TX_AUTO_ACTIVATE或TX_DONT_START) // 啟動定時器 UINT tx_timer_activate( TX_TIMER *timer_ptr); // 指向定時器控制塊的指針 // 停止定時器 UINT tx_timer_deactivate( TX_TIMER *timer_ptr); // 指向定時器控制塊的指針 // 修改定時器參數 UINT tx_timer_change( TX_TIMER *timer_ptr, // 指向定時器控制塊的指針 ULONG initial_ticks, // 新的初次觸發節拍數 ULONG reschedule_ticks); // 新的週期性觸發節拍數,0 表示僅觸發一次 ``` --- ### **Timer使用場景** 1. **延遲啟動** - 利用單次定時器,實現某任務的延遲執行。 2. **週期性任務** - 使用週期性定時器,實現定時觸發事件或執行週期性操作。 3. **資源回收** - 定時清理不再使用的資源或釋放記憶體。 4. **數據刷新** - 定期更新系統數據或顯示內容。 --- ### **Time與Timer的注意事項** #### **Time(時間)** 1. 系統時鐘的精度取決於時鐘節拍頻率,需根據應用場景配置合適的頻率。 2. 在計算時間差時,需考慮系統時鐘可能發生溢出的情況。 #### **Timer(定時器)** 1. 單次定時器若需重複使用,需顯式重新啟動。 2. 定時器的回調函數應快速執行,以避免阻塞其他定時器或執行緒的操作。 --- ## **Event Flags(事件標誌)** **事件標誌(Event Flags)** 是 ThreadX 提供的一種同步機制,用於執行緒之間的信號傳遞。事件標誌是通過設置和檢查特定位元來實現執行緒的等待或觸發條件。 事件標誌依照 32 個一組的形式排列。 --- ### **Event Flags的基本操作** 1. **`tx_event_flags_create`**:創建事件標誌組。 2. **`tx_event_flags_set`**:設置一個或多個事件標誌。 3. **`tx_event_flags_get`**:檢查和等待事件標誌是否符合條件。 > **注意** 事件標誌組內可以包含多個標誌(位元),執行緒可以等待單個或多個標誌同時滿足條件。 ![event_flags](https://hackmd.io/_uploads/ryjyijUrye.png) --- ### **Event Flags的操作模式** 1. **AND 模式** - 所有指定的標誌位元都需要被設置,執行緒才會繼續執行。 - 例如,執行緒等待標誌位元 `0x03`,只有在 `0x01` 和 `0x02` 都設置時,執行緒才被喚醒。 2. **OR 模式** - 任意一個指定的標誌位元被設置,執行緒即可繼續執行。 - 例如,執行緒等待標誌位元 `0x03`,只要 `0x01` 或 `0x02` 其中一個被設置,執行緒即可被喚醒。 3. **CLEAR 模式** - 當事件標誌滿足條件後,自動清除設置(對應)的標誌位元。 - 使用此模式需特別注意,避免清除影響其他執行緒的等待。 | **操作** | **條件滿足方式** | **應用場景** | |-----------|-----------------------------|------------------------| | **`TX_AND`** | 全部位元必須匹配 | 等待一組標誌全部完成。 | | **`TX_OR`** | 任意一個位元匹配即可 | 等待任意事件觸發。 | ```mermaid graph TD A[事件標誌: 0b1011] -->|等待條件 TX_AND<br>條件: 0b1001| B[條件滿足] B -->|TX_AND_CLEAR| G[事件標誌變為 0b0010] A -->|等待條件 TX_OR<br>條件: 0b1000| C[條件滿足] C -->|TX_OR_CLEAR| H[事件標誌變為 0b0011] D[事件標誌: 0b1011] -->|等待條件 TX_AND<br>條件: 0b0111| E[條件不滿足] D -->|等待條件 TX_OR<br>條件: 0b0010| F[條件滿足] F -->|TX_OR_CLEAR| I[事件標誌變為 0b1001] ``` --- ### **Event Flags的使用場景** 1. **執行緒間同步** - 當某個執行緒需要等待其他執行緒完成特定任務後才繼續執行時,可使用事件標誌。 2. **事件通知** - 一個執行緒設置事件標誌,通知其他執行緒某些條件已滿足。 3. **多條件觸發** - 結合 AND 或 OR 模式,執行緒可以等待多個條件的組合來決定是否執行。 --- ### **Event Flags優缺點與注意事項** #### **優點** 1. 支援多執行緒間的靈活同步。 2. 可以高效處理多條件組合的觸發機制。 3. 與其他同步機制(如互斥鎖、訊號量)結合使用,適應多種場景。 #### **缺點** 1. 操作不當可能導致條件競態。 2. 使用 CLEAR 模式時,可能意外清除其他執行緒的條件。 3. 不適用於數量型的資源管理。//帶ㄑㄩㄝ - 沒有記數功能、排隊、無法避免資源競爭,事件標誌無法控制哪個執行緒優先獲取資源 - #### **注意事項** 1. **合理使用 AND 和 OR 模式**:根據應用需求選擇合適的模式。 2. **避免過度清除標誌位**:特別是在多執行緒等待同一標誌組的情況下,清除可能導致其他執行緒無法繼續。 3. **檢查執行緒等待條件**:確保條件設置與業務邏輯一致,避免死鎖或執行緒永久阻塞。 --- ### **Event Flags API 介紹** ```c= // 創建事件標誌組 UINT tx_event_flags_create( TX_EVENT_FLAGS_GROUP *group_ptr, // 指向事件標誌組的指針 CHAR *name_ptr // 事件標誌組名稱 ); // 設置事件標誌 UINT tx_event_flags_set( TX_EVENT_FLAGS_GROUP *group_ptr, // 指向事件標誌組的指針 ULONG flags, // 要設置的標誌位元 UINT set_option // 設置選項 (TX_AND 或 TX_OR) ); // 獲取事件標誌 UINT tx_event_flags_get( TX_EVENT_FLAGS_GROUP *group_ptr, // 指向事件標誌組的指針 ULONG requested_flags, // 要檢查的標誌條件 UINT get_option, // 獲取選項 (TX_AND_CLEAR 或 TX_OR_CLEAR) ULONG *actual_flags_ptr, // 符合條件的實際標誌 ULONG wait_option // 等待時間 ); ``` --- #### **actual_flags_ptr流程:** //需要範例 ```c ULONG actual_flags; tx_event_flags_get( &event_group, 0x07, TX_OR, &actual_flags, TX_WAIT_FOREVER); ``` 1. **請求條件:** - 請求範圍是 `0x07`,也就是 **位元 0~2**。 2. **`event_group` 中的值:** - `event_group = 0b10101100`,只有 **位元 2(0x04)** 被設置。 3. **篩選:** - 根據請求範圍(0x07 = 0b00000111),只檢查 **位元 0~2**。 - 發現 **位元 2 符合條件**。 4. **輸出到 `actual_flags`:** - `actual_flags = 0x04`(僅返回符合條件的位元值)。 --- ## **ThreadX - Byte Pool 與 Block Memory 介紹** **Byte Pool** 和 **Block Memory** 是 ThreadX 提供的兩種動態記憶體管理方式,用於分配和管理記憶體資源。在嵌入式系統中,由於記憶體有限且要求高效管理,這兩種機制能夠滿足不同的記憶體需求。 --- ### **Byte Pool(字節池)** **Byte Pool** 用於動態分配可變大小的記憶體塊,適合在需要靈活管理記憶體的場景中使用。記憶體位元組池的分配與傳統的 malloc 呼叫類似,其中包含所需的記憶體量(以位元組為單位)。 記憶體採用「首次適應」的方式從池中分配;例如,使用滿足請求的第一個可用記憶體區塊。 此區塊中多餘的記憶體會轉換為新區塊,並放回可用記憶體清單中。 此過程稱為碎片。 #### **特性** 1. **靈活性高** - 支援分配不同大小的記憶體塊。 2. **減少記憶體碎片** - 系統會自動合併相鄰的空閒記憶體區域,降低碎片化問題。 3. **適合多任務共用** - 多執行緒可共享相同的記憶體池,用於各自的記憶體需求。 --- #### **API介紹** ```c= // 創建字節池 UINT tx_byte_pool_create( TX_BYTE_POOL *pool_ptr, // 指向字節池控制塊的指針 CHAR *name_ptr, // 字節池名稱 VOID *pool_start, // 字節池開始地址 ULONG pool_size // 字節池大小 ); // 分配記憶體 UINT tx_byte_allocate( TX_BYTE_POOL *pool_ptr, // 指向字節池的指針 VOID **memory_ptr, // 用於返回分配的記憶體塊指針 ULONG memory_size, // 請求的記憶體大小 ULONG wait_option // 等待選項 (如 TX_WAIT_FOREVER) ); // 釋放記憶體 UINT tx_byte_release( VOID *memory_ptr // 要釋放的記憶體指針 ); ``` --- #### **使用場景** 1. **動態記憶體分配**: 適合需要不同大小記憶體的場景,例如建立動態緩衝區。 2. **資源共用**: 適合多執行緒共享同一段記憶體。 --- ### **Block Memory(記憶體塊池)** **Block Memory** 是專為固定大小記憶體分配設計的管理機制,適用於高效分配和釋放固定大小的記憶體塊。由於記憶體區塊池由固定大小的區塊組成,因此永遠不會出現任何碎片問題。 #### **特性** 1. **固定大小的記憶體塊** - 每個分配的記憶體塊大小相同,無需管理複雜的記憶體碎片。 2. **高效率** - 固定塊的結構讓分配和釋放操作非常快速。 3. **無記憶體碎片** - 因記憶體塊大小一致,不會產生碎片問題。 --- #### **API介紹** ```c= // 創建記憶體塊池 UINT tx_block_pool_create( TX_BLOCK_POOL *pool_ptr, // 指向記憶體塊池控制塊的指針 CHAR *name_ptr, // 記憶體塊池名稱 ULONG block_size, // 每個記憶體塊的大小 VOID *pool_start, // 記憶體塊池開始地址 ULONG pool_size // 記憶體塊池總大小 ); // 分配記憶體塊 UINT tx_block_allocate( TX_BLOCK_POOL *pool_ptr, // 指向記憶體塊池的指針 VOID **block_ptr, // 用於返回分配的記憶體塊指針 ULONG wait_option // 等待選項 (如 TX_WAIT_FOREVER) ); // 釋放記憶體塊 UINT tx_block_release( VOID *block_ptr // 要釋放的記憶體塊指針 ); ``` --- #### **使用場景** 1. **固定大小記憶體需求**: 適合應用場景包括網路封包緩衝區、設備驅動中的固定結構緩衝。 --- ### **Byte Pool 與 Block Memory 的比較** | **特性** | **Byte Pool** | **Block Memory** | |----------------------|-----------------------------------|-----------------------------| | **記憶體大小** | 可變大小 | 固定大小 | | **記憶體碎片管理** | 自動合併空閒區域,減少碎片化 | 無記憶體碎片問題 | | **效率** | 較低 | 高 | | **應用場景** | 動態大小需求,例如緩衝區 | 固定大小需求,例如數據包緩衝 | --- ## 中斷 中斷(Interrupt)是一種處理器響應異步事件的機制,常用於應對外部事件(如硬體中斷)或內部異常。在 ThreadX 中,為了保護共享資源並確保資料一致性,必須在臨界區域中正確管理中斷的啟用與禁用。 --- ### **中斷的基本概念** 1. **什麼是中斷?** 中斷是一種機制,允許處理器暫停當前執行的程式,轉而處理更高優先級的事件,完成後再返回繼續執行原程式。 2. **中斷的類型**: - **硬體中斷**:來自硬體設備的請求,例如定時器、UART、I/O 引腳的觸發。優先級比軟體中斷高。 - **軟體中斷**:程式或作業系統內部發出的中斷信號。 3. **中斷處理(ISR,Interrupt Service Routine)**: 當中斷發生時,系統會執行一段特殊程式來處理中斷事件,稱為中斷服務程序(ISR)。 --- ### **什麼是臨界區域?** **臨界區域** 是指程式中一段需要被保護的代碼區段,通常涉及 **共享資源** 或 **數據一致性**。 在多執行緒或中斷情境下,如果臨界區域中的操作被中斷,可能會導致競態條件或數據不一致。 > **重點**:在進入臨界區域時,需要保證同一時間只有一個執行緒或中斷上下文可以訪問該區域。 --- ### **什麼是競態條件?** 競態條件是指在多執行緒或多進程的環境中,兩個或多個執行緒同時訪問或修改共享資源,並且這些訪問或修改的順序會影響程式執行結果的現象。 - **關鍵點**: - 共享資源(例如變數、數據結構或硬體設備)。 - 並發操作(多執行緒或多進程同時執行)。 - 操作的執行順序可能導致程式行為不一致。 ```mermaid sequenceDiagram participant Thread1 participant SharedResource participant Thread2 SharedResource->>SharedResource:counter = 0 Thread1->>SharedResource: 讀取共享變數 counter = 0 Thread1->>Thread1: 修改變數值 counter++ Thread2->>SharedResource: 讀取共享變數 counter=0 <br>(在 Thread1 尚未完成前寫回) Thread2->>Thread2: 修改變數值 counter++ Thread2->>SharedResource: 寫回共享變數 counter=1 Thread1->>SharedResource: 寫回共享變數 counter=1 SharedResource->>Thread1: counter 的最終值出錯 (預期錯誤) ``` --- ### **中斷與臨界區域的關係** 在多任務系統中,中斷可能會在執行緒進入臨界區域後觸發,導致以下問題: 1. **數據不一致**: - 臨界區域正在修改共享數據,中斷觸發後執行 ISR 也修改了相同數據,導致資料錯誤。 2. **競態條件**: - 執行緒與中斷之間競爭共享資源,若未正確同步,會導致操作順序混亂。 3. **系統穩定性**: - 中斷可能打斷正在執行的臨界區域,導致系統不穩定或死鎖現象。 --- ### **ThreadX 中斷管理:`tx_interrupt_control`** ThreadX 提供 `tx_interrupt_control` 函數,用來 **禁用或啟用中斷**,保護臨界區域的操作完整性。 `tx_interrupt_control`主要是關閉硬體的中斷,ThreadX 沒有標準的「軟體中斷」機制,但其 搶占式排程提供了類似功能。 ### **中斷 API介紹** ```c UINT tx_interrupt_control(UINT new_posture); ``` - **`new_posture`**:中斷的狀態,允許的值包括: - `TX_INT_DISABLE`:禁用中斷 - `TX_INT_ENABLE`:啟用中斷 - **返回值**:返回中斷的舊狀態。 --- ### **保護臨界區域的正確方式** #### **禁用中斷** 在進入臨界區域前,禁用中斷,確保臨界區域的代碼完整執行。 ```c= UINT old_state; // 禁用中斷 old_state = tx_interrupt_control(TX_INT_DISABLE); // 臨界區域代碼 shared_data++; printf("Shared data updated: %d\n", shared_data); // 恢復中斷狀態 tx_interrupt_control(old_state); ``` #### **使用互斥鎖與中斷禁用結合** 對於需要同時防止中斷和多執行緒訪問的臨界區域,可以結合 **互斥鎖** 和 **中斷控制**: ```c= UINT old_state; // 禁用中斷 old_state = tx_interrupt_control(TX_INT_DISABLE); // 獲取互斥鎖 tx_mutex_get(&mutex, TX_WAIT_FOREVER); // 臨界區域代碼 shared_data++; printf("Shared data protected: %d\n", shared_data); // 釋放互斥鎖 tx_mutex_put(&mutex); // 恢復中斷狀態 tx_interrupt_control(old_state); ``` --- ### **中斷管理注意事項** 1. **臨界區域代碼要精簡**: - 禁用中斷會影響系統的即時性,臨界區域中的代碼應儘量簡短快速完成。 2. **保護共享資源**: - 確保在臨界區域內完成對共享資源的修改,避免競態條件。 3. **恢復中斷狀態**: - 在臨界區域完成後,及時恢復中斷狀態,避免系統長時間無法響應其他事件。 4. **避免長時間禁用中斷**: - 禁用中斷時間過長可能導致其他中斷事件無法即時處理,影響系統效能。 --- ### **流程示意圖** ```markdown 進入臨界區域 │ ├── 禁用中斷 (TX_INT_DISABLE) │ ├── 保護共享資源 │ ├── 修改數據 │ └── 執行代碼 │ ├── 恢復中斷 (TX_INT_ENABLE) │ 結束臨界區域 ``` 這樣可以有效確保執行緒或中斷上下文之間對臨界區域的安全訪問。 --- ### 中斷實作 ![image](https://hackmd.io/_uploads/H1c_nYQwyg.png) tim3 硬體中斷優先級15 ![image](https://hackmd.io/_uploads/Sys5nt7vyx.png) thread1 執行緒優先級0 :::spoiler thread1_entry ```c= void thread1_entry(ULONG input) { int x=0; //UINT old_state; while (1) { //old_state = tx_interrupt_control(TX_INT_DISABLE); printf("Thread1(0) : Running...\n"); for (int i = 0; i < 10000000; i++) { x++;x--; } printf("Thread1(0) : Finished work.\n"); //tx_interrupt_control(old_state); } } ``` ::: ![image](https://hackmd.io/_uploads/SkLy2KXvJx.png) thread1 執行到一半被中斷了 :::spoiler thread1_entry ```c= void thread1_entry(ULONG input) { int x=0; UINT old_state; while (1) { old_state = tx_interrupt_control(TX_INT_DISABLE); printf("Thread1(0) : Running...\n"); for (int i = 0; i < 10000000; i++) { x++;x--; } printf("Thread1(0) : Finished work.\n"); tx_interrupt_control(old_state); } } ``` ::: ![image](https://hackmd.io/_uploads/ryWFptXP1x.png) 恢復中斷後TIM3 IQR才執行 # 實作-semaphore、mutex、queue、timer ## 流程圖 ```mermaid flowchart TD A[計時器到期函數] -->|每 5 秒| B[喚醒 thread1] A -->|每 10 秒| C[喚醒 thread2] A -->|每 1 秒 tx_queue_send| D[訊息佇列] B --> E[釋放訊號量<br>tx_semaphore_put] C --> F[釋放訊號量<br>tx_semaphore_put] E --> G[訊號量] F --> G G -->|獲取訊號量兩次<br>tx_semaphore_get| H[thread3 完成同步 輸出] H -->|等待下一次訊號量釋放| G D --> I[接收執行緒 1<br>receiver1] D --> J[接收執行緒 2<br>receiver2] I -->|獲取互斥鎖<br>tx_mutex_get| K[互斥鎖] J -->|獲取互斥鎖<br>tx_mutex_get| K K --> L[從佇列接收訊息<br>tx_queue_receive] K --> M[從佇列接收訊息<br>tx_queue_receive] L --> N[處理接收到的時間] M --> O[處理接收到的時間] L -->|每接收兩次<br>釋放互斥鎖兩次<br>tx_mutex_put| K M -->|釋放互斥鎖<br>tx_mutex_put| K ``` ## 程式碼 :::spoiler main.c ```c= #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { //printf 輸出到串口 HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0xffff); return ch; } ``` ::: :::spoiler app_threadx.c ```c= /* USER CODE BEGIN Header */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "app_threadx.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "main.h" #include "stdio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define THREAD_STACK_SIZE 1024 // 定義執行緒堆疊大小為1024 #define QUEUE_STACK_SIZE 128 // 定義佇列堆疊大小為128 #define TIMER_TICKS_PER_SECOND 100 // 每秒時鐘節拍數 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ TX_TIMER my_timer; // 定義計時器 TX_SEMAPHORE my_semaphore; // 定義訊號量 uint8_t thread_stack1[THREAD_STACK_SIZE], thread_stack2[THREAD_STACK_SIZE], thread_stack3[THREAD_STACK_SIZE]; // 定義三個執行緒的堆疊 TX_THREAD thread_ptr1, thread_ptr2, thread_ptr3; // 定義三個執行緒控制塊 TX_MUTEX queue_mutex; // 定義互斥鎖 uint8_t thread_receiver2[THREAD_STACK_SIZE], thread_receiver1[THREAD_STACK_SIZE]; // 定義兩個接收執行緒的堆疊 uint8_t queue_stack[QUEUE_STACK_SIZE]; // 定義訊息佇列堆疊 TX_THREAD receiver2_ptr, receiver1_ptr; // 定義兩個接收執行緒控制塊 TX_QUEUE message_queue; // 定義訊息佇列 extern UART_HandleTypeDef huart1; // 用來傳送UART資料的外部變數 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN PFP */ void thread1(ULONG initial_input); // 執行緒1的入口函數 void thread2(ULONG initial_input); // 執行緒2的入口函數 void thread3(ULONG initial_input); // 執行緒3的入口函數 void receiver1(ULONG initial_input); // 接收執行緒1的入口函數 void receiver2(ULONG initial_input); // 接收執行緒2的入口函數 void timer_expiration_function(ULONG input); // 計時器到期回調函數 /* USER CODE END PFP */ /** * @brief Application ThreadX Initialization. * @param memory_ptr: memory pointer * @retval int */ UINT App_ThreadX_Init(VOID *memory_ptr) { UINT ret = TX_SUCCESS; TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; /* USER CODE BEGIN App_ThreadX_Init */ (void)byte_pool; // 無使用byte_pool變數,避免編譯警告 // 創建訊號量,初始值為0 tx_semaphore_create(&my_semaphore, "my_semaphore", 0); // 創建執行緒1 tx_thread_create(&thread_ptr1, "thread1", thread1, 0, thread_stack1, THREAD_STACK_SIZE, 15, 15, 3, TX_DONT_START); // 創建執行緒2 tx_thread_create(&thread_ptr2, "thread2", thread2, 0, thread_stack2, THREAD_STACK_SIZE, 15, 15, 3, TX_DONT_START); // 創建執行緒3 tx_thread_create(&thread_ptr3, "thread3", thread3, 0, thread_stack3, THREAD_STACK_SIZE, 15, 15, 3, TX_AUTO_ACTIVATE); // 創建訊息佇列 tx_queue_create(&message_queue, "Queue0", sizeof(uint32_t), queue_stack, 128); // 創建互斥鎖 tx_mutex_create(&queue_mutex, "my_mutex", 1); // 創建計時器 tx_timer_create(&my_timer, "My Timer", timer_expiration_function, 0, TIMER_TICKS_PER_SECOND, TIMER_TICKS_PER_SECOND, TX_AUTO_ACTIVATE); // 創建接收執行緒1 tx_thread_create(&receiver1_ptr, "receiver1", receiver1, 0, thread_receiver1, THREAD_STACK_SIZE, 14, 14, 3, TX_AUTO_ACTIVATE); // 創建接收執行緒2 tx_thread_create(&receiver2_ptr, "receiver2", receiver2, 0, thread_receiver2, THREAD_STACK_SIZE, 14, 14, 3, TX_AUTO_ACTIVATE); /* USER CODE END App_ThreadX_Init */ return ret; } /** * @brief MX_ThreadX_Init * @param None * @retval None */ void MX_ThreadX_Init(void) { /* USER CODE BEGIN Before_Kernel_Start */ /* USER CODE END Before_Kernel_Start */ tx_kernel_enter(); /* USER CODE BEGIN Kernel_Start_Error */ /* USER CODE END Kernel_Start_Error */ } /* USER CODE BEGIN 1 */ void thread1 (ULONG initial_input) { while (1) { printf("Thread1 Release counting semaphore\n"); // 釋放訊號量 tx_semaphore_put(&my_semaphore); tx_thread_suspend(&thread_ptr1); } } void thread2(ULONG initial_input) { while (1) { printf("Thread2 Release counting semaphore\n"); // 釋放訊號量 tx_semaphore_put(&my_semaphore); tx_thread_suspend(&thread_ptr2); } } void thread3(ULONG initial_input) { while (1) { // 獲取訊號量 tx_semaphore_get(&my_semaphore, TX_WAIT_FOREVER); // 再次獲取訊號量 tx_semaphore_get(&my_semaphore, TX_WAIT_FOREVER); printf("Thread3 synchronized\n"); } } void receiver1 (ULONG initial_input) { ULONG received_time; static int r_timer = 0; while (1) { // 嘗試獲取互斥鎖 if (tx_mutex_get(&queue_mutex, TX_WAIT_FOREVER) == TX_SUCCESS) { // 從佇列接收訊息 if (tx_queue_receive(&message_queue, &received_time, TX_WAIT_FOREVER) == TX_SUCCESS) { printf("receiver1: Received system time %lu\n", received_time); r_timer += 1; } // 根據條件釋放互斥鎖 if (r_timer % 2 == 0) { tx_mutex_put(&queue_mutex); // get兩次,也要put兩次 tx_mutex_put(&queue_mutex); } } } } void receiver2 (ULONG initial_input) { ULONG received_time; while (1) { // 嘗試獲取互斥鎖 if (tx_mutex_get(&queue_mutex, TX_WAIT_FOREVER) == TX_SUCCESS) { // 從佇列接收訊息 if (tx_queue_receive(&message_queue, &received_time, TX_WAIT_FOREVER) == TX_SUCCESS) { printf("receiver2: Received system time %lu\n", received_time); } // 釋放互斥鎖 tx_mutex_put(&queue_mutex); } } } void timer_expiration_function(ULONG input) { ULONG current_time; UINT status; static int flag_thread=0; // 獲取當前系統時間 current_time = tx_time_get(); // 傳遞系統時間到佇列 status = tx_queue_send(&message_queue, &current_time, TX_NO_WAIT); if (status == TX_SUCCESS) { printf("Sent success\n"); flag_thread++; if(flag_thread%5==0){ tx_thread_resume(&thread_ptr1); } if(flag_thread%10==0){ tx_thread_resume(&thread_ptr2); } } else { printf("Queue full, system time %lu not sent\n", current_time); } } /* USER CODE END 1 */ ``` ::: --- # 實作-block_pool、event_flags ## 流程圖 ```mermaid flowchart TD Thread1Start[啟動 Thread1] Thread2Start[啟動 Thread2] subgraph Thread2[Thread2 數據接收] Thread2Start --> WaitEventFlag2[等待 Thread1 的事件標誌] WaitEventFlag2 --> AllocateMemory2[分配記憶體塊] AllocateMemory2 --> ProcessData2[處理記憶體塊中的數據] ProcessData2 --> ReleaseMemory2[釋放記憶體塊] ReleaseMemory2 --> Sleep2[Thread2 休眠] Sleep2 --> WaitEventFlag2 end subgraph Thread1[Thread1 數據寫入] Thread1Start --> AllocateMemory1[從記憶體塊池分配記憶體塊] AllocateMemory1 --> FillData1[填充數據到記憶體塊] FillData1 --> SetEventFlag1[設置事件標誌通知thread2] SetEventFlag1 --> ReleaseMemory1[釋放記憶體塊] ReleaseMemory1 --> Sleep1[Thread1 休眠] Sleep1 --> AllocateMemory1 end Thread1Start --> WaitEventFlag2 ``` ## 程式碼 :::spoiler main.c ```c= #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { //printf 輸出到串口 HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0xffff); return ch; } ``` ::: :::spoiler app_threadx.c ```c= /* USER CODE BEGIN Header */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "app_threadx.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "main.h" #include "stdio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define BLOCK_SIZE 32 // 每個記憶體塊的大小 #define POOL_SIZE 128 // 記憶體塊池的總大小 #define THREAD_STACK_SIZE 1024 // 執行緒堆疊大小 #define THREAD1_FLAG 0x01 // Thread1 的事件標誌 #define THREAD2_FLAG 0x02 // Thread2 的事件標誌 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ TX_BLOCK_POOL block_pool; // 記憶體塊池 TX_THREAD thread1_ptr; // Thread1 (生產者執行緒) TX_THREAD thread2_ptr; // Thread2 (消費者執行緒) TX_EVENT_FLAGS_GROUP event_flags; // 事件標誌組 uint8_t pool_memory[POOL_SIZE]; // 記憶體塊池記憶體 uint8_t thread1_stack[THREAD_STACK_SIZE]; // Thread1 的堆疊 uint8_t thread2_stack[THREAD_STACK_SIZE]; // Thread2 的堆疊 extern UART_HandleTypeDef huart1; // 用於 UART 資料傳輸的外部變數 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN PFP */ void thread1_entry(ULONG initial_input); // Thread1 的入口函數 void thread2_entry(ULONG initial_input); // Thread2 的入口函數 /* USER CODE END PFP */ /** * @brief Application ThreadX Initialization. * @param memory_ptr: memory pointer * @retval int */ UINT App_ThreadX_Init(VOID *memory_ptr) { UINT ret = TX_SUCCESS; TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; /* USER CODE BEGIN App_ThreadX_Init */ (void)byte_pool; // 無使用 byte_pool 變數,避免編譯警告 // 創建記憶體塊池 tx_block_pool_create(&block_pool, "Block Pool", BLOCK_SIZE, pool_memory, POOL_SIZE); // 創建事件標誌組 tx_event_flags_create(&event_flags, "Event Flags"); // 創建生產者執行緒 (Thread1) tx_thread_create(&thread1_ptr, "Thread1", thread1_entry, 0, thread1_stack, THREAD_STACK_SIZE, 14, 14, TX_NO_TIME_SLICE, TX_AUTO_START); // 創建消費者執行緒 (Thread2) tx_thread_create(&thread2_ptr, "Thread2", thread2_entry, 0, thread2_stack, THREAD_STACK_SIZE, 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START); /* USER CODE END App_ThreadX_Init */ return ret; } /** * @brief MX_ThreadX_Init * @param None * @retval None */ void MX_ThreadX_Init(void) { /* USER CODE BEGIN Before_Kernel_Start */ /* USER CODE END Before_Kernel_Start */ tx_kernel_enter(); // 啟動 ThreadX 核心 /* USER CODE BEGIN Kernel_Start_Error */ /* USER CODE END Kernel_Start_Error */ } /* USER CODE BEGIN 1 */ // Thread1 (生產者) 入口函數 void thread1_entry(ULONG input) { VOID *allocated_memory; // 指向分配的記憶體塊 static int val = 0; // 用於模擬數據的變數 while (1) { // 從記憶體塊池分配記憶體 if (tx_block_allocate(&block_pool, &allocated_memory, TX_WAIT_FOREVER) == TX_SUCCESS) { // 填充數據到記憶體塊 snprintf((char *)allocated_memory, BLOCK_SIZE, "Data: %d\n", val++); printf("Thread1: Processing block at %p\n", allocated_memory); printf("Thread1: %s", (char *)allocated_memory); // 設置事件標誌通知 Thread2 tx_event_flags_set(&event_flags, THREAD1_FLAG, TX_OR); // 釋放記憶體塊 tx_block_release(allocated_memory); printf("Thread1: Block released\n"); // 休眠 tx_thread_sleep(100); } } } // Thread2 (消費者) 入口函數 void thread2_entry(ULONG input) { VOID *allocated_memory; // 指向分配的記憶體塊 ULONG actual_flags; // 實際取得的事件標誌 while (1) { // 等待 Thread1 的事件標誌 tx_event_flags_get(&event_flags, THREAD1_FLAG, TX_AND_CLEAR, &actual_flags, TX_WAIT_FOREVER); // 從記憶體塊池分配記憶體 if (tx_block_allocate(&block_pool, &allocated_memory, TX_WAIT_FOREVER) == TX_SUCCESS) { // 處理數據 printf("Thread2: Processing block at %p\n", allocated_memory); printf("Thread2: Received data: %s", (char *)allocated_memory); // 釋放記憶體塊 tx_block_release(allocated_memory); printf("Thread2: Block released\n"); // 休眠 tx_thread_sleep(100); } } } /* USER CODE END 1 */ ``` ::: ### 實作-3 ```mermaid flowchart TD A(接收溫溼度數據)-->|tx_block_allocate <br>tx_mutex|H[儲存數據] H -->|tx_queue_send| B[Heat Index Thread] H -->|tx_queue_send| C[Logger Thread] B --> D[計算並輸出體感溫度] C --> F[計算並輸出平均溫溼度] ``` :::spoiler main.c ```c= #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { //printf 輸出到串口 HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0xffff); return ch; } ``` ::: :::spoiler app_threadx.c ```c= /* USER CODE BEGIN Header */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "app_threadx.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "main.h" #include "stdio.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define QUEUE_SIZE 10 // 定義隊列大小 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ uint8_t sensor_thread_stack[1024]; uint8_t logger_thread_stack[1024]; uint8_t heat_index_thread_stack[1024]; /* 定義資源 */ TX_THREAD sensor_thread; // 感測器執行緒 TX_THREAD logger_thread; // 記錄執行緒 TX_THREAD heat_index_thread; // 體感溫度執行緒 TX_BLOCK_POOL block_pool; // 記憶體塊池 TX_QUEUE heat_index_queue; // 體感溫度隊列 TX_QUEUE logger_queue; // 記錄隊列 TX_MUTEX data_mutex; // 資料互斥鎖 /* 緩衝區與其他資源 */ UCHAR block_pool_memory[1024]; // 記憶體塊池緩衝區 UCHAR heat_index_queue_memory[QUEUE_SIZE * sizeof(UINT)]; // 體感溫度隊列緩衝區 UCHAR logger_queue_memory[QUEUE_SIZE * sizeof(UINT) * 4]; // 記錄隊列緩衝區 extern UART_HandleTypeDef huart1; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN PFP */ /* Function Prototypes */ UINT App_ThreadX_Init(VOID *memory_ptr); void sensor_thread_entry(ULONG thread_input); // 感測器執行緒入口 void logger_thread_entry(ULONG thread_input); // 記錄執行緒入口 void heat_index_thread_entry(ULONG thread_input); // 體感溫度執行緒入口 void queue_send_notify_callback(TX_QUEUE *queue_ptr); // 隊列通知回調函數 /* USER CODE END PFP */ /** * @brief Application ThreadX Initialization. * @param memory_ptr: memory pointer * @retval int */ UINT App_ThreadX_Init(VOID *memory_ptr) { UINT ret = TX_SUCCESS; TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL*)memory_ptr; /* USER CODE BEGIN App_ThreadX_Init */ (void)byte_pool; tx_block_pool_create(&block_pool, "Block Pool", sizeof(UINT) * 4, block_pool_memory, sizeof(block_pool_memory)); tx_queue_create(&heat_index_queue, "Heat Index Queue", TX_1_ULONG, heat_index_queue_memory, sizeof(heat_index_queue_memory)); tx_queue_send_notify(&heat_index_queue, queue_send_notify_callback); tx_queue_create(&logger_queue, "Logger Queue", TX_2_ULONG, logger_queue_memory, sizeof(logger_queue_memory)); tx_mutex_create(&data_mutex, "Data Mutex", TX_NO_INHERIT); tx_thread_create(&sensor_thread, "Sensor Thread", sensor_thread_entry, 0, sensor_thread_stack, 1024, 1, 1, TX_NO_TIME_SLICE, TX_AUTO_START) ; tx_thread_create(&logger_thread, "Logger Thread", logger_thread_entry, 0, logger_thread_stack, 1024, 2, 2, TX_NO_TIME_SLICE, TX_AUTO_START); tx_thread_create(&heat_index_thread, "Heat Index Thread", heat_index_thread_entry, 0, heat_index_thread_stack, 1024, 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START); /* USER CODE END App_ThreadX_Init */ return ret; } /** * @brief MX_ThreadX_Init * @param None * @retval None */ void MX_ThreadX_Init(void) { /* USER CODE BEGIN Before_Kernel_Start */ /* USER CODE END Before_Kernel_Start */ tx_kernel_enter(); /* USER CODE BEGIN Kernel_Start_Error */ /* USER CODE END Kernel_Start_Error */ } /* USER CODE BEGIN 1 */ /* 感測器執行緒 */ void sensor_thread_entry(ULONG thread_input) { UINT *sensor_data_heat_index; UINT *sensor_data_logger; while (1) { if (tx_block_allocate(&block_pool, (VOID **)&sensor_data_heat_index, TX_WAIT_FOREVER) == TX_SUCCESS && tx_block_allocate(&block_pool, (VOID **)&sensor_data_logger, TX_WAIT_FOREVER) == TX_SUCCESS&& tx_mutex_get(&data_mutex, TX_WAIT_FOREVER) == TX_SUCCESS) { sensor_data_heat_index[0] = rand() % 30 + 10; // 模擬溫度資料 sensor_data_heat_index[1] = rand() % 100; // 模擬濕度資料 sensor_data_logger[0] = sensor_data_heat_index[0]; sensor_data_logger[1] = sensor_data_heat_index[1]; // 發送資料到隊列 tx_queue_send(&heat_index_queue, &sensor_data_heat_index, TX_WAIT_FOREVER); tx_queue_send(&logger_queue, &sensor_data_logger, TX_WAIT_FOREVER); printf("Temp: %u°C, Hum: %u%%\n", sensor_data_heat_index[0], sensor_data_heat_index[1]); } tx_mutex_put(&data_mutex); tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); } } /* 體感溫度執行緒 */ void heat_index_thread_entry(ULONG thread_input) { UINT *sensor_data; int heat_index; while (1) { /* 接收溫濕度資料 */ if (tx_queue_receive(&heat_index_queue, &sensor_data, TX_WAIT_FOREVER) == TX_SUCCESS) { /* 計算體感溫度 */ heat_index = sensor_data[0] * 104 + 120 * sensor_data[1]/100 - 270; /* 打印體感溫度 */ printf("Heat Index: %d°C\n", heat_index/100); sensor_data[2]=heat_index/100; tx_block_release(sensor_data); // 釋放記憶體塊 } } } /* 記錄執行緒 */ void logger_thread_entry(ULONG thread_input) { UINT *sensor_data; static UINT avg_data[2]={0,0}; // 平均溫濕度 static int receive_times=0; // 接收次數 while (1) { /* 接收並打印溫濕度資料 */ if (tx_queue_receive(&logger_queue, &sensor_data, TX_WAIT_FOREVER) == TX_SUCCESS) { avg_data[0] += sensor_data[0]; // 累加溫度 avg_data[1] += sensor_data[1]; // 累加濕度 receive_times++; if(receive_times%10==0) printf("Avg Temp: %u°C, Avg Hum: %u%%\n", avg_data[0] / receive_times, avg_data[1] / receive_times); tx_block_release(sensor_data); // 釋放記憶體塊 } } } /* 隊列通知回調函數 */ void queue_send_notify_callback(TX_QUEUE *queue_ptr) { printf("A new message is sent to the queue.\n"); } /* USER CODE END 1 */ ``` :::