ESP32能否在指定地址永久存数据?无库Flash永久存储实现方法
在ESP32无库实现指定地址Flash永久存储
首先明确:ESP32的Flash无法像RAM那样直接通过指针赋值写入,因为Flash属于块擦除型非易失性存储,必须遵循「先擦除、后写入」的操作逻辑,且需避开系统占用的存储区域(如程序分区、NVS、OTA分区等)。以下是不依赖任何库的底层实现方法:
核心前提
- 确认Flash空闲区域:先确定你的ESP32 Flash总容量(常见4MB/8MB),地址范围对应0x00000000~0x00400000(4MB)。通过ESP-IDF的
parttool.py或自定义分区表,找到未被系统/应用占用的连续区域,比如选择Flash尾部的空闲扇区(如0x003F0000,需自行验证分区表确认)。 - 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
相关产品推荐
相关产品推荐

