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

🎯 學習目標 
 
 理解 CRC 校驗原理 - 掌握資料完整性驗證 
 使用 STM32 硬體 CRC - 加速 CRC 計算 
 應用於通訊協議 - 保障資料可靠性 
 
 
 🎓 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 
 
 左側選 Connectivity → 搜尋 CRC 
 Activated ：勾選 
 
 步驟 2：配置 CRC 參數 
 
 Polynomial ：0x04C11DB7（CRC-32 標準） 
 Input Data Inversion ：Enabled 
 Output Data Inversion ：Enabled 
 Input Data Width ：32-bit 
 
 步驟 3：生成程式碼 
 
 💻 完整程式碼 
 CRC 計算示例：main.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() 
 
 
 值總是相同 
 輸入資料錯誤 
 驗證測試資料 
 
 
 
 
 💡 進階應用 
 通訊封包驗證 
 /* 帶 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 計算完成！🚀