什么是IAP

In Application Programming:在应用编程

是用户自己的程序在运行过程中对User Flash的部分区域进行烧写

目的是为了在产品发布后可以方便地通过预留的通讯口对产品中的固件程序进行更新升级

一般实现

通常实现IAP功能时,需要在设计固件程序时编写两个项目代码

  • 第一个项目程序(bootloader程序)不执行正常的功能操作,而只是通过某种通讯方式(USB,USART)接受程序或数据,执行对第二部分代码的更新

  • 第二个项目代码(APP程序)才是真正的功能代码

这两部分代码都同时烧录user flash中,当芯片上电后,首先第一个项目代码开始运行,它的操作:

  1. 检查是否需要对第二部分代码进行更新

  2. 如果不需要更新则跳转到4

  3. 执行更新操作

  4. 跳转到第二部分代码(APP程序)执行

第一部分代码必须通过其他手段,如JTAG烧入,第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分IAP代码更新

上面两套代码存放在MCU flash的不同地址范围,一般从最低地址区开始存放bootloader,其后就是APP程序(只要容量够,放多少APP程序都是可以的)

本项目实现

APP程序(freertos实现)(freertos实现)放在bootloader程序(裸机程序)之前,先执行APP程序,在APP程序实现固件的下载保存,然后判断是否进入bootloader程序,执行更新程序

具体分析

分区

APP程序占用空间

image.png

bootloader程序占用空间

image1.png

整体分区

image2.png

处理逻辑

image3.png

程序

app程序中的bootloader跳转程序

/**
 * @brief bootloader跳转程序,当接收到上位机发送来的升级包后,执行升级程序
*/
void app_upgrade_jump_to_bootloader(void)
{
    // 定义一个易变的 32 位无符号整数变量,用于存放跳转地址 
    volatile uint32_t jump_addr;
    // 禁用所有中断,防止跳转过程被打断
    // __disable_irq();

    // 判断 APP_DATA_INFO_OTA_BOOT_ADDR 这个地址处的值是否符合引导程序的要求,即是否以 0x2000 开头
    // APP_DATA_INFO_OTA_BOOT_ADDR 是一个宏定义,表示引导程序的起始地址,通常是 0x08000000
    // 这个判断是为了确保引导程序存在且有效
    if (((*(volatile uint32_t*)APP_DATA_INFO_OTA_BOOT_ADDR) & 0x2FFF0000 ) == 0x20000000)
    {
        // bootloader在进入app之前使用__set_PRIMASK(1);函数关闭中断,在app中需要将中断打开__set_PRIMASK(0);
        __set_PRIMASK(1);
        // 使用操作系统后系统内核会使用PSP模式,跳转到APP后没有恢复到MSP模式就会导致内存异常从而进入到内存异常中断
        __set_CONTROL(0);

        // 如果符合要求,那么将 APP_DATA_INFO_OTA_BOOT_ADDR + 4 这个地址处的值赋给 jump_addr
        // 这个地址处的值是引导程序的入口点,即第一个指令的地址
        jump_addr = *(volatile uint32_t*)(APP_DATA_INFO_OTA_BOOT_ADDR + 4);

        // 将 jump_addr 强制转换为函数指针类型,赋给 jump_func
        // 这样就可以通过 jump_func 来调用引导程序
        jump_func = (FUNCTION_t)jump_addr;
        
        // 将 APP_DATA_INFO_OTA_BOOT_ADDR 这个地址处的值赋给 MSP 寄存器
        // MSP 寄存器是主堆栈指针寄存器,用于存放堆栈的起始地址
        // 这个操作是为了设置引导程序的堆栈
        __set_MSP(*(volatile uint32_t*)APP_DATA_INFO_OTA_BOOT_ADDR);
        
        // 调用 jump_func,即跳转到引导程序的入口点,开始执行引导程序
        jump_func();
    }

    // 如果不符合要求,那么重新启用所有中断,恢复正常状态
    // __enable_irq();

    // 函数返回,结束
    return;
}

bootloader程序中的main程序

int main(void)
{
    // 中断向量表偏移,VECTOR_TABLE_OFFSET也就是我们boot程序分区地址
    SCB->VTOR = VECTOR_TABLE_OFFSET;

    // __enable_irq();
	__set_PRIMASK(0);//解除中断屏蔽,打开中断
  
    // 固件更新处理,擦除app分区的固件,复制更新分区中的固件到app分区中 
    upgrade_fw_process();
  
    // 复位,然后就会跳转到app分区,开始执行app程序
    NVIC_SystemReset();  
}