# STM32 教學系列 第 2 節：時脈控制講解

## 🎯 學習目標

1. **理解 STM32F446 的時脈架構** - 掌握振盪器、倍頻器、分頻器的概念
2. **學會配置 Clock Tree** - 透過 CubeMX 設定各組態的時脈分配
3. **實現多時脈配置實驗** - 透過改變時脈速度觀察 LED 閃爍頻率變化

---

## 🎓 時脈系統基礎概念

### 為什麼需要時脈？

STM32 微控制器內部的每個元件（CPU、外設、計時器）都需要時脈信號來同步運作。時脈越快，處理速度越快，但功耗也越高。

### STM32F446 時脈來源

STM32F446 提供 **3 種時脈源**：

| 時脈源 | 頻率 | 精度 | 功耗 | 用途 |
|--------|------|------|------|------|
| **HSI (內部高速振盪器)** | 16 MHz | ±2% | 低 | 備用時脈、低功耗模式 |
| **HSE (外部高速振盪器)** | 8 MHz | ±1% | 中等 | 主系統時脈（需晶振） |
| **LSI (內部低速振盪器)** | 32 kHz | ±2% | 很低 | 看門狗、RTC |

### PLL 倍頻原理

**PLL (Phase-Locked Loop)** 是一個電路，能將低頻時脈信號倍頻至高頻。例如：
- 輸入：8 MHz（HSE）
- 倍數：21
- 輸出：8 MHz × 21 = **168 MHz**

STM32F446 的最大系統時脈為 **180 MHz**，但實驗中常用 **168 MHz**（更穩定）。

### 分頻器作用

系統時脈（168 MHz）太快，不適合直接提供給所有外設。因此使用分頻器降低特定外設的時脈：

| 分頻器 | 功能 | 典型設定 |
|--------|------|---------|
| **AHB (HCLK)** | CPU 和內存時脈 | 168 MHz / 1 = 168 MHz |
| **APB1 (PCLK1)** | 低速外設（UART、I2C） | 168 MHz / 4 = 42 MHz |
| **APB2 (PCLK2)** | 高速外設（SPI、ADC） | 168 MHz / 2 = 84 MHz |

---

## 🛠️ Clock Tree 深度解析

### Clock Tree 的流程圖

```
┌─────────────────────────────────────────────────┐
│  時脈源選擇                                      │
│  ├─ HSI (16 MHz) - 內部振盪器                   │
│  ├─ HSE (8 MHz)  - 外部晶振                     │
│  └─ LSI (32 kHz) - 內部低速振盪器              │
└────────────┬────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────┐
│  PLL 倍頻（選擇性）                              │
│  主要參數：PLLM / PLLN / PLLP / PLLQ           │
│  目標：8 MHz → 168 MHz                         │
└────────────┬────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────┐
│  系統時脈選擇（SYSCLK）                         │
│  ├─ HSI (16 MHz)                               │
│  ├─ HSE (8 MHz)                                │
│  └─ PLL (168 MHz)    ← 常用                    │
└────────────┬────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────┐
│  AHB 分頻器                                     │
│  168 MHz / 1 = 168 MHz (HCLK)                   │
└────────────┬────────────────────────────────────┘
             │
    ┌────────┼────────┐
    ▼        ▼        ▼
  APB1    APB2    其他外設
  /4       /2
  ↓        ↓
 42MHz   84MHz
```

### PLL 倍頻計算

STM32F446 PLL 配置公式：

```
f_VCO = (f_HSE / PLLM) × PLLN
f_PLLCLK = f_VCO / PLLP
f_USB = f_VCO / PLLQ
```

**常用配置（HSE = 8 MHz）：**
```
PLLM = 8      (預分頻：8 MHz / 8 = 1 MHz)
PLLN = 168    (倍頻：1 MHz × 168 = 168 MHz)
PLLP = 2      (主時脈分頻：168 MHz / 2 = 84 MHz) ❌ 這會得 84 MHz
PLLP = 2      (若要 168 MHz，應設定 PLLN = 336, PLLP = 2)
```

**更精確的配置（目標 168 MHz）：**
```
PLLM = 8      (預分頻：8 MHz / 8 = 1 MHz)
PLLN = 336    (倍頻：1 MHz × 336 = 336 MHz)
PLLP = 2      (主時脈分頻：336 MHz / 2 = 168 MHz) ✅
```

---

## ⚙️ CubeMX 時脈配置步驟

### 步驟 1：開啟時脈配置界面

1. 開啟之前的專案或建立新專案
2. 雙擊 `.ioc` 檔案開啟 CubeMX
3. 點擊 **Clock Configuration** 標籤

### 步驟 2：配置 HSE（8 MHz 晶振）

1. 在「System Clock Mux」右側，點擊 **HSE** 下拉選單
2. 選擇 **Crystal/Ceramic Resonator**
3. 確認 HSE 頻率顯示為 **8 MHz**

### 步驟 3：配置 PLL

1. 找到 **PLL 配置區塊**
2. 設定以下參數：
   - **PLLM**: 8
   - **PLLN**: 336
   - **PLLP**: 2 (對應頻率應顯示 168 MHz)
   - **PLLQ**: 7 (用於 USB，通常保持預設)

3. 若 PLLN 改為 336 後系統時脈仍未達 168 MHz，檢查 PLLP 是否為 2

### 步驟 4：選擇 PLL 作為系統時脈源

1. 在「System Clock Mux」中，選擇 **PLLCLK**
2. 確認 **SYSCLK** 顯示 **168 MHz**

### 步驟 5：配置 AHB 分頻器

1. 在「System Clock Mux」下方，找到 **AHB Prescaler**
2. 設定為 **1**（不分頻，HCLK = 168 MHz）

### 步驟 6：配置 APB 分頻器

1. **APB1 Prescaler** 設為 **4**
   - 結果：168 MHz / 4 = **42 MHz**（適合 UART、I2C）

2. **APB2 Prescaler** 設為 **2**
   - 結果：168 MHz / 2 = **84 MHz**（適合 SPI、ADC）

### 步驟 7：驗證配置

- ✅ 查看 CubeMX 下方的總結，應顯示：
  ```
  SYSCLK:  168 MHz
  HCLK:    168 MHz
  PCLK1:    42 MHz
  PCLK2:    84 MHz
  ```

---

## 💻 完整程式碼 - 時脈配置驗證

### main.c - 多時脈驗證程式

```c
/* STM32 Lesson 02 - Clock Configuration Verification
 * 功能：測試不同的時脈配置，透過 LED 閃爍頻率變化來驗證
 * 難度：中級
 */

#include "main.h"
#include "gpio.h"

/* 私有變數和函數 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
void LED_Blink(uint16_t delay_ms, uint8_t count);

/* 全域變數：存儲當前系統時脈速度 */
uint32_t SystemCoreClock = 168000000;  /* 168 MHz */

int main(void)
{
  HAL_Init();
  
  /* 配置系統時脈為 168 MHz */
  SystemClock_Config();
  
  /* 初始化 GPIO */
  MX_GPIO_Init();
  
  /* 驗證系統時脈 */
  while (1)
  {
    /* 第一階段：快速閃爍 (5 次，100ms 間隔) - 表示 168 MHz */
    LED_Blink(100, 5);
    HAL_Delay(1000);  /* 暫停 1 秒 */
    
    /* 第二階段：中速閃爍 (3 次，200ms 間隔) - 表示配置完成 */
    LED_Blink(200, 3);
    HAL_Delay(1000);
    
    /* 第三階段：慢速閃爍 (2 次，500ms 間隔) - 表示系統運行 */
    LED_Blink(500, 2);
    HAL_Delay(2000);
  }
}

/**
  * @brief LED 間隔閃爍函數
  * @param delay_ms: 每次亮/暗的延遲時間（毫秒）
  * @param count: 閃爍次數
  */
void LED_Blink(uint16_t delay_ms, uint8_t count)
{
  for (uint8_t i = 0; i < count; i++)
  {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);    /* LED 亮 */
    HAL_Delay(delay_ms);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);  /* LED 暗 */
    HAL_Delay(delay_ms);
  }
}

/**
  * @brief 系統時脈配置函數
  * 配置 HSE (8 MHz) → PLL 倍頻至 168 MHz
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /* 配置電源調節器 */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /* 配置 RCC 振盪器 */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;      /* 預分頻：8 MHz / 8 = 1 MHz */
  RCC_OscInitStruct.PLL.PLLN = 336;    /* 倍頻：1 MHz × 336 = 336 MHz */
  RCC_OscInitStruct.PLL.PLLP = 2;      /* 主輸出分頻：336 MHz / 2 = 168 MHz */
  RCC_OscInitStruct.PLL.PLLQ = 7;      /* USB 時脈：336 MHz / 7 = 48 MHz */

  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /* 配置時脈分頻器 */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                              | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;      /* HCLK = 168 MHz */
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;       /* PCLK1 = 42 MHz */
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;       /* PCLK2 = 84 MHz */

  /* Flash 延遲設定（高速時脈需要更多等待週期） */
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }

  /* 配置 SysTick 用於系統計時 */
  HAL_SYSTICK_Config(SystemCoreClock / 1000);  /* 1ms 中斷一次 */
}

/**
  * @brief GPIO 初始化函數
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOA_CLK_ENABLE();

  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

/**
  * @brief 系統錯誤處理
  */
void Error_Handler(void)
{
  while (1)
  {
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  /* 快速閃爍表示錯誤 */
    HAL_Delay(100);
  }
}
```

---

## 🔍 測試與除錯

### 預期結果

✅ **LED 閃爍模式序列**：
1. **5 次快速閃爍**（100ms）→ 系統時脈 168 MHz 正常
2. **暫停 1 秒**
3. **3 次中速閃爍**（200ms）→ 時脈配置完成
4. **暫停 1 秒**
5. **2 次慢速閃爍**（500ms）→ 系統穩定運行
6. **暫停 2 秒後重複**

### 常見問題與解決

| 問題 | 原因 | 解決方案 |
|------|------|--------|
| **LED 不閃爍** | PLL 配置失敗 | 檢查 CubeMX Clock Configuration，確認 PLLN 為 336，PLLP 為 2 |
| **閃爍節奏不對** | HAL_Delay 計算錯誤 | 確認 SysTick 配置正確，系統時脈應為 168 MHz |
| **編譯警告**「implicit conversion」| 時脈值類型不匹配 | 確認 `SystemCoreClock` 變數型別為 `uint32_t` |
| **程式掛起** | Flash 延遲不足 | 檢查 `FLASH_LATENCY_5` 是否設定，高速時脈需更多等待週期 |

### 驗證時脈速度

**方法 1：UART 輸出驗證（後續課程）**
透過串口列印 `SystemCoreClock` 值

**方法 2：邏輯分析儀**
測量 PA5 的方波頻率，應為固定值

**方法 3：計算驗證**
- 若 `HAL_Delay(100)` 實際延遲 100ms，時脈正確 ✅

---

## 📚 深入理解

### Flash 存取延遲（FLASH_LATENCY）

快閃記憶體存取速度是有限的。當系統時脈增加時，需要增加等待週期（Latency）。

| 系統時脈 | 建議 LATENCY |
|---------|-------------|
| 0-30 MHz | 0 |
| 31-60 MHz | 1 |
| 61-90 MHz | 2 |
| 91-120 MHz | 3 |
| 121-150 MHz | 4 |
| 151-168 MHz | 5 |

---

## 📱 高階實驗

### 挑戰 1：低功耗時脈配置

修改代碼，使用 **HSI (16 MHz)** 而非 PLL：

```c
/* 使用 HSI 代替 PLL（低功耗模式） */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_OFF;  /* 關閉 PLL */

RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
```

觀察 LED 閃爍速度是否變慢（因為系統時脈從 168 MHz 降至 16 MHz）

### 挑戰 2：動態時脈切換

實現在運行時在不同時脈間切換的函數

---

## 🔗 延伸學習

### 下節預覽 - 第 3 節：GPIO 進階（按鍵、中斷、去彈跳）

第 3 節將學習：
- **GPIO 輸入配置** - 按鍵檢測原理
- **中斷系統（EXTI）** - 事件驅動編程
- **軟體去彈跳** - 解決按鍵抖動問題

### 相關資源

- 📘 [STM32F446 Reference Manual - RCC章節](https://www.st.com/resource/en/reference_manual/dm00135183-stm32f446xx-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf)
- 🔗 [PLL 倍頻計算器（線上工具）](https://www.st.com/en/development-tools/stm32cubeprog.html)

---

**✨ 時脈控制掌握完成！下節進入 GPIO 進階應用 🚀**