单片机 IAP 功能基础开发篇之APP升级(三)

  • Post author:
  • Post category:其他


1、前言

上一篇单片机

IAP 功能基础开发篇之APP升级(二)

讲到了单片机给 APP 程序升级具体的设计方案,这篇介绍的是升级进阶功能,如何在编译后在程序结束地址自动加上校验标志(可以通过脚本工具,但这里介绍的是在程序中编译后添加)。

1.1、目的

本文所写的是如何不通过下载器的方式实现单片机中的程序更新,目前介绍的是 STM32 的 BootLoader 进阶设计方案。


2、前期准备

2.1、编译过程

首先我们先了解一下 C 源代码编译一共经过以下的过程:预处理 -> 编译 -> 汇编 -> 链接 -> 执行文件

预处理:    展开头文件/宏替换/去掉注释/条件编译

编译:        检查语法,生成汇编

汇编:        汇编代码转换机器码

链接:        链接到一起生成可执行程序

而 MDK 的编译过程如图所示(借网上的)

2.2、分散加载文件

ARM 的

链接器

提供了一种分散加载机制,在

链接

时可以根据分散加载文件(.scf 文件)中指定的存储器分配方案,将

可执行镜像文件分成指定的分区并定位于指定的存储器物理地址

。这样,当嵌入式系统在复位或重新上电时,在对 MCU 相应寄存器进行初始化后,首先执行 ROM 存储器的 Bootloader 代码,根据连接时的存储器分配方案,将相应代码和数据由加载地址拷贝到运行地址,这样,定位在 RAM 存储器的代码和数据就在RAM 存储器中运行,而不再从 ROM 存储器中取数据或取指令,从而大大提高了 MCU 的运行速率和效率。

应用场景:有时候用户希望将不同代码放在不同存储空间,也就是通过编译器生成的映像文件需要包含多个域,每个域在加载和运行时可以有不同的地址。要生成这样的映像文件,必须通过某种方式告知编译器相关的地址映射关系(在 MDK/ADS/IAR 等编译工具中,可通过分散加载机制实现。分散加载通过配置文件实现,这样的文件称为

分散加载文件

)这里不做详细介绍,有兴趣的可自行百度(周立功单片机:分散加载文件浅释.pdf)。


3、APP 工程应用

3.1、修改分散加载文件

设置指定的分散加载文件(拷贝默认的文件至指定路径,然后重新选择即可)

STM32 的默认分散加载文件内容如下:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

需要修改成以下的内容,添加通用信息段(code_info)和代码结束段(CodeEndFlag)

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *(.code_info, +First)
   *.o (RESET)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
   *(.CodeEndFlag, +Last)
  }
}

3.2、定义和声明

在程序中定义结束标志(方便升级时擦除时就只需要擦除升级程序需要的内存了)

/* FLASH分区校验码 */
#define FLASH_EFFECTIVE_VALUE           (uint32_t)0x5555AAAA

/**
  * @brief Flash分区通用内容信息结构体定义.
  */
typedef struct {
    uint32_t effective;                     /*!< 有效校验码 */
    uint32_t startAddr;                     /*!< 起始地址 */
    uint32_t endAddr;                       /*!< 结束地址 */
    uint32_t reserve[13];					/*!< 预留 */
} FlashComInfoType;

/* app 程序 FLASH 分区程序段起始/结束地址 */
#define CODE_FLASH_START_ADDR			(uint32_t)Load$$ER_IROM1$$Base
#define CODE_FLASH_END_ADDR				(uint32_t)(Load$$RW_IRAM1$$RW$$Limit - 0x4) // -4 的原因是减掉 section(".CodeEndFlag") 的大小

extern uint8_t Load$$ER_IROM1$$Base[];         /* 程序在 FLASH 的起始地址 */
extern uint8_t Load$$RW_IRAM1$$RW$$Limit[];    /* 程序在 FLASH 的结束地址(Code + RO + RW) */

/** Flash扇区结束标志 */
static volatile uint32_t sg_appEndFlagend __attribute__((used, section(".CodeEndFlag"))) = FLASH_EFFECTIVE_VALUE;

/**
  * @brief  APP 代码Flash扇区的信息.
  * @note   随编译自动在Flash中储存以下信息,
  *         由于系统运行时不会在RAM中分配空间(地址为Flash对应的地址), 则只读.
  *         在外部引用时会报错.
  */
const FlashComInfoType gc_appFlashInfo __attribute__((used, section(".code_info"))) = {
        FLASH_EFFECTIVE_VALUE,
		CODE_FLASH_START_ADDR,
		CODE_FLASH_END_ADDR,
        {0}
};

3.3、编译

编译完成后可以打开hex文件查看(从地址看起始地址为 0x08000000,结束地址为 0x08000D38,结束地址后紧跟代码结束校验标志)

代码所在的内存图如:


4.4、Boot 工程代码应用

在 boot 代码中定义同样的结构体,通过结构体指针的方式指向 APP 区的Flash分区通用内容信息结构体。

const FlashComInfoType *gc_pAppFlashInfo = (FlashComInfoType *)APP_CODE_FLASH_START_ADDR;

通过该 APP 的信息在 boot 中得到代码结束地址,在升级时擦除至结束地址所在的 Flash 段即可,不必全部擦除,从而缩短升级时间,且在升级完成后 APP 跳转时检查 APP 代码结束地址的标志是否符合预期。



版权声明:本文为qq_24130227原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。