MCU-OTA升级的flash分散加载应用

目录

  [1. flash分散加载介绍及优势](#1. flash分散加载介绍及优势)

  [2. flash地址分区](#2. flash地址分区)

  [3. .ld脚本配置](#3. .ld脚本配置)

  [4. 实现 LPUART 驱动共享代码](#4. 实现 LPUART 驱动共享代码)

  [5. 使用共享代码](#5. 使用共享代码)

1. flash分散加载介绍及优势

1.1 flash分散加载介绍

  Flash分散加载(Scatter Loading)是一种通过修改链接脚本(.ld脚本/GCC环境、.sct脚本/Keil环境),将单片机程序的不同模块(如BootLoader、应用程序、共用驱动、核心算法)分配到flash的不同地址分区,实现代码模块化管理、复用的加载方式。

传统的集中式加载,会将所有代码打包到一个连续的flash区域,导致BootLoader和APP中重复的驱动代码(如串口、无线通信)被多次存储,浪费宝贵的flash空间;而分散加载通过"分区管理+代码复用",让多个程序(Boot、APP1、APP2)共享同一部分代码,从根源上解决空间不足问题。

1.2 flash分散加载的核心优势

*** 省flash空间:** 将Boot和APP共用的代码(如LPUART驱动、无线通信代码)独立到共享分区,避免重复存储,尤其适合64KB及以下小容量flash芯片,可节省10%-30%的空间,为OTA升级、新增功能预留足够空间。

*** 提升调试效率:** 对于体积较大、逻辑固定的代码(如复杂算法、通用驱动),可将其固定到共享分区,调试应用程序时,无需每次都下载完整代码,仅下载应用层代码即可,大幅缩短下载时间。

*保护核心源码: 多人协作开发时,可将核心算法、加密逻辑等关键代码放到共享分区,仅提供函数调用接口,不暴露源码,避免核心技术泄露。

*支持OTA升级: OTA升级时,BootLoader和APP需共用通信驱动(如LPUART、SPI),分散加载可实现驱动代码复用,避免Boot和APP分别存储驱动导致的空间不足,确保升级流程稳定。

2. flash地址分区

以HC32系列芯片,64KB Flash为例。

分区 Flash起始地址 大小(KB) 功能
BootLoader 0x00000000 6 OTA 升级引导程序
共享固件 0x00001800 6 存放Boot和APP共用的代码程序
参数存储区 0x00003000 2 存放重要的配置参数
APP1 0x00003800 25 主应用程序1
APP2 0x00009C00 25 主应用程序2

3. .ld脚本配置

VsCode + GCC编译器环境下,链接脚本(.ld文件)是实现flash分散加载的核心,通过修改.ld脚本,划分flash分区、指定各模块的存储地址。本文以LPUART串口程序共享为例,详细讲解Boot程序.ld脚本的修改方法(APP程序.ld脚本修改逻辑类似)。

3.1 修改Boot程序的链接脚本(.ld)

首先查看原始.ld脚本的默认配置,再根据前面的分区规划,修改分区定义和代码段绑定,确保共享代码能正确定位到共享固件区。

bash 复制代码
# 原始 .ld 配置

MEMORY
{
    FLASH         (rx): ORIGIN = 0x00000000, LENGTH = 64K
    RAM           (rwx): ORIGIN = 0x20000000, LENGTH = 8K
}
bash 复制代码
# 修改后的 .ld 配置

MEMORY
{
    FLASH         (rx): ORIGIN = 0x00000000, LENGTH = 6K
    FLASH_SHARED (rx) : ORIGIN = 0x00001800, LENGTH = 6K
    RAM           (rwx): ORIGIN = 0x20000000, LENGTH = 8K
}


# /* SECTIONS 增加 */
SECTIONS
{
    
    .fini_array :
    {
        /* finit data */
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        PROVIDE_HIDDEN (__fini_array_end = .);
    } >FLASH
    # /* 前面其余部分不变 */

    # /*增加 shared_code */
    .shared_code :
    {
        . = ALIGN(4);
         KEEP(*(.shared_ptr_table))   /* 函数指针表(固定在共享区起始地址) */
         KEEP(*(.shared_func))        /*要共享的所有函数存放位置 */
        . = ALIGN(4);
    } > FLASH_SHARED  /* 绑定到共享FLASH分区 */


    # /* 后面其余部分不变 */
    __etext = LOADADDR(.data);
    .data :
    {

    } >RAM AT> FLASH

}


# 关键配置说明
# MEMORY分区修改:新增FLASH_SHARED分区,指定起始地址0x00001800、大小6K,与前面的分区规划完全对应;FLASH分区缩小为6K,仅用于存放BootLoader代码。
# .shared_code段:专门用于存放共享代码和函数指针表,绑定到FLASH_SHARED分区,确保共享代码能被固定到指定地址。
# ALIGN(4):ARM架构要求代码地址4字节对齐,否则会导致CPU取指异常,必须添加该配置。
# KEEP关键字:GCC编译器在优化时,可能会删除未被当前工程直接调用的代码(如共享函数),KEEP关键字可强制保留指定段的代码,确保共享函数不被优化删除。

4. 实现 LPUART 驱动共享代码

共享代码的核心是"将需要复用的函数(如LPUART驱动)固定到共享区,并通过函数指针表提供调用接口",Boot和APP通过访问函数指针表的固定地址,即可调用共享区的驱动函数,无需重复编写驱动代码。本节实现HC32系列芯片的LPUART驱动共享,包含头文件和源文件的示例实现。

4.1 LpuartDrv.h 头文件实现

c 复制代码
#ifndef __LPUARTDRV_H
#define __LPUARTDRV_H


#include <stdint.h>


// 函数指针类型定义
typedef void (*Ptr_LpUartInit)(void);
typedef uint32_t (*Ptr_LpUartRead)(uint8_t* buf, uint32_t bufLen);
typedef void (*Ptr_LpUartWrite)(uint8_t* data, uint32_t dataLen);


// 函数指针表结构体定义
typedef struct {
    Ptr_LpUartInit  init;
    Ptr_LpUartRead read;
    Ptr_LpUartWrite write;
} LPUART_API_TABLE_T;



// 共享区固定起始地址
#define LPUART_API_ADDR  0x00001800
#define g_LpuartDrv  ((const LPUART_API_TABLE_T *)LPUART_API_ADDR)

4.2 LPUART.c 文件实现

c 复制代码
#include "LpuartDrv.h"

#include "ddl.h"
#include "gpio.h"
#include "lpuart.h"
#include "string.h"





// / 所有函数,强制放入 .shared_func 区域
// GPIO初始化函数
__attribute__((section(".shared_func")))
static void lpUartInitGpio(void)
{

}

// 串口初始化函数
__attribute__((section(".shared_func")))
static void lpUartInit(void)
{

}


// 初始化函数
__attribute__((section(".shared_func")))
void LpUartDrvInit(void)
{
    lpUartInitGpio();
    lpUartInit();

}


// 读串口数据
__attribute__((section(".shared_func")))
uint32_t LpUartDrvRead(uint8_t* buf, uint32_t bufLen)
{


}


// 发送串口数据
__attribute__((section(".shared_func")))
void LpUartDrvWrite(uint8_t* data, uint32_t dataLen)
{

}


// 串口中断处理函数
__attribute__((section(".shared_func")))
void LPUART0_IRQHandler(void)
{


}




// 函数指针表 → 固定放在共享区起始地址 LPUART_API_ADDR
__attribute__((section(".shared_ptr_table")))
const LPUART_API_TABLE_T s_Lpuart_Api = {
    .init   = LpUartDrvInit,
    .read  = LpUartDrvRead,
    .write = LpUartDrvWrite,
};

5. 使用共享代码

共享代码编译完成后,Boot和APP工程无需再编写LPUART驱动代码,仅需包含头文件LpuartDrv.h,通过函数指针表即可调用共享区的驱动函数。同时,需配置中断向量表,确保共享区的中断函数能正常响应。

Boot/APP工程无需添加LPUART.c文件,仅需包含头文件,通过g_LpuartDrv指针访问共享驱动接口,实现串口通信。

5.1 APP工程调用示例

C 复制代码
#include "LpuartDrv.h"
#include "string.h"




int main (void)
{
    
    // 直接调用 LPUART_API_ADDR 地址共享的函数代码接口
    g_LpuartDrv->init();

    char *send = "App Project Run";
    g_LpuartDrv->write(send, strlen(send));


}

5.2 更改 LPUART0_IRQHandler 向量为绝对物理地址

共享区的串口中断函数 LPUART0_IRQHandler 是全局唯一的,Boot 和 APP 的汇编向量表中,串口中断向量必须全部直接指向共享区的这个函数物理地址 ,且两个程序都要配置中断向量重定向 (VTOR) .

bash 复制代码
# 1. 编译共享代码工程后,打开 .map 文件 ,搜索 LPUART0_IRQHandler 。
# 如下 .map文件 LPUART0_IRQHandler 示例:
.shared_code    0x00001800      0x354
                0x00001800                        . = ALIGN (0x4)
 *(.shared_ptr_table)
 .shared_ptr_table
                0x00001800       0x18 ./build/Debug/.obj/LpUart/LpuartDrv.o
                0x00001800                g_LpuartDrv
 *(.shared_func)
 .shared_func   0x00001818      0x33c ./build/Debug/.obj/LpUart/LpuartDrv.o
                0x00001918                LpUartDrvInit
                0x00001a3c                LpUartDrvRead
                0x00001ad8                LpUartDrvWrite
                0x00001b04                LPUART0_IRQHandler
                0x00001b54                        . = ALIGN (0x4)
                0x00005ab4                        __etext = LOADADDR (.data)


# 2.  记录 LPUART0_IRQHandler 的绝对物理地址 0x00001b04 ; 这个地址是固定的,Boot/APP 向量表都用它 .
      
# 3. 更改 Boot/APP 工程里的 .S文件, 将 LPUART0_IRQHandler 替换为绝对物理地址   0x00001b04 
#   .S文件的 中断向量表 如下:
               /* Interrupts */
                .long       PORTA_IRQHandler
                .long       PORTB_IRQHandler
                .long       PORTC_IRQHandler
                .long       PORTD_IRQHandler
                .long       DMAC_IRQHandler
                .long       TIM3_IRQHandler
                .long       UART0_IRQHandler
                .long       UART1_IRQHandler
               # .long       LPUART0_IRQHandler   更改此行
                .long       0x00001b04     # 替换为共享区中断物理地址
                .long       LPUART1_IRQHandler
                .long       SPI0_IRQHandler
                .long       SPI1_IRQHandler
                .long       I2C0_IRQHandler  


# 原写法 .long LPUART0_IRQHandler:链接器会找当前工程的函数地址
# 新写法 .long 0x00001B04:直接告诉 MCU 硬件物理地址,跨工程、跨分区通用

5.3 Boot/App进行 中断向量表重定向

bash 复制代码
# 1. Boot 程序 VTOR 配置 为 Boot 分区的起始地址
SCB->VTOR = FLASH_BOOT_ADDR_START;


# 2. App 程序 VTOR 配置 为 App 分区的起始地址
SCB->VTOR = FLASH_APP_ADDR_START;
相关推荐
单片机仿真设计1 小时前
【proteus仿真】基于 STM32 单片机农业灾害监测系统(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus
Rocker-Chen2 小时前
应广科技MCU使用注意事项
单片机·嵌入式硬件
HRTOS2 小时前
HRTOS Debug 正式发布:为 8051 实时系统提供可观测、可诊断的运行状态监控能力
单片机·嵌入式硬件·51单片机
llilian_162 小时前
PTP时钟服务器时间溢出隐患解决方案 1588时钟服务器 ptp服务器
大数据·网络·单片机·嵌入式硬件·51单片机
追涨杀跌老能手2 小时前
GD32H759 + RT-Thread 工控实战--第4篇 SDRAM,SDIO,以及触摸屏
开源·嵌入式
女神下凡4 小时前
带NPU的电脑 和不带NPU的电脑 在使用上有什么不样
arm开发·单片机·pcm
smartpi_ai4 小时前
CI-73T 的三个串口怎么分?下载口固定的引脚账本、词条 150 条的“词数“口径与硬 PWM 控舵机的性能边界
单片机·嵌入式硬件·语音识别
艾芯微科技4 小时前
ESDUNL24VC1 单向 ESD 静电保护二极管参数、电路设计与 PCB 防护布局
网络·单片机·嵌入式硬件·集成测试·51单片机
佳児素花痴╮4 小时前
STM32(中断)
stm32·单片机·嵌入式硬件