STM32 IAP应用编程个人经验总结

1 篇文章 0 订阅
1 篇文章 0 订阅

STM32 IAP应用编程个人经验总结

这也是本人第一次在CSDN上写的第一篇文章,希望能够对正在或者是准备弄IAP的朋友分享一下个人的小小经验。

IAP是做什么的

也不知道从哪里讲起,思考了许久,决定还是从STM32的启动讲起吧。首先我们知道每个STM32芯片都有BOOT0、BOOT1两个引脚,这两个引脚在芯片复位时的电平状态就决定了复位后程序的起始执行区域。如下:
BOOT0= 1,BOOT1 = 1时,从内置SRAM启动,在这个模式下运行烧写程序,掉电就没了,所以一般只用来调试。
BOOT0 = 1,BOOT1 = 0时,从系统存储器启动,这个是由厂家烧入并锁死,我们无法对其进行更改。
BOOT0 = 0,BOOT1 = x时,从用户闪存(FLASH)启动,这个模式就是我们程序在正常情况下的工作模式,不会因为掉电而丢失。我们本次讲解的IAP就是针对于FLASH编程。

每个STM32芯片都有一块FLASH区域,常见的大小有32k,64k,1m等,我所使用的STM32F207ZGT6的FLASH是1m大小的。

我们在对STM32进行裸机开发的过程中,通常都是使用ST-LINK或JLINK下载器或者通过FlyMcu使用USB烧入程序,

IAP:全称是在线应用编程,就是当我们需要更新程序时,不使用上述的方法,而是通过正在运行的程序来进行程序的更新,所以这里就需要引入一个新的东西,叫APP程序,APP就是我们正常运行的程序,而IAP就是对APP程序进行更新升级的程序。在正常情况下我们运行的是APP程序,当我们对APP程序进行了改进后,就需要重新的烧写进芯片,那么这个时候就通过IAP程序来将我们的APP程序烧写进FLASH,在烧写完成后,再自动跳转会APP程序正常的运行。
注:写入FALSH的程序代码是bin文件,bin文件的获取方法

MDK(KEIL5)如何生成.bin文件(亲测可用)

可能有些人疑惑既然能直接烧写进芯片,为什么要弄一个IAP程序来多此一举呢?IAP一般是已经做好的产品在发布后,在需要更新时,就通过预留的通信方式来对产品进行更新操作。就比如我们使用的华为、小米手机,也会不时的更新系统,厂家不可能挨个的来给我们的手机来进行升级操作,所以呢就可以通过IAP来对其进行操作。

IAP的主要流程如下:
(1)正常情况的运行APP程序
(2)到我们需要进行更新APP程序时,进入IAP程序
(3)进行更新操作,再更新完成后,重新运行APP程序
注:第一个IAP程序只能通过下载器或者FlyMcu烧写方式烧写到地址0X08000000。

IAP的前提准备

既然我们要将APP程序写入FLASH中,那么我们就要了解下FLASH。首先FLASH区域被划分成若干个扇区(页),如下图所示:
STM32F207ZGT6FLASH区域划分
FLASH区域在没有写入东西的情况下是全为1的,也就是说所有没使用过的地址按1字节读取都是0XFF,所以FLASH的第一个特性就是只能写0,不能写1(算是一个小重点),我们在FLASH中要至少需要划分IAP程序写入区域(从0X08000000开始)和APP程序写入区域,针对每个程序写入区域的大小我们需要提前准备好,比如我是使用的扇区0,1来存放IAP程序代码,扇区5、6分别存放APP、APP不同版本程序代码(大小一定要足够)。下面是STM32F207ZGT6的FLASH写入操作函数

// 根据传入的地址返回对应的扇区编号
//Address:传入的地址
//返回传入地址对应的扇区编号
static uint32_t GetSector(uint32_t Address)
	{
		uint32_t sector = 0;
		
		if((Address < ADDR_FLASH_SECTOR_1) && (Address >= ADDR_FLASH_SECTOR_0))
		{
			sector = FLASH_Sector_0;  
		}
		else if((Address < ADDR_FLASH_SECTOR_2) && (Address >= ADDR_FLASH_SECTOR_1))
		{
			sector = FLASH_Sector_1;  
		}
		else if((Address < ADDR_FLASH_SECTOR_3) && (Address >= ADDR_FLASH_SECTOR_2))
		{
			sector = FLASH_Sector_2;  
		}
		else if((Address < ADDR_FLASH_SECTOR_4) && (Address >= ADDR_FLASH_SECTOR_3))
		{
			sector = FLASH_Sector_3;  
		}
		else if((Address < ADDR_FLASH_SECTOR_5) && (Address >= ADDR_FLASH_SECTOR_4))
		{
			sector = FLASH_Sector_4;  
		}
		else if((Address < ADDR_FLASH_SECTOR_6) && (Address >= ADDR_FLASH_SECTOR_5))
		{
			sector = FLASH_Sector_5;  
		}
		else if((Address < ADDR_FLASH_SECTOR_7) && (Address >= ADDR_FLASH_SECTOR_6))
		{
			sector = FLASH_Sector_6;  
		}
		else if((Address < ADDR_FLASH_SECTOR_8) && (Address >= ADDR_FLASH_SECTOR_7))
		{
			sector = FLASH_Sector_7;  
		}
		else if((Address < ADDR_FLASH_SECTOR_9) && (Address >= ADDR_FLASH_SECTOR_8))
		{
			sector = FLASH_Sector_8;  
		}
		else if((Address < ADDR_FLASH_SECTOR_10) && (Address >= ADDR_FLASH_SECTOR_9))
		{
			sector = FLASH_Sector_9;  
		}
		else if((Address < ADDR_FLASH_SECTOR_11) && (Address >= ADDR_FLASH_SECTOR_10))
		{
			sector = FLASH_Sector_10;  
		}
		else if((Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11))
		{
			sector = FLASH_Sector_11;  
		}
		
		return sector;
}
var foo = 'bar';

#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base address of Sector 0, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base address of Sector 1, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base address of Sector 2, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base address of Sector 3, 16 Kbytes   */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base address of Sector 4, 64 Kbytes   */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base address of Sector 5, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base address of Sector 6, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base address of Sector 7, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_8     ((uint32_t)0x08080000) /* Base address of Sector 8, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_9     ((uint32_t)0x080A0000) /* Base address of Sector 9, 128 Kbytes  */
#define ADDR_FLASH_SECTOR_10    ((uint32_t)0x080C0000) /* Base address of Sector 10, 128 Kbytes */
#define ADDR_FLASH_SECTOR_11    ((uint32_t)0x080E0000) /* Base address of Sector 11, 128 Kbytes */
#define FLASH_END_ADDR 					((uint32_t)0x08100000) /* 								END		  						  */
// 根据传入的扇区号返回对应的扇区起始地址
//sector:传入的扇区号
//返回传入地址对应的扇区起始地址
static uint32_t GetStartAddr(uint32_t sector)
{
    u32 secstartaddr;
    switch(sector)
    {
        case FLASH_Sector_0:secstartaddr = ADDR_FLASH_SECTOR_0;break;
        case FLASH_Sector_1:secstartaddr = ADDR_FLASH_SECTOR_1;break;
        case FLASH_Sector_2:secstartaddr = ADDR_FLASH_SECTOR_2;break;
        case FLASH_Sector_3:secstartaddr = ADDR_FLASH_SECTOR_3;break;
        case FLASH_Sector_4:secstartaddr = ADDR_FLASH_SECTOR_4;break;
        case FLASH_Sector_5:secstartaddr = ADDR_FLASH_SECTOR_5;break;
        case FLASH_Sector_6:secstartaddr = ADDR_FLASH_SECTOR_6;break;
        case FLASH_Sector_7:secstartaddr = ADDR_FLASH_SECTOR_7;break;
        case FLASH_Sector_8:secstartaddr = ADDR_FLASH_SECTOR_8;break;
        case FLASH_Sector_9:secstartaddr = ADDR_FLASH_SECTOR_9;break;
        case FLASH_Sector_10:secstartaddr = ADDR_FLASH_SECTOR_10;break;
        case FLASH_Sector_11:secstartaddr = ADDR_FLASH_SECTOR_11;break;
    }
    return secstartaddr;
}
//从指定地址开始写入指定长度的数据		不判断跨扇区
//WriteAddr:起始地址(此地址必须为2的倍数!!)
//pBuffer:数据指针
//NumToWrite:本次要写入的数据字数
//size:本次写入方式-->8位/16位/32位
void Flash_Write(u32 WriteAddr,u8 *pBuffer,u32 NumToWrite,u8 size)
{	
	FLASH_Status flash_status;
    u32 length = 0; //成功写入数据字数
    u32 secpos;     //当前写入扇区号
    uint32_t secstartAddress = 0;         //扇区起始地址
    if((WriteAddr<ADDR_FLASH_SECTOR_0)||(WriteAddr>=FLASH_END_ADDR))return;//非法地址
    
    secpos=GetSector(WriteAddr);                    //扇区号 0~11 for STM32F207ZGT6		
    secstartAddress = GetStartAddr(secpos);          //扇区起始地址

    FLASH_Unlock();     //解锁
    /* 清除所有挂起标志位 */
	FLASH_ClearFlag(FLASH_FLAG_EOP|FLASH_FLAG_OPERR|FLASH_FLAG_WRPERR|FLASH_FLAG_PGAERR|FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);  
 
    if(WriteAddr == secstartAddress)     //需要擦除扇区
    {
       if (FLASH_EraseSector(secpos, VoltageRange_3) != FLASH_COMPLETE)//擦除这个扇区
       {
											/*扇区擦除失败*/												        
       }
    }
    
    if(size == 8)
		{	
				 while(length <NumToWrite)  
				 {
						if((flash_status = FLASH_ProgramByte(WriteAddr, *(u8 *)pBuffer)) == FLASH_COMPLETE)
						{
							WriteAddr = WriteAddr + 1;
							pBuffer = pBuffer + 1;
							length ++;
						}
						else
						{ 
									/*扇区写入失败*/												
						}
				 }
		}
		else if(size == 16)
		{
				 while(length <NumToWrite)  
				 {
						if((flash_status = FLASH_ProgramHalfWord(WriteAddr, *(u16 *)pBuffer))== FLASH_COMPLETE)
						{
							WriteAddr = WriteAddr + 2;
							pBuffer = pBuffer + 2;
							length ++;
						}
						else
						{ 			
								/*扇区写入失败*/
						}
				 }
		}
		else if(size == 32)
		{
				 while(length <NumToWrite)  
				 {
						if((flash_status = FLASH_ProgramWord(WriteAddr, *(u32 *)pBuffer)) == FLASH_COMPLETE)
						{
							WriteAddr = WriteAddr + 4;
							pBuffer = pBuffer + 4;
							length ++;
						}
						else
						{ 
										/*扇区写入失败*/
						}
				 }
		}		
    FLASH_Lock();	//上锁  
}
//从指定地址开始读出指定长度的数据
//ReadAddr:起始地址
//pBuffer:数据指针
//NumToWrite:字数(32位)
void Flash_Read_32(u32 ReadAddr,u8 *pBuffer,u32 NumToWrite)
{
  	u32 i;
		for(i=0;i<NumToWrite;i++)
		{
				*(u32 *)pBuffer=*(u32 *)ReadAddr;//读取4个字节.
				ReadAddr+=4;//偏移4个字节.
				pBuffer+=4; //偏移4个字节.
		}
}

将APP程序写入指定的FLASH地址,那么IAP程序的第一个大问题就已经解决了。

IAP程序跳转至APP

通过上面的FLASH操作函数,我们已经能够成功的将APP程序写入FLASH地址中,那我们现在要做的就是离开IAP程序,跳转至APP程序中,

typedef void (*iapfun)(void);       //定义一个函数类型的参数.

iapfun jump2app;

//JumpAddress:跳转地址
void iap_load_app(u32 JumpAddress)
{
		if(((*(vu32*)JumpAddress)&0x2FFE0000)==0x20000000)      //检查栈顶地址是否合法.
        {         
            // 代码的flash起始地址加4,就是Reset Handler的函数地址
            jump2app =(iapfun) *(__IO uint32_t*)(JumpAddress+4);   //用户代码区第二个字为程序开始地址(复位地址)       
            __set_MSP(*(__IO uint32_t*)JumpAddress); // 设置栈顶地址                               //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)  
            jump2app();    // 开始运行APP的Reset Handler
            
        }
}

到这里我们的IAP的流程就已经完成了,做到这里就已经能够进行跳转了,但是在我调试过程中发现了一个问题,那就是跳转过后,我的所有中断都无法响应了,这就使我还没来得及兴奋就进入失落?后来在多方查阅资料后发现是因为IAP中使用了部分中断,所以当跳转到APP程序中后,在触发了该中断时却在中断向量表找不到对应的中断响应函数了,所以程序就此崩溃了。这个问题也曾困扰我许久,最多的说法是在跳转至APP中后,进行中断向量表的偏移,也就是

//Offset = APP程序在FLASH中的写入首地址 - 0X08000000
NVIC_SetVectorTable(NVIC_VectTab_FLASH, Offset);    //重定义中断向量表起始地址

然而在我进行了这个操作后仍然无法触发中断,于是我又找到了一个新的解决方法,那就是在FLASH中存放一个标志位,在IAP的程序开始首先读取该标志位,如果是更新完成或者是正常运行的标志,就直接进行跳转函数,否则才进入升级模式,APP程序写入完成后,将这个标志位置为更新完成,然后通过软件复位重新进入IAP程序。最后完成IAP跳转至APP,同时APP跳转至IAP可直接调用该函数,注意在IAP升级模式需要将中断向量表重新偏移至0x08000000处

//软复位
void SoftReset(void) 
{
		__set_FAULTMASK(1);      // 关闭总中断 
    NVIC_SystemReset();// 复位    
}
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0);    //重定义中断向量表起始

到这里IAP基本上就已经完成了。

最后,在整理出一些需要注意的地方:
1.FLASH只能写入0,所以在需要对FLASH区域的数据进行更改时,建议先将该区域存放的有效数据读取出来,然后进行擦除操作,再存放进去。
2.IAP拥有2个模式,直接进入APP的正常运行状态、需要对APP程序进行升级的状态。所以在APP跳转至IAP之前,需要将标志位置为升级模式;同理,在升级操作完成后,也需要将该标志位置为升级完成或者是正常运行状态。(1需要注意的原因)。
3.IAP与APP之间跳转都是通过软件复位,进入到IAP程序,然后根据标志位执行IAP或APP程序。
4.IAP程序获取APP的BIN文件的方法,可以在传输BIN文件之前,先将文件的大小告知下位机,然后在进行BIN文件的传输。(我使用的是CAN通信,CAN通信每一次最多能发送8个字节DATA,文件传输的过程是成功接收到本次数据包后,就向上位机发出接收下一个数据包的请求)

最后放上一张我写代码的思路,希望这篇文章能够对大家起到一点小小的帮助。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值