STM32基于bootloader的APP升级流程详解

FLASH内存规划:

        Flash的大小就是从地址0x08000000开始的一段内存空间,可以将其划分为三个主要部分:IAP(bootloader),APP,备份APP。

        这里可以考虑按照64K+128K+128K大小进行flash内存划分,实际大小根据项目进行划分,如果出于成本考虑,也可以把FLASH后面部分空间作为EEPROM使用,用于存储状态标志位和其它设备参数,但是千万不要和前面的程序产生位置上的冲突 

 

升级参数存储:

        完成标志位:该位是指在IAP程序时存储的状态值,主要是在升级成功后通知APP

        状态标志位:该位是在APP中存储,主要是在接收到需要升级的指令时,复位进入IAP,IAP通过对该位的判断决定是否升级

        tips:前面两个参数作为流程控制逻辑参数,后面这三个参数是需要升级时由上位机给出

        校验码:判断升级过程中传输的文件是否正确完整

        升级包大小:① 可以计算要传输多少次,主动向上位机请求升级文件  ②  可以作为基数值,给上位机传输升级进度

        版本号: 升级包对应的新版本。设备上电,连上蓝牙后就应该把设备此时的软件版本上报给上位机,根据版本判断是否需要进行升级,如果升级成功之后,就把程序版本更新,再上报给上位机,避免升级死循环;升级失败也把错误码给到上位机,以便定位升级失败的原因

升级流程:

一、要对STM32的flash写入什么才能实现升级

        主要是写入keil编译完成后生成的bin文件,该文件就是升级包,bin文件不是自动生成的,在魔术棒—->User界面勾选Run #1,然后后面填入fromelf –bin !L –output ./Build/Bin/APP_Code.bin,APP_Code是bin文件名,这个可以按照自己项目填写,每次编译完成这里就会更新,也就是最新的升级包

 

二、如何传输这个升级文件,以及如何确定文件传输的正确性和完整性

        既然是通过蓝牙传输,就离不开串口,用串口UART+DMA搬运+IDLE空闲中断+FreeRTOS队列,这套组合拳拿捏传输过程;

        tips:这里简单阐述一下,用cubeMAX配置的时候,加上FreeRTOS,串口配置时打开DMA和中断,串口初始化时:① 使能空闲中断 ② 创建队列  ③ 配置DMA接收,预设大小和队列项大小一致。使用队列的好处就是有一个缓冲,不会将数据覆盖,因为队列项可以是多个

        前面提到,升级过程中,会存储一个校验码,这个校验码是上位机得到升级文件时进行的一次校验结果,底层存储后,在升级传输过程中,对于接收文件也要进行校验,传输完成之后,对比校验码就能确定传输过程是否出错,校验方式这里可以使用BCC校验,简单粗暴

        tips:BCC校验,比如一次接收1024字节,第一个字节和第二个字节异或运算,得到的结果再和第三个字节进行异或运算,如此累加异或,直达传输完成

uint8_t BccCheck(uint8_t CheckCode, uint8_t *Data, uint16_t Num)
{
    uint16_t TempA = 0;
    uint8_t TempValue = CheckCode;
	
    for (TempA = 0; TempA < Num; TempA++)
    {
        TempValue ^= Data[TempA];
    }
	
    return TempValue;
}

三、如何操作flash

        关于flash有三个函数需要封装,分别是

       1、 擦除(芯片分页大小不同,这里根据自己的芯片进行配置)

HAL_StatusTypeDef FlashErase(uint32_t FlashStarAddr,uint32_t FlashEndAddr)
{
	uint32_t Pagenum = 0, SectorError = 0;
	FLASH_EraseInitTypeDef EraseInstruct;
	
	Pagenum = (FlashEndAddr - FlashStarAddr + 1) / 2048;
	
	HAL_FLASH_Unlock();
	
	EraseInstruct.Banks       = FLASH_BANK_1;
	EraseInstruct.PageAddress = FlashStarAddr;
	EraseInstruct.TypeErase   = FLASH_TYPEERASE_PAGES;
	EraseInstruct.NbPages     = Pagenum;
	
	if(HAL_FLASHEx_Erase(&EraseInstruct,&SectorError) == HAL_ERROR)
	{
		return HAL_ERROR;
	}
	HAL_FLASH_Lock();
	
	return HAL_OK;
}

        2、写入(对flash写入前必须先擦除)

HAL_StatusTypeDef FlashWrite(uint32_t StarAddr,uint8_t *buff,uint16_t length)
{
	uint16_t TempA;
	uint32_t TempData;
	HAL_StatusTypeDef Result = HAL_OK;
	
	HAL_FLASH_Unlock();
	
	__HAL_FLASH_CLEAR_FLAG(FLASH_SR_BSY | FLASH_SR_PGERR | FLASH_SR_WRPRTERR | FLASH_FLAG_EOP);
	
	for(TempA = 0;TempA < length;)
	{
		TempData = (buff[TempA + 3] << 24)+(buff[TempA + 2] << 16)+(buff[TempA + 1] << 8) + buff[TempA];
		
		if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,StarAddr,TempData) == HAL_OK) 
		{
			TempA += 4;
			StarAddr += 4;
		}
		else
		{
			Result = HAL_ERROR;
		}	
	}
	HAL_FLASH_Lock();
	
	return Result;
}

        tips:这里会发现写入flash时,每次写入双字的大小,写入之前要对数据进行移位取反,这么操作是因为写入flash是从低地址往高地址写,但是我们主动读取或者CPU去读取指令都是从高地址往地址开始读

 warning:回来补充一些问题,上面这个写入代码其实有一个问题不知道你们看出来没有,如果写入的字节数是4的倍数,确实没什么问题,但是如果写入的是5个字节,实际上写入的是8个字节,如果你们用这个代码,一定注意这个点,尤其是使用flash模拟eeprom存储字符参数

       3、 读取

HAL_StatusTypeDef FlashRead(uint32_t FlashStarAddr,uint8_t *buff,uint16_t length)
{
	uint16_t TempA;
	for(TempA = 0; TempA < length; TempA++)
	{
		buff[TempA] = *(uint32_t *)FlashStarAddr;
		FlashStarAddr++;
	}
	
	return HAL_OK;
}

四、意外情况如何处理,如:设备掉电,传输中断

        考虑意外情况才能保证整个升级流程的万无一失,这里就有两个非常重要的函数,这两个函数的封装和使用关乎整个升级的安危,可以说是为升级流程兜底

        备份程序:在设备第一次烧录程序的时候就应该对程序进行备份,在每次升级之前也要备份程序,备份程序一定不能出错,一旦备份程序出错,调用备份也就没有意义。备份最直接的方式就是将程序的APP部分复制到备用APP部分

        调用备份:前面说到升级完成标志位是在IAP进行存储,并且也要在IAP里面对这个位进行判断,目的就是为了规避设备掉电或者传输中断导致升级被破坏。

        假设升级过程中,设备掉电,此时升级完成标志位是不应该在IAP程序里面被读到置1的,一旦置1到就说明升级出了问题,此时就要调用备份程序。调用备份最直接的方式就是将备用APP部分复制到APP部分

五、升级的注意点

        1、在做升级的时候我也遇到过一些问题,整个流程都没找到逻辑问题,但是校验就是会失败,加上延时之后勉强解决问题,还是会偶发性校验失败,而且整个升级耗时较长,当时我猜测是升级过程中的传输速度问题,于是将蓝牙和串口波特率从115200调到57600,去掉延时后多次尝试也没出现校验问题,整个升级流程大概20秒

        2、传输过程中,升级文件接收是由设备主动控制的,因为接收到还要写入flash,写入完成后发送指定指令让上位机继续传输升级文件,这个指令中就可以包含升级进度

        3、补充一个偶发bug,仅供参考,代码如下:

         /* 文件未传输提示错误码 */
        memset(DataBuff,0,32);
        sprintf(DataBuff,"0;1;30");  
        FlashErase(CORE_DATAADDR_S4,0x08061FFF);
        FlashWrite(CORE_DATAADDR_S4,(uint8_t *)DataBuff,strlen((char *)DataBuff));
        
        USART1_Receive(FlashBuff, portMAX_DELAY);
        
        memset(DataBuff,0,32);
        sprintf(DataBuff,"0;1;0");  
        FlashErase(CORE_DATAADDR_S4,0x08061FFF);
        FlashWrite(CORE_DATAADDR_S4,(uint8_t *)DataBuff,strlen((char *)DataBuff));

        如上述代码,我在接收升级文件的前后加上升级标识位的反复擦除和写入,可以看到内容不同,这样做的目的是,如果上位机没继续发文件,可以通过30这个错误码知道问题,但是这里有一个隐藏bug,那就是在这个过程中,如果刚好把标识位擦除完,设备就断电,就会导致设备宕机,因为此时APP部分有问题,调用备份的标识位也被擦除,设备就是板砖

物联沃分享整理
物联沃-IOTWORD物联网 » STM32基于bootloader的APP升级流程详解

发表评论