# 2025 嵌入式作業系統分析與實作 Lab 0 ## lab 目標 * 熟悉 STM32 系列 CPU 的核心特性與開發環境 * 了解 Cort-M4 系列 CPU 的核心特性與開發環境 * 建立開發與編譯流程,掌握交叉編譯與燒錄方式 * 嘗試驗證 FreeRTOS 是否能順利在開發板上運行 ## 實作流程 ### 1. Download STM32CubeIDE 下載專用的IDE: [STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html) &emsp; ### 2. Download FreeRTOS source code 下載 FreeRTOS 作業系統原始碼: [Github: FreeRTOS v10.2.1](https://github.com/FreeRTOS/FreeRTOS/tree/V10.2.1) &emsp; ### 3. 建立專案 在 IDE 選擇開發板並建立專案 &emsp; ### 4. 整合 FreeRTOS 至 STM32CubeIDE 專案 FreeRTOS 支援多種平台與CPU架構,僅移植與 STM32 Cortex-M4 平台相關的必要檔案,包含以下: ```txt! FreeRTOS/Source/indlude // FreeRTOS 核心的共同標頭檔 FreeRTOS/Source/*.c // 核心功能實作 FreeRTOS/Demo/CORTEX_M4F_STM32F407ZG-SK/FreeRTOSConfig.h // 參數與功能開關 FreeRTOS/Source/portable/MemMang/heap_4.c // 記憶體分配的策略 FreeRTOS/Source/portable/GCC/ARM_CM4F // 專為 GCC 編譯器 + ARM Cortex-M4F架構寫的 porting 層 ``` 並修改部分原始碼避免相容性問題: ``` C=44 // @file FreeRTOSConfig.h //#ifdef __ICCARM__ #if defined(_ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock; #endif ``` > `45`:僅針對 IAR 編譯器(ICCARM)才會執行 > `46`:確保不管使用哪一種主流編譯器,都會執行 `47-48` > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\_\_CC_ARM\_\_:Keil 編譯器(ARM Compiler) > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\_\_GNUC\_\_:GCC 編譯器(STM32CubeIDE 就是使用這個) ```C=51 // @file FreeRTOSConfig.h #define configUSE_IDLE_HOOK 0 // 原為 1 #define configUSE_TICK_HOOK 0 // 原為 1 #define configCHECK_FOR_STACK_OVERFLOW 0 // 原為 1 (# 65) #define configUSE_MALLOC_FAILED_HOOK 0 // 原為 1 (# 67) ``` 在預設狀態下,`FreeRTOSConfig.h` 中有啟用多個 Hook function,需實作對應涵式,為避免不必要的錯誤或中斷,先設為 0,以下為各參數作用: ||| |---|---| | `configUSE_IDLE_HOOK` | 是否啟用 Idle Hook(CPU idle 時會呼叫的函式) | | `configUSE_TICK_HOOK` | 是否啟用 Tick Hook(每個 tick interrupt 呼叫一次) | | `configCHECK_FOR_STACK_OVERFLOW` | 當 `pvPortMalloc()` 分配失敗時是否呼叫錯誤處理函式 | | `configUSE_MALLOC_FAILED_HOOK` | 是否啟用堆疊溢出檢查機制,2 表示使用更完整的檢查 | &emsp; ### 5. FreeRTOS 相容設定:Basic Timer 與 NVIC 為避免衝突並確保 FreeRTOS 能正確運行,修改 `.ioc` 檔: * **修改 SYS 的 Timebase Source 為 TIM7** 預設的 SysTick timer 會與 FreeRTOS 的 RTOS tick 衝突,因此改成使用基本計時器(如 TIM7)作為 Timebase,可避免衝突。 * **設定 Debug 模式為 Serial Wire** 可確保使用 ST-Link 進行 Debug 與燒錄的連線順利。 * **修改 NVIC Priority Group 為 4 bits** 為了讓 FreeRTOS 更好地控制中斷優先順序,將優先權分組設為 4 bits(代表全部 bits 都拿來處理 preemption priority)。 ![image](https://hackmd.io/_uploads/H1Xy2JEpJl.png) ![image](https://hackmd.io/_uploads/BJZe3J4T1g.png) :bangbang: 每次修改 `.ioc` 檔並儲存後,系統詢問是否生成程式碼,會覆蓋掉部分程式碼,說明如下: |可能會被覆蓋的檔案 | 說明 | |---|---| |`Src/` 資料夾底下的 `main.c`, `stm32xxxx_it.c`, `syscalls.c`, `system_stm32xxxx.c` |裡面非使用者區塊以外的程式碼 都會被自動重新產生| |`Inc/` 資料夾裡的 `main.h`, `stm32xxxx_it.h`, `gpio.h` 等|若有自訂變數或未放在保留區,可能被蓋掉| |`.c/.h` 中 HAL 初始化區域(如 GPIO、TIM、USART) |只要在 .ioc 裡做了變更,這些設定都會依照新內容重寫| &emsp; ### 6. 確保使用 FreeRTOS interrupt 生成程式碼後需在 `stm32f4xx_it.c` 及 `stm32f4xx_it.h` 檔案中註解以下 handler,以確保不會和 FreeRTOS 造成衝突 (`stm32f4xx_it.c` 中定義;`stm32f4xx_it.h`中宣告) |Handler | FreeRTOS 的用途| |---|--- |`PendSV_Handler` |進行任務切換(Context Switch) 的關鍵| |`SVC_Handler` |系統呼叫服務,FreeRTOS 透過它來啟動第一個任務| |`SysTick_Handler`| 實作 tick timer,決定 RTOS 的排程頻率| 若不註解掉,可能造成: * 編譯器出現 duplicate symbol 錯誤,因為這些 handler 在 FreeRTOS 的 porting code 已有定義 * 若編譯未出錯,也可能呼叫到錯誤的 handler 實作 &emsp; ### 7. 測試 Porting 是否完成 ```C=19 // @file main.c #include "main.h" #include "FreeRTOS.h" // 載入整個 FreeRTOS 核心、包含系統參數與設定 #include "task.h" // 包含與 Task 操作相關的 API ``` > `21-22`:在 `main.c` 內補兩個 header ```C=76 // @file main.c int main(void){ /* ... */ vTaskStartScheduler(); /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { //照理來說不會進入 while 迴圈 } } ``` >並在 while 迴圈上面補 `vTaskStartScheduler();`,做為 FreeRTOS 的任務排程器啟動點,讓預先建立好的任務可以開始被排入 CPU 執行,ROTS 排程器啟動後,主程式的 `main()` 幾乎不再返回,系統轉為由 RTOS 控制。 &emsp; ### 8. 簡單任務實作 - 綠燈閃爍 ```C=58 // @file main.c TaskHandle_t xHandle=NULL; void LED_Task( void ){ HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET); for(;;){ HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET ); vTaskDelay(500); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET ); vTaskDelay(500); } } main(){ /* ... */ xTaskCreate(LED_Task, "NAME", 128, NULL, 1, &xHandle); } ``` > `59`:宣告一個任務控制代碼(Task handle),可以用來追蹤或控制這個任務(例如暫停、刪除) > `61`:先將 D12~D15 四顆 LED 全部關閉(Low) > `64-67`:讓綠燈亮起 500 tick,再熄滅 500 tick > `72`:在 `main()` 中使用 `xTaskCreate()` 建立任務 &emsp; #### `xTaskCreate` 函式介紹 `xTaskCreate(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask)` |參數名稱|說明| |---|---| |`pvTaskCode`|任務函式名稱| |`pcName`|任務名稱(optional)| |`usStackDepth`|stack 大小(以 word 為單位)| |`pvParameters`|傳給任務的參數| |`uxPriority`|Priority| |`pxCreatedTask`|存放任務控制代碼的變數指標| --- GitHub [Embedded-System-FreeRTOS-Development/lab_0/](https://github.com/sihjie/Embedded-System-FreeRTOS-Development/tree/main/lab_0) --- [Demo Video](https://github.com/sihjie/Embedded-System-FreeRTOS-Development/blob/main/lab_0/README.md#demo-video)