单片机IAP

本文详细介绍了STM32F4设备中实现In-ApplicationProgramming(IAP)的过程,包括Bootloader的编写,涉及Flash读写函数,IAP程序的实现,以及APP程序的配置。通过IAP,可以在设备运行期间更新固件,而无需外部设备。文章还提到了STM32F0的修改方法,并提供了相关代码示例。
摘要由CSDN通过智能技术生成


前言

主要介绍IAP,以及怎么实现


一、IAP简介

  IAP(In Application Programming)即在线应用编程,IAP 是用户自己的程序在运行过程中对Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。
  我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32F4 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序。第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。

  这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:

①检查是否需要对第二部分代码进行更新
②如果不需要更新则转到
③执行更新操作
④跳转到第二部分代码执行

  当加入 IAP 程序之后,程序运行流程如图所示:
在这里插入图片描述
  在上图所示流程中,STM32F4 复位后,先是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示;在执行完 IAP 以后(即将新的 APP 代码写入 STM32F4的 FLASH,灰底部分。新程序的复位中断向量起始地址为0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示。
  在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。

二、Bootloader编写

2.1 Flash读写

  由于程序最终烧写到内部flash里,所以IAP涉及到flash的读写

#define STM32_FLASH_SIZE 256 	 		//所选STM32的FLASH容量大小(单位为K)
#define STM32_FLASH_BASE 0x08000000 	//STM32 FLASH的起始地址

#if STM32_FLASH_SIZE<256
#define STM_SECTOR_SIZE 1024 //字节
#else 
#define STM_SECTOR_SIZE	2048
#endif	

u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节

//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数
void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)   	
{
	u16 i;
	for(i=0;i<NumToRead;i++)
	{
		pBuffer[i]=*(vu16*)ReadAddr;//读取2个字节.
		ReadAddr+=2;//偏移2个字节.	
	}
}

//不检查的写入
//WriteAddr:起始地址
//pBuffer:数据指针
//NumToWrite:半字(16位)数   
void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
{ 			 		 
	u16 i;
	for(i=0;i<NumToWrite;i++)
	{
		FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
	    WriteAddr+=2;//地址增加2.
	}  
} 

//从指定地址开始写入指定长度的数据
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
{
	u32 secpos;	   //扇区地址
	u16 secoff;	   //扇区内偏移地址(16位字计算)
	u16 secremain; //扇区内剩余地址(16位字计算)	   
 	u16 i;    
	u32 offaddr;   //去掉0X08000000后的地址
	if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
	FLASH_Unlock();						//解锁
	offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址.
	secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址  0~127 for STM32F103RBT6
	secoff=(offaddr%STM_SECTOR_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)
	secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   
	if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
	while(1) 
	{	
		STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
			for(i=0;i<secremain;i++)//复制
			{
				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
			}
			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
		}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(NumToWrite==secremain)break;//写入结束了
		else//写入未结束
		{
			secpos++;				//扇区地址增1
			secoff=0;				//偏移位置为0 	 
		   	pBuffer+=secremain;  	//指针偏移
			WriteAddr+=secremain;	//写地址偏移	   
		   	NumToWrite-=secremain;	//字节(16位)数递减
			if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
			else secremain=NumToWrite;//下一个扇区可以写完了
		}	 
	};	
	FLASH_Lock();//上锁
}

2.2 IAP程序

#define FLASH_APP1_ADDR		0x08010000  	//第一个应用程序起始地址(存放在FLASH)
typedef  void (*iapfun)(void);				//定义一个函数类型的参数.   
iapfun jump2app; 
u16 iapbuf[1024];  
 
//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
	u16 t;
	u16 i=0;
	u16 temp;
	u32 fwaddr=appxaddr;//当前写入的地址
	u8 *dfu=appbuf;
	for(t=0;t<appsize;t+=2)
	{						    
		temp=(u16)dfu[1]<<8;
		temp+=(u16)dfu[0];	  
		dfu+=2;//偏移2个字节
		iapbuf[i++]=temp;	    
		if(i==1024)
		{
			i=0;
			STMFLASH_Write(fwaddr,iapbuf,1024);	
			fwaddr+=2048;//偏移2048  16=2*8.所以要乘以2.
		}
	}
	if(i)STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.  
}

//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{ 
		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		__set_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}		 

  IAP运行和跳转,其中USART_RX_BUF为串口接收APP缓存,applenth为APP长度

if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{	 
	iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   
	printf("固件更新完成!\r\n");	
}
printf("开始执行FLASH用户代码!!\r\n");
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{	 
	iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}

2.3 Bootloader程序配置

  这里Bootloader程序大概38K,最好不要超过0x10000即64K字节,因为程序是从0X08000000开始,而APP程序配置是从0X08010000开始。当然也可以减少Bootloader程序大小,增大APP空间
在这里插入图片描述

三、APP程序配置

  app部分如果实验用,可以就用点灯尝试

3.1 APP 程序起始地址设置

  点击 Options for Target→Target 选项卡,设置IROM1起始地址(Start)为 0X08010000,留给APP用的FLASH空间(Size)只有 0X100000-0X10000=0XF0000(960K 字节)大小
在这里插入图片描述

3.2 中断向量表的偏移量设置

  在系统启动的时候,会首先调用 SystemInit 函数初始化时钟系统,同时SystemInit 还完成了中断向量表的设置,如下所示

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 

  VTOR寄存器存放的是中断向量表的起始地址,可以在 FLASH APP 的main函数最开头处添加如下代码实现中断向量表的起始地址的重设:

SCB->VTOR = FLASH_BASE | 0x10000; /* Vector Table Relocation in Internal FLASH. */	 

3.3 设置编译后生成.bin 文件

  点击Options for Target→User选项卡,在After Build/Rebuild 栏,勾选 Run #1,并写入下行脚本,这样就可以使用MDK自带的fromelf.exe 转换工具,将.axf 文件转换成.bin 文件。

fromelf.exe --bin -o "$L@L.bin" "#L"

在这里插入图片描述

  最后用串口助手与板子连接,将.bin文件通过串口发送过去
在这里插入图片描述

四、IAP补充

  上面是M4内核的方法,M0要修改

4.1 GD32修改方法

  1. 重印射基地址
#define SYSCFG_MemoryRemap_Flash                ((uint8_t)0x00)
#define SYSCFG_MemoryRemap_SystemMemory         ((uint8_t)0x01)
#define SYSCFG_MemoryRemap_SRAM                 ((uint8_t)0x03)

#define CPUID_STM32F0xx		0x410cc200
#define CPUID_STM32E230xx	0x411cd200

#define VECTOR_SIZE 0xB4
#define DEFAULT_APP_START_ADDR	0x08001800		//默认APP起始地址
#define DEFAULT_APP_MAX_SIZE		0x2800		//默认APP空间54K

static void GD32E230_VTORRemap()
{
	if(SCB->CPUID == CPUID_STM32F0xx)
		{
			//注:F0系列单片机没有VOTR寄存器,故此处将中断向量表映射在SRAM处
			//memcpy((void*)0x20000000, (void*)( FLASH_BASE | 0x02000), VECTOR_SIZE);		
			//SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
			memcpy((void*)0x20000000, (void*)(DEFAULT_APP_START_ADDR), VECTOR_SIZE);		
			SYSCFG->CFGR1 |= ((uint32_t)0x00000003);
		}
		else		
			SCB->VTOR = DEFAULT_APP_START_ADDR;		//GD32E230有SCB->VTOR寄存器
}

/**
  * @brief  控制程序跳转到指定位置开始执行 。
  * @param  Addr 程序执行地址。
  * @retval 程序跳转状态。
  */
void JumpToApplication(uint32_t Addr)
{
    static pFunction Jump_To_Application;
    __IO uint32_t JumpAddress;
    /* Test if user code is programmed starting from address "ApplicationAddress" */
    if (((*(__IO uint32_t *)Addr) & 0x2FFE0000 ) == 0x20000000)		//检查栈顶地址是否合法.
    {
//        RCC_DeInit(); //关闭外设
//        DMA_DeInit(DMA1_Channel5);
//		  DMA_DeInit(DMA1_Channel4);
//        USART_DeInit(USART1);
//        TIM_DeInit(TIM3);
        /* 关闭滴答定时器且禁止中断  */
        SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;

        /* Jump to user application */
        JumpAddress = *(__IO uint32_t *) (Addr + 4);									//用户代码区第二个字为程序开始地址(复位地址)
        Jump_To_Application = (pFunction) JumpAddress;
        __set_PRIMASK(1);																						//关闭所有中断
        /* Initialize user application's Stack Pointer */
        __set_MSP(*(__IO uint32_t *)Addr);														//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
        Jump_To_Application();
    }
    else
    {
        Uart.Fault = fu_no_app;
    }
}
  1. 函数调用
void main(void)
{
	GD32E230_VTORRemap();
	//条件成立执行以下
	JumpToApplication(DEFAULT_APP_START_ADDR);
}

4.2 STM32F0修改方法

  1. 创建函数
#define APPLICATION_ADDRESS     (uint32_t)0x0800A000

#define SYSCFG_MemoryRemap_Flash                ((uint8_t)0x00)
#define SYSCFG_MemoryRemap_SystemMemory         ((uint8_t)0x01)
#define SYSCFG_MemoryRemap_SRAM                 ((uint8_t)0x03)

#if   (defined ( __CC_ARM ))
__IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
#elif (defined (__ICCARM__))
#pragma location = 0x20000000
__no_init __IO uint32_t VectorTable[48];
#elif defined   (  __GNUC__  )
__IO uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));
#elif defined ( __TASKING__ )
__IO uint32_t VectorTable[48] __at(0x20000000);
#endif

//IAP函数申明
typedef  void (*pFunction)(void);
pFunction Jump_To_Application;
void APP_Restart();

//IAP地址修改
static void STM32F0xx_VectorRemap()
{
    uint32_t i = 0;
    /* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/
    /* Copy the vector table from the Flash (mapped at the base of the application
    load address 0x0800A000) to the base address of the SRAM at 0x20000000. */
    for(i = 0; i < 48; i++)
    {
        VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
    }

    /* Enable the SYSCFG peripheral clock*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    /* Remap SRAM at 0x00000000 */
    SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
}

//IAP函数
void APP_Restart()
{
        uint32_t JumpAddress;
        /* Jump to user application */
        JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
        Jump_To_Application = (pFunction) JumpAddress;

        /* Initialize user application's Stack Pointer */
        __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);

        /* Jump to application */
        Jump_To_Application();
}
  1. 主函数调用
void main(void)
{
	STM32F0xx_VectorRemap();
	/*一系列初始化*/
	while(1)
	{
		if(/*条件成立*/)
			APP_Restart();
	}
}
  • 6
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 51单片机iap(In-Application Programming)在线升级是一种在单片机系统中通过编程的方式对在线设备进行升级的技术。 通过IAP技术,在单片机系统运行的过程中,对内部存储器进行读取和修改,并将新的程序代码下载到内存中,从而实现在线升级。IAP不需要擦除内存,能够有效地避免数据丢失,提高系统的稳定性和安全性。 51单片机IAP在线升级技术可以在系统运行时进行程序升级,更新新的功能,增强系统性能,并具有快速、灵活、方便的特点。但是,IAP技术需要对硬件进行支持,如双系统设计和FLASH存储器等。 因此,在进行51单片机IAP在线升级时,需要根据硬件支持条件选择不同的技术实现方案。同时,在进行在线升级时,需要注意保护程序的完整性和安全性,以免出现不良影响。 总之,51单片机IAP在线升级技术是一种便捷、灵活、高效、先进的升级方式,广泛应用于各类嵌入式系统中,有利于实现软件功能的快速升级和系统的优化。 ### 回答2: 51单片机指的是一种基于Intel 8051指令集的单片机,它是广泛应用于嵌入式系统中的一种芯片。而IAP(In-Application Programming)在线升级则是指在不需要任何外部设备的情况下,通过程序自身对芯片中的代码进行更新。 在51单片机中,实现IAP在线升级需要结合程序设计和硬件电路设计两个方面。首先,程序中需要预留出一定的存储空间,用于存储升级程序的代码。其次,需要设计一个与单片机相连的串行接口,如UART或SPI等,以便实现与外界通信,接收升级程序的数据。 整个IAP升级的流程如下:首先,单片机在运行过程中通过串口接收到升级程序的数据,将数据暂存至内部存储器。然后,单片机停止当前程序执行,切换到专门的IAP程序,读取内部存储器中的升级程序数据,并将其写入到指定的程序存储区。最后,单片机重新启动程序执行,完成升级。 通过IAP在线升级技术,可以大大简化芯片升级的操作流程,并节省升级的时间和成本,适用于各种嵌入式场景的应用。 ### 回答3: 51单片机iap在线升级是通过网络或串口实现对51单片机程序的在线更新和升级。它可以通过服务器端的推送或客户端的请求,将新的程序固件通过网络传送到目标单片机,然后在单片机中通过IAP(In-Application Programming)实现程序的自我升级。这种在线升级方式可以极大地简化单片机的维护和升级,提升单片机的灵活性和可用性。它适用于广泛的应用场景,如远程设备控制、智能家居、远程医疗等。同时,为了保证在线升级的安全性和准确性,需要特别注意数据传输的可靠性和过程的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别问,问就是全会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值