正點原子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格式的文件
- 【單片機開發】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功能時,需要注意以下幾點:
- 數據校驗:在數據傳輸過程中,要進行嚴格的數據校驗,確保數據的完整性和正確性。
- 分區大小:合理設置Bootloader區和App區的大小,確保App區有足夠的空間存放用戶程序。
- 兼容性:確保Bootloader和App之間的接口兼容,避免因接口不匹配導致的問題。
- 穩定性:在升級過程中,要確保系統的穩定性,避免因意外斷電等因素導致升級失敗。
八、實驗過程
從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升級和操作的方法。