学习依据的源文链接: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通常是一个小型的程序,主要负责以下几个方面的工作:
- 初始化硬件环境:BootLoader需要初始化硬件环境,包括CPU、存储器、外设、时钟等,以便系统能够正常运行。
- 加载操作系统或应用程序:BootLoader需要从存储器中加载操作系统或应用程序,并将控制权转交给操作系统或应用程序。
- 检查和修复系统错误:BootLoader需要检查系统的状态和完整性,以确保系统能够正常运行。如果发现系统错误,BootLoader需要尝试修复或恢复系统。
- 提供调试和测试功能: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功能简述:
- Bootloader启动;
- 从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;
}
}
}
- 在bootloader的主函数中,首先初始化了uart串口方便输出log信息,再调用print_boot_message()输出分区信息表(main.c line29~30);
- 之后调用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的相关参数与状态;
- 进入到while(1)的流程当中,针对boot_parameter.process的三种状态:START_PROGRAM、UPDATE_PROGRAM、 UPDATE_SUCCESS执行对应逻辑,也就是启动APP、升级APP、升级成功;
- 启动APP的case(main.c line40~49)当中,除开log信息,主要是调用jump_app()接口(bootloader.c line5~16)跳转到APP程序,而传入的参数就是APP程序的起始地址0x08004000;如果APP起始地址无程序,才会继续执行下面的log输出,不然就直接跳转到APP的程序当中,bootloader执行就结束了;
- 升级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执行;
- 升级成功的case(main.c line78~83)当中,调用write_setting_boot_state()接口(bootloader.c line 50~64)将升级成功的标志UPDATE_SUCCESS_STATE写入到地址0x08003000也就是Setting地址空间,下一次循环就会在步骤2中走到步骤4的流程,从而启动APP;
至此,bootloader的OTA过程状态机形成闭环
附文章来源:https://www.toymoban.com/news/detail-763504.html
bootloader.c文章来源地址https://www.toymoban.com/news/detail-763504.html
#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;
}
到了这里,关于STM32 OTA Bootloader部分 demo流程学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!