学习STM32 OTA Bootloader部分demo流程

学习依据的源文链接:STM32 OTA应用开发——自制BootLoader

前言

什么是OTA?

OTA是“Over-the-Air”(空中升级)的缩写,指的是通过无线通信网络(如Wi-Fi、蓝牙、LoRa等)对嵌入式系统进行远程升级或更新。

在嵌入式系统中,OTA技术可以用于更新固件、软件或配置文件等。通过OTA技术,用户可以在不需要物理接触设备的情况下,对其进行升级和更新,从而提高系统的可靠性、安全性和灵活性。

———-我的理解:

所谓的OTA其实就是通过一些无线通信协议的方式,向嵌入式系统发送应用程序(以下简称APP)和升级指令,再由单片机设备上的BootLoader完成:接收新APP—擦除旧APP—写入新APP—跳转到新APP执行 的过程;

这个demo的实际上是通过USB的连接实现上述的APP升级过程,虽然有线连接不能严格称做OTA,但是二者的差异实际上只是不同(有线与无线)的通信传输过程,主要是学习程序升级的流程,故下文也将USB升级过程视为OTA升级;

什么是BootLoader?

BootLoader是一段程序,通常位于嵌入式系统的非易失性存储器(如Flash)中,用于在系统启动时加载和运行操作系统或其他应用程序。

在嵌入式系统中,BootLoader通常是一个小型的程序,主要负责以下几个方面的工作:

  1. 初始化硬件环境:BootLoader需要初始化硬件环境,包括CPU、存储器、外设、时钟等,以便系统能够正常运行。
  2. 加载操作系统或应用程序:BootLoader需要从存储器中加载操作系统或应用程序,并将控制权转交给操作系统或应用程序。
  3. 检查和修复系统错误:BootLoader需要检查系统的状态和完整性,以确保系统能够正常运行。如果发现系统错误,BootLoader需要尝试修复或恢复系统。
  4. 提供调试和测试功能:BootLoader可以提供调试和测试功能,例如单步执行、断点调试、查看寄存器状态等,以方便开发人员进行调试和测试。

而这个demo是通过自制bootloader,实现加载应用程序和OTA的功能;

BootLoader的OTA功能工作原理

MCU要实现OTA离不开Bootloader,它是一段引导程序,在OTA过程中,MCU启动时会先运行BootLoader,Bootloader会去判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现OTA升级。

 

BootLoader的OTA功能常见分区介绍

在没有加入Bootloader之前,MCU内部的flash可以看作一整块分区,我们运行的整个裸机程序都在其中:

第一种分区方式——加入BootLoader,那么原本的Flash分区就可以划分为两个区域,Bootloader和Application,这种分区方式的好处在于既可以OTA升级,App又可以分到较大的空间,缺点是没有存放新固件的区域,需要从外部导入进来,而且一旦传输的过程被异常打断,那么原有的App代码也无法正常运行了:

第二种分区方式——在第一种分区方式的基础上,将原本独属于APP的Flash空间中再分出一块作为Download分区来存放新的APP(同下文的固件),这种方式的优点是新固件是先存放到Download区的,哪怕搬运的过程中出现异常中断的情况,也不会“变砖”,缺点是需要单独划分一块内存跟APP区差不多的区域用来存放新固件,变相的减少了APP区的空间,对于内存较小的单片机来说内存压力会比较大:

 第三种分区方式——这种方式其实与第二种分区方式一样,只是它把原本存放新APP的空间用来存放OTA升级前的旧APP,相当于在升级之前先将旧APP备份到Application2区域,再通过外部导入新APP覆盖写入Application1区域,优点在于升级了新固件以后,还保留了原来的旧版固件,必要的时候还可以进行版本的回退:

 第四种分区方式——这种方式基本上与第二种分区方式一样,只不过增加了一个区域用来存放OTA相关的一下参数和用户配置:

BootLoader的制作

BootLoader的程序是从源文找到的参考的demo,下面的内容一方面整理Bootloader的编写过程,一方面也会去详细解析Bootloader的源代码执行流程

以上述的第四种分区方式去编写Bootloader,参考demo使用的设备是STM32F103,内存128K,其内存分区表如下:

name offset size
Bootloader 0x08000000 0x00003000
Setting 0x08003000 0x00001000
Application 0x08004000 0x0000E000
Download 0x08012000 0x0000E000

其中0x08000000是内存起始地址,那么128K大小的内存空间的结束地址为0x0801FFFF,最后Download分区为0x08012000+0x0000E000 – 0x1 =0x0801FFFF,这四个部分将128K的内存空间全部分配完成;

这里记录一下地址空间与大小的计算过程:0x0801FFFF – 0x08000000 + 0x1 = 0x00020000,即0x20000个byte的空间,转换成十进制就是131072 bytes,而 128 * 1024 = 131072,所以是128K内存空间;

BootLoader功能简述:
  1. Bootloader启动;
  2. 从Setting中读取参数,确定是否需要升级;
  • 需要升级——把Download分区固件搬运到Application分区;
  • 不需要升级——直接跳转到Application分区;
  • 新固件的下载传输过程,在App里面去处理,这里不做拓展;

    源码解析

    主要针对demo中的main.c、bootloader.c、bootloader.h中的内容进行流程解析,源码中涉及到的flash、uart等原本ST驱动中带有的外设控制接口只做简要说明;

    main.c

    #include "hardware.h"
    #include "bootloader.h"
    #include "flash.h"
    #include "type.h"
    
    void print_boot_message(void)
    {
        printf("---------- Enter BootLoader ----------\r\n");
        printf("\r\n");
        printf("======== flash pration table =========\r\n");
        printf("| name     | offset     | size       |\r\n");
        printf("--------------------------------------\r\n");
        printf("| boot     | 0x08000000 | 0x00003000 |\r\n");
        printf("| setting  | 0x08003000 | 0x00001000 |\r\n");
        printf("| app      | 0x08004000 | 0x0000E000 |\r\n");
        printf("| download | 0x08012000 | 0x0000E000 |\r\n");
        printf("======================================\r\n");
    }
    
    int main() 
    {
        process_status process;
        uint16_t i;
        uint8_t boot_state;
        uint8_t down_buf[128];
        uint32_t down_addr;
        uint32_t app_addr;
    
        uart1_init();
        print_boot_message();
    
        boot_parameter.process = read_setting_boot_state();
        boot_parameter.addr = APP_SECTOR_ADDR;
    
        while (1) 
        {
            process = get_boot_state();
            switch (process) 
            {
                case START_PROGRAM:
                    printf("start app...\r\n");
                    delay_ms(50);
                    if (!jump_app(boot_parameter.addr)) 
                    {
                        printf("no program\r\n");
                        delay_ms(1000);
                    }
                    printf("start app failed\r\n");
                    break;
                case UPDATE_PROGRAM:
                    printf("update app program...\r\n");
                    app_addr = APP_SECTOR_ADDR;
                    down_addr = DOWNLOAD_SECTOR_ADDR;
    
                    printf("app addr: 0x%08X \r\n", app_addr);
                    printf("down addr: 0x%08X \r\n", down_addr);
    
                    printf("erase mcu flash...\r\n");
                    mcu_flash_erase(app_addr, APP_ERASE_SECTORS);  
                    printf("mcu flash erase success\r\n");
                
                    printf("write mcu flash...\r\n");
                    // memset(down_buf, 0, sizeof(down_buf));
                    for (i = 0; i < APP_ERASE_SECTORS * 8; i++)
                    {
                        mcu_flash_read(down_addr, &down_buf[0], 128);
                        delay_ms(5);
                        mcu_flash_write(app_addr, &down_buf[0], 128);
                        delay_ms(5);
                        down_addr += 128;
                        app_addr += 128;
                        // printf("mcu_flash_write: %d\r\n", i);
                    }
                    printf("mcu flash write success\r\n");
    
                    set_boot_state(UPDATE_SUCCESS);
                    break;
                case UPDATE_SUCCESS:
                    printf("update success\r\n");
                    boot_state = UPDATE_SUCCESS_STATE;
                    write_setting_boot_state(boot_state);
                    set_boot_state(START_PROGRAM);
                    break;
                default:
                    break;
            }
        }
    }
    1. 在bootloader的主函数中,首先初始化了uart串口方便输出log信息,再调用print_boot_message()输出分区信息表(main.c line29~30);
    2. 之后调用read_setting_boot_state()(bootloader.c line32~48)将0x08003000也就是Setting地址空间中存放的状态值读取赋值给boot_parameter.process,再把APP首地址0x08004000赋值给boot_parameter.addr作为后面跳转到APP程序的准备(main.c line32~33),而boot_parameter是bootloader.c定义的全局变量(bootloader.c line3),用于存放bootloader的相关参数与状态;
    3. 进入到while(1)的流程当中,针对boot_parameter.process的三种状态:START_PROGRAM、UPDATE_PROGRAM、 UPDATE_SUCCESS执行对应逻辑,也就是启动APP、升级APP、升级成功;
    4. 启动APP的case(main.c line40~49)当中,除开log信息,主要是调用jump_app()接口(bootloader.c line5~16)跳转到APP程序,而传入的参数就是APP程序的起始地址0x08004000;如果APP起始地址无程序,才会继续执行下面的log输出,不然就直接跳转到APP的程序当中,bootloader执行就结束了;
    5. 升级APP的case(main.c line50~77)当中,先按页擦除flash中APP地址区块(main.c line59)将旧APP清除,然后再从Download地址区块开始每128byte读取数据再写到APP地址区块中(main.c line64~73),最后把bootloader的状态置为UPDATE_SUCCESS,下次循环进入UPDATE_SUCCESS的case执行;
    6. 升级成功的case(main.c line78~83)当中,调用write_setting_boot_state()接口(bootloader.c line 50~64)将升级成功的标志UPDATE_SUCCESS_STATE写入到地址0x08003000也就是Setting地址空间,下一次循环就会在步骤2中走到步骤4的流程,从而启动APP;

    至此,bootloader的OTA过程状态机形成闭环

    bootloader.c

    #include "bootloader.h"
    
    boot_t boot_parameter = {START_PROGRAM, 0, 0, 0};
    
    uint8_t jump_app(uint32_t app_addr) 
    {
        uint32_t jump_addr;
        jump_callback cb;
        if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000) {  
            jump_addr = *(__IO uint32_t*) (app_addr + 4);  
            cb = (jump_callback)jump_addr;  
            __set_MSP(*(__IO uint32_t*)app_addr);  
            cb();
            return 1;
        } 
        return 0;
    }
    
    void set_boot_state(process_status process) 
    {
        boot_parameter.process = process;
    }
    
    process_status get_boot_state(void) 
    {
        process_status process;
        process = boot_parameter.process;
    
        return process;
    }
    
    process_status read_setting_boot_state(void)
    {
    	process_status process;
    	uint8_t boot_state;
        mcu_flash_read(SETTING_BOOT_STATE, &boot_state, 1);
        // printf("boot_state: %d \r\n", boot_state);
     
        if(boot_state != UPDATE_PROGRAM_STATE)
        {
           process = START_PROGRAM;
        }
        else
        {
           process = UPDATE_PROGRAM;
        }
    	return process;
    }
    
    uint8_t write_setting_boot_state(uint8_t boot_state)
    {
    	uint8_t result;
    	result = mcu_flash_erase(SETTING_BOOT_STATE, 1);
    	if(result)
    	{
    		result = mcu_flash_write(SETTING_BOOT_STATE, &boot_state, 1);
    		if(result != 1)
    		{
    			return result;
    		}
    	}
        
    	return result;
    }
    物联沃分享整理
    物联沃-IOTWORD物联网 » 学习STM32 OTA Bootloader部分demo流程

    发表评论