NAND Flash固件备份存储实现

AI助手已提取文章相关产品:

NAND Flash固件备份存储实现

你有没有遇到过这样的情况:设备远程升级到一半突然断电,重启后直接“变砖”?😅 或者现场调试时,一个误操作把固件刷坏了,只能拆机用烧录器救砖?——这在工业网关、IoT终端、智能家居控制器里太常见了。

其实,解决这类问题的关键,并不在于“别出错”,而在于 系统要有容错能力 。就像飞机不会因为一个引擎故障就坠毁一样,我们的嵌入式系统也得具备“双引擎”甚至“应急滑翔”的能力。而其中最经济、最实用的一招,就是: 基于 NAND Flash 的固件备份机制


现在大多数中高端嵌入式设备都用 NAND Flash 存储固件——便宜、容量大,动辄几百MB甚至几GB。但 NAND 有个“小脾气”:它天生可能带坏块,擦写多了还会产生新坏块,数据保存时间久了也可能出错。更麻烦的是,它不能像内存那样随意覆盖写入,必须先擦除整个块才能写。

所以,如果你直接把固件往物理地址一扔,不出半年,设备可能就在某个清晨默默罢工了。😤

那怎么办?聪明的做法是: 别指望 NAND 完美无瑕,而是设计一套“即使它出问题也能正常启动”的机制


我们来看看怎么玩转这套“保险系统”。

想象一下,你的设备里有两份一模一样的固件,分别放在主区和备份区。平时系统从主区启动;一旦检测到主固件损坏——比如 CRC 校验失败、签名无效、或者干脆读不出来——Bootloader 就会自动切换到备份区启动。是不是有点像“备用轮胎”?

但这还不是全部。真正的高手玩法是: OTA 升级时,新固件不是覆盖当前运行的系统,而是先写进备份区 。等写完、校验通过后,再告诉 Bootloader:“下次启动请从备份区走”。重启之后,新系统顺利跑起来,原来的主区反而变成了新的备份区。🔁

这个过程,学名叫 A/B 分区升级(或称无缝升级) ,Android 系统早就这么干了。而在资源受限的嵌入式系统里,只要合理规划,同样可以低成本实现。


要让这一切跑起来,底层驱动和顶层逻辑都得配合好。先看 NAND 自己的“性格特点”:

  • 最小读写单位是“页”(Page),一般是 2KB 到 16KB;
  • 擦除单位是“块”(Block),一个块包含几十到几百个页;
  • 每个块的擦写寿命有限,SLC 大概 10 万次,MLC/TLC 就更少;
  • 出厂就有坏块,使用中还会动态产生坏块;
  • 数据容易出错,必须配 ECC(纠错码),比如 BCH 或 LDPC;
  • OOB 区(也叫 Spare Area)虽然只有几十字节,但能存关键元信息,比如坏块标记、固件版本、状态标志等。

所以,你不能直接裸读裸写物理地址,得有一层管理机制。Linux 里的 MTD(Memory Technology Device)子系统 就是干这个的。它抽象出 mtdblock 设备节点,帮你处理 ECC、坏块跳过、地址映射等问题。你可以把它理解为 NAND 的“管家”。

在实际初始化时,第一件事就是扫描所有块,看看哪些是坏的:

int nand_init(void) {
    uint32_t block;
    for (block = 0; block < TOTAL_BLOCKS; block++) {
        if (nand_is_bad_block(block)) {
            mark_block_as_reserved(block);
            continue;
        }
        if (!nand_read_oob(block * PAGES_PER_BLOCK, &oob_data)) {
            if (is_firmware_valid(&oob_data)) {
                add_to_firmware_list(block);
            }
        }
    }
    return 0;
}

int nand_is_bad_block(uint32_t block) {
    uint8_t marker;
    read_spare_area(block * PAGES_PER_BLOCK, &marker, 1);
    return (marker != 0xFF);  // 厂标非0xFF即为坏块
}

这段代码干了三件事:
1. 遍历所有块;
2. 检查是否为坏块(通过读首个页的 OOB 区);
3. 如果不是坏块,再看看里面有没有有效的固件头。

这样一来,系统启动时就知道“哪里能读、哪里有货”,避免踩雷。💣


接下来是重头戏: Bootloader 怎么决定从哪启动?

我们可以在每个固件镜像前面加一个“头信息”,记录版本、CRC、状态等:

typedef struct {
    uint32_t fw_version;
    uint32_t crc32;
    uint8_t  status;   // 0: invalid, 1: valid, 2: upgrading
    uint8_t  reserved[7];
} firmware_header_t;

然后 Bootloader 上电后就这么判断:

boot_source_t select_boot_source(void) {
    firmware_header_t primary_hdr, backup_hdr;

    if (read_firmware_header(PRIMARY_FW_BASE, &primary_hdr) == 0 &&
        primary_hdr.status == FW_VALID &&
        verify_crc(&primary_hdr)) {
        return BOOT_PRIMARY;
    }

    if (read_firmware_header(BACKUP_FW_BASE, &backup_hdr) == 0 &&
        backup_hdr.status == FW_VALID &&
        verify_crc(&backup_hdr)) {
        return BOOT_BACKUP;
    }

    enter_recovery_mode();
    return BOOT_NONE;
}

你看,逻辑非常清晰:
- 先试主区,成功就走;
- 主区不行,再试备份区;
- 两个都挂了?那就进 recovery 模式,比如通过 USB 或串口重新烧录。

这种设计下,哪怕 OTA 升级中断,顶多就是“升级失败”,不会导致设备无法启动。👏


再来看看典型的应用场景——比如一台工业网关:

+---------------------+
|     Application     |
+---------------------+
|     OS / RTOS       |
+---------------------+
|   Firmware Image B  | ← OTA 升级目标
+---------------------+
|   Firmware Image A  | ← 当前运行系统
+---------------------+
|     FTL / MTD       | ← NAND 管理层(ECC + 坏块处理)
+---------------------+
|    NAND Flash HW    |
+---------------------+

它的升级流程大概是这样:

  1. 设备收到新固件包,通过 HTTPS 或 MQTT 下发;
  2. 固件被写入 备份分区 (Image B),不影响当前运行的 Image A;
  3. 写完后计算 CRC,验证完整性;
  4. 更新启动配置:设置“下次从 B 启动”;
  5. 发出 reboot 指令;
  6. U-Boot 启动后读取标记,发现该切了,于是加载 Image B;
  7. 新系统运行成功,再把旧的 A 区标记为备份,完成轮换。

整个过程,用户几乎无感。🎉

如果不幸在第 2 步断电了呢?没关系!下次上电,Bootloader 发现主区还是好的,继续从 A 启动,一切照常。等网络恢复后,OTA 任务还能重试。


当然,这种方案也不是“开箱即用”,实际落地时有几个坑要注意:

🔧 分区大小怎么定?

建议主备分区大小一致,且预留至少 10% 的额外空间用于磨损均衡和垃圾回收。别忘了,NAND 不是无限擦写的!

🔐 ECC 强度够不够?

SLC NAND 可以用 BCH-4bit,但如果你用的是 MLC 或 TLC 颗粒,建议上 BCH-8bit 甚至 LDPC,否则纠不了错,备份也没意义。

💾 写放大怎么控制?

频繁写小数据会导致大量无效擦写。建议采用缓冲合并策略,批量写入,减少对 NAND 的折腾。

⚡ 断电保护怎么做?

最关键的状态标记(比如“即将切换”)一定要写多次,并做 CRC 校验。最好还能加上掉电检测电路,在断电瞬间完成关键数据落盘。

🔒 安全性考虑

固件最好加上 AES 加密 + RSA 签名。否则黑客拿个编程器一读,算法全暴露;再伪造个低版本固件降级攻击,系统就危险了。防回滚机制一定要有,Bootloader 得拒绝比当前版本低的固件启动。


说到这里,你可能会问:为什么不直接用 NOR Flash?它支持 XIP(就地执行),启动快,还稳定。

答案很简单: 成本和容量
一块 32MB 的 NOR Flash 可能比 1GB 的 NAND 还贵。而现在的 Linux 系统动不动就上百MB,APP 越来越臃肿,NOR 根本装不下。NAND 虽然复杂点,但胜在“性价比之王”。


最后总结一下,真正靠谱的固件存储方案,不是靠硬件“不坏”,而是靠架构“容错”。而 NAND Flash + 双分区备份 + 智能 Bootloader 的组合,正是这一思想的最佳实践之一。

它带来的不只是“不怕升级失败”,更是:
- 远程运维的底气;
- 快速迭代的能力;
- 用户体验的保障;
- 维护成本的大幅降低。

换句话说, 这不是锦上添花的功能,而是现代智能设备的“生存底线” 。🛠️

所以,下次你在画 PCB、选 Flash 颗粒、写 Bootloader 的时候,不妨多花半小时想想:我的系统,有“备胎”吗?🚗💨

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

您可能感兴趣的与本文相关内容

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值