前言
在 STM32 嵌入式開發中,Bootloader 是一個不可或缺的模塊。ST 公司為 STM32 提供了功能完備的官方 Bootloader,支持多種通信接口(如 USART、USB DFU、I2C、SPI 等),適用于標準的固件更新方案。
然而,隨著系統需求的多樣化和定制化,自定義 Bootloader 越來越成為項目開發者的首選。
方案比較
官方 bootloader 優點
- 開箱即用:無需編寫任何代碼,即可通過 ROM 中的 Bootloader 進行下載。
- 支持多種接口:如 USART、USB、I2C 等,適合簡單的量產燒錄。
- 穩定性高:由 ST 官方提供,經過長期驗證。
官方 bootloader 局限
- 不可修改
官方 Bootloader 存儲在芯片的系統 ROM 中,屬于只讀區域,無法添加用戶邏輯或擴展功能。 - 接口受限
實際項目中可能需要使用 CAN、以太網、BLE 等接口更新固件,而這些通常未被官方 Bootloader 支持或實現較復雜。 - 協議封閉
官方 Bootloader 通信協議較為復雜,難以與高層系統(如云 OTA、手機 App)靈活集成。 - 安全性不足
默認無固件加密或簽名機制,容易被反編譯和惡意刷寫。 - 啟動方式有限
官方 Bootloader 通常通過 BOOT 引腳或 Option Byte 來觸發,不夠靈活。例如,無法通過軟件條件(如特定按鍵組合)進入 Bootloader 模式。
通過自定義bootloader,可以實現多種方式控制觸發升級邏輯:
- 按鍵時序組合
- 接收特定指令
- Flash 中標志位
- RTC Backup 寄存器
- Watchdog Timeout
- 遠程 OTA
主要思想
本項目實現了一個從 TF 讀取升級固件并更新到 flash 中的功能,同時包括了綁定 chipID 加密操作,提升操作便利性的同時提供了絕對的安全可靠性。
主要思想史利用單片機的唯一的ChipID
于用戶自定義的密鑰輸入sha256算法得出32字節的加密密鑰,然后用這個密鑰對固件進行異或運算加密
設計細節
固件加密
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <shlobj.h>
#include <commctrl.h>extern "C" {
#include "../Core/Inc/sha256.h"
}#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "shell32.lib")// 控件ID定義
#define IDC_BROWSE_CID 101
#define IDC_BROWSE_APP 102
#define IDC_BROWSE_OUTPUT 103
#define IDC_START 104
#define IDC_EDIT_CID 105
#define IDC_EDIT_APP 106
#define IDC_EDIT_OUTPUT 107
#define IDC_LOG 108#define HASH_SIZE 32// 全局變量
uint32_t chipID[3];
uint8_t encryptionKey[HASH_SIZE];
HWND g_hEditLog = NULL;
const char* userKey = "Your_Password";// 函數聲明
void generate_encryption_key(const uint32_t* chipID, const char* userKey, uint8_t* encryptionKey);
void xor_encrypt(FILE* src, FILE* dst, const uint8_t* key);
void calculate_file_hash(FILE* file, uint8_t* hash);
int parse_chip_id(const wchar_t* filename, uint32_t* chipID);
void create_firmware_file(FILE* src, const uint32_t* chipID, const uint8_t* key, const wchar_t* outputPath);
int process_all_cid_files(const wchar_t* cidDir, const wchar_t* appPath, const wchar_t* outputDir);
wchar_t* browse_folder(const wchar_t* title);
wchar_t* browse_file(const wchar_t* title, const wchar_t* filter);
void LogMessage(const wchar_t* format, ...);// 生成加密密鑰
void generate_encryption_key(const uint32_t* chipID, const char* userKey, uint8_t* encryptionKey) {SHA256_CTX ctx;sha256_init(&ctx);sha256_update(&ctx, (const uint8_t*)chipID, sizeof(uint32_t) * 3);sha256_update(&ctx, (const uint8_t*)userKey, strlen(userKey));sha256_final(&ctx, encryptionKey);
}// 異或加密
void xor_encrypt(FILE* src, FILE* dst, const uint8_t* key) {uint8_t buffer[4096];size_t bytes_read;size_t key_index = 0;fseek(src, 0, SEEK_SET);while ((bytes_read = fread(buffer, 1, sizeof(buffer), src)) > 0) {for (size_t i = 0; i < bytes_read; i++) {buffer[i] ^= key[key_index];key_index = (key_index + 1) % HASH_SIZE;}fwrite(buffer, 1, bytes_read, dst);}
}// 計算文件哈希
void calculate_file_hash(FILE* file, uint8_t* hash) {SHA256_CTX ctx;sha256_init(&ctx);uint8_t buffer[4096];size_t bytes_read;fseek(file, 0, SEEK_SET);while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {sha256_update(&ctx, buffer, bytes_read);}sha256_final(&ctx, hash);
}// 解析芯片ID
int parse_chip_id(const wchar_t* filename, uint32_t* chipID) {FILE* cidFile = _wfopen(filename, L"r");if (!cidFile) {LogMessage(L"錯誤: 無法打開芯片ID文件: %s\n", filename);return 0;}char line[50];if (!fgets(line, sizeof(line), cidFile)) {LogMessage(L"讀取文件錯誤\n");fclose(cidFile);return 0;}fclose(cidFile);char* token = strtok(line, "-");for (int i = 0; i < 3 && token != NULL; i++) {char* hex_str = (token[0] == '0' && token[1] == 'x') ? token + 2 : token;chipID[i] = strtoul(hex_str, NULL, 16);token = strtok(NULL, "-");}return 1;
}// 創建固件文件
void create_firmware_file(FILE* src, const uint32_t* chipID, const uint8_t* key, const wchar_t* outputPath) {wchar_t firmware_filename[MAX_PATH];swprintf(firmware_filename, MAX_PATH,L"%s\\0x%08X-0x%08X-0x%08X.bin",outputPath, chipID[0], chipID[1], chipID[2]);FILE* dst = _wfopen(firmware_filename, L"wb");if (!dst) {LogMessage(L"錯誤: 無法創建固件文件: %s\n", firmware_filename);return;}// 計算并寫入文件哈希uint8_t fileHash[HASH_SIZE];calculate_file_hash(src, fileHash);fseek(dst, 0, SEEK_SET);if (fwrite(fileHash, 1, HASH_SIZE, dst) != HASH_SIZE) {LogMessage(L"寫入哈希頭失敗\n");fclose(dst);return;}// 執行加密并保存xor_encrypt(src, dst, key);fclose(dst);LogMessage(L"已創建: %s\n", firmware_filename);
}// 處理所有CID文件
int process_all_cid_files(const wchar_t* cidDir, const wchar_t* appPath, const wchar_t* outputDir) {WIN32_FIND_DATAW findData;wchar_t searchPath[MAX_PATH];swprintf(searchPath, MAX_PATH, L"%s\\*.cid", cidDir);HANDLE hFind = FindFirstFileW(searchPath, &findData);if (hFind == INVALID_HANDLE_VALUE) {LogMessage(L"在目錄中未找到CID文件: %s\n", cidDir);return 0;}FILE* appSrc = _wfopen(appPath, L"rb");if (!appSrc) {LogMessage(L"錯誤: 無法打開app.bin文件: %s\n", appPath);FindClose(hFind);return 0;}int processed = 0;do {if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)continue;wchar_t cidPath[MAX_PATH];swprintf(cidPath, MAX_PATH, L"%s\\%s", cidDir, findData.cFileName);LogMessage(L"\n處理: %s\n", findData.cFileName);if (!parse_chip_id(cidPath, chipID)) {LogMessage(L"跳過: %s (解析錯誤)\n", findData.cFileName);continue;}generate_encryption_key(chipID, userKey, encryptionKey);create_firmware_file(appSrc, chipID, encryptionKey, outputDir);processed++;fseek(appSrc, 0, SEEK_SET);} while (FindNextFileW(hFind, &findData));fclose(appSrc);FindClose(hFind);return processed;
}// 文件夾瀏覽對話框
wchar_t* browse_folder(const wchar_t* title) {BROWSEINFOW bi = { 0 };bi.lpszTitle = title;bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);if (pidl) {static wchar_t path[MAX_PATH];SHGetPathFromIDListW(pidl, path);CoTaskMemFree(pidl);return path;}return NULL;
}// 文件瀏覽對話框
wchar_t* browse_file(const wchar_t* title, const wchar_t* filter) {OPENFILENAMEW ofn = { 0 };static wchar_t path[MAX_PATH] = L"";ofn.lStructSize = sizeof(OPENFILENAMEW);ofn.lpstrTitle = title;ofn.lpstrFilter = filter;ofn.nFilterIndex = 1;ofn.lpstrFile = path;ofn.nMaxFile = MAX_PATH;ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;if (GetOpenFileNameW(&ofn)) {return path;}return NULL;
}// 日志輸出函數
void LogMessage(const wchar_t* format, ...) {va_list args;va_start(args, format);wchar_t buffer[1024];vswprintf(buffer, ARRAYSIZE(buffer), format, args);// 獲取當前文本長度int len = GetWindowTextLength(g_hEditLog);SendMessage(g_hEditLog, EM_SETSEL, len, len);SendMessage(g_hEditLog, EM_REPLACESEL, FALSE, (LPARAM)buffer);va_end(args);
}// 窗口過程函數
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {switch (msg) {case WM_CREATE: {// 創建CID目錄控件CreateWindowW(L"STATIC", L"CID文件目錄:", WS_VISIBLE | WS_CHILD,20, 20, 100, 25, hWnd, NULL, NULL, NULL);CreateWindowW(L"EDIT", L"", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,130, 20, 300, 25, hWnd, (HMENU)IDC_EDIT_CID, NULL, NULL);CreateWindowW(L"BUTTON", L"瀏覽...", WS_VISIBLE | WS_CHILD,440, 20, 80, 25, hWnd, (HMENU)IDC_BROWSE_CID, NULL, NULL);// 創建APP文件控件CreateWindowW(L"STATIC", L"APP文件:", WS_VISIBLE | WS_CHILD,20, 55, 100, 25, hWnd, NULL, NULL, NULL);CreateWindowW(L"EDIT", L"", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,130, 55, 300, 25, hWnd, (HMENU)IDC_EDIT_APP, NULL, NULL);CreateWindowW(L"BUTTON", L"瀏覽...", WS_VISIBLE | WS_CHILD,440, 55, 80, 25, hWnd, (HMENU)IDC_BROWSE_APP, NULL, NULL);// 創建輸出目錄控件CreateWindowW(L"STATIC", L"輸出目錄:", WS_VISIBLE | WS_CHILD,20, 90, 100, 25, hWnd, NULL, NULL, NULL);CreateWindowW(L"EDIT", L"", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,130, 90, 300, 25, hWnd, (HMENU)IDC_EDIT_OUTPUT, NULL, NULL);CreateWindowW(L"BUTTON", L"瀏覽...", WS_VISIBLE | WS_CHILD,440, 90, 80, 25, hWnd, (HMENU)IDC_BROWSE_OUTPUT, NULL, NULL);// 創建操作按鈕CreateWindowW(L"BUTTON", L"開始處理", WS_VISIBLE | WS_CHILD,200, 130, 100, 30, hWnd, (HMENU)IDC_START, NULL, NULL);// 創建日志框g_hEditLog = CreateWindowW(L"EDIT", L"",WS_VISIBLE | WS_CHILD | WS_BORDER | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL,20, 170, 500, 200, hWnd, (HMENU)IDC_LOG, NULL, NULL);// 設置日志框字體HFONT hFont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY, DEFAULT_PITCH, L"宋體");SendMessage(g_hEditLog, WM_SETFONT, (WPARAM)hFont, TRUE);break;}case WM_COMMAND: {switch (LOWORD(wParam)) {case IDC_BROWSE_CID: {wchar_t* path = browse_folder(L"選擇包含CID文件的文件夾");if (path) {SetDlgItemTextW(hWnd, IDC_EDIT_CID, path);LogMessage(L"已選擇CID目錄: %s\n", path);}break;}case IDC_BROWSE_APP: {wchar_t* path = browse_file(L"選擇app.bin文件", L"Bin文件\0*.bin\0所有文件\0*.*\0");if (path) {SetDlgItemTextW(hWnd, IDC_EDIT_APP, path);LogMessage(L"已選擇APP文件: %s\n", path);}break;}case IDC_BROWSE_OUTPUT: {wchar_t* path = browse_folder(L"選擇輸出文件夾");if (path) {SetDlgItemTextW(hWnd, IDC_EDIT_OUTPUT, path);LogMessage(L"已選擇輸出目錄: %s\n", path);}break;}case IDC_START: {wchar_t cidDir[MAX_PATH] = { 0 };wchar_t appPath[MAX_PATH] = { 0 };wchar_t outputDir[MAX_PATH] = { 0 };GetDlgItemTextW(hWnd, IDC_EDIT_CID, cidDir, MAX_PATH);GetDlgItemTextW(hWnd, IDC_EDIT_APP, appPath, MAX_PATH);GetDlgItemTextW(hWnd, IDC_EDIT_OUTPUT, outputDir, MAX_PATH);if (!cidDir[0] || !appPath[0] || !outputDir[0]) {LogMessage(L"錯誤: 請先選擇所有路徑!\n");break;}LogMessage(L"\n=== 批量處理開始 ===\n");LogMessage(L"CID目錄: %s\n", cidDir);LogMessage(L"App文件: %s\n", appPath);LogMessage(L"輸出目錄: %s\n\n", outputDir);int count = process_all_cid_files(cidDir, appPath, outputDir);LogMessage(L"\n完成: 已處理 %d 個文件\n", count);break;}}break;}case WM_CLOSE:DestroyWindow(hWnd);break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProcW(hWnd, msg, wParam, lParam);}return 0;
}// 程序入口
int main(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {HWND console = GetConsoleWindow();if (console) {ShowWindow(console, SW_HIDE); // 隱藏現有控制臺}// 初始化通用控件INITCOMMONCONTROLSEX icc = { sizeof(INITCOMMONCONTROLSEX), ICC_STANDARD_CLASSES };InitCommonControlsEx(&icc);// 注冊窗口類WNDCLASSEXW wc = { sizeof(WNDCLASSEX) };wc.style = CS_HREDRAW | CS_VREDRAW;wc.lpfnWndProc = WndProc;wc.hInstance = hInstance;wc.hCursor = LoadCursor(NULL, IDC_ARROW);wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);wc.lpszClassName = L"FirmwareEncryptor";if (!RegisterClassExW(&wc)) {return 0;}// 創建主窗口HWND hWnd = CreateWindowW(wc.lpszClassName, L"Bootloader固件加密工具",WS_OVERLAPPEDWINDOW | WS_VISIBLE,CW_USEDEFAULT, CW_USEDEFAULT, 550, 450,NULL, NULL, hInstance, NULL);if (!hWnd) {return 0;}// 顯示窗口ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);// 新增居中代碼RECT rect;GetWindowRect(hWnd, &rect); // 獲取窗口尺寸int screenWidth = GetSystemMetrics(SM_CXSCREEN);int screenHeight = GetSystemMetrics(SM_CYSCREEN);int x = (screenWidth - (rect.right - rect.left)) / 2;int y = (screenHeight - (rect.bottom - rect.top)) / 2;SetWindowPos(hWnd, NULL, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER); // 移動到中心// 消息循環MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}return (int)msg.wParam;
}
固件解密
/* USER CODE BEGIN Header */
/********************************************************************************* @file : main.c* @brief : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "fatfs.h"
#include "sdio.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include "stm32f4xx_hal.h"
#include "stdio.h"
#include "sha256.h"/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */#define MODE_PORT GPIOD
#define MODE_PIN GPIO_PIN_13
#define LED_PORT GPIOD
#define LED_PIN GPIO_PIN_14#define READ_MODE_PIN() HAL_GPIO_ReadPin(MODE_PORT, MODE_PIN)
#define SET_LED_ON() HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_SET)
#define SET_LED_OFF() HAL_GPIO_WritePin(LED_PORT, LED_PIN, GPIO_PIN_RESET)
#define TOGGLE_LED() HAL_GPIO_TogglePin(LED_PORT, LED_PIN)
#define LED_IS_ON() (HAL_GPIO_ReadPin(LED_PORT, LED_PIN) == GPIO_PIN_SET)#define F407VE_FLASH_SIZE 0x0007FFFF
#define APP_START_ADDR 0x08020000
#define APP_END_ADDR 0x0807FFFF
#define BOOTLOADER_SIZE 0x10000 // 64KB bootloader空間
#define CPU_ID_BASE_ADDR (uint32_t*)(0x1FFF7A10)
#define FLASH_PAGE_SIZE 128 // STM32F407 Flash編程最小單位為128位(16字節)
#define HASH_SIZE 32
const char user_local_key[] = "Your_Password";
char cid_filename[48] = {0};
char cid_content[48] = {0};
char bin_filename[48] = {0};/* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
uint32_t chipID[3];
uint8_t decryptionKey[HASH_SIZE];
/* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV */FATFS fs; // FatFs文件系統對象
FIL file; // 文件對象FIL InfoFile;
FIL AuthFile;/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */int fputc(int ch, FILE *f) {HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1);HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1);HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 1);return ch;
}void Enable_RDP_Level1(void) {// 1. 解鎖Flash和選項字節HAL_FLASH_Unlock();HAL_FLASH_OB_Unlock();// 2. 配置RDP Level 1FLASH_OBProgramInitTypeDef ob_config;HAL_FLASHEx_OBGetConfig(&ob_config); // 獲取當前配置(可選)ob_config.OptionType = OPTIONBYTE_RDP;ob_config.RDPLevel = OB_RDP_LEVEL_1;// 3. 應用配置if (HAL_FLASHEx_OBProgram(&ob_config) == HAL_OK) {HAL_FLASH_OB_Launch(); // 觸發重載}// 4. 重新鎖定HAL_FLASH_OB_Lock();HAL_FLASH_Lock();
}void Check_ReadProtection(void) {FLASH_OBProgramInitTypeDef obConfig;HAL_FLASHEx_OBGetConfig(&obConfig);if (obConfig.RDPLevel == OB_RDP_LEVEL_0){// 讀保護未啟用,則手動開啟Enable_RDP_Level1();}
}/*** @brief 根據地址獲取扇區號* @param address: Flash地址* @retval 扇區號 (0-11)*/
static uint32_t Get_Sector(uint32_t address)
{uint32_t sector = 0;if((address < FLASH_BASE) || (address > FLASH_BASE + F407VE_FLASH_SIZE)) {Error_Handler();}address -= FLASH_BASE; // 轉換為偏移地址if(address < 0x10000) {// 前4個扇區各16KBsector = address / 0x4000;} else if(address < 0x20000) {sector = FLASH_SECTOR_4; // 64KB扇區} else {// 剩余128KB扇區 (Sector5-11)sector = ((address - 0x20000) / 0x20000) + FLASH_SECTOR_5;}return sector;
}/*** @brief 擦除應用程序區域* @retval HAL status: HAL_OK 成功, HAL_ERROR 失敗*/
HAL_StatusTypeDef Erase_Application_Sectors(void)
{FLASH_EraseInitTypeDef erase;uint32_t sector_error = 0;HAL_StatusTypeDef status;// 解鎖FlashHAL_FLASH_Unlock();// 清除所有錯誤標志__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);// 計算需要擦除的扇區范圍uint32_t start_sector = Get_Sector(APP_START_ADDR);uint32_t end_sector = Get_Sector(APP_END_ADDR);// 設置擦除參數erase.TypeErase = FLASH_TYPEERASE_SECTORS;erase.Banks = FLASH_BANK_1; // F407只有Bank1erase.Sector = start_sector;erase.NbSectors = end_sector - start_sector + 1;erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 適用于3.3V// 執行扇區擦除status = HAL_FLASHEx_Erase(&erase, §or_error);// 鎖定FlashHAL_FLASH_Lock();return status;
}void mount_file_system()
{/* 掛載文件系統 */FRESULT res;const TCHAR* SDPath = "0:"; // SD 卡路徑res = f_mount(&SDFatFS, SDPath, 1);if (res != FR_OK){printf("Bootloader TF Card Mount Failed! f_mount error code: %d\r\n", res);Error_Handler();}else{printf("Bootloader TF Card Mount success\r\n");}
}void get_chipid(uint8_t* id_buf) {uint32_t* id_ptr = (uint32_t*)CPU_ID_BASE_ADDR;memcpy(id_buf, id_ptr, 12);
}// 生成加密密鑰
void generate_encryption_key(const uint32_t* chipID, const char* userKey, uint8_t* encryptionKey) {SHA256_CTX ctx;sha256_init(&ctx);sha256_update(&ctx, (const uint8_t*)chipID, sizeof(uint32_t) * 3);sha256_update(&ctx, (const uint8_t*)userKey, strlen(userKey));sha256_final(&ctx, encryptionKey);
}void FormatIDString(uint32_t *id, char *output) {sprintf(output, "0x%08X-0x%08X-0x%08X", id[0], id[1], id[2]);
}ErrorStatus Get_File_Name()
{// 1. 獲取芯片唯一IDget_chipid((uint8_t *)chipID);FormatIDString(chipID, cid_filename);strcat(cid_filename, ".cid");FormatIDString(chipID, bin_filename);strcat(bin_filename, ".bin");FormatIDString(chipID, cid_content);return SUCCESS;
}FRESULT CreateChipIDFile() {FIL file;FRESULT fr; UINT bytesWritten;fr = f_open(&file, cid_filename, FA_CREATE_ALWAYS | FA_WRITE);if (fr == FR_OK) {fr = f_write(&file, cid_content, 32, &bytesWritten);f_close(&file);} else {printf("CreateChipIDFile failed! Error: %d\n", fr);}if (bytesWritten == 32){printf("CreateChipIDFile done!\r\n");return FR_OK; }else{printf("CreateChipIDFile failed! code: %d\r\n", fr);return FR_DENIED;}
}ErrorStatus DeleteFile() {FRESULT fr;fr = f_unlink(bin_filename); // 刪除文件if(fr != FR_OK) {printf("delete .bin Failed! Error: %d\r\n", fr);return ERROR;}fr = f_unlink(cid_filename); // 刪除文件if(fr != FR_OK) {printf("delete .cid Failed! Error: %d\r\n", fr);return ERROR;}f_mount(&SDFatFS, "", 0); // 卸載文件系統return SUCCESS;
}ErrorStatus Check_Firmware_License()
{FIL file;FRESULT fr;UINT bytes_read;uint32_t file_size;fr = f_open(&file, bin_filename, FA_READ);if(fr != FR_OK) {printf("Open Firmware File Failed! Error: %d\r\n", fr);return ERROR;}// 3. 獲取文件大小file_size = f_size(&file);if(file_size <= HASH_SIZE) {printf("Invalid firmware size: %d bytes\r\n", file_size);f_close(&file);return ERROR;}// 4. 生成解密密鑰(SHA256)generate_encryption_key(chipID, user_local_key, decryptionKey); // 密鑰生成函數// 5. 讀取并解密許可證區域(前32字節)uint8_t stored_hash_enc[HASH_SIZE];fr = f_read(&file, stored_hash_enc, HASH_SIZE, &bytes_read);if(fr != FR_OK || bytes_read != HASH_SIZE) {printf("Read license failed! Error: %d\r\n", fr);f_close(&file);return ERROR;}uint8_t stored_hash[HASH_SIZE];for(int i=0; i<HASH_SIZE; i++) {stored_hash[i] = stored_hash_enc[i] ^ decryptionKey[i];}// 初始化SHA-256上下文SHA256_CTX ctx;sha256_init(&ctx);// 讀取并計算文件內容哈希(每次32字節),包括解密過程,每讀取32字節都要先解密才能送入sha256uint8_t hash[HASH_SIZE];uint32_t bytes_processed = 0;uint32_t content_size = file_size - HASH_SIZE; // 實際內容大小(減去許可證)static UINT key_index = 0;while(bytes_processed < content_size) {UINT to_read = HASH_SIZE;if(content_size - bytes_processed < HASH_SIZE) {to_read = content_size - bytes_processed;}fr = f_read(&file, hash, to_read, &bytes_read);if(fr != FR_OK || bytes_read == 0) {printf("Read error at pos %d: %d\r\n", bytes_processed, fr);f_close(&file);return ERROR;}// 解密數據塊(按字節異或)for(UINT i=0; i<bytes_read; i++) {hash[i] ^= decryptionKey[key_index];key_index = (key_index + 1) % HASH_SIZE; // 循環使用密鑰}sha256_update(&ctx, hash, bytes_read);bytes_processed += bytes_read;}// 獲取最終哈希值uint8_t calculated_hash[HASH_SIZE];sha256_final(&ctx, calculated_hash);// 關閉文件f_close(&file);// 比較哈希值uint8_t diff = 0;for(int i = 0; i < HASH_SIZE; i++) {diff |= stored_hash[i] ^ calculated_hash[i];}ErrorStatus hash_valid = (diff == 0) ? SUCCESS : ERROR;if(!hash_valid) {// 處理許可證無效的情況printf("Firmware license verification failed!\n");printf("Take appropriate action: halt system, use safe mode, or notify administrator.\n");return ERROR;}return SUCCESS;
}/*** @brief 修復版SD卡固件燒錄函數* @retval HAL_OK 成功, HAL_ERROR 失敗*/
HAL_StatusTypeDef Update_Firmware_From_SD(void)
{FRESULT fr;UINT bytes_read;uint32_t total_words = 0; // 改為按字計數uint8_t buffer[512]; // 512字節緩沖區uint8_t tail_buffer[4]; // 用于處理文件尾部的非完整字uint8_t tail_count = 0;static UINT key_index = 0;// 打開固件文件fr = f_open(&file, bin_filename, FA_READ);if(fr != FR_OK) {printf("Open Frimware File Failed!\r\n");return HAL_ERROR;}// 先讀取32字節的hash值,將文件指針跳過32字節fr = f_read(&file, buffer, HASH_SIZE, &bytes_read);if(fr != FR_OK || bytes_read != HASH_SIZE) {return HAL_ERROR;}// 解鎖FlashHAL_FLASH_Unlock();__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);// 開始編程while(1) {// 處理上一塊剩余的字節(如果有)if(tail_count > 0) {// 讀取新數據,補齊4字節UINT bytes_to_read = 4 - tail_count;fr = f_read(&file, tail_buffer + tail_count, bytes_to_read, &bytes_read);if(fr != FR_OK || bytes_read == 0) {// 文件已結束,但還有部分數據需要寫入if(tail_count > 0) {// 填充剩余字節為0xFFfor(int i = tail_count; i < 4; i++) {tail_buffer[i] = 0xFF;}// 寫入最后一個不完整的字uint32_t tail_data;memcpy(&tail_data, tail_buffer, 4);if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_START_ADDR + total_words * 4, tail_data) != HAL_OK) {goto update_error;}total_words++;}break;}// 現在有完整的4字節數據uint32_t tail_data;memcpy(&tail_data, tail_buffer, 4);if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_START_ADDR + total_words * 4, tail_data) != HAL_OK) {goto update_error;}total_words++;tail_count = 0;}// 讀取文件數據fr = f_read(&file, buffer, sizeof(buffer), &bytes_read);if(fr != FR_OK || bytes_read == 0) {break;}// 解密數據塊(按字節異或)for(UINT i=0; i<bytes_read; i++) {buffer[i] ^= decryptionKey[key_index];key_index = (key_index + 1) % HASH_SIZE; // 循環使用密鑰}// 處理完整的4字節塊uint32_t full_words = bytes_read / 4;for(uint32_t i = 0; i < full_words; i++) {uint32_t addr = APP_START_ADDR + total_words * 4;uint32_t data;memcpy(&data, &buffer[i * 4], 4);if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, data) != HAL_OK) {goto update_error;}total_words++;}// 處理剩余的不完整字tail_count = bytes_read % 4;if(tail_count > 0) {memcpy(tail_buffer, &buffer[full_words * 4], tail_count);}}// 關閉文件f_close(&file);// 鎖定FlashHAL_FLASH_Lock();return HAL_OK;update_error:f_close(&file);HAL_FLASH_Lock();return HAL_ERROR;
}/*** @brief 跳轉到應用程序*/
void Jump_To_Application()
{
// for (int i = 0; i < 16; i++) {
// NVIC_DisableIRQ((IRQn_Type)i);
// }if(((*(uint32_t*)APP_START_ADDR) & 0x2FF00000) != 0x20000000) {return;}SCB->VTOR = APP_START_ADDR;// // 關閉所有中斷
// __disable_irq();__set_MSP(*(__IO uint32_t*)APP_START_ADDR);typedef void (*pFunction)(void);pFunction app_entry;app_entry = (pFunction)(*(__IO uint32_t*)(APP_START_ADDR + 4));app_entry();while(1);
}/*** @brief 主更新流程*/
ErrorStatus Perform_Firmware_Update(void)
{mount_file_system();Get_File_Name();if (Check_Firmware_License() == ERROR){printf("Check_Firmware_License Error!\r\n");printf("Please Retry\r\n");Error_Handler();return ERROR;}if(Erase_Application_Sectors() != HAL_OK) {printf("Erase App Sectors Failed!\r\n");Error_Handler();return ERROR;}printf("Erase_Application_Sectors done!\r\n");if(Update_Firmware_From_SD() != HAL_OK) {printf("Update Frimware Failed!\r\n");Error_Handler();return ERROR;}DeleteFile();return SUCCESS;
}void delay_ms(uint32_t ms)
{const uint32_t hsi_freq = 16000000; volatile uint32_t cycles = ms * (hsi_freq / 6000);while(cycles--);
}/*
* 時序檢測:上電時刻如果檢測到1,在3~5s內檢測到下降沿,則成功進入升級模式
*
*/
ErrorStatus Check_Firmware_Upgrade()
{if (READ_MODE_PIN() == GPIO_PIN_RESET){return ERROR;}delay_ms(3000);if (READ_MODE_PIN() == GPIO_PIN_RESET){return ERROR;}delay_ms(2000);if (READ_MODE_PIN() == GPIO_PIN_RESET){return SUCCESS;}return ERROR;
}void Succeed_Light()
{while (1) {const uint16_t cycle = 8; // 縮短PWM周期至8ms(125Hz)const uint16_t steps = 80; // 減少呼吸階段步數const uint16_t min_step = 2; // 設置最小亮度閾值// 漸暗階段優化for (uint16_t i = steps; i > min_step; i--) {uint16_t on_time = ((uint32_t)cycle * i * i) / (steps * steps); // 二次曲線加快變化速度SET_LED_ON();if(on_time > 0) HAL_Delay(on_time);SET_LED_OFF();uint16_t off_time = cycle - on_time;if(off_time > 0) HAL_Delay(off_time);}// 漸亮階段優化for (uint16_t i = min_step; i < steps; i++) {uint16_t on_time = ((uint32_t)cycle * i * i) / (steps * steps); // 二次曲線加快變化速度SET_LED_ON();if(on_time > 0) HAL_Delay(on_time);SET_LED_OFF();uint16_t off_time = cycle - on_time;if(off_time > 0) HAL_Delay(off_time);}}
}void Failed_Light()
{for (uint16_t i = 0; i < 10; i++) {TOGGLE_LED();HAL_Delay(200);}SET_LED_OFF();
}/* USER CODE END 0 *//*** @brief The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 */// enable memory read protection.Check_ReadProtection();MX_GPIO_Init();if (Check_Firmware_Upgrade() == ERROR){Jump_To_Application();for (uint16_t i = 0; i < 10; i++) {TOGGLE_LED();delay_ms(200);}SET_LED_OFF();while(1);}/* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();MX_SDIO_SD_Init();MX_FATFS_Init();MX_USART2_UART_Init();MX_USART3_UART_Init();/* USER CODE BEGIN 2 */setvbuf(stdout, NULL, _IONBF, 0);printf("Start to Upgrade Firmware\r\n");if (Perform_Firmware_Update() == SUCCESS){printf("Perform_Firmware_Update done!\r\n");printf("Please Restart the Device\r\n");Succeed_Light();}/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Configure the main internal regulator output voltage*/__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;RCC_OscInitStruct.PLL.PLLM = 8;RCC_OscInitStruct.PLL.PLLN = 168;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 7;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/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();}
}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */CreateChipIDFile();Failed_Light();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */