STM32 教學系列 第 2 節:時脈控制講解
🎯 學習目標
- 理解 STM32F446 的時脈架構 - 掌握振盪器、倍頻器、分頻器的概念
- 學會配置 Clock Tree - 透過 CubeMX 設定各組態的時脈分配
- 實現多時脈配置實驗 - 透過改變時脈速度觀察 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:開啟時脈配置界面
- 開啟之前的專案或建立新專案
- 雙擊
.ioc檔案開啟 CubeMX - 點擊 Clock Configuration 標籤
步驟 2:配置 HSE(8 MHz 晶振)
- 在「System Clock Mux」右側,點擊 HSE 下拉選單
- 選擇 Crystal/Ceramic Resonator
- 確認 HSE 頻率顯示為 8 MHz
步驟 3:配置 PLL
-
找到 PLL 配置區塊
-
設定以下參數:
- PLLM: 8
- PLLN: 336
- PLLP: 2 (對應頻率應顯示 168 MHz)
- PLLQ: 7 (用於 USB,通常保持預設)
-
若 PLLN 改為 336 後系統時脈仍未達 168 MHz,檢查 PLLP 是否為 2
步驟 4:選擇 PLL 作為系統時脈源
- 在「System Clock Mux」中,選擇 PLLCLK
- 確認 SYSCLK 顯示 168 MHz
步驟 5:配置 AHB 分頻器
- 在「System Clock Mux」下方,找到 AHB Prescaler
- 設定為 1(不分頻,HCLK = 168 MHz)
步驟 6:配置 APB 分頻器
-
APB1 Prescaler 設為 4
- 結果:168 MHz / 4 = 42 MHz(適合 UART、I2C)
-
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 - 多時脈驗證程式
/* 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 閃爍模式序列:
- 5 次快速閃爍(100ms)→ 系統時脈 168 MHz 正常
- 暫停 1 秒
- 3 次中速閃爍(200ms)→ 時脈配置完成
- 暫停 1 秒
- 2 次慢速閃爍(500ms)→ 系統穩定運行
- 暫停 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:
/* 使用 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) - 事件驅動編程
- 軟體去彈跳 - 解決按鍵抖動問題
相關資源
✨ 時脈控制掌握完成!下節進入 GPIO 進階應用 🚀