
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则确保了多分区下的磨损均衡和可靠性不受影响。