STM32安全固件升级:使用自定义 bootloader 实现SD卡固件升级,包含固件加密_stm32升级bootloader
前言
在 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字节的加密密钥,然后用这个密钥对固件进行异或运算加密
#mermaid-svg-D2ZuMapZbxSk7OBJ {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .error-icon{fill:#552222;}#mermaid-svg-D2ZuMapZbxSk7OBJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D2ZuMapZbxSk7OBJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .marker.cross{stroke:#333333;}#mermaid-svg-D2ZuMapZbxSk7OBJ svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D2ZuMapZbxSk7OBJ .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .cluster-label text{fill:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .cluster-label span{color:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .label text,#mermaid-svg-D2ZuMapZbxSk7OBJ span{fill:#333;color:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .node rect,#mermaid-svg-D2ZuMapZbxSk7OBJ .node circle,#mermaid-svg-D2ZuMapZbxSk7OBJ .node ellipse,#mermaid-svg-D2ZuMapZbxSk7OBJ .node polygon,#mermaid-svg-D2ZuMapZbxSk7OBJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D2ZuMapZbxSk7OBJ .node .label{text-align:center;}#mermaid-svg-D2ZuMapZbxSk7OBJ .node.clickable{cursor:pointer;}#mermaid-svg-D2ZuMapZbxSk7OBJ .arrowheadPath{fill:#333333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D2ZuMapZbxSk7OBJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-D2ZuMapZbxSk7OBJ .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-D2ZuMapZbxSk7OBJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D2ZuMapZbxSk7OBJ .cluster text{fill:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ .cluster span{color:#333;}#mermaid-svg-D2ZuMapZbxSk7OBJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D2ZuMapZbxSk7OBJ :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}输入输入ChipIDSHA-256用户密钥32字节加密密钥异或加密固件
设计细节
固件加密
#define _CRT_SECURE_NO_WARNINGS#include #include #include #include #include #include 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);}// 解析芯片IDint 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 #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 32const 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 1 FLASH_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个扇区各16KB sector = 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; // 解锁Flash HAL_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只有Bank1 erase.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); // 锁定Flash HAL_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. 获取芯片唯一ID get_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字节都要先解密才能送入sha256 uint8_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; } // 解锁Flash HAL_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) { // 填充剩余字节为0xFF for(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); // 锁定Flash HAL_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 */