STM32远程升级设计

STM32 的内部闪存地址起始于0x08000000。一般情况下,程序从此地址开始写入。

由于STM32 内部是通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004。

中断代码响应过程简单表述如下:

1、当中断来临,STM32 的内部硬件机制亦会自动将PC 指针定到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序;

2、STM32 在复位后,先从0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序;

3、在复位中断服务程序执行完之后,会跳转到我们的main 函数;

4、在main 函数执行过程中,如果收到中断请求(发生重中断),此STM32 强制将PC 指针指回中断向量表处;

5、根据中断源进入相应的中断服务程序;在执行完中断服务程序以后,程序再次返回main 函数执行。

STM32管理升级的代码段习惯沿用arm的称呼,将其称为bootlader,或者IAP程序。这段代码是专门用于升级的(本身也可以把这段代码融入到正式程序中(APP程序),但是为了方便,本文将IAP和APP分开使用,防止代码交叉,升级失败处理困难)。

加入IAP程序后,执行过程如下:

1、STM32 复位后,还是从0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序;

2、在运行完复位中断服务程序之后跳转到bootlader 的main 函数;

3、在执行完bootlader以后,跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main 函数。

如此,远程升级程序必须满足两个要求:
1、 新程序必须在bootader 程序之后的某个偏移量为x 的地址开始。
2、 必须将新程序的中断向量表相应的移动,移动的偏移量为x。

 IAP程序实现的功能(本质上该程序也是一段单片机可运行的代码,其中也有main()函数):

1、判断是否需要升级,如果升级,进入升级流程,如果不升级,跳转到APP执行;

2、使用大数组,接收升级文件数据,每一次接收都应该有数据校验;

3、将现在APP区间的代码数据备份到flash;

4、把大数组中的数据写到APP的地址中;

5、防止升级失败,应该设置APP无法正常运行的情况下,重新恢复原程序;

6、在APP代码中添加中断向量表偏移。

下面贴出代码:

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(uint32_t addr) 
{
    MSR MSP, r0 			//set Main Stack value
    BX r14
}

//设置栈顶地址
//addr:栈顶地址
__asm void MSR_PSP(uint32_t addr) 
{
    MSR PSP, r0 			//set process Stack value
    BX r14
}

//开启所有中断
__asm void INTX_ENABLE(void)
{
	CPSIE   I
	BX      LR  
}

//关闭所有中断(但是不包括fault和NMI中断)
__asm void INTX_DISABLE(void)
{
	  CPSID   I
	  BX      LR	  
}
typedef  void (*iapfun)(void);//定义一个函数类型的参数.
iapfun jump2app;

//跳转到应用程序段
//appxaddr:用户代码起始地址.
uint8_t iap_load_app(uint32_t appxaddr)
{
	if(((*(__IO uint32_t*)appxaddr)&0x2FF00000)==0x20000000)//检查栈顶地址是否合法.
	{ 	
		printf("jump to APP...\r\n");
		jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);//用户代码区第二个字为程序开始地址(复位地址)		
//		MSR_PSP(*(__IO uint32_t*)appxaddr);
//		__set_CONTROL(0);
		MSR_MSP(*(__IO uint32_t*)appxaddr);//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)

		INTX_DISABLE();//关闭全部中断(但是不包括fault和NMI中断)
		//__set_PRIMASK(1);
//		__disable_irq();
		__disable_fault_irq (); 
		jump2app();//跳转到APP
		return 0;
	}
	else
	{
		printf("jump error...\r\n");
		return 1;
	}
}
uint8_t getAppNumber(uint64_t *num)//读取升级标志位,并且擦除该页
{
	uint8_t state = 0;
	uint64_t tmpbuf[4];
	for(int i=0;i<4;i++)
	{
		tmpbuf[i] = *(__IO uint64_t*)(ADDR_UPDATA_START+i*8);
		printf("tmpbuf[%d]:%lld\r\n",i,tmpbuf[i]); 
	}
	if((tmpbuf[0] == (~0x0))&&(tmpbuf[1] == (~0x0)))
	{
		printf("升级标志位中未读取到数据,亦不升级!!!\r\n");
		*num = 0;
		state = 0;
//		return 0;
	}
	else if(tmpbuf[0] == 1)//tmpbuf[0]是升级标志位,tmpbuf[1]是iap重启次数
	{
		state = 1;
	}
	else
		state = 0;
	printf("升级标志位:\t%d(0不升级,1升级)\r\n",state);
	*num = tmpbuf[1] + 1;
	tmpbuf[0] = 0;
	tmpbuf[1] = *num;
	printf("IAP跳转次数:\t%lld\r\n",*num);
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
	eraseFlash(ADDR_UPDATA_START,ADDR_UPDATA_END);
	HAL_FLASH_Unlock();
	//tmpbuf[0]是升级标志位,tmpbuf[1]是iap重启次数
	for(int i=0;i<2;i++)
	{
		if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ADDR_UPDATA_START+8*i, (uint64_t)tmpbuf[i]) != HAL_OK)
		{
			HAL_FLASH_Lock();
			return 2;
		}
	}
	HAL_FLASH_Lock();
	return state;
}
//获取当前APP代码的数据量,为备份该数据做准备
uint32_t getValidByte(uint32_t start_addr,uint32_t end_addr)//获取地址中的数据量,单位双字节
{
	uint32_t addrNum = end_addr - start_addr + 1;
	addrNum = addrNum / 8 * 8;
	uint64_t data = 0;
	do
	{
		addrNum -= 8;
		data = *(__IO uint64_t*)(start_addr + addrNum);

	}while((data==(~0x0))&&(addrNum>0));
	return addrNum/8*8+8;
}

//擦除flash
uint8_t eraseUpdateFlash(uint32_t start_addr,uint32_t end_addr)
{
	printf("开始擦除FLASH...");
	__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPTVERR);
	eraseFlash(start_addr,end_addr);
	return 0;
}

//获取已经写入大数组的数据条数,该数组是一个二维数组,返回值是当前存入的数据条数
uint16_t getLine(const uint8_t (*buf)[COLCOUNT],uint32_t length)
{
	int i = 0;
	for(i=0;i

main()函数中在最开始加入如下代码:

SCB->VTOR = FLASH_BASE | 0x4000;//0x4000是偏移地址
__enable_fault_irq();
INTX_ENABLE();

项目地址:https://download.csdn.net/download/sehanlingfeng/12625369

你可能感兴趣的:(STM32开发)