【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)

参考博客:STM32 BootLoader升级固件_cyang's blog-CSDN博客

STM32固件升级详解(BootLoader)_EmbeddedOsprey-CSDN博客_stm32升级bootloader

一、关于BootLoader

  • 1、BootLoader就是单片机启动时候运行的一段小程序,这段程序负责单片机固件的更新,也就是单片机选择性的自己给自己下程序。可以更新,也可以不更新,更新的话,BootLoader更新完程序后,跳转到新程序运行;不更新的话,BootLoader直接跳转到原来的程序去运行。
  • 2、BootLoader更新完程序后并不擦除自己,下次启动后依然先运行BootLoader程序,又可以选择性的更新或者不更新程序,所以BootLoader就是用来管理单片机程序的更新。
  • 3、在实际的单片机工程项目中,如果加入了BootLoader功能,就可以给单片机日后升级程序留出一个接口,方便日后单片机程序更新。当然,这就需要创建两个工程项目,一个为BootLoader工程,一个为APP工程。
  • 4、BootLoader工程生成的.hex或者.bin文件通常下载到ROM或Flash中的首地址,这样可以保证上电后先运行BootLoader程序。而APP工程生成的.hex或者.bin文件则下载到ROM或Flash中BootLoader后面的地址中。也就是说,存在ROM/Flash中的内容是分为两部分的。
  • 5、要实现在同一个ROM/Flash中保存两段程序,并且保证不能相互覆盖,则需要在下载程序时指定地址。如在Keil下,可以进行如下的调整

【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)_第1张图片

 (版权声明:本文为CSDN博主「cyang812」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011303443/article/details/53378602)

二、OTA升级方式:(涉及到BootLoader的编写)

1.全量升级:

完整的下载新版本固件,下载完成后将固件搬运到APP程序运行的位置。(一般来说是将APP从片外flash搬运到片内flash上)。搬运完成后校验通过后重启APP。

【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)_第2张图片

2.差分升级:

利用算法,做出原版APP和新版APP程序的差分包,将差分包下载到flash,内部的BootLoader程序在利用算法将新版APP合成,合成后在搬运,搬运后校验,重启。
一般制作出来的差分包只有原包的5%左右。省空间!

【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)_第3张图片

服务器端: 生成差分包,bsdiff算法

1.对old文件中所有子字符串形成一个字典;

2.对比old文件和new文件,产生diff string        和extra string;

3.将diff string 和extra string 以及相应的控制        字用zip压缩成一个patch包。    

设备端: 生成new File,bspatch算法

1.接收patch包;

2.解压patch包;

3.还原new文件。

3.原地升级:

相比差分升级,合包的过程,直接搬运。

容错率低。

【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)_第4张图片

4.AB面升级:

如图所示:

【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)_第5张图片

A/B 系统更新可带来以下好处:

  • OTA 更新可以在系统运行期间进行,而不会打断用户。用户可以在 OTA 期间继续使用其设备。在更新期间,唯一的一次宕机发生在设备重新启动到更新后的磁盘分区时。
  • 更新后,重新启动所用的时间不会超过常规重新启动所用的时间。
  • 如果 OTA 无法应用(例如,因为刷机失败),用户将不会受到影响。用户将继续运行旧的操作系统,并且客户端可以重新尝试进行更新。
  • 如果 OTA 更新已应用但无法启动,设备将重新启动回旧分区,并且仍然可以使用。客户端可以重新尝试进行更新。
  • 任何错误(例如 I/O 错误)都只会影响未使用的分区组,并且用户可以进行重试。由于 I/O 负载被特意控制在较低水平,以免影响用户体验,因此发生此类错误的可能性也会降低。
    更新包可以流式传输到 A/B 设备,因此在安装之前不需要先下载更新包。流式更新意味着用户没有必要在 /data 或 /cache 上留出足够的可用空间来存储更新包。
  • 缓存分区不再用于存储 OTA 更新包,因此无需确保缓存分区的大小要足以应对日后的更新。
  • dm-verity 可保证设备将使用未损坏的启动映像。如果设备因 OTA 错误或 dm-verity问题而无法启动,则可以重新启动到旧映像。(Android 验证启动不需要 A/B 更新。)

几种OTA方式的对比:

优点

缺点

差分升级

1.差分包小(5%),下载更快,节省OTA流程的时间。

1.一个差分包只能由特定的原包升级到特定的新包。2.保证基础包的一致性。若原包数据损毁,得到差分包也无法升级。

全量升级

1.拿到新的包就能升级。不需要指定原包。2.不需保证基础包的一致性。

1.全量包的size更大,下载相比差分包需要更长时间。

AB面升级

1.容错率高,更能避免固件升级出错

2.OTA过程中不影响用户APP程序的运行

3.下载完成后不需要搬运,不需要差分包还原

1.需要最多的flash空间的OTA方式。

原地升级

1.最节省flash空间的方式,不需要合包存储,直接根据差分包搬运到APP的位置

2.容错率低,若搬运过程中断电,则设备变砖

关于差分升级的缺点,如图所示,你懂得(懒得打字了......)

【STM32】BootLoader介绍、编写 以及 OTA常见方案分析(差分升级 全量升级 AB面升级)_第6张图片

三、编写BootLoader

1.外设驱动-----CubeMX工具生成代码:GPIO uart(debug uart) SPI flash

2.md5库添加 、w25q80驱动文件添加

3.Boot_Start()函数

bootloader.h 

#ifndef BOOTLOADER_H
#define BOOTLOADER_H

#include 
#include 
#include "flash.h"

#define MD5_CHECK_MSG_FLAG            	"firmware-header"
#pragma pack(1)
typedef struct {
    uint8_t project_id;
    uint16_t vendor_id;
    uint16_t software_version;
    uint16_t hardware_version;
    uint32_t buildtime;
    uint32_t filesize;
    uint8_t  md5[16];
} mcu_update_info_t;
#pragma pack()


#define APP_START_ADDRESS   (0x08008000) //APP main   //map ResetHandle :0800825d  //0x08008000
#define MCU_OTA_FLAG        (0xA8A8A8A8)

//printf boot:
void boot_message_print();

//boot_start    : main调用这个函数
void boot_start(void);

//check bin file 
void check_bin_file(void);

//update bin file 
uint8_t update_bin_file(uint32_t addr, HDC01_ota_info_t *params);

//go to main
void go_to_app_main(uint32_t address); //APP_START_ADDRESS 实参

//
bool firmware_verify(uint32_t start_address, uint32_t end_address);

bootloader.c

#include "bootloader.h"
#include "stm32f4xx_hal.h"
#include "flash.h"
#include "lib_md5.h"

char version[] = {"V00.00"};


typedef  void (*pFunction)(void);
pFunction JumpToApplication;

void go_to_app_main(uint32_t address) //APP_START_ADDRESS 实参
{
     __IO uint32_t fun;
    fun = (*(__IO uint32_t *)APP_START_ADDRESS);
    
    // check the Vactor Table Head // 检查栈顶地址是否合法
    if ((fun & 0x2FFE0000) == 0x20000000) 
    {
        fun = *(__IO uint32_t *)(APP_START_ADDRESS + 4);
        JumpToApplication = (pFunction)fun;
        
        // set Vactor Table address
        __set_MSP(*(__IO uint32_t *)APP_START_ADDRESS);
        printf("Set Vactor Table address ...\n");

        // start Jumping and go to App_main
        printf("Real Jump and go to App_main...\r\n\r\n");        
        
        //关闭 总中断
        __disable_irq();

        JumpToApplication();
    }
    else 
    {
        // app file error
        printf("App Bin file size err 0x%8X...\r\n", fun);

        // go to start
        HAL_NVIC_SystemReset();
    }
    
}


//printf boot:
void boot_message_print(void )
{
    printf("Start Bootloader,version = %s...\n",version);
    
    
}

//boot_start : main use
void boot_start(void)
{
    boot_message_print();
    
    check_bin_file();
    
    go_to_app_main(APP_START_ADDRESS);
}


//check bin file / updata bin
void check_bin_file(void)
{
    HDC01_ota_info_t params;
    
    //1. read firmware upadte flag
    flash_read(FLASH_TYPE_EXTERNAL,(uint8_t *)¶ms, HDC01_MCU_OTA_INFO_START_ADDR,sizeof(HDC01_ota_info_t));
    
    //2.check the bin file exception flag
    if (params.magic == MCU_OTA_FLAG) 
    {
        // Need update
        port_trace("We need update the new firmware. len:%d\n", params.firmware_len);

        // update the bin file
        update_bin_file(HDC01_MCU_OTA_DATA_START_ADDR, ¶ms);
        
        //erase OTA flag
        flash_erase_sector(FLASH_TYPE_EXTERNAL, HDC01_MCU_OTA_INFO_SECTOR);
    } 
    else 
    {
        port_trace("Normal firmware start...\n");
    }
}


//update the bin file
uint8_t buff[2048];
uint8_t check[2048];
uint8_t update_bin_file(uint32_t addr, HDC01_ota_info_t *params)
{
    bool ret;
    uint32_t remain_length = 0, write_num;
    uint32_t index = 0;
    uint32_t i = 0;
    uint32_t bin_length = 0;
	
    bin_length = params->firmware_len;

    if (bin_length > (224 * 1024)) 
    {
        // error of bin file length
        port_trace("Read Firmware length err: 0x%8X\n", bin_length);
        return 0;
    }
		
    // erase all code segment
    uint16_t sector_max_num = GetMaxSectorNum(bin_length);
    for (i = 2; i <= sector_max_num; i++) 
    {
        port_trace("erase sector %d.\n", i);
        flash_erase_sector(FLASH_TYPE_INTERNAL, i);
    }
		
	// copy data from the extend flash to inner flash
    port_trace("Copy data Start\n");
    remain_length = bin_length;
    index = 0;
		
    while (1) 
    {
        if (remain_length > 2048)
            write_num = 2048;
        else
           write_num = remain_length;
            
        flash_read(FLASH_TYPE_EXTERNAL, buff, HDC01_MCU_OTA_DATA_START_ADDR + index, write_num);
        flash_write(FLASH_TYPE_INTERNAL, buff, APP_START_ADDRESS + index, write_num);
        
        // 写入后立刻读出,并与外部Flash中的数据做校验。
        flash_read(FLASH_TYPE_INTERNAL, check, APP_START_ADDRESS + index, write_num);
        for (i = 0; i < write_num; i++) 
        {
            if (buff[i] != check[i]) 
            {
                // 一旦出现问题,随后再次重启。
                HAL_NVIC_SystemReset();
            }
        }
            
        index = index + write_num;
        remain_length = remain_length - write_num;
        if (remain_length == 0)	 break;
            
    }
		
    //ret = firmware_verify(APP_START_ADDRESS, APP_START_ADDRESS + params->firmware_len);
//		if (ret == false) 
//		{
//			port_trace("md5 check fail.\n");
//			// 一旦出现问题,随后再次重启。
//			HAL_NVIC_SystemReset();
//		} 
//		else 
//		{
//			port_trace("md5 check success.\n");
//		}
    
    port_trace("Copy data finish.\n");
    return 1;
}


/*
		check app validity
*/
static uint32_t boot_timeout_starting_time = 0;

/* ------------------------------------------------------------------------ */
/* @Description: check app validity
 * @parameters:
     start_address: app start addr
     end_address:   app end addr */
/* ------------------------------------------------------------------------ */
bool firmware_verify(uint32_t start_address, uint32_t end_address)
{
    uint8_t md5_check_flag_size = sizeof(MD5_CHECK_MSG_FLAG) - 1;

    if ((start_address >= end_address) || ((end_address - start_address) <= md5_check_flag_size)) {
        return false;
    }

    end_address -= md5_check_flag_size;
    for (; end_address > start_address; end_address--) {

        if (memcmp(MD5_CHECK_MSG_FLAG, (uint8_t *)end_address, md5_check_flag_size) == 0) {
            mcu_update_info_t *md5_and_size = (mcu_update_info_t *)(end_address + md5_check_flag_size);

            if (md5_and_size->filesize == (end_address - start_address)) 
							{
                MD5_CTX md5;
                uint8_t md5_value[16] = {0};

                port_trace("......APP_buildtime-->%d\n", md5_and_size->buildtime);
                port_trace("......firmware_size-->%d\n", md5_and_size->filesize);

                port_trace("find_md5-->");
                //port_dump(md5_and_size->md5, 16);

                MD5Init(&md5);
                MD5Update(&md5, (uint8_t *)start_address, md5_and_size->filesize);
                MD5Final(md5_value, &md5);

                port_trace("flash_md5-->");
                //port_dump(md5_value, 16);

                if (memcmp(md5_value, md5_and_size->md5, 16) == 0) {
                    return true;
                }
            }
        }
    }
    port_trace("......No_find_APP_flag-->error\n");
    return false;
}

完整工程:CSDN下载:stm32f407-BootLoader程序_stm32f407bootloader跳转-嵌入式文档类资源-CSDN下载

百度云网盘:链接:https://pan.baidu.com/s/1dwrG1nA5voIhGBAimJgKDg 
提取码:yls4 
复制这段内容后打开百度网盘手机App,操作更方便哦

你可能感兴趣的:(嵌入式,stm32,单片机,arm)