OTA升级指的是。。。现在网络上很多教程都把原理讲的很明白了但是我自己学的时候没有找到很详细的步骤从0开始一步一步完成双区OTA升级的实验过程因此我把我的学习过程记录下面供和我一样的小白参考。原理我这里就不细说了相信来了解这部分内容的大家应该有一定的理解。这里我写我的实现过程。bootloader程序我参考的下面这篇博客STM32网络在线升级(OTA)完整实现指南_mob64ca1403528a的技术博客_51CTO博客他是基于hal库来编写的我是使用的标准库因此缺少一些结构体和函数的定义但是现在ai可以很方便的帮你补全这部分的定义。#include string.h #include usart1.h #include stm32f10x.h #include led.h #include delay.h // Flash分区定义适用于 STM32F103C8T664KB Flash #define BOOTLOADER_BASE 0x08000000 #define APPLICATION_BASE 0x08003000 // APP A 起始 #define OTA_BUFFER_BASE 0x0800C000 // APP B / 缓冲区 #define CONFIG_BASE 0x0800F800 // 参数区页对齐 #define BOOTLOADER_SIZE (12 * 1024) // 12KB给 Bootloader 留足余量 #define APPLICATION_SIZE (36 * 1024) // 从 0x08003000 到 0x0800C000 36KB #define OTA_BUFFER_SIZE (36 * 1024) // 从 0x0800C000 到 0x0800F800 36KB #define FLASH_PAGE_SIZE 1024 // F103C8T6 的页大小 1KB // 升级标志结构 typedef struct { uint32_t magic; // 魔术字 0xDEADBEEF uint32_t update_flag; // 升级标志 1:需要升级 uint32_t firmware_size; // 固件大小 uint32_t crc32; // CRC32校验值 } ota_config_t; typedef enum { HAL_OK 0x00, HAL_ERROR 0x01, HAL_BUSY 0x02, HAL_TIMEOUT 0x03 } StatusTypeDef; // 函数声明 static void jump_to_application(uint32_t address); static int check_application_valid(uint32_t address); StatusTypeDef flash_erase(uint32_t start_addr, uint32_t size); StatusTypeDef flash_write(uint32_t dest_addr, uint8_t *src, uint32_t size); uint32_t calculate_crc32(uint8_t *data, uint32_t size); int perform_firmware_update(void); // CRC32计算 static uint32_t calculate_crc32(uint8_t *data, uint32_t size) { uint32_t i; uint32_t *p (uint32_t *)data; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE); // 使能 CRC 时钟 CRC_ResetDR(); // 复位 CRC 数据寄存器到 0xFFFFFFFF for (i 0; i (size / 4); i) { CRC-DR p[i]; } return CRC-DR; } // 检查应用程序有效性 static int check_application_valid(uint32_t address) { uint32_t sp *(__IO uint32_t*)address; // 检查栈指针是否在RAM范围内 if ((sp 0x2FFE0000) 0x20000000) { return 1; } return 0; } void HAL_DeInit(void) { /* 复位 APB1 总线上的外设 (USART2/3/4/5, TIM2-7, SPI2, I2C1/2 等) */ RCC_APB1PeriphResetCmd(RCC_APB1Periph_ALL, ENABLE); RCC_APB1PeriphResetCmd(RCC_APB1Periph_ALL, DISABLE); /* 复位 APB2 总线上的外设 (USART1, SPI1, TIM1, GPIOA-E 等) */ RCC_APB2PeriphResetCmd(RCC_APB2Periph_ALL, ENABLE); RCC_APB2PeriphResetCmd(RCC_APB2Periph_ALL, DISABLE); } // 跳转到应用程序 static void jump_to_application(uint32_t address) { typedef void (*pFunction)(void); pFunction jump_to_app; uint32_t jump_address; // 获取复位向量 jump_address *(__IO uint32_t*)(address 4); jump_to_app (pFunction)jump_address; // 设置主栈指针 __set_MSP(*(__IO uint32_t*)address); // 关闭所有中断 __disable_irq(); // 复位所有外设 HAL_DeInit(); // 设置向量表 SCB-VTOR address; // 跳转到应用程序 jump_to_app(); } // Flash擦除 static StatusTypeDef flash_erase(uint32_t start_addr, uint32_t size) { uint32_t page_addr start_addr; uint32_t end_addr start_addr size; FLASH_Status status; FLASH_Unlock(); // 解锁 Flash 控制寄存器[citation:7] // 按页循环擦除 while (page_addr end_addr) { status FLASH_ErasePage(page_addr); if (status ! FLASH_COMPLETE) { FLASH_Lock(); return HAL_ERROR; } page_addr FLASH_PAGE_SIZE; } FLASH_Lock(); return HAL_OK; } // Flash写入 static StatusTypeDef flash_write(uint32_t dest_addr, uint8_t *src, uint32_t size) { uint32_t i; uint32_t word; FLASH_Status status; FLASH_Unlock(); for (i 0; i size; i 4) { // 拼装一个 32 位字 word (uint32_t)src[i] | ((uint32_t)src[i 1] 8) | ((uint32_t)src[i 2] 16) | ((uint32_t)src[i 3] 24); status FLASH_ProgramWord(dest_addr i, word); if (status ! FLASH_COMPLETE) { FLASH_Lock(); return HAL_ERROR; } } FLASH_Lock(); return HAL_OK; } // 执行固件更新 static int perform_firmware_update(void) { ota_config_t *config (ota_config_t*)CONFIG_BASE; uint8_t *ota_data (uint8_t*)OTA_BUFFER_BASE; uint32_t calculated_crc; // 检查魔术字 if (config-magic ! 0xDEADBEEF) { return -1; } // 检查更新标志 if (config-update_flag ! 1) { return -1; } // 验证CRC calculated_crc calculate_crc32(ota_data, config-firmware_size); if (calculated_crc ! config-crc32) { return -2; // CRC错误 } // 擦除应用程序区域 if (flash_erase(APPLICATION_BASE, config-firmware_size) ! HAL_OK) { return -3; } // 写入新固件 if (flash_write(APPLICATION_BASE, ota_data, config-firmware_size) ! HAL_OK) { return -4; } // 清除更新标志 ota_config_t clear_config {0}; flash_erase(CONFIG_BASE, sizeof(ota_config_t)); flash_write(CONFIG_BASE, (uint8_t*)clear_config, sizeof(ota_config_t)); return 0; } // Bootloader主函数 int main(void) { SystemInit(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); USART1_Config(); delay_init(); // 初始化延时 LED_GPIO_Config(); //led、蜂鸣器初始化 // ★ 初始化串口内部会开中断 //uart_init(115200); // ★ 配置DMA内部会开中断 //DMA_Config(DMA1_Channel5,(u32)USART1-DR,(u32)CercleBuffer, CercleBufferSize);//DMA1通道5,外设为串口1,存储器为SendBuff,长度SEND_BUF_SIZE. // 检查是否需要升级 ota_config_t *config (ota_config_t*)CONFIG_BASE; if (config-magic 0xDEADBEEF config-update_flag 1) { // LED快闪表示正在升级 for (int i 0; i 10; i) { //HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); //HAL_Delay(100); LED1(i); delay_ms(100); } // 执行升级 if (perform_firmware_update() 0) { // 升级成功LED常亮1秒 //HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); //HAL_Delay(1000); LED1(1); delay_ms(100); } else { // 升级失败LED慢闪 for (int i 0; i 5; i) { //HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); //HAL_Delay(500); LED1(i); delay_ms(100); } } } // 检查应用程序是否有效 if (check_application_valid(APPLICATION_BASE)) { // 跳转到应用程序 jump_to_application(APPLICATION_BASE); } // 如果无法跳转LED持续慢闪 while (1) { //HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); //HAL_Delay(1000); GPIO_SetBits(GPIOC, GPIO_Pin_13 ); delay_ms(100); USART_SendData(USART1,a); GPIO_ResetBits(GPIOC, GPIO_Pin_13 ); delay_ms(100); USART_SendData(USART1,b); } }由于他这篇博客目标是实现单区的OTA升级因此在他的bootloader中没有写固件的接收部分他把接收部分放到了A区中进行。我这里主要是跟他复现了一边实际来说烧录bootloader和正常烧录程序没有什么区别唯一需要注意的是这部分函数需要写的尽可能精简因为所有的程序共享一块flash这里占的空间多主要的运行程序剩下的空间就少。在烧录时需要注意如下图步骤6所示的位置它代表的是允许烧录算法访问的范围。当你的bootloader程序体积超出这个范围是就会出现烧录失败的情况这是为了避免出现越区导致烧坏的情况。所以如果对自己的程序有把握这里也可以之间填0x08000000和 0x10000避免反复修改在keil中编译后的程序体积可以在build output中查看上面的bootloader程序烧录成功现象为板上PC13小灯不断快速闪烁可以使用串口助手看到交替打印ab在完成bootloader程序有个基本概念后继续写APP1分区的程序这里作为基本实验只看有没有成功运行因此以简单的程序为代表。需要注意的是SCB-VTOR 0x08003000;这一句是必要的它的作用是修改向量表,不改写中断向量表APP 里的中断会全部跑到 Bootloader 的向量表里去结果是主循环能跑一进中断就死。这里的地址的APP1的起始地址。#include string.h #include usart1.h #include stm32f10x.h #include led.h #include delay.h int main(void) { SystemInit(); SCB-VTOR 0x08003000; // 和 APPLICATION_BASE 一致放在 SystemInit 之后 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); USART1_Config(); delay_init(); LED_GPIO_Config(); char *str1 jump success!\r\n; int len strlen(str1); while (1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); delay_ms(500); for (int i 0; i len; i) { USART_SendData(USART1, str1[i]); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); } GPIO_ResetBits(GPIOC, GPIO_Pin_13); delay_ms(500); } }由于板中已经存在了bootloader所以很好理解的APP1不能从原理的初始地址开始写否则会把bootloader程序覆盖因此在烧录前需要修改代码的起始位置同时和烧录bootloader一样也要将擦除方式修改为Erase Sectors。完成这两个步骤就可以烧写了上面的APP1程序烧录成功现象为板上PC13小灯不断缓慢闪烁可以使用串口助手看到打印jump success!代表程序成功从bootloader跳到了APP1