正點原子STM32F407 U盤升級程序(IAP)OTA Bootloader APP USB升級+FATFS+USB Host

正點原子STM32F407 U盤升級程序(IAP)OTA Bootloader APP USB升級+FATFS+USB Host

  • Chapter0 解決STM32 Bootloader跳轉APP失敗問題
    • 問題背景
    • 問題描述
    • 問題解決
      • 原APP跳轉的函數為:
      • 修改APP程序main入口處
  • Chapter1 MDK如何生成*.bin格式的文件
  • Chapter2 正點原子STM32F407 U盤升級程序(IAP)OTA Bootloader APP USB升級+FATFS+USB Host+FreeRTOS 附上源碼
    • 一、引言
    • 二、軟硬件準備
    • 三、CUBEmx配置
    • 四、分區管理
    • 五、Bootloader層開發
      • 5.1 U盤接口實現
      • 5.2 升級流程控制
      • 5.3 bootloader.c
      • 5.4 bootloader.h
    • 六、App層開發
      • 6.1 App程序設計
      • 6.2 App魔術棒配置
      • 6.3底層代碼修改
      • 6.4 Bin文件生成
    • 七、注意事項
    • 八、實驗過程
    • 九、總結
    • 十、源碼分享
  • Chapter3 HAL庫U盤升級 STM32F407 CUBEMX:FATFS + USB_HOST + USB_OTG_FS
    • 一、測試平臺:
    • 二、實驗目的:
    • 三、BootLoader:
      • 1 下載器配置
      • 2 時鐘源配置
      • 3 LED配置
      • 4 串口配置 開啟全局中斷
      • 5 USB_OTG_FS配置
      • 6 USB_HOST配置
      • 7 FATFS配置
      • 8 時鐘樹配置 48M是用來操作U盤的,所以必須要有
      • 9 工程配置
      • 10 生成工程
      • 11 創建兩個空白文件文件
      • BootLoader.h
      • BootLoader.c
    • 四、APP:
      • 1 APP程序只進行LED閃爍,只配置這三個功能就夠了
      • 2 時鐘樹配置
      • 3 工程配置
      • 4 接下來是APP程序
    • 五、實驗現象:
    • 六、文件篇:
  • Chapter4 01-STM32+Air724UG遠程升級篇OTA(自建物聯網平臺)-STM32如何實現的升級程序
  • Chapter5 02-STM32+Air724UG遠程升級篇OTA(自建物聯網平臺)-什么是http,怎么通過http下載文件數據
    • 說明
    • 搭建好web服務器(Windows)
  • Chapter6 一個簡單粗暴易用的遠程調試方案——OTA http update
  • Chapter7 在線升級:OTA升級的原理和實現方式
    • 1、OTA 在線升級
    • 2、實現方式
    • 3、操作方式
      • 3.1、后臺式升級
      • 3.2、非后臺式式更新
    • 4、STM32 的在線升級


Chapter0 解決STM32 Bootloader跳轉APP失敗問題

原文鏈接:https://blog.csdn.net/2303_79637659/article/details/149101151

問題背景

在STM32開發中,通過Bootloader跳轉到用戶應用程序(APP)是常見需求。但在實際開發中,可能會遇到 Bootloader能正常啟動,但跳轉到APP后卡死或進入Error_Handler 的問題。本文將詳細記錄一個典型案例:因時鐘配置沖突導致跳轉失敗,并給出完整解決方案。

問題描述

現有一個Bootloader程序,和一個APP程序,Bootloader程序存儲在地址0x08000000,APP程序存儲在0x08010000,將兩個程序通過Keil燒錄到對應的地址后,Bootloader程序能夠正常運行,但APP程序不能運行。
但是如果單獨下載運行APP程序,程序是可以正常運行的。

起初,我是認為程序沒有正常跳轉,于是在網上搜素了很多相關資料,這些文章很多都講了MSP指針對程序跳轉的影響和解決方案,根據這些資料排查問題后,發現還是不能成功跳轉,于是,我使用Keil的斷點調試功能對代碼進行單步調試。

單步調試時發現,程序是能夠正常跳轉的,但在APP程序的SystemClock_Config函數中,HAL_RCC_OscConfig函數會配置失敗,導致進入Error_Handler中斷。

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_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 4;RCC_OscInitStruct.PLL.PLLN = 168;RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;RCC_OscInitStruct.PLL.PLLQ = 4;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();}
}

進一步分析發現,在Bootloader中由于使用了USB等外設,所以Bootloader和APP的時鐘樹配置有些許不同,導致APP時鐘配置失敗,程序卡死。

問題解決

原APP跳轉的函數為:

typedef void (*appfun)(void);void APP_JUMP(uint32_t APP_ADDR)
{__disable_irq();uint32_t JUMP_ADDR = APP_ADDR;uint32_t RESET_IRQ_ADDR = JUMP_ADDR + 4;appfun boot2app;boot2app = (appfun)*(__IO uint32_t *)RESET_IRQ_ADDR;__set_MSP(*(__IO uint32_t *)JUMP_ADDR);boot2app();}

在APP跳轉之前,將時鐘和外設全部復位

typedef void (*appfun)(void);void APP_JUMP(uint32_t APP_ADDR)
{HAL_RCC_DeInit();   //復位時鐘HAL_DeInit();  // 復位所有外設__disable_irq();uint32_t JUMP_ADDR = APP_ADDR;uint32_t RESET_IRQ_ADDR = JUMP_ADDR + 4;appfun boot2app;boot2app = (appfun)*(__IO uint32_t *)RESET_IRQ_ADDR;__set_MSP(*(__IO uint32_t *)JUMP_ADDR);boot2app();}

修改APP程序main入口處

/**************這三句必須加上,否則APP程序無法運行************************/
__enable_irq();
HAL_DeInit();
HAL_Init();
/**************這三句必須加上,否則APP程序無法運行************************/
/*** @brief  The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. *//**************這三句必須加上,否則APP程序無法運行************************/__enable_irq();HAL_DeInit();HAL_Init();/**************這三句必須加上,否則APP程序無法運行************************//* 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();/* USER CODE BEGIN 2 *//* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE */HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);HAL_Delay(300);/* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

修改之后,APP能夠成功跳轉運行,至此,問題解決。

Chapter1 MDK如何生成*.bin格式的文件

  1. 【單片機開發】KEIL如何生成*.bin格式的文件

Chapter2 正點原子STM32F407 U盤升級程序(IAP)OTA Bootloader APP USB升級+FATFS+USB Host+FreeRTOS 附上源碼

原文鏈接:https://blog.csdn.net/2202_75941163/article/details/146256031

一、引言

    在嵌入式開發領域,程序的現場升級功能是非常實用的,它允許用戶在產品已經部署到現場后,仍能方便地對程序進行更新和維護。使用STM32實現U盤升級程序(IAP)功能,可以極大地提高產品的可維護性和用戶體驗。本文將詳細介紹如何實現基于STM32的U盤IAP功能,主要分為Bootloader層和App層的開發。

-------------------------------------------------------------結尾附上源碼---------------------------------------------------------------

二、軟硬件準備

硬件:正點原子STM32F407ZGT6最小系統板
軟件:CUBEMx版本:MX.6.12.0
調試工具:sscom串口調試助手

三、CUBEmx配置

     本文暫不詳細介紹CUBEmx的配置步驟,因為網上資源豐富,大家可以參考上一篇(讀寫U盤)配置教程,里面有提到大佬的配置鏈接:

單片機讀取U盤 FATFS文件系統 USB MSC STM32f105 GD32f305 讀取U盤 exFAT FAT32_gd32f105usb例程-CSDN博客
https://blog.csdn.net/2202_75941163/article/details/145942897

注:讀寫U盤 與 U盤IAP升級所需的HAL庫基本配置是一樣的

四、分區管理

    為了實現IAP功能,需要對STM32的Flash存儲器進行分區管理。一般將Flash存儲器分為兩個主要區域:Bootloader區和App區。Bootloader區用于存放Bootloader程序,而App區用于存放用戶應用程序。
#define BOOTLOADER_START_ADDR    0x08000000    // Bootloader起始地址#define BOOTLOADER_SIZE            0x00010000    // Bootloader大小(64KB)#define FLASH_USER_START_ADDR      0x08010000   // App起始地址#define FLASH_USER_END_ADDR       (0x08010000 + APP_Size)  // APP結束地址#define filename "APP.bin"                     // APP_Size為U盤bin文件大小

五、Bootloader層開發

5.1 U盤接口實現

    為了實現U盤升級功能,需要在Bootloader中實現USB設備接口。STM32提供了豐富的USB外設功能,通過FatFS文件管理系統與USB Host功能,可以實現識別U盤升級Bin文件。在代碼中,通過調用f_mount函數掛載U盤,f_open函數打開U盤中的固件文件,然后使用f_read函數將固件數據讀取到RAM緩沖區中。

5.2 升級流程控制

在Bootloader中,升級流程控制的實現基于對U盤檢測和用戶操作的響應。具體流程如下:

U盤檢測與初始化:系統上電后,Bootloader首先檢測U盤是否插入。這是通過USB主機功能實現的,一旦檢測到U盤,便初始化FatFS文件系統,為后續的文件操作做準備。
固件文件讀取:在成功掛載U盤后,Bootloader嘗試打開并讀取固件文件。文件讀取操作通過FatFS的f_read函數完成,將固件數據從U盤讀取到內部RAM緩沖區中。
固件數據校驗:讀取固件數據后,需要對數據進行校驗,確保數據的完整性和正確性。這一步驟對于防止因數據損壞導致的升級失敗至關重要。
Flash擦除與寫入:如果固件數據校驗通過,Bootloader將執行Flash擦除操作,為新的固件寫入騰出空間。擦除操作針對應用程序區域的Flash扇區進行。擦除完成后,將RAM緩沖區中的固件數據寫入Flash。
跳轉到應用程序:在固件成功寫入Flash后,Bootloader設置好應用程序的堆棧指針和程序計數器,然后跳轉到應用程序的入口點,開始運行新的應用程序。
錯誤處理與重試:如果在升級過程中任何一步出現錯誤,例如U盤讀取失敗、數據校驗錯誤或Flash寫入失敗,Bootloader將留在當前模式下,等待用戶重新發起升級操作或進行故障排除。

5.3 bootloader.c

#include "bootloader.h"
#include "main.h"extern ApplicationTypeDef Appli_state;
FRESULT res;
static FLASH_EraseInitTypeDef EraseInitStruct;uint8_t RAM_Buffer[RAM_BUFFER_SIZE];                         // 用于暫存從U盤讀取的固件數據
uint32_t APP_Size;                                           // 從U盤讀取的固件大小
uint32_t FirstSector = 0;
uint32_t NbOfSectors = 0;
uint32_t SectorError = 0;
uint32_t Address = 0;volatile uint32_t data32 = 0 ;
volatile uint32_t MemoryProgramStatus = 0 ;
uint8_t errorcode;
uint32_t *p;uint8_t SystemUpdateFlag = 0, state = 0;                     // 狀態標志變量
uint16_t t = 0;typedef  void (*pFunction)(void);
pFunction Jump_To_Application;uint32_t JumpAddress;uint32_t FLASH_Erase_Write(void)
{uint32_t i = 0;HAL_FLASH_Unlock();                                                // 解鎖FlashFirstSector = GetSector(FLASH_USER_START_ADDR);                    // 獲取應用程序起始地址所在的扇區編號NbOfSectors = GetSector(FLASH_USER_END_ADDR) - FirstSector + 1;    // 計算需要擦除的扇區數量printf("擦除的扇區數量為%d",NbOfSectors);// 配置 Flash 擦除結構體EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;              // 設置擦除類型為扇區擦除EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;             // 設置電壓范圍EraseInitStruct.Sector = FirstSector;                             // 設置起始扇區EraseInitStruct.NbSectors = NbOfSectors;                          // 設置扇區數量// 執行Flash擦除if(HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK) {errorcode = HAL_FLASH_GetError();  // 獲取錯誤碼printf("errorcode %d", errorcode);Error_Handler();}// 禁用和清除Flash緩存__HAL_FLASH_DATA_CACHE_DISABLE();__HAL_FLASH_INSTRUCTION_CACHE_DISABLE();__HAL_FLASH_DATA_CACHE_RESET();__HAL_FLASH_INSTRUCTION_CACHE_RESET();__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();__HAL_FLASH_DATA_CACHE_ENABLE();Address = FLASH_USER_START_ADDR;              // 設置Flash寫入起始地址// 遍歷 RAM_Buffer,將數據寫入 Flashprintf("正在寫入數據 請稍后... ...\r\n");while (Address < FLASH_USER_END_ADDR){p = (uint32_t *)&RAM_Buffer[i];             // 獲取要寫入的數據if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, *p) == HAL_OK)           // 將數據寫入 Flash{Address = Address + 4;   // 地址遞增 4 字節(32位)i = i + 4;}else{printf("Address-error\r\n");Error_Handler();}}printf("數據寫入完畢\r\n");HAL_FLASH_Lock();                                 // 鎖定Flash// 驗證Flash寫入是否成功Address = FLASH_USER_START_ADDR;                  // 重置地址指針MemoryProgramStatus = 0x0;                        //初始化驗證狀態變量// 遍歷 Flash 寫入范圍,驗證數據while (Address < FLASH_USER_END_ADDR){data32 = *(__IO uint32_t*)Address;      // 讀取Flash中的數據if (data32 != *(uint32_t*)RAM_Buffer)   // 比較Flash數據和原始數據{MemoryProgramStatus++;}Address = Address + 4;}return HAL_OK;
}/**********************************************************************
**** 函數名: GetSector()
**** 功  能: 獲取Flash的扇區
**** 參  數: Address   Flash的地址
**** 返回值: 扇區編號
**** 時  間: 2025年3月10日
**** 設  計: 
**** 備  注: STM32F407的Flash大小(1M)    1個扇區16KB(0x4000字節)   一共11個扇區
**********************************************************************/static uint32_t GetSector(uint32_t Address)
{uint32_t sector = 0;              // 初始化扇區編號為0// 判斷地址所在的扇區if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)){sector = FLASH_SECTOR_0;  }else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)){sector = FLASH_SECTOR_1;  }else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)){sector = FLASH_SECTOR_2;  }else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)){sector = FLASH_SECTOR_3;  }else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)){sector = FLASH_SECTOR_4;  }else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)){sector = FLASH_SECTOR_5;  }else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)){sector = FLASH_SECTOR_6;  }else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7)){sector = FLASH_SECTOR_7;  }else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8)){sector = FLASH_SECTOR_8;  }else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9)){sector = FLASH_SECTOR_9;  }else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10)){sector = FLASH_SECTOR_10;  }else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11) */{sector = FLASH_SECTOR_11;}return sector;      // 返回扇區編號
}/**********************************************************************
**** 函數名: jumpToApp()
**** 功  能: 跳轉到appa運行程序
**** 參  數:
**** 返回值: 無
**** 時  間: 2025年3月11日
**** 設  計: 
**** 備  注: 
**********************************************************************/
void jumpToApp()
{ // 檢查應用程序的棧頂地址是否有效// 應用程序的棧頂地址存儲在FLASH_USER_START_ADDR處// 有效棧頂地址的高16位必須是0x2000(即位于SRAM區域)if (((*(__IO uint32_t*)FLASH_USER_START_ADDR) & 0x2FFE0000 ) == 0x20000000){printf("跳轉到應用程序\r\n");HAL_RCC_DeInit(); //復位時鐘HAL_DeInit(); //復位所有外設__disable_irq(); //關閉所有終端//    printf("ADDR == 0x20000000\r\n");JumpAddress = *(__IO uint32_t*) (FLASH_USER_START_ADDR + 4);    // 應用程序的入口地址存儲在FLASH_USER_START_ADDR + 4處Jump_To_Application = (pFunction) JumpAddress;                  // 將入口地址轉換為函數指針// 設置棧指針(MSP)為應用程序的棧頂地址 __set_MSP(*(__IO uint32_t*) FLASH_USER_START_ADDR);             //應用程序的棧頂地址存儲在FLASH_USER_START_ADDR處Jump_To_Application();                                          // 跳轉到應用程序}printf("ADDR != 0x20000000\r\n");     //  棧頂地址無效printf("跳轉到應用程序失敗!\r\n");
}void UP_Data(void)
{while(t < 1010){MX_USB_HOST_Process();HAL_Delay(1);if(SystemUpdateFlag == 0 && Appli_state == APPLICATION_READY)       // 檢查是否準備好進行固件更新{printf("檢測到升級程序(U盤已經插入)\r\n");SystemUpdateFlag = 1;t = 1000;state = 1;//掛載U盤res = f_mount(&USBHFatFS, (TCHAR const*)USBHPath, 0);if(res != FR_OK){printf("U盤掛載失敗  %d\r\n", res);Error_Handler();}else{printf("U盤掛載成功\r\n");}//打開U盤文件res = f_open(&USBHFile, filename, FA_READ);if(res != FR_OK){printf("打開U盤文件失敗  %d\r\n", res);Error_Handler();}else{printf("打開U盤文件成功\r\n");}//讀取U盤文件           讀取固件數據到RAM_Bufferres = f_read(&USBHFile, RAM_Buffer, sizeof(RAM_Buffer), (void *)&APP_Size);if(res != FR_OK){printf("讀取U盤文件失敗  %d\r\n", res);Error_Handler();}else{printf("讀取U盤文件成功\r\n");}   // 檢查固件大小是否合法if((0<APP_Size) && (APP_Size<FLASH_USER_END_ADDR))     // 確保固件大小在合理范圍內{printf("APP_Size大小為 : %d \r\n",APP_Size);printf("FLASH開始擦除\r\n");    //FLASH擦除//正在升級,指示燈常快速閃爍10次for(int k = 0; k < 20; k++){HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);HAL_Delay(50);}FLASH_Erase_Write();  // 調用Flash擦除和寫入函數//升級完成,指示燈常亮1秒鐘HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);HAL_Delay(1000);HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);jumpToApp();                  // 跳轉到新應用程序            }else{printf("APP_Size_Erase\r\n");   //bin文件大小不符合}f_close(&USBHFile);}else{t++;}// 如果t超過1000且未進入更新流程,直接跳轉到應用程序if(state == 0 && t > 1000){state = 1;printf("\r\n未檢測到升級程序\r\n");jumpToApp();}}
}

5.4 bootloader.h

#ifndef __BOOTLOADER_H
#define __BOOTLOADER_H#include "stm32f4xx_hal.h"
#include "usb_host.h"
#include "fatfs.h"
#include "usart.h"#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9     ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10    ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11    ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */#define RAM_BUFFER_SIZE               ((uint32_t)30*1024)       /*KBytes*/#define filename "APP.bin"             //識別U盤文件名稱#define FLASH_USER_START_ADDR   ADDR_FLASH_SECTOR_4   /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR     ADDR_FLASH_SECTOR_4 + APP_Size 
uint32_t FLASH_Erase_Write(void);
static uint32_t GetSector(uint32_t Address);
void jumpToApp(void);
void UP_Data(void);#endif

六、App層開發

6.1 App程序設計

   APP程序設計可以按照自己的需要進行書寫(試驗過程建議先進行 升級亮燈)本例程設計:使用FreeRTOS 隨機寫了幾個外設任務,串口通信、LED亮滅、按鍵檢測、DA信號DMA轉換等任務供學習使用;

6.2 App魔術棒配置

在這里插入圖片描述
IRAM1 (0x20000000 起始地址):

這是片上SRAM,通常用于存儲變量、數據結構以及堆棧等。STM32F407ZET6中,SRAM的大小是128KB(0x20000字節)。SRAM是通用的隨機存取存儲器,用于程序運行時的數據存儲。

IRAM2 (0x10000000 起始地址):

這是CCM RAM,它是一種緊耦合存儲器,直接連接到CPU,具有更快的訪問速度。在STM32F407ZGT6中,CCM RAM的大小是64KB(0x10000字節)。CCM RAM通常用于存儲需要快速訪問的數據,例如實時數據處理或緩存。

6.3底層代碼修改

在這里插入圖片描述
在這里插入圖片描述

6.4 Bin文件生成

在這里插入圖片描述
使用fromelf.exe --bin -o “$L@L.bin” “#L”

這條命令的含義是:在工程編譯完成后,自動調用fromelf.exe工具,將生成的elf格式的可執行文件(通常是.axf文件)轉換為bin格式的文件。其中,–bin參數指定輸出為bin格式,-o參數指定輸出文件的路徑和名稱,"#L"表示輸入的elf文件路徑和名稱

注:直接在工程文件中搜索.Bin文件即可

七、注意事項

在實現STM32 U盤IAP功能時,需要注意以下幾點:

  1. 數據校驗:在數據傳輸過程中,要進行嚴格的數據校驗,確保數據的完整性和正確性。
  2. 分區大小:合理設置Bootloader區和App區的大小,確保App區有足夠的空間存放用戶程序。
  3. 兼容性:確保Bootloader和App之間的接口兼容,避免因接口不匹配導致的問題。
  4. 穩定性:在升級過程中,要確保系統的穩定性,避免因意外斷電等因素導致升級失敗。

八、實驗過程

從APP中復制 .Bin文件到U盤中
在這里插入圖片描述
在這里插入圖片描述
然后將U盤插入USB OTG口,重新上電或復位,即可實現U盤IAP升級,實驗現象如下所示
在這里插入圖片描述

九、總結

   通過以上步驟,可以實現基于STM32的U盤IAP功能。該功能允許用戶通過U盤方便地對設備進行程序升級,極大地提高了產品的可維護性和用戶體驗。在實際開發中,可以根據具體需求對上述方案進行優化和擴展,以滿足不同的應用場景。

十、源碼分享

U-disk_IAP: STM32 U盤升級程序(IAP)是一種實用的嵌入式開發技術,允許用戶通過U盤對設備進行程序升級,提高產品的可維護性和用戶體驗。本文詳細介紹基于STM32的U盤IAP功能實現,涵蓋Bootloader和App層開發。通過合理分區管理Flash存儲器,確保數據傳輸的完整性和正確性,實現穩定可靠的升級過程。該功能適用于需要現場升級的嵌入式產品,具有較高的實用價值。
https://gitee.com/Lucky_17wow/U-disk_IAP


Chapter3 HAL庫U盤升級 STM32F407 CUBEMX:FATFS + USB_HOST + USB_OTG_FS

原文鏈接:https://blog.csdn.net/qq_44742284/article/details/123132331

一、測試平臺:

MCU:STM32F407VET6
固件庫:CUBEMX
IDE:MDK

二、實驗目的:

將U盤里面的bin文件插入要升級的設備,通過BootLoader來進行升級

在這是用板載的LED燈來顯示升級情況:
不進行升級:LED燈是滅的狀態
升級成功:LED燈以100ms在閃爍

接下來先進行BootLoader的配置以及程序編寫,再配置APP

三、BootLoader:

1 下載器配置

在這里插入圖片描述

2 時鐘源配置

在這里插入圖片描述

3 LED配置

在這里插入圖片描述

4 串口配置 開啟全局中斷

在這里插入圖片描述

5 USB_OTG_FS配置

在這里插入圖片描述

6 USB_HOST配置

在這里插入圖片描述

7 FATFS配置

在這里插入圖片描述

8 時鐘樹配置 48M是用來操作U盤的,所以必須要有

在這里插入圖片描述

9 工程配置

在這里插入圖片描述

10 生成工程

在這里插入圖片描述

11 創建兩個空白文件文件

在這里插入圖片描述

BootLoader.h

#ifndef __BOOTLOADER_H
#define __BOOTLOADER_H#include "stm32f4xx_hal.h"
#include "usb_host.h"
#include "fatfs.h"
#include "usart.h"#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */
#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base @ of Sector 8, 128 Kbytes */
#define ADDR_FLASH_SECTOR_9     ((uint32_t)0x080A0000) /* Base @ of Sector 9, 128 Kbytes */
#define ADDR_FLASH_SECTOR_10    ((uint32_t)0x080C0000) /* Base @ of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11    ((uint32_t)0x080E0000) /* Base @ of Sector 11, 128 Kbytes */#define RAM_BUFFER_SIZE               ((uint32_t)30*1024)       /*KBytes*/#define filename "LED.bin"#define FLASH_USER_START_ADDR   ADDR_FLASH_SECTOR_4   /* Start @ of user Flash area */
#define FLASH_USER_END_ADDR     ADDR_FLASH_SECTOR_4  +  0x10000uint32_t FLASH_Erase_Write(void);
static uint32_t GetSector(uint32_t Address);
void jumpToApp(void);
void UP_Data(void);#endif 

BootLoader.c

#include "BootLoader.h"extern ApplicationTypeDef Appli_state;
FRESULT res;
static FLASH_EraseInitTypeDef EraseInitStruct;uint8_t RAM_Buffer[RAM_BUFFER_SIZE];
uint32_t APP_Size;
uint32_t FirstSector = 0, NbOfSectors = 0, Address = 0;
uint32_t SectorError = 0;
__IO uint32_t data32 = 0 , MemoryProgramStatus = 0;
uint8_t errorcode;
uint32_t *p;uint8_t SystemUpdateFlag = 0, state = 0;
uint16_t t = 0;typedef  void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;int fputc(int ch, FILE *f)		//用來打印信息的 便于觀察
{HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 100);return ch;
}
uint32_t FLASH_Erase_Write(void)		//FLASH擦寫
{uint32_t i = 0;HAL_FLASH_Unlock();FirstSector = GetSector(FLASH_USER_START_ADDR);NbOfSectors = GetSector(FLASH_USER_END_ADDR) - FirstSector + 1;EraseInitStruct.TypeErase = FLASH_TYPEERASE_SECTORS;EraseInitStruct.VoltageRange = FLASH_VOLTAGE_RANGE_3;EraseInitStruct.Sector = FirstSector;EraseInitStruct.NbSectors = NbOfSectors;if(HAL_FLASHEx_Erase(&EraseInitStruct, &SectorError) != HAL_OK){errorcode = HAL_FLASH_GetError();		//擦除失敗的扇區printf("errorcode %d", errorcode);Error_Handler();}__HAL_FLASH_DATA_CACHE_DISABLE();__HAL_FLASH_INSTRUCTION_CACHE_DISABLE();__HAL_FLASH_DATA_CACHE_RESET();__HAL_FLASH_INSTRUCTION_CACHE_RESET();__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();__HAL_FLASH_DATA_CACHE_ENABLE();printf("HAL_OK\r\n");Address = FLASH_USER_START_ADDR;while (Address < FLASH_USER_END_ADDR){p = (uint32_t *)&RAM_Buffer[i];if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, *p) == HAL_OK)	//FLASH寫入{Address = Address + 4;i = i + 4;}else{printf("Address-error\r\n");Error_Handler();}}HAL_FLASH_Lock(); Address = FLASH_USER_START_ADDR;		//校驗MemoryProgramStatus = 0x0;printf("CHEAK\r\n");while (Address < FLASH_USER_END_ADDR){data32 = *(__IO uint32_t*)Address;if (data32 != *(uint32_t*)RAM_Buffer){MemoryProgramStatus++;}Address = Address + 4;}printf("CHEAK-finish\r\n");return HAL_OK;
}
static uint32_t GetSector(uint32_t Address)		//獲取扇區
{uint32_t sector = 0;if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0)){sector = FLASH_SECTOR_0;  }else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1)){sector = FLASH_SECTOR_1;  }else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2)){sector = FLASH_SECTOR_2;  }else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3)){sector = FLASH_SECTOR_3;  }else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4)){sector = FLASH_SECTOR_4;  }else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5)){sector = FLASH_SECTOR_5;  }else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6)){sector = FLASH_SECTOR_6;  }else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7)){sector = FLASH_SECTOR_7;  }else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8)){sector = FLASH_SECTOR_8;  }else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9)){sector = FLASH_SECTOR_9;  }else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10)){sector = FLASH_SECTOR_10;  }else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11) */{sector = FLASH_SECTOR_11;}return sector;
}
void jumpToApp()
{if (((*(__IO uint32_t*)FLASH_USER_START_ADDR) & 0x2FFE0000 ) == 0x20000000){printf("ADDR == 0x20000000\r\n");JumpAddress = *(__IO uint32_t*) (FLASH_USER_START_ADDR + 4);Jump_To_Application = (pFunction) JumpAddress;__set_MSP(*(__IO uint32_t*) FLASH_USER_START_ADDR);__HAL_UART_DISABLE(&huart3);				//關閉相應中斷__HAL_RCC_USB_OTG_FS_CLK_DISABLE();__HAL_UART_CLEAR_FLAG(&huart3, UART_FLAG_TC);__HAL_UART_DISABLE_IT(&huart3, UART_IT_RXNE);Jump_To_Application();}printf("ADDR != 0x20000000\r\n");
}
void UP_Data(void)
{if(state == 0){if(t < 2010){if(SystemUpdateFlag == 0 && Appli_state == APPLICATION_READY){printf("APPLICATION_READY\r\n");SystemUpdateFlag = 1;t = 2000;state = 1;res = f_mount(&USBHFatFS, (TCHAR const*)USBHPath, 0);if(res != FR_OK){printf("U盤掛載失敗  %d\r\n", res);Error_Handler();}else{printf("U盤掛載成功\r\n");}res = f_open(&USBHFile, filename, FA_READ);if(res != FR_OK){printf("打開U盤文件失敗  %d\r\n", res);Error_Handler();}else{printf("打開U盤文件成功\r\n");}res = f_read(&USBHFile, RAM_Buffer, sizeof(RAM_Buffer), (void *)&APP_Size);if(res != FR_OK){printf("讀取U盤文件失敗  %d\r\n", res);Error_Handler();}else{printf("讀取U盤文件成功\r\n");}	if((0<APP_Size) && (APP_Size<FLASH_USER_END_ADDR)){printf("FLASH_Erase\r\n");FLASH_Erase_Write();jumpToApp();}else{printf("APP_Size錯誤\r\n");}f_close(&USBHFile);//		FATFS_UnLinkDriver(USBHPath);		}else{printf("%d\r\n", t);		t++;HAL_Delay(1);}if(state == 0 && t > 2000)		//超過2s 讀取U盤失敗{state = 1;printf("DISCONNECT\r\n");jumpToApp();}}}
}	

四、APP:

1 APP程序只進行LED閃爍,只配置這三個功能就夠了

在這里插入圖片描述

2 時鐘樹配置

在這里插入圖片描述

3 工程配置

在這里插入圖片描述
在這里插入圖片描述

4 接下來是APP程序

(1)main.c 只放LED閃爍的功能
在這里插入圖片描述
(2)中斷偏移地址 這個地址要和BootLoader程序里面flash的起始地址要一樣
在這里插入圖片描述
(3)魔術棒配置
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述
注意:下面這是一整行命令語句,不是分三行。

C:\Keil_v5\ARM\ARMCLANG\bin\fromelf.exe --bin -o C:\Users\Administrator\Desktop\USB_BootLoader\APP\MDK-ARM\APP.bin C:\Users\Administrator\Desktop\USB_BootLoader\APP\MDK-ARM\APP\APP.axf

配置好后點擊OK,編譯一下工程,bin文件就生成了
在這里插入圖片描述
變量前面有個逗號 英文的

在這里插入圖片描述
將生成的bin文件放到U盤里
在這里插入圖片描述

五、實驗現象:

BootLoader第一次下載是最好是進行一下全片擦除,之后使用扇區擦除

在這里插入圖片描述
(1)沒有檢測到U盤現象
藍色是電源指示燈,紅框才是實驗燈(紅色的)
在這里插入圖片描述

六、文件篇:

U盤升級STM32F407(文件篇)

Chapter4 01-STM32+Air724UG遠程升級篇OTA(自建物聯網平臺)-STM32如何實現的升級程序

原文鏈接:https://blog.csdn.net/qq_14941407/article/details/115594577

說明
這節提供給用戶一份實現更新STM32的程序(兼容STM32f103全系列)

主要說明STM32是如何實現的升級程序.后面的章節都是在這節的基礎上進行優化.

該節源碼開源: https://gitee.com/yang456/STM32_IAP_Learn.git

請用戶認真學習此節!該代碼只使用了5字節數組接收程序文件!

測試
1.說明
在這里插入圖片描述

BootLoader作為引導程序,負責把接收的程序文件寫入flash,然后加載執行.
STM32F10xTemplate 是用戶程序,這套程序采用串口升級進去.然后執行

Chapter5 02-STM32+Air724UG遠程升級篇OTA(自建物聯網平臺)-什么是http,怎么通過http下載文件數據

原文鏈接:https://blog.csdn.net/qq_14941407/article/details/115594623

說明

什么是http?http的實質是什么?

大家都在說GET指令,POST指令.這又是什么?

其實沒什么!繼續看!

搭建好web服務器(Windows)

1.按照基本控制篇以下兩節搭建好web服務器;

注意:如果只是做遠程升級不需要安裝mqtt軟件,主需要購買云主機,然后安裝上Nginx

當然安裝tomcat也可以
在這里插入圖片描述

2.網站根目錄

在這里插入圖片描述

Chapter6 一個簡單粗暴易用的遠程調試方案——OTA http update

原文鏈接:https://blog.csdn.net/tiandiren111/article/details/107421355

文字簡單描述一下思路,8266定時或主循環輪詢服務器(樹莓派)的一個文件(隨便個文件,我用的txt),文件中的內容是標志,我用的是時間如:200716即昨天程序日期的版本號,今天我如果要更新8266的程序,就將最新的bin文件通過ftp發送到樹莓派上,然后修改程序日期版本號。8266定時去詢問服務器,并比較程序版本號,如果服務器程序的版本號大于當前的就更新,反之就不更新。就這么簡單
在這里插入圖片描述

在這里插入圖片描述

Chapter7 在線升級:OTA升級的原理和實現方式

原文鏈接:https://blog.csdn.net/weixin_43866583/article/details/127706079

在平常的項目開發和調試中,下載程序一般使用的是外部下載器或者串口的方式實現對單片機的程序下載和刷新,這種方法在項目的開發階段是常用的方式。

但是當項目開發完成推向市場的時候,很多時候需要對產品進行升級,而這個時候產品又已經是加了外殼的或者被封裝起來了,一般也不會在外面預留出來下載接口之類的。

如果這個時候我想要更新產品的程序的話,可能就得要重新打開產品的外殼,然后通過下載器更新程序,更新完成之后再把外殼裝上,這種做法顯然是不太現實的。但是我們又必須要給產品進行升級,那該怎么辦呢?這個時候就可以考慮使用產品本身預留的一些外部通信接口(如:USB、RS232、ES485、以太網口等)或者內部無線(如:wifi、藍牙、4/5G網絡)等對產品進行升級。

上面介紹的這種通過外部有線接口或者無線通信的方式進行的更新其實是一種在線更新的方式,即OTA升級技術。

那問題來了,到底什么是OTA升級技術呢?待我慢慢道來!

1、OTA 在線升級

  • OTA:Over-the-Air Technology,字面意思理解為:空中下載技術。

  • OTA 在線升級:通過OTA的方式實現產品軟件更新的一種方式。

所以,簡單而言,通過外部的方式(有線 / 無線)對產品進行更新,而不是用傳統的編程器刷入固件的方式就可以稱之為 OTA 在線升級。

嚴格意義上來講,OTA 指的是空中下載,即只有通過無線的方式進行更新的才稱之為 OTA 升級;而那種通過外部的接口接線來實現的更新,應該稱之為本地升級。這兩者還是有點區別的,只是一般我們都沒有那么嚴格去區分罷了!

2、實現方式

那既然理解了升級的概念了,該怎么去實現呢?

我們一般的做法是會將這個升級功能進行劃分,分為兩部分:

1)接收新的升級固件并完成新舊固件的替換,這部分代碼為 BootLoader;

2)產品功能的正常程序,用于執行各種應用功能,這部分程序稱為 App。

那就是說,要實現在線升級,就需要準備兩份程序,一份是BootLoader ,另一份是App。其中 bootloader 用于將外部傳入的新固件(應用程序App)接收到內部并存儲,接收完成以后,由 bootloader 用新接收到的固件去替換舊的固件,替換完成之后跳轉到新的應用程序中進行執行。這樣就完成了產品的固件更新。

注意:需要將 bootloader 和應用程序App的空間分開,兩者是不能發生重疊的。

3、操作方式

3.1、后臺式升級

后臺式升級的意思是:在進行升級的時候,接收新固件包的方式是在后臺進行的,不會影響功能的正常執行。等到固件更新完成之后,再跳轉到Bootloader中去用新的固件替換舊的固件,替換完成之后呢再跳轉到App去執行。

比如,現在的智能手機的在線更新就是后臺式升級的方式。在你升級系統的時候,接收升級包的過程中,你還是可以正常使用的手機的,打電話、看視頻、玩游戲等都不耽誤,直到下載完成,你點擊了開始更新之后,手機才進入更新狀態,不讓你操作,等更新完畢之后重啟就又可以繼續操作了。

3.2、非后臺式式更新

非后臺式升級的意思是:在進行升級的時候,接收固件時需要跳轉到Bootloader,這個時候你不能在使用這個產品的任何功能,只能一直等著它接收并完成更新,完成之后你才能繼續操作其他的功能。

4、STM32 的在線升級

本文以STM32為例展開講解怎么實現OTA升級和操作的方法。

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

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

相關文章

MySQL 8.0 在 Ubuntu 22.04 中如何將啟用方式改為mysql_native_password(密碼認證)

MySQL 8.0 在 Ubuntu 22.04 中默認啟用了 auth_socket 認證方式(而非密碼認證),導致 mysql_secure_installation 跳過了 root 密碼設置。這會直接影響后續用 Navicat 連接 MySQL(因為 Navicat 需要密碼登錄),必須手動調整 root 用戶的認證方式并設置密碼。 核心問題:au…

七層網絡協議-面試

七層網絡協議概述七層網絡協議&#xff0c;即OSI&#xff08;Open Systems Interconnection&#xff09;模型&#xff0c;是由國際標準化組織&#xff08;ISO&#xff09;提出的網絡通信框架。它將網絡通信過程劃分為七個層次&#xff0c;每一層負責特定的功能&#xff0c;并通…

【Blender】二次元人物制作【二】:五官的制作

一、制作眼睛 選中眼眶內部的一圈線。shiftD復制出來調整成圓形&#xff0c;然后F快捷鍵填充將眼睛放在眼框內合適的位置&#xff0c;并用i鍵進行幾次內插&#xff0c;做出瞳孔&#xff0c;并且將內部的眼瞳做得稍微向內凹陷一點。二、制作睫毛 選中眼眶上半部分的面&#xff0…

Deepin 25 系統安裝 Docker:完整教程 + 常見問題解決

Deepin 25 系統安裝 Docker&#xff1a;完整教程 常見問題解決 作為基于 Debian 的 Linux 發行版&#xff0c;Deepin 25 因系統目錄&#xff08;如/usr&#xff09;默認只讀的特性&#xff0c;安裝 Docker 時需特殊處理 GPG 公鑰存儲路徑。本文結合社區實踐&#xff0c;整理出…

Redis MySQL小結

問題1&#xff1a;Redis為什么高效&#xff1f;答&#xff1a;基于內存&#xff0c;reactor&#xff0c;value的數據組織&#xff08;五種數據結構&#xff09;&#xff0c;KV的數據組織方式&#xff08;漸進hash&#xff09;問題2&#xff1a;跳表是什么&#xff1f;和紅黑樹的…

Flink on YARN 實戰問題排查指南(精華版)

一、客戶端常見問題速查 ?1. JAR加載失敗終極解法?報錯提示&#xff1a;"Could not build the program from JAR file" 核心原因&#xff1a;80%的情況是Hadoop依賴缺失 黃金配置&#xff1a;export HADOOP_CONF_DIR${HADOOP_HOME}/etc/hadoop export HADOOP_CLASS…

迅為RK3576開發板Android12制作使用系統簽名

配套資料在網盤資料“iT0P-RK3576 開發板\02_【iTOP-RK3576 開發板】開發資料\ 08Android 系統開發配套資料\ 07 Android 制作使用系統簽名”目錄下制作簽名文件 在 Android 源碼 build/make/target/product/security/下存放著簽名文件&#xff0c;如下所示&#xff1a;將北京迅…

django連接minio實現文件上傳下載(提供接口示例)

django連接minio實現文件上傳下載&#xff08;提供接口示例&#xff09;項目環境前提1.模型創建2. 在 settings.py 中添加 MINIO 配置3.創建 MINIO 工具類4.創建序列化器5. 創建視圖6. 配置 URL 路由7.接口測試項目環境前提 已安裝python3.8以上環境已安裝djangorestframework…

Kafka消息隊列進階:發送策略與分區算法優化指南

Kafka消息隊列進階&#xff1a;發送策略與分區算法優化指南 目錄Kafka消息隊列進階&#xff1a;發送策略與分區算法優化指南摘要1. Kafka消息發送模式概述1.1 消息發送的核心流程1.2 三種發送模式對比2. 同步發送模式詳解2.1 同步發送實現原理2.2 同步發送性能優化3. 異步發送模…

【VScode】ssh報錯

【VScode】ssh報錯1. ssh報錯2. 解決1. ssh報錯 Failed to parse remote port from server output 2. 解決 windows電腦刪除 C:\Users\username\.ssh\known_hosts linux cd /home/username/.vscode-server/ rm -rf ~/.vscode-server重新回到Vscode連接ok

Grafana+Loki+Alloy構建企業級日志平臺

1.日志系統介紹日志系統&#xff1a;GLA、ELK、數倉 ?志處理流程&#xff1a;采集 > 存儲 > 檢索 > 可視化日志系統工作流程&#xff1a;日志平臺的目的&#xff1a;統一聚合分散的日志日志平臺搭建方案&#xff1a;ELK&#xff1a;ElasticSearch:存儲日志&#xff0…

老梁聊全棧系列:(階段一)現代全棧的「角色邊界」與「能力雷達圖」

JAVA Vue/React 雙棧工程師的「T 型→E 型」進化指南 接上篇《從單體到云原生的演進脈絡》 大家好&#xff0c;我是技術老梁&#xff0c;這是系列文章的第五篇。歡迎大家討論&#xff0c;分享經驗。如果知識對你有用&#xff0c;關注我&#xff0c;多多支持老梁&#xff0c;鼓…

使用 C# 設置 Excel 單元格格式

在實際報表開發中&#xff0c;Excel 的可讀性和美觀性與數據本身同樣重要。合理的單元格格式設置不僅能讓數據一目了然&#xff0c;還能讓報表顯得更專業。通過使用 C#&#xff0c;開發者可以精確控制 Excel 文件的單元格樣式&#xff0c;無需依賴 Microsoft Office。 本文演示…

Redis篇章3:Redis 企業級緩存難題全解--預熱、雪崩、擊穿、穿透一網打盡

在企業級應用場景中&#xff0c;Redis 作為高性能緩存利器&#xff0c;極大提升了系統響應速度&#xff0c;但隨著業務復雜度和并發量的攀升&#xff0c;緩存相關的各類挑戰也接踵而至。比如系統啟動時緩存缺失導致的數據庫壓力、大量緩存同時失效引發的連鎖故障、熱點數據過期…

【數值分析】02-緒論-誤差

參考資料&#xff1a; 書籍&#xff1a; 數值分析簡明教程/王兵團&#xff0c;張作泉&#xff0c;張平福編著. --北京&#xff1a;清華大學出版社&#xff1b;北京交通大學出版社&#xff0c;2012.8 視頻&#xff1a;學堂在線APP中北京交通大學“數值分析I” 前期回顧 【數值分…

P3918 [國家集訓隊] 特技飛行

P3918 [國家集訓隊] 特技飛行 - 洛谷 思路&#xff1a; 因為如果連續進行相同的動作&#xff0c;乘客會感到厭倦&#xff0c;所以定義某次動作的價值為(距上次該動作的時間) ci?&#xff0c;若為第一次進行該動作&#xff0c;價值為 0。同一個動作&#xff0c;價值為ci*(最后一…

Python爬蟲實戰:研究Pandas,構建期貨數據采集和分析系統

1. 引言 1.1 研究背景 期貨市場作為金融市場的重要組成部分,具有價格發現、風險管理和資源配置的重要功能。上海期貨交易所(Shanghai Futures Exchange, SHFE)作為中國四大期貨交易所之一,上市交易的品種包括銅、鋁、鋅、黃金、白銀等多種大宗商品期貨,其交易數據反映了…

Linux第十七講:應用層自定義協議與序列化

Linux第十七講&#xff1a;應用層自定義協議與序列化1.什么是序列化和反序列化2.重新理解read、write為什么支持全雙工3.網絡版計算器的實現3.1socket的封裝 -- 模板方法模式引入3.2序列化和反序列化 && json3.3協議的實現3.4 服務端整體看 -- 所有代碼3.5七層協議&…

附錄:Tomcat下載及啟動

一、打開Tomcat官網windows下載第四個壓縮包&#xff0c;下載完成后解壓縮。&#xff08;安裝路徑不要帶有中文和特殊符號&#xff09;二、啟動Tomcat進入bin文件夾&#xff1a;\Tomcat\apache-tomcat-11.0.11\bin&#xff0c;找到startup.bat文件點擊&#xff0c;黑窗口常駐即…

【CTF-WEB】表單提交(特殊參數:?url=%80和?url=@)(通過GBK編碼繞過實現文件包含讀取flag)

題目 尋找這個單純的網站的flag 前端代碼&#xff1a; <!DOCTYPE html> <head><title>CAT</title> </head><body> <h1>Cloud Automated Testing</h1> <p>輸入你的域名&#xff0c;例如&#xff1a;loli.club</p>…