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是中移物联网有限公司搭建的开放.共赢设备云平台,为各种跨平台物联网应用.行业解决方案,提供简便的云端接入.存储.计算 ...
随机推荐
- 可视化图解算法:按之字形顺序打印二叉树( Z字形、锯齿形遍历)
1. 题目 描述 给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替) 数据范围:0≤n≤1500,树上每个节点的val满足 |val| <= 1500 ...
- Python3循环结构(一)for循环
Python3循环结构 在Python中主要有两种类型的循环结构:for循环和while循环.for循环一般用于有明显边界范围的情况,例如,计算1+2+3+4+5+-+100等于几的问题,就可以用fo ...
- Java编程--接口(interface)简单用法(一)
接口是Java中的一个重要的概念. interface:定义了子类要实现的功能.由全局常量和抽象方法组成. 接口的定义 定义一个简单的interface public interface A { p ...
- 时间工具之“js初始化当前时间数据”
⑨前端:初始化当前时间数据 方案一(峰哥认可) // 2023-02this.$moment().format('yyyy-MM'),// 2023-02-02this.$moment().form ...
- SQL 日常练习(十五)
这两周真的是被客户搞怕了, 我一个数据分析师, 干着比程序员还复杂的活, 拿着文员的工资, 看这我每天下班的打卡时间, 感觉我一点求生欲都没有,真的不知道图啥. 快速理解业务, 马上建数据库表, 写后 ...
- MongoDB创建数据库文件的存放位置
为什么要写这篇呢,故事还得从MongoDB如下所示的罢工说起 怎么就拒绝访问了呢???? 在执行mongod命令可发现如下问题: 于是,在蜘蛛网上到处扒拉,以解它这不解之症,也解我燃眉之急 终于... ...
- C#之线程基础
创建线程 using System; using System.Threading; using System.Threading.Tasks; namespace threadDemo { clas ...
- 【AI+教学】让课堂实时讲解语音知识库沉淀下来
今天给大家分享一个教学的 AI 使用场景,主要用来解决课堂老师实时讲解的内容如何让学生快速了解学习. 一.教学场景说明: 课堂上老师上完课后,课堂实时讲解的内容,部分与教材或者课件有偏差(临场发挥), ...
- WindowsPE文件格式入门08.导出表
https://bpsend.net/thread-377-1-1.html 通过cff , depends灯等软件可以看到dll,导出函数的信息,因为dll中本身就存了这些信息,存了dll中有哪些导 ...
- 【公众号搬运】React-Native开发鸿蒙NEXT(5)
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...