正点原子STM32F407 U盘升级程序(IAP)OTA Bootloader APP USB升级+FATFS+USB Host

发布于:2025-09-15 ⋅ 阅读:(19) ⋅ 点赞:(0)

正点原子STM32F407 U盘升级程序(IAP)OTA Bootloader APP USB升级+FATFS+USB Host


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

  1. 【单片机开发】KEIL如何生成*.bin格式的文件

Chapter1 正点原子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();                                                // 解锁Flash
  FirstSector = 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,将数据写入 Flash
  printf("正在写入数据 请稍后... ...\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("ADDR == 0x20000000\r\n");
      printf("跳转到应用程序\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处
        
      __HAL_UART_DISABLE(&huart1);                                    // 禁用串口
      __HAL_RCC_USB_OTG_FS_CLK_DISABLE();                             // 禁用USB时钟
      __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);                   // 清除串口标志
      __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);                   // 禁用串口中断
      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_Buffer
            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("APP_Size大小为 : %d \r\n",APP_Size);
                printf("FLASH开始擦除\r\n");    //FLASH擦除
                FLASH_Erase_Write();          // 调用Flash擦除和写入函数
                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


Chapter2 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  +  0x10000

uint32_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(文件篇)

Chapter3 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 是用户程序,这套程序采用串口升级进去.然后执行

Chapter4 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.网站根目录

在这里插入图片描述

Chapter3 一个简单粗暴易用的远程调试方案——OTA http update

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

文字简单描述一下思路,8266定时或主循环轮询服务器(树莓派)的一个文件(随便个文件,我用的txt),文件中的内容是标志,我用的是时间如:200716即昨天程序日期的版本号,今天我如果要更新8266的程序,就将最新的bin文件通过ftp发送到树莓派上,然后修改程序日期版本号。8266定时去询问服务器,并比较程序版本号,如果服务器程序的版本号大于当前的就更新,反之就不更新。就这么简单
在这里插入图片描述

在这里插入图片描述

Chapter5 在线升级: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升级和操作的方法。