# 開發紀錄 ## 背景 在嵌入式系統中,RTOS 常用於管理多個任務之間的協作。 本專案建立一個基於 STM32 的 RTOS 系統,包含: - Sensor Task(Producer):透過 I2C 讀取感測器資料 - Logger Task(Consumer):透過 UART 輸出資料 - 使用 message queue 作為任務間通訊機制 此架構是嵌入式系統中常見的 producer-consumer 模型。 ## 動機 在實際嵌入式系統中,任務之間的速率不匹配(rate mismatch)是一個常見問題, 可能導致: - queue 堆積(backlog) - latency 增加 - 資料丟失(drop) - 系統不穩定 然而,這類問題往往難以觀察與量化。 因此,本專案的目標是: - 建立一個可量測的 RTOS benchmark 系統 - 分析 producer-consumer 架構下的系統行為 - 找出 latency 與 bottleneck 的來源 - 設計機制改善系統穩定性 ## Future Work 目前系統仍有以下待改善項目: - 將 I2C driver 由 polling 改為 interrupt-driven(non-blocking) - 解決 busy wait 導致 CPU 無法讓出 scheduler 的問題 - 加入 mutex 保護 I2C peripheral(確保 thread safety) - 完善 error handling(BERR / ARLO / OVR) - 加入完整 timeout 機制(避免 deadlock) ## 00. Initial Architecture (RTOS Bring-up) [cb71881](https://github.com/rain20010126/stm32-rtos-benchmark/commit/cb718810ae2da2a208d25faed96e1854c5fb971e) ### System Setup 本專案起始於 STM32CubeIDE 產生的基本 RTOS 架構, 使用 CMSIS-RTOS v2(FreeRTOS)作為系統核心。 初始系統包含: - RTOS scheduler - 基本 thread(default task) - Message queue 機制 在此基礎上,逐步建立應用層架構。 ### Architecture Design 系統採用 producer-consumer 架構,將資料取得與輸出解耦: - **Sensor Task(Producer)** - 透過 I2C 讀取感測器資料 - 週期性產生資料(固定 rate) - **Logger Task(Consumer)** - 從 message queue 取出資料 - 透過 UART 輸出 - **Message Queue** - 作為 task 間通訊機制 - decouple producer 與 consumer 的執行速率 ### Data Flow Sensor Task → Queue → Logger Task → UART ### Design Rationale 此設計的目的為: - 避免 I/O blocking 影響系統整體運作 - 將資料取得與處理解耦(decoupling) - 提供可觀察系統行為的基礎架構(benchmark-ready) 此架構也成為後續效能分析與控制策略實驗的基礎。 ## 01. Benchmark (Overloaded System) [c42fe26](https://github.com/rain20010126/stm32-rtos-benchmark/commit/c42fe26261161339167cfaa9b7b129e9bd7ba397) ### Benchmark 系統主要量測以下指標: - **Throughput** - 每秒處理的資料數(msg/sec) - **Latency** - 定義為 enqueue → processing 完成(end-to-end latency) - **Queue Depth** - queue 中資料數量(觀察 backlog) - **Drop Rate** - 因 queue 滿載而丟失的資料數 - **CPU Usage** - 使用 idle-based estimation(透過 idle counter 計算) 測量方法如下: - 使用 timestamp(HAL_GetTick 或 cycle counter)記錄時間 - 在 enqueue 時記錄 T0 - 在 dequeue 時量測 queue latency - 在 processing 完成時量測 total latency - processing latency = total - queue ### 實驗設定 Producer rate:10 Hz(osDelay(100)) Producer workload:50000 loop Consumer workload:300000 loop Queue size:10 Queue 模式:non-blocking(timeout = 0) Latency 定義:enqueue → processing 完成(end-to-end latency) CPU 計算方式:idle-based estimation ### 實驗結果 Throughput:約 4 msg/sec CPU usage:約 70% ~ 87% Max queue depth:10(持續滿) Drop rate:約 2 ~ 3 msg/sec Latency:約 300 ~ 310 ms ![image](https://hackmd.io/_uploads/Byumsn8obg.png) ### 問題觀察(Problem Observation) 在 baseline 實驗中觀察到以下現象: - Throughput 僅約 4 msg/sec(遠低於 10 Hz 輸入) - Queue 長時間維持滿載(maxQ = 10) - 持續發生資料丟失(drop 約 2~3/sec) - Latency 約 300 ms,明顯偏高 - CPU 使用率約 70%~87% 雖然觀察到系統效能不佳,但僅從「總 latency」無法判斷: - latency 是來自 queue 等待? - 還是來自 processing 過慢? 無法明確定位 bottleneck 推測系統 bottleneck 可能在: 1. Consumer processing 過慢(CPU-bound) 2. Queue backlog 導致等待時間增加 ### 解決方法(Approach) 為了定位問題來源,將 latency 拆解為兩部分: 1. **Queue latency** - enqueue → dequeue - 代表資料在 queue 中等待時間 2. **Processing latency** - dequeue → processing 完成 - 代表資料處理時間 ### 實驗設計(Experiment Design) 在 LoggerTask 中加入兩段量測: - dequeue 當下量測 queue latency - processing 結束後量測 total latency - processing latency = total - queue ## 02. Bottleneck Analysis (Latency Decomposition) [0fc0e7e](https://github.com/rain20010126/stm32-rtos-benchmark/commit/0fc0e7e0e76fb0b38efe0d4b042877f1a4301b2f) ### 實驗結果 透過 latency 拆解後,觀察到以下數據: - queue latency:約 250~300 ms - processing latency:約 27~30 ms - total latency:約 300~312 ms ![image](https://hackmd.io/_uploads/Bk6SSwPj-x.png) ### 結果分析 1. queue latency 明顯大於 processing latency - 約為 9 倍以上 - 表示大部分延遲來自資料等待時間 2. processing latency 相對穩定 - 約 25~30 ms - 與 busy loop 設定一致 3. total latency ≈ queue + processing - 驗證量測方法正確 ### 結論 系統 bottleneck 位於: - [x] Queue backlog(主要原因) - [ ] Processing(次要) 整體延遲主要來自資料在 queue 中等待,而非計算本身。 ### 發現 - 系統瓶頸來自「處理能力 < 輸入速率」,導致 queue 堆積 - 優化方向應優先考慮: - 提升 consumer throughput - 或降低 producer rate - 單純優化 processing 速度未必能解決 latency 問題 將 LoggerTask 的 workload 由 300000 逐步降低至: - 200000 - 100000 | workload | tp | queue latency | processing | drop | CPU | |---------|----|--------------|------------|------|-----| | 200000 | ~5 | ~180 ms | ~18 ms | 有 | ~100% | | 100000 | ~11 | ≈ 0 | ~7 ms | 無 | ~70% | ![image](https://hackmd.io/_uploads/rkvhLiwoWl.png) 實驗結果驗證: - 系統 bottleneck 並非單純 processing 慢,而是 processing 能力不足以應付輸入速率 - 當 processing rate ≥ input rate 時,queue backlog 消失,系統進入穩定狀態 - latency 的主要來源為 queue 等待時間,而非計算時間 ### 系統背景 在一個基於 STM32 的 RTOS 系統中,系統包含兩個主要任務: - Sensor Task(Producer):透過 I2C 讀取感測器資料,並將資料送入 message queue - Logger Task(Consumer):從 queue 取出資料,透過 UART 輸出 系統透過 RTOS queue 作為兩個 task 之間的資料傳遞機制。 ### 問題描述 在實驗中發現: 當 Producer 產生資料的速率高於 Consumer 處理能力 時 queue 會逐漸累積(queue buildup)導致: - latency 增加 - queue overflow - 系統行為變得不穩定 本質問題為: Producer 與 Consumer 之間速率不匹配(rate mismatch) ### 設計目標 本系統希望達成: - 維持 queue depth 在合理範圍(避免爆滿或過空) - 降低 latency 並維持穩定性 - 系統可以根據負載自動調整,而非使用固定參數 **不同負載下都能維持穩定** ## 03. Rate Control (PI Controller) [5503d44](https://github.com/rain20010126/stm32-rtos-benchmark/commit/5503d44c648cf3a5e767f2f4a8b41b0ba4da155c) ### 解法 為了解決 rate mismatch 問題,將系統建模為一個feedback control system,使用 PI 控制: - queue depth 作為系統狀態(feedback) - 根據目前 queue 狀態,動態調整 Producer 的速率 ### 實驗結果 P Control 問題(限制) 觀察實驗結果: - target = 5 - 實際 queue depth ≈ 6~7 **steady-state error(穩態誤差)** 為了解決 steady-state error,引入 integral term: 避免 integral 無限制累積: ```c if (integral > limit) integral = limit; if (integral < -limit) integral = -limit; ``` ![image](https://hackmd.io/_uploads/BykLaCDsZl.png) | 指標 | Baseline(無控制) | PI Control | |------|------------------|-----------| | Queue Depth | 滿載(maxQ=10) | 穩定於 target(≈5) | | Latency | ~270000 us | ~106000 us | | Drop | 有(7~8 / s) | 0 | | 穩定性 | 不穩定 | 穩定 | | 系統狀態 | 發散(divergent) | 收斂(steady state) | 在未加入控制器時,系統因 throughput mismatch 導致 queue 堆積、latency 上升與資料丟失。 導入 PI control 後: - 系統成功達到穩定狀態 - queue depth 收斂至目標值 - latency 大幅下降 - 完全避免資料丟失 顯示: > PI control 能有效解決 producer-consumer 不平衡問題,並顯著提升系統效能與穩定性。 ## 04. Driver Integration (I2C under RTOS) [259231d](https://github.com/rain20010126/stm32-rtos-benchmark/commit/259231dce980fa48285dcecfdb17f27bc4203b7e) ### 目的 將自製 register-level I2C driver 整合至 RTOS 系統中, 驗證其在 multi-task 環境下的正確性與穩定性, 並取代 HAL I2C API。 - 使用 register-level 操作(非 HAL) - 支援: - START / STOP condition - ACK / NACK control - repeated start - 加入 timeout 機制避免 deadlock ### 問題 #### Timeout 不完整 (未處理) i2c_read_byte 沒加 timeout #### Error handling 不完整 (未處理) - BERR(bus error) - ARLO(arbitration lost) - OVR(overrun) #### blocking (未處理) I2C driver 採用 polling(busy wait)方式: - while (!TXE) - while (!RXNE) 在 RTOS 環境下: - 會佔用 CPU - 無法讓出 scheduler 觀察 sensor_read() 需等待 I2C transaction 完成, 包含: - start condition - address phase - data transfer #### Thread safety(未處理) I2C peripheral 為 shared resource 即使在單核心 RTOS 系統中, task scheduling 仍可能導致: - transaction interleaving - bus corruption 目前 driver 未使用 mutex 保護 I2C access ## 05. Benchmark Architecture (Non-intrusive Measurement) [bb72452](https://github.com/rain20010126/stm32-rtos-benchmark/commit/bb72452fd411e22ed4def9cce9587b4768b39342) ### 實驗設計 建立一個無額外計算負載(no synthetic workload)的 baseline,用於觀察 RTOS 架構本身(polling + blocking queue)的行為。設定如下: - 固定 producer rate(osDelay(50)) - 關閉 PI controller - 使用 polling-based producer - 使用 blocking consumer 建立穩定基準,用於後續比較 - PI control vs no control - Polling vs Event-driven ### 實驗設定 - Producer rate:50 Hz(osDelay(50)) - Producer workload:0 loop (無額外計算負載) - Consumer workload:0 loop(or current)(無額外計算負載) - Queue size:10 - Queue mode:blocking - Benchmark:non-intrusive ### 實驗結果 ![image](https://hackmd.io/_uploads/BkchZI6oWe.png) | 指標 | 數值 | |------|------| | Throughput | 10 msg/sec | | CPU usage | ~52% | | Max queue depth | 1 | | Drop rate | 0 | | Latency | ~12 us | - 系統達到穩定狀態(steady state) - Producer 與 Consumer 速率匹配 - Queue 無 backlog(maxQ ≈ 1) - 無資料丟失(drop = 0) - Latency 極低,主要為 processing 時間 CPU usage 約為 52%,主要來自 polling-based producer 持續喚醒與系統基本運作開銷,顯示在無 workload 情況下,polling 本身仍具有固定成本 ## h2 ### 架構比較 #### blocking ```c i2c_read_reg() { start wait SB send addr wait ADDR write reg wait TXE repeated start wait SB read data wait RXNE } ``` #### interrupt 版本 ```c i2c_read_reg_async() { set state = START enable interrupt trigger START return // CPU 立刻回來 } ```