返回首页

Flash (内部 Flash) 学习笔记

本笔记学习 Zephyr 的 Flash 驱动,用于内部 Flash 编程操作。


示例路径

zephyr/samples/drivers/flash_shell/

核心概念

Flash 存储特性

特性说明
擦除单位扇区 (Sector)
写入单位页 (Page) 或字节
擦除后状态全 1 (0xFF)
写入后状态只能从 1 变为 0
重写需要先擦除

nRF54L15 内部 Flash 大小约 1.5 MB。


核心 API

头文件

#include <zephyr/drivers/flash.h>

设备获取

const struct device *const flash_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash));

扇区信息获取

获取扇区大小

struct flash_pages_info info;
uint32_t sector_size;

flash_get_page_info_by_offs(flash_dev, 0, &info);
sector_size = info.size;  // 扇区大小

获取扇区数量

size_t sector_count = flash_get_page_count(flash_dev);

擦除操作

擦除单个扇区

int flash_erase(const struct device *dev, off_t offset, size_t size);

示例

// 擦除 offset 0 开始的一个扇区
int ret = flash_erase(flash_dev, 0, sector_size);
if (ret != 0) {
    printk("Flash erase failed! ret = %d\n", ret);
}

写入操作

写入数据

int flash_write(const struct device *dev, off_t offset, 
                const void *data, size_t len);

示例

uint8_t data[] = {0x01, 0x02, 0x03, 0x04};
off_t offset = 0;

int ret = flash_write(flash_dev, offset, data, sizeof(data));
if (ret != 0) {
    printk("Flash write failed! ret = %d\n", ret);
}

读取操作

读取数据

int flash_read(const struct device *dev, off_t offset,
               void *data, size_t len);

示例

uint8_t buf[4];
off_t offset = 0;

int ret = flash_read(flash_dev, offset, buf, sizeof(buf));
if (ret != 0) {
    printk("Flash read failed! ret = %d\n", ret);
} else {
    printk("Data read: %02x %02x %02x %02x\n", 
           buf[0], buf[1], buf[2], buf[3]);
}

完整示例:写入、读取、验证

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/sys/printk.h>

#define TEST_OFFSET  0
#define TEST_DATA    0xAA

int main(void)
{
    const struct device *const flash_dev = 
        DEVICE_DT_GET(DT_CHOSEN(zephyr_flash));
    struct flash_pages_info info;
    uint8_t buf[1];
    int ret;
    
    if (!device_is_ready(flash_dev)) {
        printk("Flash device not ready\n");
        return 0;
    }
    
    // 获取扇区信息
    flash_get_page_info_by_offs(flash_dev, TEST_OFFSET, &info);
    printk("Sector size: %u bytes\n", info.size);
    
    // 擦除
    ret = flash_erase(flash_dev, TEST_OFFSET, info.size);
    if (ret != 0) {
        printk("Erase failed: %d\n", ret);
        return 0;
    }
    printk("Sector erased\n");
    
    // 写入
    buf[0] = TEST_DATA;
    ret = flash_write(flash_dev, TEST_OFFSET, buf, 1);
    if (ret != 0) {
        printk("Write failed: %d\n", ret);
        return 0;
    }
    printk("Data written: 0x%02X\n", TEST_DATA);
    
    // 读取验证
    ret = flash_read(flash_dev, TEST_OFFSET, buf, 1);
    if (ret != 0) {
        printk("Read failed: %d\n", ret);
        return 0;
    }
    
    if (buf[0] == TEST_DATA) {
        printk("Verification OK: 0x%02X\n", buf[0]);
    } else {
        printk("Verification FAILED: 0x%02X\n", buf[0]);
    }
    
    return 0;
}

Flash Shell 命令

如果启用了 CONFIG_FLASH_SHELL,可以使用 shell 命令:

# 擦除扇区
flash erase 0 4096

# 写入数据
flash write 0 deadbeef

# 读取数据
flash read 0 16

# 获取扇区信息
flash page_info 0

设备树配置示例

/ {
    chosen {
        zephyr,flash = &flash0;
    };
};

&flash0 {
    status = "okay";
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;
        
        storage_partition: partition@0 {
            label = "storage";
            reg = <0x00000000 0x00010000>;
        };
    };
};

上面的分区地址和大小只是示例,实际项目里要避开引导区、镜像区和已有存储分区。


编译命令

cd /home/wangpei/ncs-sdk
west build -p -b nrf54l15dk/nrf54l15/cpuapp zephyr/samples/drivers/flash_shell
west flash

配置选项

# prj.conf
CONFIG_FLASH=y
CONFIG_FLASH_SHELL=y
CONFIG_FLASH_PAGE_LAYOUT=y

应用场景

  1. 固件升级 (OTA): 存储 OTA 镜像
  2. 参数存储: 保存配置参数
  3. 数据日志: 存储运行日志
  4. 用户数据: 存储用户数据

注意事项

  1. 擦除后写入: Flash 只能从 1 写成 0,重写前必须擦除
  2. 擦除寿命: Flash 有擦写次数限制(约 10,000 - 100,000 次)
  3. 中断保护: 擦写过程中避免中断,防止数据损坏
  4. 地址对齐: 某些 Flash 要求地址对齐
  5. 避免代码区: 不要擦写正在运行的代码区域

安全建议

// 使用 Flash 地图 API 更安全
#include <zephyr/storage/flash_map.h>

#define STORAGE_PARTITION storage_partition

// 获取分区信息
const struct flash_area *fa;
flash_area_open(FIXED_PARTITION_ID(STORAGE_PARTITION), &fa);

// 在分区内操作
flash_area_erase(fa, 0, fa->fa_size);
flash_area_write(fa, 0, data, len);
flash_area_read(fa, 0, buf, len);

flash_area_close(fa);

*小白 🤖 - 2026-03-16*