STM32 IAP(OTA)
一、背景知识
STM32启动流程(从内部flash启动)[1]
正常情况下,程序从Flash启动时的流程如下:(转载自)
https://blog.csdn.net/qq_42190402/article/details/139671333
程序从Flash启动,根据中断向量表找到复位中断处理函数的地址。(0x0800 0004处是中断向量表的起始地址,也是中断向量表的起始,记录了复位中断处理函数的地址)。
执行复位中断处理函数,初始化系统环境后跳转到main函数。
在main函数的死循环中运行,直到有中断发生。
中断发生时,跳转到中断向量表起始处,根据中断信号源跳找到相应的中断处理函数。
中断处理函数执行完后返回到main函数继续运行。
值得注意的是在bootloader中,CM4内核上电后从地址 0x0000 0000 处取出堆栈指针 MSP 的初始值,该值就是栈顶地址。而我们通过内部flash中启动,因此这个地址被映射到了:0X0800000
以上我们得到一个重要的信息,也就是,STM32上电后,是从0X08000000 flash地址启动的。因此我们的boot程序需要烧录至0X08000000,在boot中完成app代码搬运后,跳转至APP程序中,app程序的flash地址需要我们自己指定。
STM32程序烧录方式
通过J-link/ST-link等烧录器将hex文件(hex文件中包含flash起始地址)烧录至指定位置。
例如在Keil中指定的on-chip 起始flash地址为:
那么这种方式会将hex文件烧录至0X8000000地址中。
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 中,当芯片上电后,首先是第一部分代码开始运行,它作如下操作:
检查是否需要对第二部分代码进行更新;
如果不需要更新则转到第二部分代码执行;
执行更新操作;
跳转到第二部分代码执行;
二、IAP实现OTA升级
由以上得知,想要实现在APP程序里实现在线升级我们可以用以下方式实现:
程序还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数。
在执行完 IAP 以后(即将新的 APP 代码写入 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,但在不同位置上,共有两个中断向量表。
编写一个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 finsh
if(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 flag
UpgradeFlag = 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 flash
addr = 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 flash
os_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 flash
addr = 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;
}
编写一个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 flash
upgradeAddr = (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 flash
addr = 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 flash
err = 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 flash
addr = 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 flash
addr = 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升级了,合并方式如下:
打开J-flash工具,选择目标期间,例如STM32F407VG
打开boot程序的HEX文件
Merge APP程序的HEX文件,并将文件另存。得到合并的HEX文件。
可以发现,hex文件是带FLASH存储信息的,APP程序被放在了08020000目标位置了
STM32 IAP(OTA)的更多相关文章
- STM32开发(一):简介及开发环境
1. 背景 STM32是意法(ST)公司开发的基于ARM Cortex-M系列的一系列微控制器(MCU). 有两种库 标准外设库(StdPeriph_Driver.Standard Periphera ...
- STM32.SPI(25Q16)
1.首先认识下W25Q16DVSIG, SOP8 SPI FLASH 16MBIT 2MB(4096个字节) (里面可以放字库,图片,也可以程序掉电不丢失数据放里面) 例程讲解: ① 1.用到SPI ...
- 增量式PID的stm32实现(转)
源:增量式PID的stm32实现,整定过程 首先说说增量式PID的公式,这个关系到MCU算法公式的书写,实际上两个公式的写法是同一个公式变换来得,不同的是系数的差异. 资料上比较多的是: 还有一种是: ...
- stm32.cube(一)——系统架构及目录结构
一.前言 Arm的应用场景往往比51单片机复杂得多,如果一个高级应用的开发需要连底层的结构性代码都要重构,那么在成本和研发周期上就会面临巨大的风险.为了简化编码过程,芯片厂商经常会提供一些板卡级支持的 ...
- 北醒激光模块TFmini——STM32驱动程序(STM32F103C8T6)
背景:该激光模块属于精度比较高的激光测距模块,使用方便. 代码地址:https://github.com/W-yt/YuTian_Pro/tree/master/TFMini_Driver 平台: 硬 ...
- STM32 --- 断言(assert_param)的开启和使用
默认,STM32的assert_param是没有开启检测,需要 #define USE_FULL_ASSERT 开启后,才能检测形参是否符合要求 // #define assert_param(exp ...
- 基于zigbee协议的空中下载技术(OTA)
首先镜像服务器的解释: 镜像服务器(Mirror server)与主服务器的服务内容都是一样的,只是放在一个不同的地方,分担主机的负载. 简单来说就是和照镜子似的,能看,但不是原版的.在网上内容完全相 ...
- 嵌入式单片机STM32应用技术(课本)
目录SAIU R20 1 6 第1页第1 章. 初识STM32..................................................................... ...
- 蓝牙固件升级(OTA升级)原理设计
转:http://blog.csdn.net/yueqian_scut/article/details/50849033 固件空中升级(OTA)与固件二次引导的原理和设计 原创 2016年03月10日 ...
- 国内物联网平台(8):中移物联网开放平台OneNet
国内物联网平台(8)——中移物联网开放平台OneNet 马智 平台定位 OneNET是中移物联网有限公司搭建的开放.共赢设备云平台,为各种跨平台物联网应用.行业解决方案,提供简便的云端接入.存储.计算 ...
随机推荐
- Python日志模块Logging使用指北
Python日志模块Logging使用指北 作者:SkyXZ CSDN:SkyXZ--CSDN博客 博客园:SkyXZ - 博客园 Logging模块是Python中一个很重要的日志模块,它提供了灵活 ...
- 【完结】【一本通提高】KMP做题记录
题目编号 标题 估分 正确 提交 Y 2076 Problem A [一本通提高篇KMP]剪花布条 --- 156 293 Y 2077 Problem B [一本通提高篇KMP]Radio Tr ...
- EFCore 实体追踪
理解: EFCore通过一种机制实时追踪实体的属性是否有改变的一种机制,比如下方代码 通过EFCore查出来的数据List集合里的实体,在item.Manager = "菲菲";属 ...
- http2和http3
HTTP/2 和 HTTP/3 是 HTTP 协议的升级版本,主要为了解决 HTTP/1.x 协议的性能瓶颈和安全性问题.以下是它们的主要目标和解决的问题: HTTP/2 的主要目标和解决的问题 1. ...
- 【工具】秘塔AI搜索|推荐一个现在还免费的AI聚合搜索工具
网址:https://metaso.cn/ 使用时间:2024/03/27 . 2024/04/10 以前其实用过它家的秘塔写作猫,当时感觉非常不错. 这次看到它出AI搜索,感觉开发者挺有野心和实力的 ...
- ASP.NET Core Web API中操作方法中的参数来源
在ASP.NET Core Web API中,有多种方式可以传递参数给操作方法.以下是一些常见的参数传递方式: 路由参数(Route Parameters):参数值从URL的路由中提取. // Rou ...
- geekai开源项目二次开发 AI大模型 AI 助手全套开源解决方案
geekai-django 基于极客学长大佬的开源项目geekai 二次开发而来. GeekAI 是基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案,自带运营管理后台,开箱即用. 介 ...
- 【中文】【吴恩达课后编程作业】Course 1 - 神经网络和深度学习 - 第二周作业
[吴恩达课后编程作业]Course 1 - 神经网络和深度学习 - 第二周作业 - 具有神经网络思维的Logistic回归 上一篇:[课程1 - 第二周测验]※※※※※ [回到目录]※※※※※下一篇: ...
- CAN304 W2
CAN304 W2 Classical and modern cryptography Classical cryptography 完全依赖于通信双方之间共享的秘密信息(Private-key cr ...
- 五、小程序网络API·天气查询
本节主要介绍使用小程序网络API的相关应用制作一款天气查询小程序.掌握wx.request接口的用法. 前期准备:自行到和风天气官网(https://dev.qweather.com/) 申请API的 ...