---
# System prepended metadata

title: lab 0
tags: [Embedded_System]

---

# 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)