STM32安全固件升級:使用自定義 bootloader 實現SD卡固件升級,包含固件加密

前言

在 STM32 嵌入式開發中,Bootloader 是一個不可或缺的模塊。ST 公司為 STM32 提供了功能完備的官方 Bootloader,支持多種通信接口(如 USART、USB DFU、I2C、SPI 等),適用于標準的固件更新方案。

然而,隨著系統需求的多樣化和定制化,自定義 Bootloader 越來越成為項目開發者的首選。

方案比較

官方 bootloader 優點

  1. 開箱即用:無需編寫任何代碼,即可通過 ROM 中的 Bootloader 進行下載。
  2. 支持多種接口:如 USART、USB、I2C 等,適合簡單的量產燒錄。
  3. 穩定性高:由 ST 官方提供,經過長期驗證。

官方 bootloader 局限

  1. 不可修改
    官方 Bootloader 存儲在芯片的系統 ROM 中,屬于只讀區域,無法添加用戶邏輯或擴展功能。
  2. 接口受限
    實際項目中可能需要使用 CAN、以太網、BLE 等接口更新固件,而這些通常未被官方 Bootloader 支持或實現較復雜。
  3. 協議封閉
    官方 Bootloader 通信協議較為復雜,難以與高層系統(如云 OTA、手機 App)靈活集成。
  4. 安全性不足
    默認無固件加密或簽名機制,容易被反編譯和惡意刷寫。
  5. 啟動方式有限
    官方 Bootloader 通常通過 BOOT 引腳或 Option Byte 來觸發,不夠靈活。例如,無法通過軟件條件(如特定按鍵組合)進入 Bootloader 模式。

通過自定義bootloader,可以實現多種方式控制觸發升級邏輯:

  • 按鍵時序組合
  • 接收特定指令
  • Flash 中標志位
  • RTC Backup 寄存器
  • Watchdog Timeout
  • 遠程 OTA

主要思想

本項目實現了一個從 TF 讀取升級固件并更新到 flash 中的功能,同時包括了綁定 chipID 加密操作,提升操作便利性的同時提供了絕對的安全可靠性。

主要思想史利用單片機的唯一的ChipID于用戶自定義的密鑰輸入sha256算法得出32字節的加密密鑰,然后用這個密鑰對固件進行異或運算加密

輸入
輸入
ChipID
SHA-256
用戶密鑰
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, &sector_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 */

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88794.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88794.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88794.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一步部署APache編譯安裝腳本

接下來我來介紹以下編譯安裝的好處 編譯安裝的優點與缺點 一、優點 高度可定制 可根據實際需求啟用或關閉特性&#xff08;如 Apache 的模塊、MySQL 的引擎等&#xff09;。 靈活控制編譯參數、優化性能&#xff08;如 --enable-xxx、--with-xxx&#xff09;。 更高的性能…

[Linux]mmap()函數內存映射原理及用法

一、內存映射 內存映射&#xff0c;簡而言之就是將用戶空間的一段內存區域映射到內核空間&#xff0c;映射成功后&#xff0c;用戶對這段內存區域的修改可以直接反映到內核空間&#xff0c;同樣&#xff0c;內核空間對這段區域的修改也直接反映用戶空間。那么對于內核空間和用…

通信無BUG,ethernet ip轉profinet網關,汽車焊接設備通信有心機

在運用“激光釬焊”對汽車車頂、側面板、后行李箱蓋等位置進行接合時&#xff0c;必須配備能夠沿著復雜車身線條&#xff0c;對細窄焊接線實施高精度快速檢測及模仿控制的“焊縫跟蹤控制”。 那么汽車生產線的系統升級改造迫在眉睫&#xff0c;當西門子PLC和庫卡機器人無法通信…

python腳本ETH獲取最新發行版本并將是否更新信息發送到釘釘

import requests import json import time import hmac import hashlib import base64 import urllib.parse# 1. 配置釘釘機器人 webhook "https://oapi.dingtalk.com/robot/send?access_tokenXXX" secret "XXX" # 如果沒有加簽驗證&#xff0c;請設…

【Docker基礎】Docker容器管理:docker ps及其參數詳解

目錄 1 docker ps命令概述 1.1 命令定位與作用 1.2 命令基本語法 2 基礎參數詳解 2.1 默認輸出解析 2.2 核心參數解析 2.2.1 -a, --all 2.2.2 -q, --quiet 2.2.3 --no-trunc 3 高級過濾與格式化 3.1 過濾器(--filter)詳解 3.1.1 常用過濾條件 3.1.2 實際應用示例 …

應急響應-感染Neshta病毒

病毒確定&#xff1a; 根據感染現象確定為Virus/Win32.Neshta家族病毒 病毒表現&#xff1a; 該病毒為感染式病毒。該病毒會在系統%SystemRoot%目錄下釋放svchost.com文件&#xff0c;并通過添加注冊表的方式確保每個exe文件執行的時候都會先執行這個文件。該病毒還會收集系統信…

Hyperledger Fabric 入門筆記(二十)Fabric V2.5 測試網絡進階之Tape性能測試

文章目錄 前言一、介紹二、架構三、安裝說明四、使用方法4.1. 修改配置文件4.2. 啟動測試網絡4.3. 運行測試 前言 本文介紹由Hyperledger中國技術工作組提供的另一款區塊鏈網絡性能測試工具Tape的架構、安裝和在Fabric測試網絡中的使用。 一、介紹 Tape是一款輕量級的、可以快…

怎樣在 VS Code 中快速創建 Vue 單文件組件(SFC)的基礎模板結構?

問題 在Vue項目的開發中&#xff0c;我們經常遇到一個問題&#xff0c;創建新組件時要自己輸入基本的框架&#xff0c;比如&#xff1a; <template><div class"page-box"></div> </template><script> export default {name: ,data()…

高防IP在服務器中的作用都有哪些?

高防IP作為一種通過技術手段讓用戶網絡服務更加安全的一種IP地址&#xff0c;有著更高的防御能力&#xff0c;有著強大的流量清洗中心和防御系統&#xff0c;幫助企業實時監控網絡流量&#xff0c;將惡意的用戶請求識別并過濾掉&#xff0c;保護目標服務器不會受到網絡攻擊&…

實戰 X-AnyLabeling:構建高效自動標注系統的工程實踐

文章目錄 一、項目背景與目標二、系統架構與模塊劃分2.1 模塊組成說明2.2 架構圖 三、模型封裝與平臺對接3.1 模型封裝接口3.2 接入 X-AnyLabeling 平臺 四、可視化與預測驗證4.1 UI 預測標簽預覽 五、性能優化與工程經驗5.1 模型加速與推理優化5.2 經驗總結5.3 實際效果 本文將…

UC3842/UC3843反激教程教學開關電源 反激設計步驟,每一關鍵元器件計算

資料下載地址&#xff1a;UC3842/UC3843反激教程教學開關電源 反激設計步驟&#xff0c;每一關鍵元器件計算 1、原理圖 2、PCB圖 3、變壓器設計資料 4、開關電源設計資料 5、主要元器件說明書 6、系統整體資料 7、說明文檔 7.1、電源設計概述 電源規格&#xff1a;設計一款 2…

Docker 入門教程(二):Docker 的基本原理

文章目錄 &#x1f433; Docker 入門教程&#xff08;二&#xff09;&#xff1a;Docker 的基本原理1. Docker 架構總覽&#xff1a;三大核心角色2. 鏡像與容器的關系3. 容器啟動流程&#xff1a;docker run 背后發生了什么&#xff1f; &#x1f433; Docker 入門教程&#xf…

21.安卓逆向2-frida hook技術-HookOkHttp的攔截器

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 內容參考于&#xff1a;圖靈Python學院 工具下載&#xff1a; 鏈接&#xff1a;https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwdzy89 提取碼&#xff1…

小程序入門:swpier 和 swpier-item 的基本使用

在前端開發中&#xff0c;創建交互式的用戶界面組件是至關重要的。今天&#xff0c;我們將深入探討 swpier 和 swpier-item 的基本使用方法&#xff0c;這兩個組件在構建輪播圖等滑動效果的場景中非常實用。 一、swpier 組件概述 swpier 組件是實現滑動效果的核心容器。它負責…

SQL學習筆記4

約束 1、約束 約束&#xff0c;是指作用在表中字段上的規則&#xff0c;用于限制字段輸入的數據&#xff0c;使得表格式統一&#xff0c;數據內容正確。同一個字段的約束可以有多個 約束包括&#xff1a; 非空約束&#xff0c;限制表中的值不為null:not null 唯一約束&…

力扣刷題(第七十天)

靈感來源 - 保持更新&#xff0c;努力學習 - python腳本學習 比特位計數 解題思路 對于任意整數 x&#xff0c;其 1 的個數等于 x // 2 的 1 的個數加上 x % 2。狀態轉移方程&#xff1a;dp[x] dp[x // 2] (x % 2)。 class Solution:def countBits(self, n: int) ->…

鴻蒙網絡編程系列56-倉頡版通過數據包結束標志解決TCP粘包問題

1. TCP粘包問題解決思路 在本系列的上一篇文章演示了TCP數據粘包的原因以及可能的解決方法&#xff0c;本文將通過其中的添加數據包結束標志的方法來解決這個問題。我們知道&#xff0c;數據粘包的原因是因為發送的時候沒有標明數據包的邊界&#xff0c;那么&#xff0c;我們人…

Redis網絡通信模塊深度解析:單線程Reactor到多線程IO的架構演進

一、核心架構&#xff1a;單線程Reactor模型 Redis網絡模塊采用經典Reactor模式&#xff0c;核心流程如下&#xff1a; void aeMain(aeEventLoop *eventLoop) {while (!eventLoop->stop) {// 前置鉤子&#xff08;集群心跳/數據持久化&#xff09;if (eventLoop->befor…

PILCO: 基于模型的高效策略搜索方法原理解析

PILCO: 基于模型的高效策略搜索方法原理解析 PILCO (Probabilistic Inference for Learning Control) 是一種基于模型的強化學習算法&#xff0c;由Marc Deisenroth和Carl Rasmussen于2011年提出。該算法在數據效率方面表現出色&#xff0c;能夠以極少的樣本數據實現有效學習。…

大語言模型訓練中的自監督學習和其他訓練方式

大語言模型訓練中的自監督學習和其他訓練方式。 自監督學習&#xff08;Self-Supervised Learning&#xff09; 1. 什么是自監督學習&#xff1f; 自監督學習是一種不需要人工標注數據的訓練方式&#xff0c;模型從數據本身學習特征和模式。 在語言模型中的具體實現&#x…