技术问答

MK米客方德 SD NAND分区设置指南:双分区与多分区设计及切换方法

2026-07-09 拓优星辰


MK米客方德SD NAND产品图

数字标牌存储应用
一、为什么需要多分区?
在嵌入式产品设计中,存储空间的分区规划是一项容易被忽视但至关重要的架构决策。合理的分区设计可以带来三大好处:
安全性提升:将固件区和数据区物理隔离,防止误操作覆盖固件
可靠性增强:一个分区文件系统损坏不会波及其他分区
管理灵活性:各分区可独立格式化、独立挂载、设置不同访问权限
MK米客方德SD NAND兼容标准SD协议,支持MBR分区表,可实现最多4个主分区。本文将从实际工程角度,提供完整的分区设计指南和切换方法。
二、分区设计前的规划
2.1 容量评估
以MK米客方德SD NAND 32Gbit(4GB)为例,规划三个分区:
分区 用途 文件系统 大小 访问模式
分区1 系统固件 FAT32 64MB 只读(升级时可写)
分区2 应用数据 FAT32 3.5GB 读写
分区3 运行日志 FAT32 256MB 读写
2.2 分区命名规范
为便于管理和识别,建议为每个分区设置有意义的卷标(Volume Label):
分区1卷标:MK_FW(Firmware)
分区2卷标:MK_DATA(用户数据)
分区3卷标:MK_LOG(运行日志)
FAT32卷标最长11个字符(8.3格式),建议使用大写字母加下划线的命名方式。
2.3 分区访问权限规划
不同分区的访问权限应有明确区分:
分区 正常运行时 升级模式时 出厂模式时
固件分区 只读 读写 读写
数据分区 读写 只读 格式化
日志分区 读写 只读 格式化
通过软件层面的访问控制,确保固件区在日常运行中不被意外修改。
三、双分区创建实操
3.1 使用Linux parted工具创建
# 确认设备节点(假设为/dev/sdb)
lsblk

# 步骤1:创建MBR分区表
sudo parted /dev/sdb mklabel msdos

# 步骤2:创建固件分区(1MB对齐,64MB大小)
sudo parted /dev/sdb mkpart primary fat32 1MiB 65MiB

# 步骤3:创建数据分区(剩余空间)
sudo parted /dev/sdb mkpart primary fat32 65MiB 100%

# 步骤4:格式化分区
sudo mkfs.vfat -F 32 -n MK_FW /dev/sdb1
sudo mkfs.vfat -F 32 -n MK_DATA /dev/sdb2

# 步骤5:验证
sudo fdisk -l /dev/sdb
3.2 使用Windows diskpart创建
diskpart
list disk
select disk X
clean
create partition primary size=64
format fs=fat32 label=MK_FW quick
create partition primary
format fs=fat32 label=MK_DATA quick
assign
exit
3.3 MCU端动态创建分区
在生产烧录环节,可能需要在MCU端动态创建分区。以下是基于STM32 HAL库的分区创建代码:
// 分区表项结构
#pragma pack(push, 1)
typedef struct {
    uint8_t  boot_flag;
    uint8_t  start_chs[3];
    uint8_t  part_type;
    uint8_t  end_chs[3];
    uint32_t start_lba;
    uint32_t num_sectors;
} partition_entry_t;
#pragma pack(pop)

int32_t create_partitions(void)
{
    uint8_t mbr[512] = {0};
    partition_entry_t *pt = (partition_entry_t *)&mbr[446];
   
    // 获取SD NAND总容量(扇区数)
    uint32_t total_sectors = sd_get_capacity_sectors();
    uint32_t align = 2048;  // 1MB对齐
    uint32_t fw_sectors = 64 * 1024 * 1024 / 512;  // 64MB
   
    // 分区1:固件区
    pt[0].boot_flag = 0x00;
    pt[0].part_type = 0x0C;  // FAT32 LBA
    pt[0].start_lba = align;
    pt[0].num_sectors = fw_sectors;
    // CHS地址设为最大值表示使用LBA
    memset(pt[0].start_chs, 0xFE, 3);
    memset(pt[0].end_chs, 0xFE, 3);
   
    // 分区2:数据区
    pt[1].boot_flag = 0x00;
    pt[1].part_type = 0x0C;
    pt[1].start_lba = align + fw_sectors;
    pt[1].num_sectors = total_sectors - pt[1].start_lba;
    memset(pt[1].start_chs, 0xFE, 3);
    memset(pt[1].end_chs, 0xFE, 3);
   
    // MBR签名
    mbr[510] = 0x55;
    mbr[511] = 0xAA;
   
    // 写入MBR
    if (sd_write_blocks(0, mbr, 1) != 0) return -1;
   
    // 使用FatFS格式化各分区
    MKFS_PARM parm = {.fmt = FM_FAT32, .n_fat = 1, .au_size = 4096};
    uint8_t workbuf[8192];
   
    f_mkfs("0:", &parm, workbuf, sizeof(workbuf));
    f_mkfs("1:", &parm, workbuf, sizeof(workbuf));
   
    return 0;
}
四、分区切换方法详解
4.1 FatFS多分区配置
FatFS通过VolToPart数组实现分区映射。需要在ffconf.h中启用多分区支持:
// ffconf.h
#define FF_MULTI_PARTITION  1
#define FF_VOLUMES          3  // 支持最多3个卷
然后定义分区映射表:
// 在diskio.c或主程序中
PARTITION VolToPart[FF_VOLUMES] = {
    {0, 1},  // "0:" -> 物理驱动器0,分区1(固件区)
    {0, 2},  // "1:" -> 物理驱动器0,分区2(数据区)
    {0, 3}   // "2:" -> 物理驱动器0,分区3(日志区,如有)
};
4.2 分区挂载与卸载
FATFS fs_fw, fs_data, fs_log;

// 挂载所有分区
void mount_all_partitions(void)
{
    FRESULT res;
   
    res = f_mount(&fs_fw, "0:", 1);   // 固件区
    if (res != FR_OK) printf("FW mount failed: %d\n", res);
   
    res = f_mount(&fs_data, "1:", 1);  // 数据区
    if (res != FR_OK) printf("DATA mount failed: %d\n", res);
   
    res = f_mount(&fs_log, "2:", 1);   // 日志区
    if (res != FR_OK) printf("LOG mount failed: %d\n", res);
}

// 安全卸载
void unmount_all_partitions(void)
{
    f_mount(NULL, "0:", 0);
    f_mount(NULL, "1:", 0);
    f_mount(NULL, "2:", 0);
}
4.3 分区级格式化
当某个分区的文件系统损坏时,可以单独格式化该分区,不影响其他分区:
// 仅格式化日志分区
FRESULT format_log_partition(void)
{
    MKFS_PARM parm = {.fmt = FM_FAT32, .n_fat = 1, .au_size = 4096};
    uint8_t workbuf[8192];
   
    // 先卸载
    f_mount(NULL, "2:", 0);
   
    // 格式化
    FRESULT res = f_mkfs("2:", &parm, workbuf, sizeof(workbuf));
    if (res == FR_OK) {
        // 重新挂载
        f_mount(&fs_log, "2:", 1);
    }
    return res;
}
五、分区切换的工程实践
5.1 运行时分区切换场景
在某些应用中,需要在运行时动态切换访问的分区。例如:
固件升级模式:从正常运行的数据读取模式切换到固件写入模式
诊断模式:从数据分区切换到日志分区进行日志导出
恢复模式:格式化数据分区恢复出厂设置
5.2 固件升级分区切换流程
typedef enum {
    MODE_NORMAL,    // 正常运行模式
    MODE_UPGRADE,   // 固件升级模式
    MODE_RECOVERY   // 恢复模式
} system_mode_t;

system_mode_t current_mode = MODE_NORMAL;

void enter_upgrade_mode(void)
{
    // 1. 卸载数据分区
    f_mount(NULL, "1:", 0);
   
    // 2. 重新挂载固件分区为可写模式
    f_mount(NULL, "0:", 0);
    f_mount(&fs_fw, "0:", 1);
   
    current_mode = MODE_UPGRADE;
}

void exit_upgrade_mode(void)
{
    // 1. 卸载固件分区
    f_mount(NULL, "0:", 0);
   
    // 2. 重新挂载固件分区为只读
    f_mount(&fs_fw, "0:", 1);
   
    // 3. 重新挂载数据分区
    f_mount(&fs_data, "1:", 1);
   
    current_mode = MODE_NORMAL;
}
5.3 分区健康检查
在每次启动时,建议对各分区进行健康检查:
void check_partition_health(void)
{
    FATFS fs;
    FRESULT res;
   
    // 检查固件分区
    res = f_mount(&fs, "0:", 1);
    if (res == FR_NO_FILESYSTEM) {
        // 固件分区文件系统损坏 - 严重错误
        log_error("Firmware partition corrupted!");
        enter_recovery_mode();
    } else if (res == FR_DISK_ERR) {
        // 硬件错误
        log_error("SD NAND hardware error!");
        halt_system();
    }
    f_mount(NULL, "0:", 0);
   
    // 检查数据分区
    res = f_mount(&fs, "1:", 1);
    if (res == FR_NO_FILESYSTEM) {
        // 数据分区损坏 - 格式化恢复
        log_warning("Data partition corrupted, formatting...");
        MKFS_PARM parm = {.fmt = FM_FAT32, .au_size = 4096};
        uint8_t buf[8192];
        f_mkfs("1:", &parm, buf, sizeof(buf));
    }
    f_mount(NULL, "1:", 0);
}
六、分区设计最佳实践
1MB对齐:所有分区起始地址必须1MB对齐,确保与NAND页边界对齐
固件区隔离:固件区应设置最小的写入权限,降低意外损坏风险
日志区独立:日志写入频繁,独立分区避免影响数据区文件系统性能
保留余量:不要将分区填满整个容量,保留1-2%作为FTL的预留空间
卷标命名:使用有意义的卷标,便于PC端识别和调试
定期检查:利用MK米客方德SD NAND的Smart Function监测各分区的写入量和剩余寿命
七、总结
MK米客方德SD NAND的多分区设计为嵌入式产品提供了灵活的存储管理方案。通过双分区实现固件与数据分离、三分区增加日志隔离,以及A/B分区支持安全OTA升级,工程师可以根据产品需求灵活配置存储空间。FatFS的多分区支持使得在MCU端访问不同分区如同访问不同驱动器一样简单,而MK米客方德SD NAND内置控制器的FTL则确保了多分区下的磨损均衡和可靠性不受影响。