STM32具备升级功能的bootloader及APP/IAP的实现

本文介绍如何在STM32上实现升级功能,程序包括:bootloader和APP(也叫IAP, In Application Programming),基于STM32F103RCT6型号的MCU作为实验平台,以STM32CubeMX工具进行工程的建立及底层配置等工程,工程基于STM32 HAL库开发。

 

一、整体框架

整体上,在flash上烧写2个程序,bootloader和APP。

bootloader程序位于0x80000000处,即默认的程序启动地址;

APP程序则位于bootloader程序的往后某地址,空间大小需自行定义。

STM32F103RCT6的flash大小为256K。

 

1、flash分区概述

如我flash空间分配如下:

 

2、各分区说明

分区说明
分区 地址 大小 作用
bootloader0x8000 000032K 校验、引导APP、升级
param0x8000 800016K 参数区,保存一些断电不丢失参数
APP0x8000 C000192K 主应用程序
reserve0x8003 000016K 预留区(可用作参数的备份,或其他)

 

二、bootloader

bootloader的主要功能:校验数据、启动APP、升级APP。

bootloader的工作流程如下:

 

1、基础功能初始化(时钟、外设等);

主要进行一些BSP板级初始化:(仅供参考,因工程而异)

/******************************************************************/
/**
 * BSP初始化函数\n
 *
 */
/******************************************************************/
void BSP_Init(void)
{
	
	/* MCU Configuration--------------------------------------------------------*/
	
	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();
	
	/* Configure the system clock */
	SystemClock_Config();
	
	/* Initialize all configured peripherals */
	
	/* Initialize GPIO */
	MX_GPIO_Init();
	
	/* Initialize usart */
	MX_UART_Init();

	/* Initialize timer */
	Timer_ParamInit();
	MX_TIM3_Init();
	MX_TIM4_Init();

}

 

2、数据校验(参数区信息、APP程序的检验)

此步骤为后续启动过程读取一些基础参数,以及校验数据的准确性等。

读取参数区的参数:如我将升级相关的参数写在此分区(目前仅是启动标志,具体可自行定义),根据此标志来判断下一步该如何走。

/* flash参数区信息结构 */
struct param_info
{
	UINT16		usStartFlag;	// 启动标志 0x0A-跳至APP 0x0B-等待升级 0x0F-已强制启动过
};

校验数据:如我将校验APP程序区的数据是否正常,采用CRC校验。

/******************************************************************/
/**
 * 检验flash参数区函数\n
 *
 */
/******************************************************************/
int check_paramInfo(UINT32 unParamAddr, UINT32 unAppAddr, UINT32 unAppRunOffset)
{
	struct param_info		stParams = {0};
	INT32		nRetAppHead = 0;

	/* 检查参数区-判断启动APP/升级? */
	memset(&stParams, 0, sizeof(struct param_info));
	cpuflash_read(unParamAddr, (UINT8 *)&stParams, sizeof(struct param_info));
	if(stParams.usStartFlag == BOOT_FALG_NORMAL_RUNAPP)		// 直接跳转APP
	{
		/* APP校验 */
		nRetAppHead = check_AppInfo(unAppAddr, unAppRunOffset);
		if(nRetAppHead == 0)
		{
			HAL_TIM_Base_Stop_IT(&htim3);	// ??? 不关中断跳转不了-原因未明
			loadAPP(unAppAddr +unAppRunOffset);
		}
		else
		{
			printf("%s: check_AppInfo failed, [may be crc error] !\n", __FUNCTION__);
		}
	}

	/* 若参数/APP头信息错误-尝试强制启动 */
	if((stParams.usStartFlag!=BOOT_FALG_WAIT_UPGRADE && stParams.usStartFlag!=BOOT_FALG_FORCE_RUNAPP) || nRetAppHead!=0)
	{
		force_loadAPP(unParamAddr, unAppAddr, unAppRunOffset);
	}
	else
		printf("%s: ------------ wait to upgrade ------------\n", __FUNCTION__);

	return 0;
}

流程:先读取参数,判断下一步是直接启动APP还是留在bootloader等待升级。若是启动APP则校验APP程序数据是否正常,若校验失败则可尝试强制启动一次(启动失败也没关系,看门狗会自动复位)。

 

3、跳转APP或升级APP。

如何跳转至APP呢?跳转函数:

/*****************************************************************/
/**
 * 加载APP \n
 * 
 */
/******************************************************************/
void loadAPP(INT32U unLoadAddr)
{
	void (*fnJump2APP)(void);
	INT32U	unJumpAddr;
	
	if(((*(__IO INT32U *)unLoadAddr) & 0x2FFE0000) == 0x20000000)	/* 检查栈顶地址是否合法 */
	{
		printf("%s: ----------------------> run APP addr: 0x%x\r\n", __FUNCTION__, unLoadAddr);

		/* 用户代码区第5~8字节为程序开始地址(复位地址) */
		unJumpAddr = *(__IO INT32U *)(unLoadAddr + 4);
		fnJump2APP = (void (*)(void))unJumpAddr;
		/* 初始化APP堆栈指针(用户代码区的前4个字节用于存放栈顶地址) */
		__set_MSP(*(__IO INT32U *)unLoadAddr);
		fnJump2APP();
	}
	else
	{
		printf("ERROR: %s: Stack top address is not valid! Can not run func!\r\n", __FUNCTION__);
		while(1);
	}
}

 

升级,即读取到的参数标志为升级状态,则留在bootloader等待接收升级包数据(APP程序数据),并将其写入flash的APP分区。(具体见程序)

 

三、APP

APP的主要功能:除升级功能外的所有应用功能,及跳转至bootloader准备升级。

中断向量表重映射:由于APP程序的起始地址的变化,因此需要重映射,否则程序异常。

/* 宏定义 */
#define IS_NVIC_VECTTAB(VECTTAB) (((VECTTAB) == SRAM_BASE) || ((VECTTAB) == FLASH_BASE))
										  
#define IS_NVIC_OFFSET(OFFSET)  ((OFFSET) < 0x000FFFFF)

/****************************************************************
* Func 	:
* Desc	:	中断向量表重映射
* Input	:	flash中断向量地址(一般为0x08000000U)、偏移地址(flash程序烧写地址相对NVIC_VectTab偏移)
* Output:
* Return:
*****************************************************************/
void NVIC_SetVectorTable(UINT32 NVIC_VectTab, UINT32 Offset)
{
  /* Check the parameters */
  assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
  assert_param(IS_NVIC_OFFSET(Offset));

  SCB->VTOR = NVIC_VectTab | (Offset & (UINT32)0x1FFFFF80);
}

 

跳转功能可用bootloader的跳转函数,也可直接重启reboot,两种方式都可达到目的---进入bootloader运行。


 

四、升级功能

升级,即是将新的程序数据替换旧的程序数据,因此,只需在程序数据所在区域擦除旧数据再写上新数据即可。

程序数据位置CPU内部flash区,因此需要以flash的读写擦等函数操作为基础,如下:

/****************************************************************
* Func 	:
* Desc	:	读取CPU内部flash
* Input	:
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_read(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{

	if(pData == NULL)
		return -1;

	memcpy(pData, (INT8U *)unStartAddr, usSize);

	return 0;
}

/****************************************************************
* Func 	:
* Desc	:	写入CPU内部flash (要先erase才能写)
* Input	:
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_write(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
	INT32 	i = 0;
    INT32 	nRet = 0;
    UINT16 	usTemp1 = 0;
    UINT16 	usTemp2 = 0;
    UINT16 	usTempALL = 0;

	
    if(usSize%2 != 0)
    {
        usSize += 1;
    }

	HAL_FLASH_Unlock();		// unlock
	
    for(i=0; i<usSize/2; i++)
    {
		usTemp1 = *pData;
		usTemp2 = *(pData+1);
		usTempALL = ((usTemp1&0X00FF) | ((usTemp2<<8)&0XFF00));
		//usTemp = ((*pData>>8)&0X00FF) | (*(pData+1)&0XFF00);
		//usTemp = *(INT16U *)pData;/*这个会导致硬件崩溃*/
		nRet = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, unStartAddr, usTempALL);
		if(nRet != HAL_OK)
		{
			HAL_FLASH_Lock();		// lock
			printf("ERROR: %s: program[%d %d] failed-code[%d]\n", __FUNCTION__, usTemp1, usTemp2, nRet);
			return -1;
		}
	
		unStartAddr += 2;
		pData += 2;
	}

    HAL_FLASH_Lock();		// lock

	return 0;
}

/****************************************************************
* Func 	:
* Desc	:	擦除CPU内部flash(整页)
* Input	:
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_erase(UINT32 unStartAddr, UINT32 unEndAddr)
{
	FLASH_EraseInitTypeDef	stEraseInit;
	UINT32		ucPageErr = 0;
    UINT32  	unTempAddr = 0;
	INT32		nRet = 0;


	HAL_FLASH_Unlock();		// unlock

	for(unTempAddr=unStartAddr; unTempAddr<=unEndAddr; unTempAddr+=FLASH_PAGE_SIZE)
	{
		stEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
		stEraseInit.PageAddress = unTempAddr;
		stEraseInit.NbPages = 1;
		nRet = HAL_FLASHEx_Erase(&stEraseInit, &ucPageErr);
		if(nRet != HAL_OK)
		{
			HAL_FLASH_Lock();
			return -1;
		}
		GPIO_feedDog();
	}

    HAL_FLASH_Lock();		// lock

	return 0;
}

 

顺便说下,STM32内部flash库的保护问题,若不加保护,则内部程序可轻易被J-Flash等工具读出。因此,常用的措施是:对内部flash添加读写保护机制。锁定与解除函数如下:

/****************************************************************
* Func 	:
* Desc	:	使能读保护函数
* Input	:
* Output:
* Return:
*****************************************************************/
void cpuflash_enableReadProtect(void)
{
  FLASH_OBProgramInitTypeDef OBInit;
  
  __HAL_FLASH_PREFETCH_BUFFER_DISABLE();
  
  HAL_FLASHEx_OBGetConfig(&OBInit);
  if(OBInit.RDPLevel == OB_RDP_LEVEL_0)
  {
  	printf("%s: ------------ set ----------\n", __FUNCTION__);
    OBInit.OptionType = OPTIONBYTE_RDP;
    OBInit.RDPLevel = OB_RDP_LEVEL_1;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    HAL_FLASHEx_OBProgram(&OBInit);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
	//HAL_FLASH_OB_Launch();
  }
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();

}

/****************************************************************
* Func 	:
* Desc	:	失能读保护函数
* Input	:
* Output:
* Return:
*****************************************************************/
void cpuflash_disableReadProtect(void)
{
  FLASH_OBProgramInitTypeDef OBInit;
  
  __HAL_FLASH_PREFETCH_BUFFER_DISABLE();
  
  HAL_FLASHEx_OBGetConfig(&OBInit);
  if(OBInit.RDPLevel == OB_RDP_LEVEL_1)
  {
	  printf("%s: ------------ set ----------\n", __FUNCTION__);
    OBInit.OptionType = OPTIONBYTE_RDP;
    OBInit.RDPLevel = OB_RDP_LEVEL_0;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    HAL_FLASHEx_OBProgram(&OBInit);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
	//HAL_FLASH_OB_Launch();
  }
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();

}

以上为flash操作的相关函数。

有了flash操作的函数,升级操作也就顺理成章了,无非就是一包一包地接收数据,然后一包一包地写入flash。

其中,通信是通过串口,自己定义协议传送数据,将程序数据拆成N个包,一包一包地传输,例如设定3条协议,分别是:开始升级、发送升级数据、升级完成确认(自定义啦,暂不提供)。

处理一包升级数据的函数:

/****************************************************************
* Func 	:
* Desc	:	初始化
* Input	:	usSeq-顺序号 pData-数据 usDatalen-长度 pstAPPHead-APP头信息(只第一包有用)
* Output:
* Return:
*****************************************************************/
int update_packDataHandle(UINT16 usSeq, UINT8 *pData, UINT16 usDatalen, struct APP_headinfo *pstAPPHead)
{
	static INT16U		usLastSeq = 0;		// 上一次成功的序号
	static INT32U		unProgAddr = 0;		// 烧写地址
	INT32		nRet = 0;

	if(usSeq == usLastSeq)		// 重复包
		return 0;

	if(usSeq == 1)
	{
		printf("%s: ----------------- the first packet -----------------\n", __FUNCTION__);

		/* erase APP area */
		nRet = cpuflash_erase(FLASH_PAGE_APP_START, FLASH_PAGE_APP_START +pstAPPHead->unAPPSize);
		if(nRet != 0)
			return -1;

		unProgAddr = FLASH_PAGE_APP_START;
	}

	if(unProgAddr<FLASH_PAGE_APP_START || unProgAddr>=FLASH_PAGE_APP_END+FLASH_PAGE_SIZE)
		return -1;

	nRet = cpuflash_write(unProgAddr, pData, usDatalen);
	if(nRet != 0)
		return -1;

	/* 设置烧写地址偏移和升级包序号-0xFFFF表示最后一包 */
	if(usLastSeq!=0xffff && usSeq!=0xffff)
	{
		unProgAddr += usDatalen;
	}
	usLastSeq = usSeq;
	if (usLastSeq == 0xffff)
	{
		printf("%s: Recv the last packet data.\r\n", __FUNCTION__);
		usLastSeq = 0;
	}

	return 0;
}

升级大概如此,不尽详细,欢迎吐槽。

 

 

附件:示例工程链接

https://github.com/zengzhaorong/stm32_IAP-demo

  • 13
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值