一、简介
IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。 通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
1)检查是否需要对第二部分代码进行更新 2)如果不需要更新则转到 4) 3)执行更新操作 4)跳转到第二部分代码执行
第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新是再通过第一部分 IAP 代码更新。
我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的,本章我们只讨论两个 APP 程序的情况)。这样我们就是要实现 3 个程序:Bootloader 和 APP1、APP2。
也就是说我们正常运行时,开机进入 Bootloader 程序,从 Bootloader 程序跳转到 APP1 或者 APP2 来执行真正的用户功能。这样在执行 APP1 的时候,如果要升级,程序就会进入 Bootloader ,在 Bootloader 下将需要更新的固件数据写道 APP2 的位置。写完后重启,进入 Bootloader ,用户自行判断运行 APP2 ,即升级成功了。其实APP1和APP2功能一摸一样,只是地址不同而已。
STM32 的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行,本章,我们介绍的两个 APP,全部都用于 FLASH 运行。
我们先来看看 STM32 正常的程序运行流程,如下图所示: STM32 的内部闪存(FLASH)地址起始于 0x08000000,一般情况下,程序文件就从此地址开始写入。此外 STM32 是基于 Cortex-M3 内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,STM32 的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
在上图中,STM32 在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的 main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生重中断),此时 STM32 强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。
当加入 IAP 程序之后,程序运行流程如下图所示: 在上图所示流程中,STM32 复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示,此部分同图 53.1.1 一样;在执行完 IAP 以后(即将新的 APP 代码写入 STM32的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,并且注意到此时 STM32 的 FLASH,在不同位置上,共有两个中断向量表。
在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。 通过以上两个过程的分析,我们知道 IAP 程序必须满足两个要求: 1) 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始; 2) 必须将新程序的中断向量表相应的移动,移动的偏移量为 x;
介绍到此结束,下面让我们到实例中去体验吧。
二、实例代码
2.1 hal库配置
flash的要加上,其中别的没必要就可以删除。
2.2 主要代码
bsp_iap.h
#ifndef __BSP_IAP_H__
#define __BSP_IAP_H__
#include "stm32f1xx_hal.h"
typedef void ( * pIapFun_TypeDef ) ( void );
#define User_Flash
#ifdef User_Flash
#define APP_START_ADDR 0x8010000
#else
#define APP_START_ADDR 0x20001000
#endif
#define APP_FLASH_LEN 15320u
extern struct STRUCT_IAP_RECIEVE
{
uint8_t ucDataBuf[APP_FLASH_LEN];
uint16_t usLength;
} strAppBin;
void IAP_Write_App_Bin( uint32_t appxaddr, uint8_t * appbuf, uint32_t applen);
void IAP_ExecuteApp( uint32_t appxaddr );
#endif
bsp_iap.c
#include "bsp_iap.h"
#include "stm_flash.h"
#if defined ( __CC_ARM )
struct STRUCT_IAP_RECIEVE strAppBin __attribute__((at(0x20001000)))={{0},0};
#elif defined ( __ICCARM__ )
struct STRUCT_IAP_RECIEVE strAppBin;
#endif
static uint16_t ulBuf_Flash_App[1024];
void IAP_Write_App_Bin ( uint32_t ulStartAddr, uint8_t * pBin_DataBuf, uint32_t ulBufLength )
{
uint16_t us, usCtr=0, usTemp;
uint32_t ulAdd_Write = ulStartAddr;
uint8_t * pData = pBin_DataBuf;
for ( us = 0; us < ulBufLength; us += 2 )
{
usTemp = ( uint16_t ) pData[1]<<8;
usTemp += ( uint16_t ) pData[0];
pData += 2;
ulBuf_Flash_App [ usCtr ++ ] = usTemp;
if ( usCtr == 1024 )
{
usCtr = 0;
STMFLASH_Write ( ulAdd_Write, ulBuf_Flash_App, 1024 );
ulAdd_Write += 2048;
}
}
if ( usCtr )
STMFLASH_Write ( ulAdd_Write, ulBuf_Flash_App, usCtr );
}
#if defined ( __CC_ARM )
__asm void MSR_MSP ( uint32_t ulAddr )
{
MSR MSP, r0
BX r14
}
#elif defined ( __ICCARM__ )
void MSR_MSP ( uint32_t ulAddr )
{
asm("MSR MSP, r0");
asm("BX r14");
}
#endif
void IAP_ExecuteApp ( uint32_t ulAddr_App )
{
pIapFun_TypeDef pJump2App;
if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 )
{
pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 );
MSR_MSP( * ( __IO uint32_t * ) ulAddr_App );
pJump2App ();
}
}
stm_flash.h
#ifndef __STMFLASH_H__
#define __STMFLASH_H__
#include "stm32f1xx_hal.h"
#define STM32_FLASH_SIZE 512
#define STM32_FLASH_WREN 1
uint16_t STMFLASH_ReadHalfWord(uint32_t faddr);
void STMFLASH_WriteLenByte(uint32_t WriteAddr, uint32_t DataToWrite, uint16_t Len );
uint32_t STMFLASH_ReadLenByte(uint32_t ReadAddr, uint16_t Len );
void STMFLASH_Write( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite );
void STMFLASH_Read( uint32_t ReadAddr, uint16_t * pBuffer, uint16_t NumToRead );
void Test_Write( uint32_t WriteAddr, uint16_t WriteData );
#endif
stm_flash.c
#include "stm_flash.h"
#if STM32_FLASH_SIZE < 256
#define STM_SECTOR_SIZE 1024
#else
#define STM_SECTOR_SIZE 2048
#endif
#if STM32_FLASH_WREN
static uint16_t STMFLASH_BUF [ STM_SECTOR_SIZE / 2 ];
static FLASH_EraseInitTypeDef EraseInitStruct;
#endif
uint16_t STMFLASH_ReadHalfWord ( uint32_t faddr )
{
return *(__IO uint16_t*)faddr;
}
#if STM32_FLASH_WREN
void STMFLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
{
uint16_t i;
for(i=0;i<NumToWrite;i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
WriteAddr+=2;
}
}
void STMFLASH_Write ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t NumToWrite )
{
uint32_t SECTORError = 0;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint32_t secpos;
uint32_t offaddr;
if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;
HAL_FLASH_Unlock();
offaddr=WriteAddr-FLASH_BASE;
secpos=offaddr/STM_SECTOR_SIZE;
secoff=(offaddr%STM_SECTOR_SIZE)/2;
secremain=STM_SECTOR_SIZE/2-secoff;
if(NumToWrite<=secremain)secremain=NumToWrite;
while(1)
{
STMFLASH_Read(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);
for(i=0;i<secremain;i++)
{
if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;
}
if(i<secremain)
{
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.PageAddress = secpos*STM_SECTOR_SIZE+FLASH_BASE;
EraseInitStruct.NbPages = 1;
HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
for(i=0;i<secremain;i++)
{
STMFLASH_BUF[i+secoff]=pBuffer[i];
}
STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);
}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);
if(NumToWrite==secremain)break;
else
{
secpos++;
secoff=0;
pBuffer+=secremain;
WriteAddr+=secremain;
NumToWrite-=secremain;
if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;
else secremain=NumToWrite;
}
};
HAL_FLASH_Lock();
}
#endif
void STMFLASH_Read ( uint32_t ReadAddr, uint16_t *pBuffer, uint16_t NumToRead )
{
uint16_t i;
for(i=0;i<NumToRead;i++)
{
pBuffer[i]=STMFLASH_ReadHalfWord(ReadAddr);
ReadAddr+=2;
}
}
void Test_Write( uint32_t WriteAddr, uint16_t WriteData )
{
STMFLASH_Write(WriteAddr,&WriteData,1);
}
至于user_iap是我串口数据的处理,这个不涉及IAP功能。 main.c
#define APP_SELECT_ADDR 0x800FFF0
int main()
{
uint16_t g_appSelect[1] = {0x1100};
STMFLASH_Write ( APP_SELECT_ADDR, g_appSelect, 1 );
g_appSelect[0] = 0x1234;
STMFLASH_Read(APP_SELECT_ADDR, g_appSelect, 1 );
printf ( "读 %x,APP\n" ,g_appSelect[0]);
}
? 由 青梅煮久 写于 2021 年 08 月 11 日
|