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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…