You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32能否在指定地址永久存数据?无库Flash永久存储实现方法

在ESP32无库实现指定地址Flash永久存储

首先明确:ESP32的Flash无法像RAM那样直接通过指针赋值写入,因为Flash属于块擦除型非易失性存储,必须遵循「先擦除、后写入」的操作逻辑,且需避开系统占用的存储区域(如程序分区、NVS、OTA分区等)。以下是不依赖任何库的底层实现方法:

核心前提

  1. 确认Flash空闲区域:先确定你的ESP32 Flash总容量(常见4MB/8MB),地址范围对应0x00000000~0x00400000(4MB)。通过ESP-IDF的parttool.py或自定义分区表,找到未被系统/应用占用的连续区域,比如选择Flash尾部的空闲扇区(如0x003F0000,需自行验证分区表确认)。
  2. Flash操作规则:
    • 最小擦除单位为4KB扇区(部分Flash芯片为64KB块,需参考对应Flash datasheet);
    • 写入需以页为单位(通常256字节/页),且不能跨页;
    • 写入前必须擦除目标扇区(Flash只能将位从1置0,擦除是将扇区所有位恢复为1)。

底层寄存器操作实现

直接操作ESP32的SPI1寄存器(Flash挂载在SPI1总线),以下是关键寄存器定义及操作函数:

寄存器定义

// SPI1寄存器基地址(ESP32的SPI1负责Flash通信)
#define SPI1_BASE 0x3FF42000
// Flash操作相关寄存器
#define SPI_FLASH_CMD_REG    (*(volatile uint32_t*)(SPI1_BASE + 0x00))
#define SPI_FLASH_ADDR_REG   (*(volatile uint32_t*)(SPI1_BASE + 0x04))
#define SPI_FLASH_WDATA_REG  (*(volatile uint32_t*)(SPI1_BASE + 0x08))
#define SPI_FLASH_RDATA_REG  (*(volatile uint32_t*)(SPI1_BASE + 0x0C))
#define SPI_FLASH_STATUS_REG (*(volatile uint32_t*)(SPI1_BASE + 0x10))

基础操作函数

// 等待Flash操作完成(轮询BUSY位)
void flash_wait_busy(void) {
    while (SPI_FLASH_STATUS_REG & (1 << 0)); // BUSY位为状态寄存器第0位
}

// 发送Flash写使能命令(所有擦除/写入操作前必须执行)
void flash_write_enable(void) {
    SPI_FLASH_CMD_REG = 0x06; // 写使能命令码0x06
    flash_wait_busy();
}

// 擦除4KB扇区(地址必须是扇区起始地址,如0x003F0000)
void flash_erase_sector(uint32_t addr) {
    flash_write_enable();
    SPI_FLASH_CMD_REG = 0x20; // 扇区擦除命令码0x20
    SPI_FLASH_ADDR_REG = addr & 0xFFFFFF; // Flash为24位地址,取低24位
    flash_wait_busy();
}

// 页编程写入(地址需在已擦除扇区内,长度≤256字节且不跨页)
void flash_write_page(uint32_t addr, const uint8_t* data, uint32_t len) {
    if (len == 0 || len > 256) return;
    flash_write_enable();
    SPI_FLASH_CMD_REG = 0x02; // 页编程命令码0x02
    SPI_FLASH_ADDR_REG = addr & 0xFFFFFF;
    for (uint32_t i = 0; i < len; i++) {
        SPI_FLASH_WDATA_REG = data[i]; // 逐字节写入
        flash_wait_busy();
    }
}

// 读取Flash数据
void flash_read(uint32_t addr, uint8_t* data, uint32_t len) {
    SPI_FLASH_CMD_REG = 0x03; // 读命令码0x03
    SPI_FLASH_ADDR_REG = addr & 0xFFFFFF;
    for (uint32_t i = 0; i < len; i++) {
        data[i] = SPI_FLASH_RDATA_REG & 0xFF; // 逐字节读取
        flash_wait_busy();
    }
}

使用示例

// 自定义的空闲Flash地址(需根据你的ESP32分区表确认)
#define MY_FREE_FLASH_ADDR 0x003F0000

int main(void) {
    uint8_t write_buf[] = {0xFF, 0xAA, 0x55, 0x00};
    uint8_t read_buf[4] = {0};

    // 1. 擦除目标扇区(必须先擦除才能写入)
    flash_erase_sector(MY_FREE_FLASH_ADDR);
    // 2. 写入数据
    flash_write_page(MY_FREE_FLASH_ADDR, write_buf, sizeof(write_buf));
    // 3. 读取验证
    flash_read(MY_FREE_FLASH_ADDR, read_buf, sizeof(read_buf));

    // 此时read_buf应与write_buf内容一致,且重启后数据不会丢失
    while(1);
}

关键注意事项

  • 地址合法性:必须确保目标地址未被系统分区占用,否则会破坏程序、NVS等关键数据,导致设备无法启动;
  • 擦写寿命:Flash擦写寿命约10万次/扇区,避免频繁擦写同一区域;
  • 缓存一致性:ESP32将Flash映射到0x40000000开始的内存区域,若通过指针读取映射区域,需刷新缓存才能获取最新写入的数据(直接使用上述flash_read函数可避免此问题);
  • 跨页限制:页编程不能跨256字节边界,若需写入超过256字节的数据,需分多页处理,且每页对应地址需对齐到页起始位置。

内容的提问来源于stack exchange,提问作者Omar Faruque Swapon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:00:59