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<length/COLCOUNT+1;i++)
	{
		if(buf[i][0] != 0xDD)
		{
			break;
		}
	}
	return i;
}

//把大数组内容写入到flash中
uint8_t updataFlash(uint16_t rowSize,uint32_t onceSize,uint32_t start_addr,uint32_t end_addr)//把数据写入flash
{
	HAL_Delay(10);
	printf("开始准备写flash...\r\n");
	HAL_IWDG_Refresh(&hiwdg);	
	uint64_t tmpbuf[128] = {0};
	HAL_FLASH_Unlock();																								//准备写flash,升级
	for(int m=0;m<rowSize;m++)
	{
		for(int j=1;j<onceSize+1;j = j+8)
		{
			tmpbuf[(j-1)/8] = ((uint64_t)updateBuf[m][j+7]<<7*8)|((uint64_t)updateBuf[m][j+6]<<6*8)|((uint64_t)updateBuf[m][j+5]<<5*8)|
												((uint64_t)updateBuf[m][j+4]<<4*8)|((uint64_t)updateBuf[m][j+3]<<3*8)|((uint64_t)updateBuf[m][j+2]<<2*8)|
												((uint64_t)updateBuf[m][j+1]<<1*8)|((uint64_t)updateBuf[m][j+0]<<0*8);
			if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, start_addr+(onceSize)*m+(j-1), (uint64_t)tmpbuf[(j-1)/8]) != HAL_OK)
			{
				HAL_FLASH_Lock();
				printf("flash写入失败。。。\r\n");
				return 1;
			}
		}
	}
	HAL_FLASH_Lock();
	HAL_IWDG_Refresh(&hiwdg);	
	for(int m=0;m<rowSize;m++)
	{
		for(int j=1;j<onceSize+1;j++)
		{
			if(updateBuf[m][j] != *(__IO uint8_t*)(start_addr+(onceSize)*m+(j-1)))
			{
				printf("flash对比失败。。。\r\n");
				return 1;
			}
		}
	}
	return 0;
}

//备份APP中的数据
uint8_t programMove(uint32_t sour_addr,uint32_t dest_addr,uint32_t length)//转移大量flash数据,length单位双字节
{
	if(sour_addr == dest_addr)
		return 1;
	uint64_t data = 0;

	eraseUpdateFlash(dest_addr,dest_addr+0x12000-1);

	HAL_Delay(10);
	HAL_FLASH_Unlock();
	length = length/8*8+8;
	printf("程序迁移length:%d\r\n",length);
	for(int m=0;m<=length;m+=8)
	{
		data = *(__IO uint64_t*)(sour_addr + m);
		if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, dest_addr+m, (uint64_t)data) != HAL_OK)
		{
			HAL_FLASH_Lock();
			printf("flash error\r\n");
			return 2;
		}
	}
	HAL_FLASH_Lock();
	HAL_Delay(20);
	return 0;
}

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

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

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

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值