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