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部分有问题,调用备份的标识位也被擦除,设备就是板砖

六、如何实现IAP到APP的跳转(回来补充这个点)

typedef void (* fun)(void);   //这里重定义一个void (*)(void)的数据类型,fun等价于void (*)(void)这个数据类型
fun Jump;                           //Jump就是真正的函数指针

__asm void MSR_MSP(uint32_t addr) 
{
    MSR     MSP, r0
    BX      r14
}
 

//上面这个汇编指令我也没注意去理解,应该是保存了寄存器的值
void JumptoAPP()
{
    if(((*(__IO uint32_t*)APP_FLASHADDR_S) & 0x2FFE0000 ) == 0x20000000)  //这里是判读堆栈是否越界,具体判断方式就是,*(__IO uint32_t*)APP_FLASHADDR_S)得到的是栈顶地址,这个栈顶地址就是从0x2000000开始的那段RAM空间,这个栈顶的地址就是计算了APP程序对RAM进行占用之后,最大的地址,然后去与上0x2FFE0000这个值,正点原子的程序中,不同的跳转地址你们会发现这个值不一样,为什么会写成0x2FFE0000,是因为2000是整个RAM的大小(默认是这么大),那么从地址上计算就是0x2000000到0x2001FFF这段空间,等于你能占用的RAM最大地址就是0x2001FFF,有没有发现这个值和0x2FFE0000有什么关系,如果栈顶地址已经越界的话,这里相与就不会等于0x2000000,这就是判断栈顶地址是否越界的具体含义
    {
        log_i("T-BOX Loading App.");
        log_i("Jump Address:0x%08X", ((*(uint32_t*)APP_FLASHADDR_S) & 0x2FFE0000 ));
        vTaskDelay(10);
        
        __set_PRIMASK(1);    /*关闭总中断*/

        //这里操作寄存器关闭中断,避免跳转过程中被干扰,中断的关闭有几个不同的寄存器可以操作,分别关闭不同优先级的中断
        vTaskDelay(10);
        
        Jump = (fun)*(uint32_t*)(APP_FLASHADDR_S + 4);
        

        //这里的给函数指针赋值,具体就是执行了APP程序Reset_Handle中断服务函数具体的操作还是比较考究C语言功底的,这里不做详细解释
        MSR_MSP(*(__IO uint32_t*) APP_FLASHADDR_S);
        Jump();    /*跳转App*/
    }
    {
        log_i("T-BOX Loading App ERROR.");
        vTaskDelay(50);
        log_i("Jump Address:0x%08X", ((*(__IO uint32_t*)APP_FLASHADDR_S) & 0x2FFE0000 ));
        vTaskDelay(50);
        HAL_NVIC_SystemReset();
    }
}

     

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值