# STM32 教學系列 第 6 節：定時器 Timer + PWM

## 🎯 學習目標

1. **理解定時器工作原理** - 掌握計時與計數功能
2. **掌握 PWM 信號產生** - 實現 LED 亮度調控
3. **實現呼吸燈效果** - 漸亮漸暗的動態視覺效果

---

## 🎓 定時器基礎

### 定時器是什麼？

定時器是用來計時或計數的硬體模組。STM32F446 內建 **14 個定時器**，可用於：
- 時間測量和延遲
- PWM 信號產生
- 外部事件計數
- 週期中斷觸發

### STM32F446 定時器分類

| 定時器 | 數量 | 位寬 | 功能 |
|--------|------|------|------|
| **高級定時器** (TIM1, TIM8) | 2 | 16-bit | PWM、死區時間、重複計數 |
| **通用定時器** (TIM2-5) | 4 | 16/32-bit | PWM、計時、中斷 |
| **基本定時器** (TIM6-7) | 2 | 16-bit | 定時中斷、DMA 觸發 |
| **低功耗定時器** (TIM9-14) | 6 | 16-bit | 低功耗應用 |

本課程使用 **TIM2（通用定時器）** 作為示例。

### PWM 是什麼？

PWM (Pulse Width Modulation) 是透過調整脈衝寬度來控制平均功率的技術。

```
佔空比 25%：
┌──┐           ┌──┐           ┌──┐
│  │───────────│  │───────────│  │
└──┘           └──┘           └──┘

佔空比 50%：
┌─────┐       ┌─────┐       ┌─────┐
│     │───────│     │───────│     │
└─────┘       └─────┘       └─────┘

佔空比 75%：
┌──────────┐  ┌──────────┐  ┌──────────┐
│          │──│          │──│          │
└──────────┘  └──────────┘  └──────────┘

週期 (T) 固定，脈衝寬度 (W) 變化
佔空比 (Duty Cycle) = W / T × 100%
```

### PWM 應用

| 應用 | 說明 |
|------|------|
| **LED 亮度調控** | 改變佔空比調節亮度（0%-100%） |
| **馬達速度控制** | 調整佔空比控制轉速 |
| **伺服馬達控制** | 脈衝寬度決定角度 |
| **功率調控** | 進行電源管理 |

---

## 🛠️ 硬體接線

### 所需元件

| 元件 | 數量 |
|------|------|
| LED | 1 |
| 限流電阻 (220Ω) | 1 |
| 跳線 | 2 |

### 接線圖

| 組件 | 接腳 | 說明 |
|------|------|------|
| LED 正極 | PA15 (TIM2_CH1) | PWM 輸出 |
| LED 負極 | GND | 地線 |
| 限流電阻 | LED 正負極間 | 保護 LED |

---

## ⚙️ CubeMX 配置步驟

### 步驟 1：啟用 TIM2 PWM

1. 開啟 CubeMX
2. 點擊 **Timers** → **TIM2**
3. 在 Pinout 圖中選擇 **PA15** 配置為 **TIM2_CH1**

### 步驟 2：配置 TIM2 參數

1. 在左側選擇 **Timers** → **TIM2**
2. **Clock Source**: Internal Clock
3. **Channel1**: PWM Generation CH1
4. **Configuration** 中設定：
   - **Prescaler**: 839（分頻）
   - **Counter Period**: 99（自動重載值，ARR）
   - **Pulse**: 50（初始佔空比 50%）

**頻率計算：**
```
PWM Frequency = SystemClock / ((Prescaler + 1) × ARR)
              = 168MHz / ((839 + 1) × 100)
              = 168MHz / 84000
              ≈ 2 kHz
```

### 步驟 3：配置定時器中斷

1. 點擊 **NVIC Settings** 標籤
2. 勾選 **TIM2 global interrupt**（用於更新佔空比）
3. 設定優先級：Preemption Priority = 3

### 步驟 4：生成程式碼

點擊 **Generate Code**

---

## 💻 完整程式碼

### main.c - PWM 呼吸燈效果

```c
/* STM32 Lesson 06 - PWM Breathing LED
 * 功能：使用 PWM 實現 LED 呼吸燈效果
 * 難度：中級
 */

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

/* 私有定義 */
#define PWM_MAX_VALUE    99    /* PWM 最大值 (ARR) */
#define BREATHE_SPEED    10    /* 呼吸速度 */

/* 函數宣告 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
void Breathing_LED(void);
void Set_PWM(uint16_t pulse_value);

/* 全域變數 */
TIM_HandleTypeDef htim2;
volatile uint16_t pwm_value = PWM_MAX_VALUE / 2;  /* 初始 50% 佔空比 */
volatile int8_t breathe_direction = 1;             /* 1: 變亮, -1: 變暗 */

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  
  /* 啟動 TIM2 PWM */
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_TIM_Base_Start_IT(&htim2);

  while (1)
  {
    Breathing_LED();
  }
}

/**
  * @brief 呼吸燈算法
  * 平滑改變 PWM 佔空比
  */
void Breathing_LED(void)
{
  static uint8_t counter = 0;
  
  counter++;
  
  /* 每 BREATHE_SPEED 次中斷更新一次 PWM */
  if (counter >= BREATHE_SPEED)
  {
    counter = 0;
    
    /* 改變 PWM 值 */
    pwm_value += breathe_direction;
    
    /* 邊界檢查：反向 */
    if (pwm_value <= 0)
    {
      pwm_value = 0;
      breathe_direction = 1;  /* 開始變亮 */
    }
    else if (pwm_value >= PWM_MAX_VALUE)
    {
      pwm_value = PWM_MAX_VALUE;
      breathe_direction = -1;  /* 開始變暗 */
    }
    
    /* 更新 PWM */
    Set_PWM(pwm_value);
  }
}

/**
  * @brief 設定 PWM 佔空比
  * @param pulse_value: 脈衝寬度 (0 ~ PWM_MAX_VALUE)
  */
void Set_PWM(uint16_t pulse_value)
{
  __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse_value);
}

/**
  * @brief 定時器中斷回調
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2)
  {
    /* 由主迴圈處理呼吸邏輯 */
  }
}

/**
  * @brief 系統時脈配置
  */
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_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;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 7;

  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;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief GPIO 初始化
  */
static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

/**
  * @brief TIM2 初始化
  */
static void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 839;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 99;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 50;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;

  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_TIM_MspPostInit(&htim2);
}

/**
  * @brief TIM2 MSP 初始化
  */
void HAL_TIM_MspInit(TIM_HandleTypeDef* htim)
{
  if(htim->Instance == TIM2)
  {
    __HAL_RCC_TIM2_CLK_ENABLE();
    HAL_NVIC_SetPriority(TIM2_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
  }
}

/**
  * @brief TIM2 MSP 後初始化（GPIO 配置）
  */
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  if(htim->Instance == TIM2)
  {
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

/**
  * @brief TIM2 中斷處理
  */
void TIM2_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim2);
}

void Error_Handler(void)
{
  while(1);
}
```

---

## 🔍 測試與除錯

### 預期結果

✅ **LED 漸亮漸暗效果**：
- LED 從完全熄滅漸漸變亮至最亮
- 再漸漸變暗至完全熄滅
- 週期循環

### 常見問題

| 問題 | 原因 | 解決方案 |
|------|------|--------|
| **LED 不亮** | GPIO 配置為 AF 模式失敗 | 檢查 `HAL_TIM_MspPostInit()` 中 Alternate 設定 |
| **PWM 頻率過高或過低** | Prescaler 或 ARR 設定不當 | 根據需求調整計算 |
| **呼吸效果不平滑** | BREATHE_SPEED 值不當 | 減小值使效果更平滑 |

---

## 🔗 延伸學習

### 下節預覽 - 第 7 節：DMA（直接記憶體存取）

第 7 節將實現：
- **DMA 基本概念與配置**
- **ADC + DMA 高速採樣**
- **UART + DMA 快速傳輸**

---

**✨ 定時器與 PWM 掌握完成！🚀**