# STM32 教學系列 第 4 節：UART 串列通訊 + 命令介面

## 🎯 學習目標

1. **掌握 UART 通訊協定** - 理解串列通訊的原理與配置
2. **實現收發功能** - 透過電腦與 STM32 交互通訊
3. **建立命令介面** - 實現簡單的命令解析系統

---

## 🎓 UART 通訊基礎

### UART 是什麼？

UART (Universal Asynchronous Receiver/Transmitter) 是最常見的串列通訊協定。特點：
- **異步通訊**：發送端和接收端不需同步時脈
- **全雙工**：可同時收發
- **點對點**：通常用於連接兩個設備

### UART 信號線

STM32F446 UART 最少需要 3 條線：

| 訊號 | 功能 | 連接 |
|------|------|------|
| **TX (USART1_TX)** | 傳輸 | PA9 |
| **RX (USART1_RX)** | 接收 | PA10 |
| **GND** | 地線參考 | GND |

### 波特率與資料格式

| 參數 | 典型值 | 說明 |
|------|--------|------|
| **Baud Rate** | 9600 / 115200 | 每秒傳輸位元數 |
| **Data Bits** | 8 | 每個字符包含 8 位資料 |
| **Stop Bits** | 1 | 停止位 |
| **Parity** | None | 無奇偶校驗 |

### UART 資料幀結構

```
1 個 UART 字符的構成：
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┬─────┐
│Start│Bit0 │Bit1 │Bit2 │Bit3 │Bit4 │Bit5 │Bit6 │Bit7  │Stop │
│ 0   │  D0 │ D1  │ D2  │ D3  │ D4  │ D5  │ D6  │ D7   │ 1   │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┴─────┘
```

---

## 🛠️ 硬體連接

### 所需元件

| 元件 | 數量 | 說明 |
|------|------|------|
| USB-UART 轉換器 | 1 | PL2303 或 CP2102 晶片 |
| USB Type-A 傳輸線 | 1 | 連接電腦 |
| 杜邦線 | 3 | 連接 STM32 和轉換器 |

### 接線圖

| STM32 | USB-UART | 說明 |
|------|----------|------|
| PA9 (TX) | RX | STM32 發送給電腦 |
| PA10 (RX) | TX | STM32 接收來自電腦 |
| GND | GND | 共同地線 |

> ⚠️ **重要** - 開發板已內建 ST-Link 調試器，可透過 USB 進行 UART 通訊，無需額外轉換器！

---

## ⚙️ CubeMX 配置步驟

### 步驟 1：啟用 USART1

1. 開啟 CubeMX
2. 在 Pinout 圖上找到 PA9 和 PA10
3. 分別配置為：
   - PA9：**USART1_TX**
   - PA10：**USART1_RX**
4. 或直接點擊 Connectivity → **USART1** 自動配置

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

1. 在左側選擇 **Connectivity** → **USART1**
2. 設定以下參數：
   - **Baud Rate**: 115200
   - **Word Length**: 8 Bits
   - **Stop Bits**: 1
   - **Parity**: None
   - **Mode**: Asynchronous (RX and TX)

### 步驟 3：啟用 USART1 中斷

1. 點擊 **NVIC Settings** 標籤
2. 勾選 **USART1 global interrupt**
3. 設定優先級：
   - **Preemption Priority**: 1
   - **Sub Priority**: 0

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

點擊 **Generate Code**

---

## 💻 完整程式碼

### main.c - UART + 命令介面

```c
/* STM32 Lesson 04 - UART Communication with Command Interface
 * 功能：透過 UART 接收命令並控制 LED
 * 命令：
 *   "ON"  - LED 點亮
 *   "OFF" - LED 熄滅
 *   "TOGGLE" - LED 切換
 * 難度：中級
 */

#include "main.h"
#include "usart.h"
#include "gpio.h"
#include <string.h>
#include <stdio.h>

/* 私有定義 */
#define UART_RX_BUFFER_SIZE 50
#define COMMAND_MAX_LEN 20

/* 函數宣告 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void UART_Print(const char *format, ...);
void Process_Command(char *command);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

/* 全域變數 */
UART_HandleTypeDef huart1;
uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];
uint8_t uart_rx_index = 0;
char command_buffer[COMMAND_MAX_LEN];

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  
  /* 初始化 LED 為熄滅狀態 */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
  
  /* 列印歡迎訊息 */
  UART_Print("\r\n===============================\r\n");
  UART_Print("STM32 UART Command Interface\r\n");
  UART_Print("Commands: ON, OFF, TOGGLE\r\n");
  UART_Print("===============================\r\n");
  
  /* 啟用 UART 中斷接收 */
  HAL_UART_Receive_IT(&huart1, uart_rx_buffer, 1);

  while (1)
  {
    /* 主迴圈 */
  }
}

/**
  * @brief UART 傳輸函數（printf 風格）
  */
void UART_Print(const char *format, ...)
{
  char buffer[100];
  va_list args;
  va_start(args, format);
  vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);
  
  HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY);
}

/**
  * @brief UART 接收完成中斷回調
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1)
  {
    uint8_t received_char = uart_rx_buffer[0];
    
    /* 處理回車符（\r）或換行符（\n） */
    if (received_char == '\r' || received_char == '\n')
    {
      if (uart_rx_index > 0)
      {
        command_buffer[uart_rx_index] = '\0';
        UART_Print("\r\nReceived: %s\r\n", command_buffer);
        Process_Command(command_buffer);
        uart_rx_index = 0;
      }
    }
    else if (received_char == 0x08 || received_char == 0x7F)  /* Backspace */
    {
      if (uart_rx_index > 0)
      {
        uart_rx_index--;
        UART_Print("\b \b");  /* 刪除顯示 */
      }
    }
    else
    {
      /* 一般字符 */
      if (uart_rx_index < COMMAND_MAX_LEN - 1)
      {
        command_buffer[uart_rx_index++] = received_char;
        HAL_UART_Transmit(&huart1, (uint8_t *)&received_char, 1, HAL_MAX_DELAY);  /* Echo */
      }
    }
    
    /* 繼續接收下一個字符 */
    HAL_UART_Receive_IT(&huart1, uart_rx_buffer, 1);
  }
}

/**
  * @brief 命令處理函數
  */
void Process_Command(char *command)
{
  /* 轉換為大寫以便比較 */
  for (int i = 0; command[i]; i++)
  {
    if (command[i] >= 'a' && command[i] <= 'z')
      command[i] -= 32;
  }
  
  /* 命令解析 */
  if (strcmp(command, "ON") == 0)
  {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
    UART_Print("LED ON\r\n");
  }
  else if (strcmp(command, "OFF") == 0)
  {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    UART_Print("LED OFF\r\n");
  }
  else if (strcmp(command, "TOGGLE") == 0)
  {
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
    UART_Print("LED TOGGLED\r\n");
  }
  else
  {
    UART_Print("Unknown command. Try: ON, OFF, TOGGLE\r\n");
  }
  
  UART_Print("> ");  /* 命令提示符 */
}

/**
  * @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)
{
  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 USART1 初始化
  */
static void MX_USART1_UART_Init(void)
{
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;

  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }

  __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
}

/**
  * @brief UART MSP 初始化
  */
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  if(huart->Instance == USART1)
  {
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  }
}

/**
  * @brief USART1 中斷處理
  */
void USART1_IRQHandler(void)
{
  HAL_UART_IRQHandler(&huart1);
}

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

---

## 🔍 測試與除錯

### 預期結果

✅ **通訊驗證：**
1. 編譯並燒錄程式
2. 開啟終端機軟體（PuTTY、Arduino IDE 或 Tera Term）
3. 連接 COM 埠，波特率 115200
4. 應看到歡迎訊息：`STM32 UART Command Interface`
5. 輸入 `ON` → LED 點亮，列印 `LED ON`
6. 輸入 `OFF` → LED 熄滅，列印 `LED OFF`
7. 輸入 `TOGGLE` → LED 切換狀態

### 常見問題

| 問題 | 原因 | 解決方案 |
|------|------|--------|
| **無訊息輸出** | UART 未初始化或波特率不對 | 檢查 CubeMX 中 USART1 設定，確認波特率為 115200 |
| **亂碼** | 波特率不匹配 | 改變終端機波特率，通常試試 9600 或 115200 |
| **無法接收命令** | 中斷未啟用 | 確認 `HAL_UART_Receive_IT()` 已調用 |
| **命令無反應** | 命令格式不正確 | 確保輸入 `ON`、`OFF` 或 `TOGGLE`（大小寫會自動轉換） |

---

## 📚 進階概念

### 圓形緩衝區實現

優化版本使用圓形緩衝區防止緩衝區溢出

### DMA + UART

使用 DMA 進行高效率的 UART 傳輸（後續課程）

---

## 🔗 延伸學習

### 下節預覽 - 第 5 節：ADC（多種轉換方式與如何調用 channel）

第 5 節將實現：
- **ADC 單次轉換與掃描模式**
- **軟體觸發與計時器觸發**
- **多通道採樣**

---

**✨ UART 通訊掌握完成！🚀**