什么是IAP升级?
IAP,即In Application Programming,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。简单来说,就是开发者代码出bug了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级
UART、SPI、IIC、USB等等,当然还有wifi、4G、蓝牙等无线通讯手段,都可以作为IAP升级的方式,今天主要介绍如何使用串口对固件进行升级
STM32的代码启动过程
要想设计IAP,首先需要对MCU的代码启动过程有个了解,先来看看STM32的代码启动过程是怎样的吧
此部分参考:https://www.cnblogs.com/gulan-zmc/p/12248509.html
在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南):
从0x0000 0000地址取出MSP(主堆栈寄存器)的值
从0x0000 0004地址取出PC(程序计数器)的值
然后取出第一条指令执行
启动文件源代码分析
;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
;* File Name : startup_stm32f10x_hd.s
;* Author : MCD Application Team
;* Version : V3.5.0
;* Date : 11-March-2011
;* Description : STM32F10x High Density Devices vector table for MDK-ARM
;* toolchain.
;* This module performs:
;* (上电复位后会做下面的几件事情)
;* - Set the initial SP(设置堆栈,就是设置MSP的值)
;* - Set the initial PC == Reset_Handler(设置PC的值)
;* - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
;* - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
;* SRAM mounted on STM3210E-EVAL board to be used as data
;* memory (optional, to be enabled by user)
;* - Branches to __main in the C library (which eventually (调用C库的__main函数,然后调用main函数执行用户的)
;* calls main()).
;* After Reset the CortexM3 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
;* <<< Use Configuration Wizard in Context Menu >>>
;*******************************************************************************
; ------------------分配栈空间----------------
Stack_Size EQU 0x00000400 ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
Stack_Mem SPACE Stack_Size ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
__initial_sp ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
; ------------------分配堆空间----------------
;和分配栈空间一样不过大小只是512字节
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base ;__heap_base堆的起始地址
Heap_Mem SPACE Heap_Size ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的
__heap_limit ;__heap_base堆的结束地址
PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐
THUMB;THUMB作用是后面的指令使用Thumb指令集
; ------------------设置中断向量表----------------
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY ;定义一个段,段名是RESET的只读数据段
;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性
EXPORT __Vectors ;声明一个__Vectors标号允许其他文件引用
EXPORT __Vectors_End ;声明一个__Vectors_End标号允许其他文件引用
EXPORT __Vectors_Size ;声明一个__Vectors_Size标号允许其他文件引用
;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化
;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
__Vectors DCD __initial_sp ;* Top of Stack 定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp)
;* __initial_sp的值是链接后,由链接器生成
DCD Reset_Handler ;* Reset Handler 定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
;* Reset_Handler是一个函数,在下面定义
;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
.....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End ;__Vectors_End向量表的结束地址
__Vectors_Size EQU __Vectors_End - __Vectors ;定义__Vectors_Size标号,值是向量表的大小
AREA |.text|, CODE, READONLY ;定义一个代码段,段名是|.text|,属性是只读
;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)
; Reset handler
Reset_Handler PROC ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
EXPORT Reset_Handler [WEAK] ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
;*编译时使用另一个地方的函数,否则使用这个函数
;*IMPORT 表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
IMPORT __main ;*__main 和 SystemInit 函数都是外部文件的标号
IMPORT SystemInit ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
LDR R0, =SystemInit
BLX R0
LDR R0, =__main ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
;下面定义的都是异常服务函中断服务函数
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
.....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
.....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
EXPORT DMA2_Channel2_IRQHandler [WEAK]
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
.....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B .
ENDP
ALIGN ;四字节对齐
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
;下面函数是初始化堆栈的代码
IF :DEF:__MICROLIB
;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
;这种方式初始化堆栈是由 __main 初始化的
EXPORT __initial_sp ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
EXPORT __heap_base ;堆的起始地址
EXPORT __heap_limit ;堆的结束地址
ELSE
;由用户初始化堆
;否则编译下面的
IMPORT __use_two_region_memory ;__use_two_region_memory 由用户实现
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem ;堆的起始地址
LDR R1, =(Stack_Mem + Stack_Size);栈顶地址
LDR R2, = (Heap_Mem + Heap_Size);堆的结束地址
LDR R3, = Stack_Mem ;栈的结束地址
BX LR
ALIGN
ENDIF
END
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
STM32的启动步骤如下:
1、上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的
2、从0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成调用SystemInit函数初始化系统时钟
3、跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序
IAP设计思路
大体分为两部分设计,bootloader、APP代码设计,bootloader用于检查APP区代码是否需要更新,以及跳转到APP区执行APP程序
下面这个流程比较通用一些,大概是下图所示升级流程:
升级流程图
Flash分区
是以STM32L431KBU6为主控做的flash分区,主要功能:
bootloader区:0x0800 0000 到 0x0800 5000 地址的flash块划分给bootloader,用于升级固件,大小是20kb
APP区:0x0800 5000 到 0x0801 1800 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是50Kb
APP缓存区: 0x0801 1800 到 0x0801 E000 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 50kb
用户参数区+未定义:0x0801 E000 到 0x0802 0000 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是8Kb,flash块划分未定义区,可以根据具体用途定义
代码实现
内部Flsah读写及擦除操作
Flash擦除:
/*
功能:擦除用户地址所在页
参数:EraseAddr 需要擦除的Flash地址
NumberPage 擦除的页数
*/
HAL_StatusTypeDef MY_FLASH_Erase(uint32_t EraseAddr,uint8_t NumberPage)
{
uint8_t page;
uint32_t PAGEError=0;
FLASH_EraseInitTypeDef FLASH_EraseInitStruct;
/** 计算用户编程地址在FLASH中哪个页(每页2K字节) **/
page = (EraseAddr - FLASH_BASE) / FLASH_PAGE_SIZE;//编程地址在Bank1上用此公式
// page = (addr - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE;//地址在Bank2
FLASH_EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; //擦除方式:页擦除
FLASH_EraseInitStruct.Banks = FLASH_BANK_1; //擦除页所在的区域:BANK1
FLASH_EraseInitStruct.Page = page; //擦除页的编号
FLASH_EraseInitStruct.NbPages = NumberPage; //擦除页的数量
return HAL_FLASHEx_Erase(&FLASH_EraseInitStruct,&PAGEError);
}
写入Flash:
/*
功能:写入Flash
参数:WriteNumber 写入Flash的数据
addr 写的地址
*/
void Flash_WriteNumber(uint32_t addr,uint32_t WriteNumber)
{
uint8_t page;
uint32_t PageError = 0;
FLASH_EraseInitTypeDef FlashSet;
HAL_StatusTypeDef status;
/* 擦除Flash */
/** 计算用户编程地址在FLASH中哪个页(每页2K字节) **/
page = (addr - FLASH_BASE) / FLASH_PAGE_SIZE;//编程地址在Bank1上用此公式
// page = (addr - (FLASH_BASE + FLASH_BANK_SIZE)) / FLASH_PAGE_SIZE;//地址在Bank2
FlashSet.TypeErase = FLASH_TYPEERASE_PAGES; //擦除方式:页擦除
FlashSet.Banks = FLASH_BANK_1; //擦除页所在的区域:BANK1
FlashSet.Page = page; //擦除页的编号
FlashSet.NbPages = 1; //擦除页的数量
//解锁Flash操作
HAL_FLASH_Unlock();
status = HAL_FLASHEx_Erase(&FlashSet, &PageError);//擦除flash
HAL_FLASH_Lock();
if(status != HAL_OK)
{
printf("erase fail, PageError = %d\r\n", PageError);
}
else
printf("erase success\r\n");
/* 写入新的数据 */
HAL_FLASH_Unlock();//解锁
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, (uint64_t)WriteNumber);//写入Flash
if(status != HAL_OK)
{
printf("Flash write error,0x%08x\r\n",addr);
}
else
printf("Flash write success\r\n");
HAL_FLASH_Lock();//上锁
}
读取FLASH:
/*
功能:读取Flash内容
参数:addr 需要读取的Flash地址
size 读取字节大小
*/
uint32_t* Flash_ReadNumber(uint32_t addr,uint16_t size)
{
static uint32_t ReadData[] = {0};
/* 读取Flash内容 */
for(uint32_t i=0;i<size;i++)
{
ReadData[i] = (*(uint32_t*)(addr+4*i));
printf("Flash read data:%d\r\n",ReadData[i]);
}
return ReadData;
}
CAN中断接收升级固件包
//CAN1中断服务函数
void CAN1_RX0_IRQHandler(void)
{
HAL_CAN_IRQHandler(&hcan1);
}
/**
* \brief 函数功能:CAN中断回调函数
* 此函数会被CAN_Receive_IT()调用
* \param[in] hcan:CAN句柄
* \return
*
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
// 可以使用rxHeader和rxData来获取消息的ID和数据内容
CAN_RxHeaderTypeDef RxHeader;
uint8_t rxData[8];
// 检查是否有新的CAN消息到达
if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, rxData) == HAL_OK)
{
// 在这里对接收到的CAN消息进行处理
// 可以使用rxHeader和rxData来获取消息的ID和数据内容
if(RxHeader.StdId == 0x666)
{
CAN_RxData = 0;
CAN_RxData= ((uint64_t)rxData[7]<<56)|((uint64_t)rxData[6]<<48)|((uint64_t)rxData[5]<<40)|((uint64_t)rxData[4]<<32) \
|((uint64_t)rxData[3]<<24)|((uint64_t)rxData[2]<<16)|((uint64_t)rxData[1]<<8)|((uint64_t)rxData[0]<<0);
if(CAN_RxData == 0x8888888800000000)//固件包发送完毕
{
Rx_Complete_State = 0xF;//0-未接收完固件 F-固件全部接收完成 1-单次接收完成
}
else
{
Rx_Complete_State = 0x1;
}
}
}
}
IAP代码设计
bootloader代码设计:
#include "main.h"
uint8_t Rx_Complete_State = 0; //0-未接收完固件 F-固件全部接收完成 1-单次接收完成
volatile uint8_t recv_tcp_complete = 0; //接收到平板发送的一帧tcp命令 1:接收到 处理完成后需要把此变量清零
#define Fifo_Buf_Size_MAX 2048
uint8_t Fifo_Buf[Fifo_Buf_Size_MAX] = {0};
/* 定义变量 ----------------------------------------------------------*/
#define APP_BASE_ADDR (uint32_t)0x08005000 //定义应用程序的起始地址
#define APP_CODE_MAX (uint32_t)50*1024 //定义APP程序最大长度 50K
#define UPDATE_CODESAVE_ADDR (uint32_t)0x08011800 //定义升级程序待存放的起始地址
#define PAPER_FLAG_UPSTART_ADDR (uint32_t)0x0801F800 //升级标志存放的地址
#define PAPER_BIN_LEN_ADDR (uint32_t)0x0801F808 //bin文件大小存放的地址
uint32_t Updatepara = 0; //定义更新参数在flash中的位置
typedef void (*piapfunc)(void); //定义一个函数类型的参数.
void iap_load_app(uint32_t JumpAddress)
{
piapfunc Jump_To_Application;
__disable_irq();
/* Check if the top address of the stack is legal*/
if (((*(__IO uint32_t*)APP_BASE_ADDR)&0x2FFE0000)==0x20000000)
{
DEBUG("goto APP Start...\r\n");
// HAL_Delay(100);
JumpAddress = *(__IO uint32_t*)(APP_BASE_ADDR + 4);
/* Jump to user application */
Jump_To_Application = (piapfunc)JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APP_BASE_ADDR);
__enable_irq();
Jump_To_Application();
}
else
{
printf("\r\n No APP found!!!\r\n");
}
}
int main(void)
{
uint32_t bin_total_len = 0; //定义固件总大小
// uint32_t recv_total_bin_len = 0; //需要接收的总固件的大小
uint32_t recv_bin_len = 0; //已经接收的固件大小
/* USER CODE BEGIN SysInit */
HAL_Init();
SystemClock_Config();
LL_mDelay(500);
/* 初始化外围配置 */
MX_GPIO_Init();
MX_USART2_UART_Init();
printf("\r\n Bootloader function!!\r\n");
CAN1_Config();
HAL_FLASH_Unlock();
/* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
HAL_FLASH_Lock();
uprintf("p_Updatepara1:0x%x\r\n\r\n",*((uint32_t*)PAPER_FLAG_UPSTART_ADDR));
uprintf("PAPER_BIN_LEN:0x%x--%dk\r\n\r\n ",*((uint32_t*)(PAPER_FLAG_UPSTART_ADDR+8)),*((uint32_t*)(PAPER_FLAG_UPSTART_ADDR+8))/1024);
Updatepara = *((uint32_t*)PAPER_FLAG_UPSTART_ADDR);
uprintf("Updatepara3:0x%08x\r\n\r\n ",Updatepara);
if(Updatepara == 0x66668888)//需要更新固件,接收固件保存至备份区
{
/* 保存bin文件长度 */
bin_total_len = *((uint32_t *)(PAPER_BIN_LEN_ADDR));
printf("\r\n bin_total_len:%d\r\n",bin_total_len);
if(bin_total_len > APP_CODE_MAX) //如果固件长度超过存储空间 发送error类 擦除升级标志 复位
{
// SendError(ERR_BIN_LARGE,40); //固件太大 存不下
printf("\r\n Updating firmware file too large \r\n");//更新文件过大
goto pp1;
}
/* 更新flash中固件升级标志位状态改为正在升级 */
HAL_FLASH_Unlock();
/* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
MY_FLASH_Erase(PAPER_FLAG_UPSTART_ADDR,1);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,PAPER_FLAG_UPSTART_ADDR,0x11112222);
HAL_FLASH_Lock();
/* 擦除备份区flash数据 */
printf("\r\n Start erase falsh!\r\n");
HAL_FLASH_Unlock();
/* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
MY_FLASH_Erase(UPDATE_CODESAVE_ADDR,APP_CODE_MAX/2048);
HAL_FLASH_Lock();
printf("\r\n Erase compelte!\r\n");
printf("\r\n Waiting to receive updated firmware package \r\n");
/* 发送Bootloader准备接收数据标志 */
// CAN1_Send_Msg();//发送Bootloader准备完毕,可以发送固件包
/* 等待接受更新固件包并写入备份区 */
while(1)
{
if(Rx_Complete_State == 1)//接收到一次固件包
{
Rx_Complete_State = 0;
HAL_FLASH_Unlock();
/* 接收到一次写入备份区 */
uint64_t data_to_write = 0;
printf("\r\n receive CAN_RxData: 0x%16llx\r\n\r\n",CAN_RxData);
// data_to_write = ((CAN_RxData & 0x00000000ffffffff) << 32) | (CAN_RxData >> 32);
data_to_write = CAN_RxData;
printf("data_to_write: 0x%16llx\r\n\r\n",data_to_write);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,UPDATE_CODESAVE_ADDR+recv_bin_len,data_to_write);
HAL_FLASH_Lock();
printf("\r\n Program addr: 0x%08x\r\n",UPDATE_CODESAVE_ADDR+recv_bin_len);
/* 校验接收数据错误或flash写入错误 */
uint64_t CheckData = ((uint64_t)(*(uint32_t*)(UPDATE_CODESAVE_ADDR+recv_bin_len+4)) << 32) | (*(uint32_t*)(UPDATE_CODESAVE_ADDR+recv_bin_len)) ;
if(CheckData != CAN_RxData)
{
printf("\r\n Check data fail,addr:0x%08x\r\n",UPDATE_CODESAVE_ADDR+recv_bin_len);
printf("CheckData:0x%llx \r\n data_to_write: 0x%llx\r\n",CheckData,CAN_RxData);
//发送error类 数据出错 需要重发
// SendError(ERR_CALC_FAIL,CLASS_UPDATE_DATA_INDEX);
}
recv_bin_len = recv_bin_len+8;//一次接受8个字节(64位)
printf("\r\n recv cuccess,recv_total_bin_len:%d\r\n",recv_bin_len);
uint8_t Return_Buf[] = {0xFF,0x4A,0x5A,0xA5,0xFF,0x4A,0x5A,0xA5};
CAN1_Send_Msg(0x68,Return_Buf,8);//告诉上位机单次接收完成,准备接受下次数据
}
else if(Rx_Complete_State == 0xF)//固件包全部接收完成 /* 拷贝备份区数据至app */
{
// Rx_Complete_State = 0;
HAL_FLASH_Unlock();
/* Clear All pending flags 清除所有错误标志(如果不清除会导致写失败)*/
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
MY_FLASH_Erase(APP_BASE_ADDR,APP_CODE_MAX/2048);
/* 拷贝备份区固件至app */
uint64_t data_to_write = 0;
printf("\r\n Copy the firmware of the backup area to the app\r\n");
for(uint32_t i=0;i<recv_bin_len;i+=8)
{
data_to_write = ((uint64_t)*((uint32_t *)(UPDATE_CODESAVE_ADDR+i+4)) << 32) | ((uint64_t)*((uint32_t *)(UPDATE_CODESAVE_ADDR+i)));
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,APP_BASE_ADDR+i,data_to_write);
}
for(uint32_t i=0;i<recv_bin_len;i+=4)
{
printf("\r\n addr:0x%08x data:0x%08x \r\n",APP_BASE_ADDR+i,*((uint32_t *)(APP_BASE_ADDR+i)));
}
/* Clear All pending flags */
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
MY_FLASH_Erase(PAPER_FLAG_UPSTART_ADDR,1);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,PAPER_FLAG_UPSTART_ADDR,0x88886666);//跟新完之后这里改变标志位
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,(PAPER_FLAG_UPSTART_ADDR+8),0x8000);
HAL_FLASH_Lock();
printf("\r\n Successfully copied to the app \r\n");
printf("FLAG_UPSTART:%x\r\n",*((uint32_t*)PAPER_FLAG_UPSTART_ADDR));
HAL_Delay(100);
printf("\r\n iap_load_app! \r\n");
iap_load_app(APP_BASE_ADDR);
//复位 运行bootloaderr
// printf("\r\n system reset!\r\n");
// NVIC_SystemReset();
}
}
}
else if(Updatepara == 0x88886666)//不需要更新固件 直接运行app
{
printf("\r\n iap_load_app! \r\n");
iap_load_app(APP_BASE_ADDR);
}
else
{
iap_load_app(APP_BASE_ADDR);
}
pp1: HAL_FLASH_Unlock();
/* Clear All pending flags */
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
MY_FLASH_Erase(PAPER_FLAG_UPSTART_ADDR,1);
HAL_FLASH_Lock();
HAL_Delay(100);
//复位 运行bootloaderr
// printf("\r\n system reset!\r\n");
// NVIC_SystemReset();
iap_load_app(APP_BASE_ADDR);
}
bin文件生成
$K\ARM\ARMCC\bin\fromelf.exe --bin --output=Bin\@L.bin !L
APP代码设计
APP代码是用户功能代码,实现业务逻辑,本次测试用的比较简单,接收到APP代码之后,会自动重启,更新APP区代码
SCB->VTOR = FLASH_BASE | 0x5000;//设置中断偏移