STM32 IAP(OTA)

news/2025/1/24 10:22:29/文章来源:https://www.cnblogs.com/dy-stairmed/p/18689159

一、背景知识

  1. STM32启动流程(从内部flash启动)[1]

正常情况下,程序从Flash启动时的流程如下:(转载自)

https://blog.csdn.net/qq_42190402/article/details/139671333

  1. 程序从Flash启动,根据中断向量表找到复位中断处理函数的地址。(0x0800 0004处是中断向量表的起始地址,也是中断向量表的起始,记录了复位中断处理函数的地址)。

  2. 执行复位中断处理函数,初始化系统环境后跳转到main函数。

  3. 在main函数的死循环中运行,直到有中断发生。

  4. 中断发生时,跳转到中断向量表起始处,根据中断信号源跳找到相应的中断处理函数。

  5. 中断处理函数执行完后返回到main函数继续运行。

值得注意的是在bootloader中,CM4内核上电后从地址 0x0000 0000 处取出堆栈指针 MSP 的初始值,该值就是栈顶地址。而我们通过内部flash中启动,因此这个地址被映射到了:0X0800000

以上我们得到一个重要的信息,也就是,STM32上电后,是从0X08000000 flash地址启动的。因此我们的boot程序需要烧录至0X08000000,在boot中完成app代码搬运后,跳转至APP程序中,app程序的flash地址需要我们自己指定。

  1. STM32程序烧录方式

    1. 通过J-link/ST-link等烧录器将hex文件(hex文件中包含flash起始地址)烧录至指定位置。

    例如在Keil中指定的on-chip 起始flash地址为:

    那么这种方式会将hex文件烧录至0X8000000地址中。

    1. ISP(In System Program)方式:

ISP(In-System Programming,在系统可编程)是一种技术,它允许用户直接在电路板上对空白或已编程的器件进行编程、擦除或更新,无需将器件从电路板上取下。这一过程依赖于bootloader(自举程序),它存储在微控制器如STM32的内部ROM(系统存储器)中,主要负责通过串行外设(如USART、CAN、USB、I2C等)接收应用程序代码,并将其写入Flash内存。不同的串行接口定义了各自的通信协议,包括命令集和数据传输序列。

2.3 IAP(In Application Program)

IAP(In Application Programming)即在应用编程, IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。

通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两部分代码,第一部分程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、 USART)接收程序或数据,执行对第二部分代码的更新;第二部分代码才是真正的功能代码。

两部分代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一部分代码开始运行,它作如下操作:

  1. 检查是否需要对第二部分代码进行更新;

  2. 如果不需要更新则转到第二部分代码执行;

  3. 执行更新操作;

  4. 跳转到第二部分代码执行;

二、IAP实现OTA升级

由以上得知,想要实现在APP程序里实现在线升级我们可以用以下方式实现:

程序还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数。

在执行完 IAP 以后(即将新的 APP 代码写入 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,但在不同位置上,共有两个中断向量表。

  1. 编写一个APP程序,烧录地址为0x802000000

在该程序中,需要将中断向量表偏移至0x80200000,

SCB->VTOR = 0x8020000;

通过串口/SPI等通讯接口,接收来自上位机的升级数据包(bin)文件,保存到一个大缓存中

int arm_mcu_upgrade_data_set(ArmSpiQueue_str *UpgradeData)
{int RecvIndex = UpgradeBuffIndex;char CheckSum = 0;if(MCU_UpgradeStartFlag == 1){os_printf_log("UpgradeData->len = %d\n", UpgradeData->len);for(int i = 1; i < UpgradeData->len; i++){CheckSum += UpgradeData->data[i];MCU_UpgradeBuff[RecvIndex+i-1] = UpgradeData->data[i];}if(CheckSum == UpgradeData->data[0]){UpgradeBuffIndex += UpgradeData->len-1;MCU_UpgradeACK = 1;}else{MCU_UpgradeACK = 0;}os_printf_log("CheckSum = %x, UpgradeData->data[0] = %x\r\n", CheckSum, UpgradeData->data[0]);}else{MCU_UpgradeACK = 0;}//fw pack upgrade finshif(UpgradeBuffIndex >= MCU_FWSumSize){os_printf_log("UpgradeBuffIndex = %d, MCU_FWSumSize = %d\r\n", UpgradeBuffIndex, MCU_FWSumSize);aw911c_led_control(led_null);}return 0;
}

接收完数据包后,将升级包数据、升级标志写入在一个内部flash区域(注意不要和启动位置冲突),例如((uint32_t)0x08040000)地址;

int mcu_upgrade_start(uint8_t *UpgradeData, uint32_t size)
{int err = 0;uint32_t addr = 0;uint8_t sector = 0;uint32_t UpgradeFlag = 0, FW_Size = 0;flash_unlock();//check upgrade prevent flagUpgradeFlag = flash_read_u32(MCU_USER_UPGRADE_FLAG_ADDR);os_printf_log("UpgradeFlag = %x\n", UpgradeFlag);if(UpgradeFlag != MCU_UPGRADE_PREV_FLAG){flash_lock();os_printf_log("UpgradeFlag = %x, != MCU_UPGRADE_PREV_FLAG\r\n", UpgradeFlag);return 1;}//start write flash//erase flashaddr = MCU_UPGRADE_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){flash_lock();os_printf_log("flash_addr_erase is fail\n");return err;}//write flashos_printf_log("flash_write_u8 size = %d, %x-%x-%x-%x\n", size, UpgradeData[0], UpgradeData[1], UpgradeData[2], UpgradeData[3]);err = flash_write_u8(addr, UpgradeData, size);if(err){flash_lock();os_printf_log("flash_write_u8 is fail err = %d\r\n", err);return err;}//erase flashaddr = MCU_USER_PARAM_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){flash_lock();os_printf_log("flash_addr_erase is fail\n");return err;}//UpgradeFlag = MCU_UPGRADE_FINSH_FLAG;flash_write_u32(MCU_USER_UPGRADE_FLAG_ADDR, &UpgradeFlag, 1);FW_Size = size;flash_write_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR, &FW_Size, 1);flash_lock();return 0;
}
  1. 编写一个boot程序,烧录在0X08000000 flash地址,

上电后,先执行的是该程序,因此,需要去flash保存升级标志的区域读回升级标志,如果需要升级,则先将0x802000000地址数据擦除,并将0x08040000地址数据写入到0x802000000。

int mcu_fw_update(void)
{int err = 0;uint32_t addr = 0;uint8_t sector = 0;uint32_t UpgradeFlag = 0, FW_Size = 0;uint8_t *upgradeAddr = 0;UpgradeFlag = flash_read_u32(MCU_USER_UPGRADE_FLAG_ADDR);FW_Size = flash_read_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR);os_printf_log("UpgradeFlag = %x, FW_Size = %d\r\n", UpgradeFlag, FW_Size);flash_unlock();if(UpgradeFlag == MCU_UPGRADE_FINSH_FLAG){if(FW_Size == 0x00000000 || FW_Size == 0xFFFFFFFF){os_printf_log("flash_read_u32 FW_Size is err, FW_Size = %d\r\n", FW_Size);flash_lock();return 1;}//start write flashupgradeAddr = (uint8_t *)MCU_UPGRADE_BASE_ADDR;os_printf_log("MCU_UPGRADE_BASE_ADDR = %x-%x-%x-%x\n", *(upgradeAddr), *(upgradeAddr+1), *(upgradeAddr+2), *(upgradeAddr+3));memcpy(UpgradeData, upgradeAddr, FW_Size);//erase flashaddr = MCU_APP1_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){os_printf_log("flash_addr_erase is fail\n");flash_lock();return 1;}//write flasherr = flash_write_u8(addr, UpgradeData, FW_Size);if(err){os_printf_log("flash_write_u8 is fail err = %d\r\n", err);flash_lock();return 1;}//erase flashaddr = MCU_USER_PARAM_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){os_printf_log("flash_addr_erase is fail\n");flash_lock();return 1;}UpgradeFlag = MCU_UPGRADE_PREV_FLAG;flash_write_u32(MCU_USER_UPGRADE_FLAG_ADDR, &UpgradeFlag, 1);FW_Size = 0;flash_write_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR, &FW_Size, 1);}else if(UpgradeFlag == MCU_UPGRADE_FINSH_FLAG){flash_lock();return 0;}else{//erase flashaddr = MCU_USER_PARAM_BASE_ADDR;sector = flash_sector_get(addr);os_printf_log("upgrade addr = %x, sector = %d\r\n", addr, sector);err = flash_addr_erase(sector);if(err != 0){os_printf_log("flash_addr_erase is fail\n");flash_lock();return 1;}UpgradeFlag = MCU_UPGRADE_PREV_FLAG;flash_write_u32(MCU_USER_UPGRADE_FLAG_ADDR, &UpgradeFlag, 1);FW_Size = 0;flash_write_u32(MCU_USER_UPGRADE_FW_SIZE_ADDR, &FW_Size, 1);}flash_lock();return 0;
}

写入完成后,跳转至0x802000000地址启动,跳转方式:

__asm void SET_MSP (uint32_t ulAddr) 
{MSR MSP, r0   //??Main Stack??BX r14  //
}/* ?????? */
typedef void (*Jump_Fun)(void); //????
void IAP_jumpApp (uint32_t App_Addr)
{Jump_Fun JumpToApp; //if (((*( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) == 0x20000000 )  //{os_printf_log("IAP_jumpApp \n");JumpToApp = (Jump_Fun) * ( __IO uint32_t *)(App_Addr + 4);  //SET_MSP( * ( __IO uint32_t * ) App_Addr ); //JumpToApp(); //}else{os_printf_log("*( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ) != 0x20000000, = %x\r\n", ((*( __IO uint32_t * ) App_Addr ) & 0x2FFE0000 ));}
}

三、烧录

两个程序编写完成后,需要将两个程序合并为一个hex文件烧录,主要是需要先将boot程序烧录进去,后续就可以进行IAP升级了,合并方式如下:

  1. 打开J-flash工具,选择目标期间,例如STM32F407VG

  1. 打开boot程序的HEX文件

  1. Merge APP程序的HEX文件,并将文件另存。得到合并的HEX文件。

可以发现,hex文件是带FLASH存储信息的,APP程序被放在了08020000目标位置了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/874633.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

稀疏基因组学:大规模基因组分析的新范式

随着基因测序技术的飞速发展,我们面临着一个巨大的挑战:如何高效地处理和分析海量的基因组数据。 2025年1月21日,发表在《Nature Communications》上的一篇论文提出了一个名为“稀疏化基因组学”(Sparsified Genomics)的新概念,通过系统性地排除基因组序列中的大量碱基,…

【译】我们最喜欢的2024年的 Visual Studio 新功能

去年,Visual Studio 团队发布了许多新的面向开发人员的改进和 AI 集成,其中许多直接来自您在开发者社区的反馈。在这篇文章中,我们将重点介绍2024年团队最喜欢的功能,这些功能可以提高生产力,简化工作流程,并增强您的编码体验。让我们开始吧! 图像悬停预览:立即看到您的…

如何修改PHP网站的名称,以确保名称准确且符合品牌形象?

修改PHP网站的名称是一个重要的任务,它不仅关系到网站的准确性和专业性,还影响到用户体验和品牌形象。以下是详细的步骤和建议:备份现有文件:在进行任何修改之前,务必备份网站的原始文件和数据库,以防止意外情况发生。 确定修改位置:通常,网站名称位于模板文件中,如he…

【分享】晶尊微MC802:打造炫酷触控发光方案,轻松点亮创意未来

MC802 带 2 个自校正容性触摸按键功能和 4 个 I/O 口的单片机,是以 EPROM 作为记忆体的 8 位微控制器,专为多 IO 产品的应用而设计。最近,科技圈出现这样一个好东西。它不仅能随时为手机、耳机等充电设备提供应急充电,还能瞬间变身露营灯、氛围灯、台灯,满足不同场景下的需…

二-2、代码生成-swagger

地址 http://192.168.0.115:39999/swagger-ui.html?docExpansion=none/ 位置步骤配置数据库数据Cb-SYSMICROSERVICE(微服务)Cb-SYSRELATIONTABLE(表)生成代码传参 { "author": "jmc",--作者 "lngmsid": 207,--SYSMICROSERVICE表ID "ln…

为什么PHP无法正常修改网站?

PHP无法正常修改网站可能有多种原因,以下是一些常见的问题和解决方法:文件权限问题:确保PHP脚本有足够的权限来修改网站文件。检查文件和文件夹的权限设置,确保PHP进程有权读取、写入和执行相关文件。路径问题:确认PHP脚本中使用的文件路径是正确的。如果路径错误,PHP将无…

如何修改后台网站底部信息?

要修改后台网站底部信息,通常可以按照以下步骤进行:登录到后台管理系统:使用管理员账号和密码登录到网站的后台管理系统。找到页面编辑功能:在后台管理系统中,找到可以编辑页面内容的功能或模块。这可能是一个可视化编辑器、HTML编辑器或其他类似的工具。定位到底部区域:…

如何修改PHP网站的页面内容?

要修改PHP网站的页面内容,通常可以按照以下步骤进行:确定要修改的页面:首先,明确您想要修改的具体页面。这可能是首页、产品页面、文章页面等。找到页面文件:根据页面的类型和位置,找到对应的PHP文件。这些文件通常位于网站的根目录或特定的文件夹中。备份文件:在进行任…

Vite + Vue/React:用 import.meta 解决图片路径问题

在 Vite 中开发 Vue 和 React 项目时,import.meta.url 是一个非常有用的工具,它可以帮助我们动态获取模块的路径,进而处理静态资源(如图片)的加载路径。本文将结合实际例子,展示如何使用 import.meta.url 动态切换图片。 一. import.meta.url 是什么? import.meta 是 EC…

天通ERP S系列如何设置根据不同字段区分商品显示汇总,且在两个表中显示

展示效果:操作方式: 1、任我行打印管理器,点击右上角添加公式按钮,添加一个字段名称为空、字符类型为字符数据,且表达内容为空的公式字段保存(英文输入法下两个’’)2、在表头的最后添加一列,绑定刚才添加的空的公式字段,并绑定新增的字段为分单总计 且该字段取消显示边框 3、…

kettle从入门到精通 第九十一课 ETL之kettle http接口下载文件流

1、场景需求 群里一位老朋友想通过http接口下载文件流,然后将文件流保存为文件存储到本地,如下图所示: 2、做过应用程序研发,对http知识有所了解的,结合对方发的postman截图,一眼就知道了接口的真实面目。接口返回的content-type是application/octet-stream且有文件下载说…

搭建latex服务

1.领取免费服务器,推荐免费服务器(SanFengYun)见下图。2.安装宝塔面板,配置内网为127.0.0.1,访问外网地址。 3.可以在宝塔面板一键部署网站,输入自己的域名即可。 4.关键:安装docker,安装yum,设置github可以访问。 5.更换docker镜像,自带镜像无法访问 6.按照overleaf…