STM32 实现 OTA 升级全攻略:从原理到代码实现OTA
本文详细介绍了在STM32F103C8T6上实现OTA升级的全过程。主要内容包括:1)OTA基本原理,通过通信接收新固件并写入Flash;2)Flash分区规划,将64KB空间分为8KB Bootloader和56KB应用程序区;3)Bootloader实现,包含升级标志检测、固件接收、Flash擦写和程序跳转功能;4)应用程序设计,支持触发OTA升级;5)配套的Python工具,用于计算固件大小
(Over-The-Air)升级是嵌入式设备的重要功能,尤其对于无法物理接触的设备(如智能家居、工业传感器等),OTA 能极大降低维护成本。本文将详细介绍如何在 STM32 上实现 OTA 升级功能,从原理到代码实现,适合有一定 STM32 基础的开发者参考。
一、OTA 升级基本原理
STM32 的 OTA 升级本质是通过通信方式(如串口、WiFi、蓝牙等)接收新固件,将其写入 Flash,再引导系统从新固件地址启动。核心需要解决三个问题:
- 固件存储:如何划分 Flash 区域,避免升级过程中覆盖原有程序
- 程序引导:如何实现从 Bootloader 跳转到 App 程序
- 固件传输:如何安全可靠地接收新固件(校验、断点续传等
二、硬件与环境准备
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_ReceiveData、USART_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文件。
更多推荐



所有评论(0)