正点原子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升级和操作的方法。