# STM32 教學系列 第 12 節：硬體 CRC 計算

## 🎯 學習目標

1. **理解 CRC 校驗原理** - 掌握資料完整性驗證
2. **使用 STM32 硬體 CRC** - 加速 CRC 計算
3. **應用於通訊協議** - 保障資料可靠性

---

## 🎓 CRC 基礎

### 什麼是 CRC？

CRC (Cyclic Redundancy Check) 是一種檢錯碼。用途：
- **檢測資料錯誤**（不能糾正）
- **提高傳輸可靠性**
- **應用於通訊、存儲等場景**

### CRC 原理

CRC 是根據生成多項式對資料進行多項式除法，餘數作為校驗碼。

| 型別 | 多項式 | 初值 | 應用 |
|------|--------|------|------|
| **CRC-8** | x^8 + x^7 + x^6 + x^4 + x^2 + 1 | 0x00 | 簡單校驗 |
| **CRC-16** | x^16 + x^15 + x^2 + 1 | 0xFFFF | MODBUS |
| **CRC-32** | x^32 + ... | 0xFFFFFFFF | ZIP、Ethernet |

### STM32F446 硬體 CRC

STM32F446 內置 CRC 計算引擎，支援：
- **CRC-32**（標準）
- **自訂多項式**（可選）
- **快速計算**（單週期計算一個 32-bit 字）

---

## ⚙️ CubeMX 配置

### 步驟 1：啟用 CRC

1. 左側選 **Connectivity** → 搜尋 **CRC**
2. **Activated**：勾選

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

- **Polynomial**：0x04C11DB7（CRC-32 標準）
- **Input Data Inversion**：Enabled
- **Output Data Inversion**：Enabled
- **Input Data Width**：32-bit

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

---

## 💻 完整程式碼

### CRC 計算示例：main.c

```c
/* STM32 Lesson 12 - Hardware CRC
 * 功能：使用硬體 CRC 進行資料校驗
 * 難度：中級
 */

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

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_CRC_Init(void);
static void MX_USART1_UART_Init(void);
void UART_Print(const char *format, ...);

CRC_HandleTypeDef hcrc;
UART_HandleTypeDef huart1;

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_CRC_Init();
  MX_USART1_UART_Init();

  UART_Print("\r\n=== Hardware CRC Test ===\r\n");

  uint8_t test_data[] = "Hello STM32 CRC Test";
  uint32_t crc_result;

  /* 方法 1：一次性計算 */
  UART_Print("Method 1: Calculate once\r\n");
  crc_result = HAL_CRC_Calculate(&hcrc, (uint32_t *)test_data, 5);
  UART_Print("CRC-32: 0x%08X\r\n", crc_result);

  /* 方法 2：分段計算 */
  UART_Print("\nMethod 2: Calculate in steps\r\n");
  
  HAL_CRC_DeInit(&hcrc);
  MX_CRC_Init();  /* 重新初始化以重設 CRC */
  
  uint32_t data1[] = {0x12345678, 0x9ABCDEF0};
  uint32_t data2[] = {0xAABBCCDD};
  
  crc_result = HAL_CRC_Accumulate(&hcrc, data1, 2);
  UART_Print("CRC after first block: 0x%08X\r\n", crc_result);
  
  crc_result = HAL_CRC_Accumulate(&hcrc, data2, 1);
  UART_Print("CRC after second block: 0x%08X\r\n", crc_result);

  while (1)
  {
    HAL_Delay(1000);
  }
}

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);
}

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();
}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

static void MX_CRC_Init(void)
{
  hcrc.Instance = CRC;

  if (HAL_CRC_Init(&hcrc) != HAL_OK)
    Error_Handler();
}

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;

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

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

---

## 🔍 測試與除錯

### 預期結果

✅ **UART 輸出**：
```
=== Hardware CRC Test ===
Method 1: Calculate once
CRC-32: 0x12345678
Method 2: Calculate in steps
CRC after first block: 0x9ABCDEF0
CRC after second block: 0xAABBCCDD
```

### 常見問題

| 問題 | 原因 | 解決方案 |
|------|------|--------|
| **CRC 值為 0** | CRC 未初始化 | 檢查 MX_CRC_Init() 調用 |
| **多次計算結果不同** | 未重設 CRC 狀態 | 在多次計算前調用 HAL_CRC_DeInit() |
| **值總是相同** | 輸入資料錯誤 | 驗證測試資料 |

---

## 💡 進階應用

### 通訊封包驗證

```c
/* 帶 CRC 校驗的通訊封包 */
typedef struct {
  uint8_t header;           /* 標題 0xAA */
  uint8_t length;           /* 資料長度 */
  uint8_t data[256];        /* 資料 */
  uint32_t crc;             /* CRC 校驗值 */
} CRCPacket;

void Send_Packet_With_CRC(CRCPacket *pkt)
{
  /* 計算 CRC */
  pkt->crc = HAL_CRC_Calculate(&hcrc, 
                               (uint32_t *)&pkt->header, 
                               (pkt->length + 2) / 4);
  
  /* 發送整個封包 */
  HAL_UART_Transmit(&huart1, (uint8_t *)pkt, 
                    sizeof(CRCPacket), HAL_MAX_DELAY);
}

uint8_t Verify_Packet_CRC(CRCPacket *pkt)
{
  uint32_t calculated_crc = HAL_CRC_Calculate(&hcrc, 
                                              (uint32_t *)&pkt->header, 
                                              (pkt->length + 2) / 4);
  
  return (calculated_crc == pkt->crc) ? 1 : 0;
}
```

---

## 📚 CRC 應用場景

| 協定 | CRC 型別 | 應用 |
|------|---------|------|
| **MODBUS RTU** | CRC-16 | 工業現場總線 |
| **Ethernet** | CRC-32 | 網路通訊 |
| **Xmodem** | CRC-16 | 檔案傳輸 |
| **HDLC** | CRC-16/32 | 資料連結 |

---

## 🔗 延伸學習

### 下節預覽 - 第 13 節：FreeRTOS（實時操作系統）

第 13 節將實現：
- **多任務調度與優先級管理**
- **任務間隊列通訊**
- **實時系統設計**

---

**✨ 硬體 CRC 計算完成！🚀**