4. Flash分区表概念

在嵌入式系统中,Flash存储器通常被划分为多个逻辑区域,每个区域承担特定的功能。这种划分方案被称为分区表。分区表是系统固件管理Flash空间的核心依据,它定义了Bootloader、应用程序、配置数据、日志等分区的位置、大小和访问权限。

核心观点:分区表是Flash存储管理的“地图”。没有分区表,Bootloader无法找到应用程序入口,应用程序无法持久化配置数据,日志系统也无法确定写入范围。在Zephyr RTOS中,分区表通过设备树(Devicetree)或Kconfig进行静态定义。

4.1 分区表的作用

分区表在嵌入式系统中的核心作用可以归纳为以下四点:

  • 空间隔离:防止不同功能模块(如Bootloader与应用程序)互相覆盖,避免因越界写入导致系统崩溃。
  • 启动引导:Bootloader根据分区表定位应用程序的起始地址和大小,实现固件加载与跳转。
  • 数据持久化:为配置参数、校准数据、运行日志等提供专用的非易失性存储区域,支持擦写均衡和掉电保护。
  • 固件升级:OTA升级时,新固件写入备用分区,升级完成后交换分区指针,实现安全回滚。
工程经验:分区表的设计应在项目初期完成,一旦量产,修改分区表可能导致固件不兼容。建议预留10%~20%的Flash空间作为未来扩展储备。

4.2 典型分区结构

一个典型的嵌入式Flash分区表包含以下五个核心分区。下表列出了各分区的名称、功能、典型大小及访问属性:

分区名称 功能描述 典型大小 访问属性
Bootloader 系统上电后最先执行的代码,负责硬件初始化、校验应用程序、引导跳转 64 KB ~ 256 KB 只读(运行时),可写(升级时)
应用程序 用户业务逻辑固件,包含主循环、协议栈、驱动等 512 KB ~ 2 MB 只读(运行时),可写(OTA时)
配置数据 设备校准参数、网络凭证、用户设置等持久化数据 16 KB ~ 128 KB 读写
日志存储 系统运行日志、错误记录、调试信息 64 KB ~ 256 KB 读写(循环覆盖)
OTA备份 固件升级时存放新固件,升级失败时用于回滚 与应用程序分区等大 读写(升级期间)
注意:分区大小必须与Flash的擦除块(Sector/Page)大小对齐。例如,若Flash擦除块为4 KB,则分区大小应为4 KB的整数倍,否则会导致擦写操作越界或效率低下。

4.3 分区布局SVG示意图

下图展示了一个典型的Flash分区布局,从低地址到高地址依次排列Bootloader、应用程序、配置数据、日志存储和OTA备份分区:

Flash分区布局示意图 Flash 分区布局示意图 低地址 高地址 Bootloader 分区 (64 KB ~ 256 KB) 应用程序分区 (512 KB ~ 2 MB) 配置数据分区 (16 KB ~ 128 KB) 日志存储分区 (64 KB ~ 256 KB) OTA 备份分区 (与应用程序等大) 地址 递增 方向 分区 连续 排列 大小 对齐 扇区

4.4 分区表在Zephyr中的定义方式

在Zephyr RTOS中,分区表通常通过设备树(Devicetree)的fixed-partitions节点进行定义。以下是一个典型的Flash分区定义示例:

/ {
  soc {
    flash_controller: flash@40000000 {
      compatible = "soc-nv-flash";
      reg = <0x40000000 0x100000>;  /* 1 MB Flash */
      
      partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;
        
        boot_partition: partition@0 {
          label = "bootloader";
          reg = <0x00000000 0x00010000>;  /* 64 KB */
          read-only;
        };
        
        app_partition: partition@10000 {
          label = "application";
          reg = <0x00010000 0x00080000>;  /* 512 KB */
          read-only;
        };
        
        config_partition: partition@90000 {
          label = "config";
          reg = <0x00090000 0x00004000>;  /* 16 KB */
        };
        
        log_partition: partition@94000 {
          label = "log";
          reg = <0x00094000 0x00010000>;  /* 64 KB */
        };
        
        ota_partition: partition@A4000 {
          label = "ota_backup";
          reg = <0x000A4000 0x00080000>;  /* 512 KB */
        };
      };
    };
  };
};
关键字段说明:
  • label:分区名称,用于在代码中通过DT_LABEL()宏引用。
  • reg:分区的起始地址和大小,单位字节。地址必须与Flash基地址对齐。
  • read-only:可选属性,标记分区为只读,防止运行时误写入。
  • fixed-partitions:固定分区表,分区在编译时确定,运行时不可动态调整。

4.5 分区设计原则与避坑指南

在实际项目中,分区表设计需要遵循以下原则,以避免常见的工程陷阱:

  • 对齐原则:每个分区的起始地址和大小必须是Flash擦除块大小的整数倍。例如,若擦除块为4 KB,则分区起始地址应为0x1000的倍数。
  • 预留空间:在分区表末尾预留一个未分配区域,用于未来扩展或紧急存储。
  • 备份分区:对于关键数据(如配置参数),建议保留两个备份分区,采用双备份策略防止写入过程中掉电导致数据损坏。
  • 日志循环:日志分区通常设计为环形缓冲区,需要预留足够的空间以避免频繁擦写导致Flash磨损。
  • Bootloader保护:Bootloader分区应设置为硬件写保护(如果MCU支持),防止应用程序异常时意外擦除Bootloader。
常见错误:分区大小未对齐擦除块,导致擦写操作跨越分区边界,破坏相邻分区的数据。例如,将配置数据分区大小设为20 KB,而Flash擦除块为4 KB,则最后一个4 KB块中只有2 KB属于配置分区,剩余2 KB可能被误擦除。

4.6 分区表与Flash驱动的关系

分区表是Flash驱动层的上层抽象。在Zephyr中,Flash驱动提供底层的读、写、擦除接口,而分区表驱动(flash_map)在此基础上增加了分区管理和偏移计算功能。下图展示了分区表在软件架构中的位置:

分区表在软件架构中的位置 分区表在软件架构中的位置 应用层(Bootloader / App / 日志服务) 分区表层(flash_map API) Flash驱动层(read / write / erase) 硬件层(Flash存储器芯片) 分区表 将逻辑分区 映射到 物理地址

通过分区表,应用层无需关心Flash的物理地址和擦除块大小,只需通过分区名称(如"config")即可读写数据。这种抽象大大提高了代码的可移植性和可维护性。