(Over-The-Air)升级是嵌入式设备的重要功能,尤其对于无法物理接触的设备(如智能家居、工业传感器等),OTA 能极大降低维护成本。本文将详细介绍如何在 STM32 上实现 OTA 升级功能,从原理到代码实现,适合有一定 STM32 基础的开发者参考。

一、OTA 升级基本原理

STM32 的 OTA 升级本质是通过通信方式(如串口、WiFi、蓝牙等)接收新固件,将其写入 Flash,再引导系统从新固件地址启动。核心需要解决三个问题:

  1. 固件存储:如何划分 Flash 区域,避免升级过程中覆盖原有程序
  2. 程序引导:如何实现从 Bootloader 跳转到 App 程序
  3. 固件传输:如何安全可靠地接收新固件(校验、断点续传等

二、硬件与环境准备

1. 硬件选择

  • 主控:STM32F103C8T6(本文以该型号为例,其他型号原理相通)
  • 通信模块:蓝牙模块(JDY-31-SSP)
  • 下载器:ST-Link V2

2. 软件环境

  • IDE:Keil MDK 
  • 通信调试:正点原子ATK-XCOM调试助手

三、分区规划

STM32F103C8T6 内置 64KB Flash,此处我将其分为两部分分区,bootloader分区以及应用程序分区,其中bootloader地址区间为0x08000000~0x08002000,大小为8K,剩余56K作为APP分区。

四、bootloader程序实现

Bootloader 是 OTA 的核心,负责判断启动模式(正常启动 / 升级),并执行跳转。

1. 启动流程设计

  • 上电后先运行 Bootloader
  • 检测 "升级标志"(存储在 Flash 特定地址或 SRAM),我存储在了Flash中的0x8003000处
    • 无标志:跳转到 App 程序
    • 有标志:进入 OTA 模式,接收新固件并写入 App 区域(我加入了错误重试机制,当重试次数耗尽后还未接收到有效数据,则给出提示,只需要复位即可重新进入bootloader程序)

2. Bootloader 代码实现

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
uint8_t size_buf[4] = {0};
#define MAX_BIN_SIZE  1024*8    // Ram大小为20kb,此处需要接收的固件实际大小不足4KB,故采用8KB用作接收缓存数组
uint8_t received_bin[MAX_BIN_SIZE];  // 存储接收的.bin数据
#define AppStartAddress 0x08002000  // 应用程序起始地址
#define UPDATE_FLAG_ADDR 0x8003000  // 此处选用0x8003000作为固件升级标志位也是由实际.bin文件写入后的地址确定,只要该值不在应用程序写入区
//且在flash有效地址处即可
#define UPDATE_FLAG      0xAA55AAAA  // 升级标志值
// 串口接收数据函数
void USART_ReceiveData(uint8_t *pData, uint16_t Size)
{
  HAL_UART_Receive(&huart1, pData, Size, HAL_MAX_DELAY);
}

// 串口发送数据函数
void USART_SendData(uint8_t *pData, uint16_t Size)
{
  HAL_UART_Transmit(&huart1, pData, Size, HAL_MAX_DELAY);
}
// Flash擦除函数
void Flash_Erase(void)
{
   FLASH_EraseInitTypeDef erase_init;
	uint32_t page_error = 0xFFFFFFFF;
	erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
	erase_init.Banks = FLASH_BANK_1;  // 
	erase_init.PageAddress =AppStartAddress;
	erase_init.NbPages = 1;
	HAL_FLASH_Unlock();
  __disable_irq(); // 禁用中断
  if (HAL_FLASHEx_Erase(&erase_init, &page_error) == HAL_OK && page_error == 0xFFFFFFFF)
  {
    printf("ERASE Success\r\n");
  }
  __enable_irq();  // 恢复中断
  HAL_FLASH_Lock();
}

// Flash编程函数
void Flash_Program(uint32_t Address, uint32_t Data)
{
    // 检查地址是否合法
    if (Address < 0x08000000 || Address >= 0x08010000) {
        printf("地址超出范围: 0x%X\r\n", Address);
        return;
    }
    if (Address % 4 != 0) {
        printf("地址未对齐: 0x%X\r\n", Address);
        return;
    }

    // 解锁 Flash
    HAL_FLASH_Unlock();

    // 清除所有 Flash 状态标志
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_BSY | FLASH_FLAG_PGERR | FLASH_SR_WRPRTERR | FLASH_FLAG_EOP);

    // 检查 Flash 是否忙
    if (__HAL_FLASH_GET_FLAG(FLASH_FLAG_BSY) != RESET) {
        printf("Flash 正忙,无法编程。\r\n");
        HAL_FLASH_Lock();
        return;
    }

    // 执行编程操作
    __disable_irq();
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, Address, Data) == HAL_OK) {
        // 等待编程完成
        if (FLASH_WaitForLastOperation(500) == HAL_OK) {
            printf("编程成功: 0x%X\r\n", Address);
        } else {
            printf("编程失败: 0x%X\r\n", Address);
        }
    } else {
        printf("编程失败: 0x%X\r\n", Address);
    }
    __enable_irq();

    // 锁定 Flash
    HAL_FLASH_Lock();
}


typedef  void (*pFunction)(void);
/***********************跳转函数***************************/
void Jump_To_Application(uint32_t ApplicationAddress)
{
  // 验证栈顶地址有效性(F103 SRAM范围:0x20000000~0x20005000)
  if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000) == 0x20000000)
  { 
    pFunction JumpToApplication;
    // 正确:读取 ApplicationAddress + 4 地址处的32位数据(即入口地址)
    uint32_t app_entry = *(__IO uint32_t*)(ApplicationAddress + 4); 

    // 打印入口地址(需在关闭外设前执行)
    printf("应用入口地址: 0x%X\r\n", app_entry);

    // 1. 关闭所有中断和系统异常
    __disable_irq();
    SCB->SHCSR &= ~(SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk | SCB_SHCSR_MEMFAULTENA_Msk);

    // 2. 重定向向量表到应用程序地址
    SCB->VTOR = ApplicationAddress;

    // 3. 关闭Bootloader使用的外设
    HAL_UART_DeInit(&huart1);
    __HAL_RCC_USART1_CLK_DISABLE();
    __HAL_RCC_GPIOA_CLK_DISABLE();

    // 4. 重置SysTick定时器
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL = 0;

    // 5. 设置栈顶并跳转
    __set_MSP(*(__IO uint32_t*)ApplicationAddress);
    JumpToApplication = (pFunction)app_entry;  // 直接将入口地址转换为函数指针
		
    JumpToApplication(); // 执行应用程序
  }
}
/**********************************************/
uint8_t Check_App_Valid(uint32_t addr)
{
  // 检查栈顶地址是否在SRAM范围内(F103的SRAM:0x20000000~0x20005000)
  if (((*(__IO uint32_t*)addr) & 0x2FFE0000) == 0x20000000)
  {
    return 1; // 有效
  }
  return 0; // 无效
}
void Flash_Erase_Multiple(uint32_t start_addr, uint32_t size)
{
  // STM32F103C8T6的Flash页大小为1KB(0x400字节),根据芯片手册确认
  //#define FLASH_PAGE_SIZE  0x400  // 1024字节/页

  FLASH_EraseInitTypeDef erase_init;
  uint32_t page_error = 0xFFFFFFFF;

  // 计算起始页和结束页(STM32的Flash起始地址为0x08000000)
  uint32_t start_page = (start_addr - FLASH_BASE) / FLASH_PAGE_SIZE;
  uint32_t end_addr = start_addr + size - 1;  // 数据结束地址
  uint32_t end_page = (end_addr - FLASH_BASE) / FLASH_PAGE_SIZE;

  // 计算需要擦除的页数(向上取整)
  uint32_t page_count = end_page - start_page + 1;

  // 配置擦除参数
  erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
  erase_init.Banks = FLASH_BANK_1;  // F103只有一个Bank
  erase_init.PageAddress = start_addr;  // 从应用程序起始地址开始擦除
  erase_init.NbPages = page_count;      // 需要擦除的页数

  // 执行擦除
  HAL_FLASH_Unlock();                // 解锁Flash
  __disable_irq();                   // 禁用中断,防止擦除过程被打断

  if (HAL_FLASHEx_Erase(&erase_init, &page_error) == HAL_OK && page_error == 0xFFFFFFFF)
  {
    printf("多扇区擦除成功,共擦除 %d 页\r\n", page_count);
  }
  else
  {
    printf("多扇区擦除失败!错误页地址:0x%X\r\n", page_error);
    while(1);  // 擦除失败则死循环
  }

  __enable_irq();                    // 恢复中断
  HAL_FLASH_Lock();                  // 锁定Flash
}
void Flash_ErasePage(uint32_t Page_Address)
{
    FLASH_EraseInitTypeDef EraseInitStruct;
    uint32_t PageError;

    // 解锁 Flash
    HAL_FLASH_Unlock();

    // 配置擦除结构体
    EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
    EraseInitStruct.Banks = FLASH_BANK_1;
    EraseInitStruct.PageAddress = Page_Address;
    EraseInitStruct.NbPages = 1;

    // 执行擦除操作
    if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)
    {
        // 擦除失败处理
        printf("Flash 擦除失败,错误页地址:0x%X\r\n", PageError);
    }
    else
    {
        printf("Flash 擦除成功\r\n");
    }

    // 锁定 Flash
    HAL_FLASH_Lock();
}
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	 
//		uint32_t bin_size = 0;              // .bin文件实际大小
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	printf("bootloader start\r\n");
  printf("跳转前 VTOR 地址: 0x%X\r\n", SCB->VTOR);  // 应打印 0x08002000

//  // 步骤1:检查是否已有有效应用,有则直接跳转
	uint32_t update_flag = *(__IO uint32_t*)UPDATE_FLAG_ADDR;
	printf("升级标志处数据为0x%X\r\n",update_flag);
	if (update_flag == UPDATE_FLAG) {
    printf("检测到升级标志,进入更新模式...\r\n");
    // 清除标志位(避免下次启动重复进入)
    // 计算包含 UPDATE_FLAG_ADDR 的 Flash 页地址
        uint32_t page_address = (UPDATE_FLAG_ADDR / FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE;

        // 擦除包含 UPDATE_FLAG_ADDR 的 Flash 页
        Flash_ErasePage(page_address);

        // 验证擦除结果
        update_flag = *(__IO uint32_t*)UPDATE_FLAG_ADDR;
        printf("清除标志位后升级标志处数据为0x%X\r\n", update_flag);
	}else {
    // 无升级标志,检查应用是否有效,有效则直接跳转
		printf("未检测到升级标志\r\n");
    if (Check_App_Valid(AppStartAddress)) {
        printf("检测到有效应用,跳转中...\r\n");
        Jump_To_Application(AppStartAddress);
        while(1) { printf("跳转失败,死循环\r\n"); }
    } else {
        printf("未检测到有效应用,等待接收.bin文件...\r\n");
    }
}

//  // 步骤2:接收.bin文件大小(4字节,小端模式)
  printf("等待接收文件大小(4字节)...\r\n");
  // 接收4字节的文件大小(小端模式)
	uint8_t retry = 3;
		while (retry--) {
    if (HAL_UART_Receive(&huart1, size_buf, 4, 10000) == HAL_OK) {
        break; // 接收成功,退出重试
    }
    printf("接收文件大小失败,剩余重试次数:%d\r\n", retry);
	}
		if (retry == 0 && HAL_UART_Receive(&huart1, size_buf, 4, 10000) != HAL_OK) {
    printf("接收文件大小超时!\r\n");
    // 错误处理:闪烁LED提示,而非死循环
    while(1) {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        HAL_Delay(200);
    }
}

// 解析为 uint32_t(小端模式拼接)
	uint32_t bin_size = (size_buf[0] | (size_buf[1] << 8) | (size_buf[2] << 16) | (size_buf[3] << 24));
	printf("解析的文件大小:%d 字节\r\n", bin_size);
	
	if (bin_size == 0 || bin_size > MAX_BIN_SIZE) {
    printf("文件大小无效(0或超过最大限制)!\r\n");
    while(1) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(200); }
	}

// 步骤3:接收完整的.bin文件数据
  printf("开始接收.bin文件数据(共%d字节)...\r\n", bin_size);
  if (HAL_UART_Receive(&huart1, received_bin, bin_size, 30000) != HAL_OK)  // 超时30秒(根据大小调整)
  {
    printf("接收.bin数据超时!\r\n");
    while(1){printf("接收.bin数据时间太短!\r\n");};
  }
  printf(".bin文件接收完成!\r\n");
  // 步骤4:擦除应用程序区域的Flash(根据.bin大小计算需要擦除的扇区)
  printf("开始擦除Flash...\r\n");
  Flash_Erase_Multiple(AppStartAddress, bin_size);  // 多扇区擦除函数(需实现)
  // 步骤5:将接收的.bin数据写入Flash(按4字节对齐)
  printf("开始写入Flash...\r\n");
  for (uint32_t i = 0; i < bin_size; i += 4)
  {
    uint32_t write_data = 0;
    // 处理最后一次可能不足4字节的情况,不足部分补0
    uint8_t copy_len = (i + 4 > bin_size) ? (bin_size - i) : 4;
    memcpy(&write_data, received_bin + i, copy_len);
    Flash_Program(AppStartAddress + i, write_data);
  }
  printf("Flash写入完成!\r\n");

  // 步骤6:跳转到应用程序
  printf("准备跳转至应用程序...\r\n");  
	printf("应用程序 VTOR 地址: 0x%X\r\n", SCB->VTOR);  // 必须打印 0x08002000,否则重定向失败
  Jump_To_Application(AppStartAddress);

  // 若执行到这里,说明跳转失败
  printf("应用程序跳转失败!\r\n");
//	while(1){};
//    

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** 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.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

3.函数具体功能描述:

1. 升级标志检测与处理

  • 读取指定 Flash 地址(UPDATE_FLAG_ADDR)的升级标志值。
  • 若检测到升级标志(UPDATE_FLAG),则擦除该标志所在的 Flash 页,进入固件更新模式;若未检测到,检查应用程序是否有效,有效则跳转到应用程序,否则等待接收新的固件文件。

2. 固件接收

  • 通过串口接收表示固件大小的 4 字节数据,解析出固件实际大小。
  • 接收到字符"U"进入固件接收模式
  • 接收完整的固件(.bin 格式)数据到指定缓存数组(received_bin)中。

3. Flash 操作

  • 擦除:提供了擦除单个 Flash 页(Flash_ErasePage)和多扇区 Flash(Flash_Erase_Multiple)的函数,用于在写入新固件前擦除应用程序区域的 Flash。
  • 编程Flash_Program函数用于将接收到的固件数据按 4 字节对齐写入 Flash 的指定地址。

4. 程序跳转

  • Jump_To_Application函数实现从 Bootloader 跳转到应用程序的功能,包括关闭 Bootloader 使用的外设、重定向向量表、设置栈顶指针等操作,确保应用程序能正常启动运行。

5. 辅助功能

  • 包含串口收发函数(USART_ReceiveDataUSART_SendData),用于与上位机进行数据交互,接收固件。
  • 有检查应用程序是否有效的函数(Check_App_Valid),判断应用程序起始地址的栈顶地址是否在合法的 SRAM 范围内。

五、应用程序代码

#include "stm32f10x.h"                  // Device header 
#include "led.h"
#include "motor.h"
#include "SysTick.h"
#include "usart.h"
#include "system.h"
#define UPDATE_FLAG_ADDR 0x8003000  // 更新标志位
#define UPDATE_FLAG      0xAA55AAAA
uint8_t TX_BUF[] = {0x41,0x42,0x43,0x44,0x45};
uint16_t TX_LEN = sizeof(TX_BUF) / sizeof(TX_BUF[0]); // 计算数组长度
uint8_t irq_enter_flag = 0;  // 全局变量
// 发送一组数据的函数
void USART_SendArray(USART_TypeDef* USARTx, uint8_t* buf, uint16_t len) {
    for (uint16_t i = 0; i < len; i++) {
			while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
			USART_SendData(USARTx, buf[i]);
    }
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
uint8_t Flash_Program(uint32_t addr, uint32_t data)
{
    // 1. 检查地址合法性
    if (addr < 0x08000000 || addr >= 0x08010000) {
        return 1; // 地址超出范围
    }
    if (addr % 4 != 0) {
        return 1; // 地址未对齐
    }

    // 2. 解锁 Flash
    FLASH_Unlock();

    // 3. 清除标志位
    FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);

    // 4. 检查 Flash 是否忙
    while (FLASH_GetFlagStatus(FLASH_FLAG_BSY) != RESET);

    // 5. 写入32位数据(字编程)
    FLASH_Status status = FLASH_ProgramWord(addr, data);

    // 6. 等待 Flash 操作完成
    while (FLASH_GetFlagStatus(FLASH_FLAG_BSY) != RESET);

    // 7. 检查操作是否完成
    if (FLASH_GetFlagStatus(FLASH_FLAG_EOP) != RESET) {
        FLASH_ClearFlag(FLASH_FLAG_EOP);
    }

    // 8. 检查是否有错误
    if (FLASH_GetFlagStatus(FLASH_FLAG_PGERR) != RESET) {
        FLASH_ClearFlag(FLASH_FLAG_PGERR);
        return 1; // 编程错误
    }
    if (FLASH_GetFlagStatus(FLASH_FLAG_WRPRTERR) != RESET) {
        FLASH_ClearFlag(FLASH_FLAG_WRPRTERR);
        return 1; // 写保护错误
    }

    // 9. 锁定 Flash
    FLASH_Lock();

    // 10. 返回结果
    return (status == FLASH_COMPLETE) ? 0 : 1;
}

// 应用程序中触发更新的函数
void TriggerBootloaderUpdate(void)
{
    // 写入升级标志
    if (Flash_Program(UPDATE_FLAG_ADDR, UPDATE_FLAG) == 0) {
        printf("升级标志写入成功,系统即将重启...\r\n");
        // 软件复位,进入Bootloader
        NVIC_SystemReset();
    } else {
        printf("升级标志写入失败!\r\n");
    }
}

void NVIC_SETVectorTable(void)
{
//	SCB->VTOR = FLASH_BASE | 0x3000;//中断向量表的地址偏移,寄存器写法
//	void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);   //库函数写法  misc.h 198行
	NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x2000);
}

int main()
{	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //中断优先级分组分2组
	NVIC_SETVectorTable();
	SysTick_Init(72);
	USART1_Init(9600);
	LED_init();
	printf("当前VTOR值: 0x%X\r\n", SCB->VTOR);
	printf("串口1中断服务程序地址:0x%X\r\n",USART1_IRQHandler);
	printf("应用任务开始启动\r\n");
	printf("固件版本2\r\n");
	
	while(1)
	{
        if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) {
            uint8_t rx_data = USART_ReceiveData(USART1);
            printf("接收到数据: 0x%X\r\n", rx_data);

            // 如果接收到 'U',触发更新
            if (rx_data == 'U') {
								
                TriggerBootloaderUpdate();
            }
        }
		GPIO_SetBits(LED0_PORT,LED0_PIN);
		delay_ms(500);
		GPIO_ResetBits(LED0_PORT,LED0_PIN);
		delay_ms(500);

	}
}

六、固件大小计算python程序

import os

file_path = "C:\\Users\\21444\\Desktop\\stm32project\\2-点亮第一个LED灯固件版本2\\Objects\\Template.bin"
size = os.path.getsize(file_path)

# 转换为小端模式的4字节
size_bytes = size.to_bytes(4, byteorder='little')

# 转换为16进制字符串格式
hex_string = ''.join([f'{byte:02X}' for byte in size_bytes])

print(f"文件大小:{size} 字节")
print(f"4字节16进制格式:{hex_string}")

七、补充说明:

本人的bootloader程序是用HAL库写的,应用程序是使用标准库写的。

下载地址设置:(对于bootloader程序)

应用程序

八、视频演示效果

OTA效果演示

视频演示效果中刚开始发送的数据即为Python处理后的.bin文件大小。随后通过串口调试助手发送.bin文件。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐