news 2026/4/23 14:39:32

STM32中OTA介绍及使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32中OTA介绍及使用

1.OTA的介绍

OTA(Over-The-Air)是⼀项通过⽆线⽹络远程更新设备软件、固件或配置的技术,它让设备升级像⼿机系统 更新⼀样⽅便,⽆需连接电脑或前往服务中⼼。对于⼚商⽽⾔,它可以快速修复系统缺陷,避免⼤规模线下召回,显著降低售后成本; 对于⽤户⽽⾔,设备可以持续迭代更新,获得新功能和安全补丁。⼀次完整的OTA升级通常包含三个步骤: 1. ⽣成更新包:⼚商制作包含新功能或修复补丁的软件包,常采⽤差分升级技术,只⽣成新旧版本间的差异部分,⼤⼤减⼩下载体积。 2. 传输更新包:加密后的更新包被推送到云端服务器,设备在合适的时机⾃动或经⽤⼾同意后下载。 3. 安装与验证:设备安装更新,并严格验证其完整性和正确性。成功后,新版本即开始运⾏。

2.OTA在STM32中使用思路

为了再stm32中使用好ota我们要来先了解一下bootloader,BootLoader 可以理解成是引导程序,它的作⽤是启动正式的App应⽤程序。Bootloader是在应⽤程序开始前运⾏的⼀个⼩程序,⾥⾯可以进⾏⼀些初始化操作,升级引⽤程序等,在嵌⼊式设备中很常⻅。了解了bootloader后我们就要在stm32的内存中对其进行分区处理,BootLoader区存放启动代码 ,App1区存放应⽤代码, App2区 存放暂存的升级代码。然后执行流畅大体如下:先执⾏ BootLoader程序,先去检查APP2区有没有程序,如果有就将App2(备份区)的程序拷⻉到App1,然后再跳转去执⾏App1的 程序. 因为 BootLoaderApp1这两个程序的向量表不⼀样,所以跳转到App1之后第⼀步是先去更改程序的向量表然后再去执⾏其他的应⽤程序. 在应⽤程序⾥⾯会加⼊程序升级的部分,这部分主要⼯作是拿到升级程序,然后将他们放到App2(备份区),以便下次启动的时候通过 BootLoader 更新App1 的程序。将 App2的最后4个字节(0x0801FFFC)⽤来表⽰App2是否有升级程序,STM32在擦除之后Flash的数据存放的都是0xFFFFFFFF,如果有,我们将这个地址存放为0xAAAAAAAA 。

3.程序及具体实现

首先我们先编写引导程序即boot程序,boot源文件程序及详解如下:

#include "main.h" #include <stdio.h> #include "boot.h" /** * @bieaf 擦除页 * * @param pageaddr 起始地址 * @param num 擦除的页数 * @return 1 */ static int Erase_page(uint32_t pageaddr, uint32_t num) { HAL_FLASH_Unlock(); /* 擦除FLASH*/ FLASH_EraseInitTypeDef FlashSet; FlashSet.TypeErase = FLASH_TYPEERASE_PAGES; FlashSet.PageAddress = pageaddr; FlashSet.NbPages = num; /*设置PageError,调用擦除函数*/ uint32_t PageError = 0; HAL_FLASHEx_Erase(&FlashSet, &PageError); HAL_FLASH_Lock(); return 1; } /** * @bieaf 写若干个数据 * * @param addr 写入的地址 * @param buff 写入数据的起始地址 * @param word_size 长度 * @return */ static void WriteFlash(uint32_t addr, uint32_t * buff, int word_size) { /* 1/4解锁FLASH*/ HAL_FLASH_Unlock(); for(int i = 0; i < word_size; i++) { /* 3/4对FLASH烧写*/ HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + 4 * i, buff[i]); } /* 4/4锁住FLASH*/ HAL_FLASH_Lock(); } /** * @bieaf 读若干个数据 * * @param addr 读数据的地址 * @param buff 读出数据的数组指针 * @param word_size 长度 * @return */ static void ReadFlash(uint32_t addr, uint32_t * buff, uint16_t word_size) { for(int i =0; i < word_size; i++) { buff[i] = *(__IO uint32_t*)(addr + 4 * i); } return; } /* 读取启动模式 */ unsigned int Read_Start_Mode(void) { unsigned int mode = 0; ReadFlash((Application_2_Addr + Application_Size - 4), &mode, 1); return mode; } /** * @bieaf 进行程序的覆盖 * @detail 1.擦除目的地址 * 2.源地址的代码拷贝到目的地址 * 3.擦除源地址 * * @param 搬运的源地址 * @param 搬运的目的地址 * @return 搬运的程序大小 */ void MoveCode(uint32_t src_addr, uint32_t des_addr, uint32_t byte_size) { /*1.擦除目的地址*/ printf("> Start erase des flash......\r\n"); Erase_page(des_addr, (byte_size/PageSize)); printf("> Erase des flash down......\r\n"); /*2.开始拷贝*/ unsigned int temp[256]; printf("> Start copy......\r\n"); for(int i = 0; i < byte_size/1024; i++) { ReadFlash((src_addr + i*1024), temp, 256); WriteFlash((des_addr + i*1024), temp, 256); } printf("> Copy down......\r\n"); /*3.擦除源地址*/ printf("> Start erase src flash......\r\n"); Erase_page(src_addr, (byte_size/PageSize)); printf("> Erase src flash down......\r\n"); } /* 采用汇编设置栈的值 */ __asm void MSR_MSP (uint32_t ulAddr) { MSR MSP, r0 //set Main Stack value BX r14 } /* 程序跳转函数 */ typedef void (*Jump_Fun)(void); void IAP_ExecuteApp (uint32_t App_Addr) { Jump_Fun JumpToApp; if ( ( ( * ( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法. { JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4); //用户代码区第二个字为程序开始地址(复位地址) MSR_MSP( * ( __IO uint32_t * ) App_Addr ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) JumpToApp(); //跳转到APP. } } /** * @bieaf 进行BootLoader的启动 * * @param none * @return none */ void Start_BootLoader(void) { /*==========打印消息==========*/ printf("\r\n"); printf("***********************************\r\n"); printf("* BootLoader *\r\n"); printf("***********************************\r\n"); //printf("> Choose a startup method......\r\n"); switch(Read_Start_Mode()) // 读取是否启动应用程序 { case Startup_Normol: // 正常启动 { printf("> Normal start...\n"); break; } case Startup_Update: // 升级再启动 { printf("> Start update...\n"); MoveCode(Application_2_Addr, Application_1_Addr, Application_Size); printf("> Update down......\r\n"); break; } default: // 启动失败 { printf("> Error:%X!!!......\r\n", Read_Start_Mode()); return; } } /* 跳转到应用程序 */ printf("> Start APP1...\r\n"); IAP_ExecuteApp(Application_1_Addr); }

boot头文件内容如下:

#ifndef __BOOT_H_ #define __BOOT_H_ #define PageSize FLASH_PAGE_SIZE // 1K /*=====用户配置(根据自己的分区进行配置)=====*/ // 0x400 1K // 0x800 2K // 0x1000 4K // 0x2000 8K // 0x7000 4K*7 = 28K #define BootLoader_Size 0x2000U // BootLoader的大小 8K #define Application_Size 0x7000U // APP 应用程序的大小 28K #define Application_1_Addr 0x08002000U // 应用程序1的首地址 大小 28K 0x08002000 + 0x7000 #define Application_2_Addr 0x08009000U // 应用程序2的首地址 大小 28K 0x08009000 + 0x7000 /*==========================================*/ /* 启动的步骤 */ #define Startup_Normol 0xFFFFFFFF // 正常启动 #define Startup_Update 0xAAAAAAAA // 升级再启动 void Start_BootLoader(void); #endif

在main函数的循环中我们调用如下:

while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ Start_BootLoader(); HAL_Delay(1000*10); }

然后编写我们的app程序,首先在app程序中我们要更改中断地址表,程序如下:

SCB->VTOR = FLASH_BASE | Application_1_Addr ; /* 更改中断向量表地址 偏移到APP1 地址 */

然后将你要实现的功能写入到app文件中即可,需要进行ota更新的app文件要将前面说到的标志位进行一个更改。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 12:19:05

谈谈AI对新人的影响和对行业的作用

让新手用AI的都是傻福&#xff0c;今天给大家讲一讲关于AI对于新手来讲来讲是利还是弊。这个视频可能会让一些人不舒服&#xff0c;但是讲的都是实话。先说一些核心的观点&#xff0c;当AI工具铺天盖地&#xff0c;新手该何去何从&#xff1f;一个普遍的现象就是大家打开任何一…

作者头像 李华
网站建设 2026/4/23 12:24:39

19、Dockerfile 入门与优化实践

Dockerfile 入门与优化实践 1. Dockerfile 基础设置与用户账户 在构建基础镜像时,若在基础镜像中进行某些设置,可能会阻止下游 Dockerfile 安装软件。例如,若权限设置不当,下游 Dockerfile 可能需要反复切换默认权限,这会额外增加至少两层。更好的做法是在基础镜像中设置…

作者头像 李华
网站建设 2026/4/18 6:15:01

CoreCycler终极指南:5步搞定CPU稳定性测试

CoreCycler终极指南&#xff1a;5步搞定CPU稳定性测试 【免费下载链接】corecycler Stability test script for PBO & Curve Optimizer stability testing on AMD Ryzen processors 项目地址: https://gitcode.com/gh_mirrors/co/corecycler CoreCycler是一款专为AM…

作者头像 李华
网站建设 2026/4/23 10:27:54

24、Docker 镜像构建模式与元数据记录全解析

Docker 镜像构建模式与元数据记录全解析 1. 镜像构建流程概述 在构建 Docker 镜像时,通常需要遵循以下步骤: 1. 检索或生成将包含在镜像中的工件,如应用程序包和运行时库。 2. 使用 Dockerfile 构建镜像。 3. 验证镜像的结构和功能是否符合预期。 4. (可选)验证镜像是…

作者头像 李华
网站建设 2026/4/23 11:53:02

26、Docker Swarm 模式下服务的使用与管理

Docker Swarm 模式下服务的使用与管理 1. 服务的基本概念 在网络中可被发现和使用的进程、功能或数据被称为服务。服务是一个抽象的概念,它简化了我们对相关事物的描述。当我们提及一个具体的服务时,无需明确说明其名称可通过 DNS 或相应的服务发现机制被发现,也不必强调在…

作者头像 李华
网站建设 2026/4/23 11:50:57

29、Docker 配置与密钥管理:深入解析与实战操作

Docker 配置与密钥管理:深入解析与实战操作 1. 一流配置抽象 在实际应用中, env_specific_config 资源会经过一些调整后映射到 greetings 服务容器里。默认情况下,配置资源会挂载到容器文件系统的 /<config_name> 路径,比如 /env_specific_config 。在这个…

作者头像 李华