当前位置: 首页 > news >正文

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.lryc.cn/news/575970.html

相关文章:

  • 【stm32】HAL库开发——CubeMX配置串口通讯(中断方式)
  • virtual box 配置ubuntu 22.04网络与SSH服务
  • A模块 系统与网络安全 第三门课 网络通信原理-2
  • 24CJ87-4:圆拱型采光排烟天窗
  • Pytorch基础函数速查
  • A Machine Learning Approach for Non-blind Image Deconvolution论文阅读
  • AI助力基因数据分析:用Python玩转生命密码的秘密
  • 高标准+安全可控:关键领域研发选择什么软件?
  • QT编译wasm报错:The program “mingw32-make.exe“ does not exist or is not executable
  • 《二分枚举答案(配合数据结构)》题集
  • 数据透视表学习笔记
  • 如何将两个不同类性的类组合成一个json数据
  • Vue 3 计算属性的应用
  • Valkey与Redis评估对比:开源替代方案的技术演进
  • 基于Odoo 18的生产报工系统架构与开发
  • 利用云雾自动化在智能无人水面航行器中实现自主碰撞检测和分类
  • 不同信创系统如何集中远程运维?贝锐向日葵提供稳定方案
  • 操作系统之文件管理(王道)
  • Day 10:Shell正则表达式终极指南:从“抓狂“到“掌控“的奇幻之旅
  • 设计模式 | 桥接模式
  • LeetCode 349题解 | 两个数组的交集
  • SAP PP模块与MM模块作用详解
  • 航天VR赋能,无人机总测实验舱开启高效新篇​
  • Maven生命周期与阶段扩展深度解析
  • Linux系统(信号篇)信号的保存
  • Post-Training on PAI (1):一文览尽开源强化学习框架在PAI平台的应用
  • 信息抽取数据集全景分析:分类体系、技术演进与挑战_DEEPSEEK
  • CloudFormation 实现 GitHub Actions OIDC 与 AWS ECR 的安全集成(支持多组织配置)
  • 【Linux】ghb工具
  • 论文研读2-3:多GNSS双历元纯相位定位-定位精度分析