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是中移物联网有限公司搭建的开放.共赢设备云平台,为各种跨平台物联网应用.行业解决方案,提供简便的云端接入.存储.计算 ...
随机推荐
- 解决宝塔环境composer报错:TypeError: Return value of Symfony\Component\Process\Process::close
问题: 解决宝塔环境安装运行composer时报错:TypeError: Return value of Symfony\Component\Process\Process::close 不熟悉的人看 ...
- 在Ubuntu Server上安装Checkmk监控系统
一.安装前准备 更新系统并安装依赖: sudo apt update && sudo apt upgrade -y sudo apt install -y wget apt-trans ...
- 『Plotly实战指南』--在科学数据可视化中的应用(上)
在科学研究中,数据可视化是连接实验与理论的关键桥梁. 它不仅能够清晰地呈现实验规律,还能验证假设并支持科研决策. Plotly作为一款强大的可视化工具,凭借其交互性.动态图表支持和灵活的可定制性,在科 ...
- Nacos源码—5.Nacos配置中心实现分析
大纲 1.关于Nacos配置中心的几个问题 2.Nacos如何整合SpringBoot读取远程配置 3.Nacos加载读取远程配置数据的源码分析 4.客户端如何感知远程配置数据的变更 5.集群架构下节 ...
- 遇到的问题之"数据库编写SQL-》子查询中加入limit报错:This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'"
一.问题 > 1235 - This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' 二. ...
- vue3 基础-常用模板语法
一个 vue 的单文件 SAP ( single page web application ) 即在一个 .vue 为后缀的文件中, 会包含3个部分. 模板: html 逻辑: javascript ...
- codeup之奖金计算
codeup c2奖金计算 Description 某企业发放的奖金根据利润提成.利润I低于或等于100000时,奖金可提10%:利润高于100000元,低于200000元(100000<I&l ...
- Nuxt的SEO实践
第9章:Nuxt的SEO实践 1. 引言 Nuxt框架在SEO方面的优势主要体现在以下几个方面: 服务器端渲染(SSR): Nuxt默认支持SSR,这意味着搜索引擎爬虫可以直接看到完整的页面内容,而不 ...
- 聊一聊 C# NativeAOT 多平台下的函数导出
一:背景 1. 讲故事 昨晚训练营里有一位朋友提到一个问题,说 C# AOT程序能否编译为一个dll,供其他语言调用,其实这个是完全没有问题的,也确实我的的文章体系中没有涉及到这块,那今天就补充完整吧 ...
- Hibernate Validator 提示javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint
问题背景:Spring boot项目不想写诸多校验代码,避免代码既丑陋又繁琐,故使用hibernate validator校验参数的时候,但出现如下所示的的错误提示: javax.validation ...