# 引言 STM32F4 系列微控制器(如 STM32F407)内部集成了大容量 Flash,但缺少真正的 EEPROM。Flash 的擦写寿命(通常 10,000 次)远低于 EEPROM(100,000 次以上),且必须以扇区为单位擦除。直接使用 Flash 存储频繁更新的参数会导致寿命迅速耗尽。因此,需要设计一种软件层,模拟 EEPROM 的按字节读写功能,同时通过磨损均衡(Wear Leveling)延长 Flash 寿命,并通过掉电保护确保数据一致性。 # 原理讲解 ## Flash 硬件特性 - **扇区擦除**:STM32F4 的 Flash 扇区大小不等(如 16KB、64KB),擦除操作会将整个扇区置为 0xFF。 - **写入限制**:只能将 1 写为 0,不能反向操作。写入最小单位通常为 16 位(半字)或 32 位(字)。 - **寿命限制**:每个扇区擦写次数有限,超过后可能损坏。 ## 磨损均衡算法 磨损均衡的核心思想是**将写入操作分散到多个扇区**,避免固定扇区被频繁擦写。常见方法有: - **日志结构(Log-structured)**:将数据以记录形式追加写入当前活动扇区,当扇区写满时,擦除下一个扇区并继续。 - **双扇区轮换**:使用两个扇区,交替写入,每次写入前先检查当前扇区剩余空间,若不足则切换到另一个扇区并擦除旧扇区。 本文采用**多扇区日志式**方案,每个扇区内部维护记录头,包含有效标志、数据长度和 CRC 校验。 ## 掉电保护策略 掉电可能发生在写入过程中,导致数据半写或记录不完整。保护策略包括: - **原子写入**:先写入数据到临时位置,再更新有效标志。 - **CRC 校验**:每条记录附带 CRC,读取时校验,无效记录视为垃圾。 - **启动扫描**:上电时扫描所有扇区,找到最新有效记录,并清理无效记录。 # 配置步骤 ## 1. 硬件资源 - 使用 STM32F407 开发板,Flash 扇区 11(地址 0x080E0000,大小 16KB)和扇区 12(0x080E4000,16KB)作为模拟 EEPROM 区域。 - 注意:避免使用存放程序代码的扇区。 ## 2. 软件设计 定义数据结构: ```c #define EEPROM_SECTOR_SIZE (16 * 1024) #define EEPROM_SECTOR_COUNT 2 #define EEPROM_START_ADDR 0x080E0000 // 记录头(8字节) typedef struct { uint16_t crc; // CRC16 校验 uint16_t len; // 数据长度(字节) uint32_t valid; // 有效标志(0xA5A5A5A5 表示有效) } RecordHeader; ``` ## 3. 核心函数实现 ### 3.1 擦除扇区 ```c void eeprom_erase_sector(uint32_t addr) { FLASH_EraseInitTypeDef erase; uint32_t page_error = 0; erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Sector = get_sector_num(addr); erase.NbSectors = 1; erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; HAL_FLASH_Unlock(); HAL_FLASHEx_Erase(&erase, &page_error); HAL_FLASH_Lock(); } ``` ### 3.2 写入记录 ```c int eeprom_write(uint32_t key, uint8_t *data, uint16_t len) { // 1. 查找当前活动扇区(最后一个有效记录所在扇区) uint32_t active_sector = find_active_sector(); uint32_t write_addr = find_free_addr(active_sector); // 2. 若剩余空间不足,切换到下一个扇区并擦除 if (write_addr + sizeof(RecordHeader) + len > active_sector + EEPROM_SECTOR_SIZE) { uint32_t next_sector = get_next_sector(active_sector); eeprom_erase_sector(next_sector); write_addr = next_sector; } // 3. 写入数据(先写数据,再写头部) HAL_FLASH_Unlock(); // 写入数据内容(按半字) for (uint16_t i = 0; i < len; i += 2) { uint16_t half = (i+1 < len) ? (data[i] | (data[i+1] << 8)) : data[i]; HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, write_addr + sizeof(RecordHeader) + i, half); } // 写入头部(先写 crc 和 len,最后写 valid) RecordHeader hdr; hdr.crc = crc16(data, len); hdr.len = len; hdr.valid = 0xA5A5A5A5; HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, write_addr, *(uint32_t*)&hdr); HAL_FLASH_Lock(); return 0; } ``` ### 3.3 读取最新记录 ```c int eeprom_read(uint32_t key, uint8_t *buf, uint16_t *len) { // 从最后一个扇区向前扫描,找到匹配 key 的最新有效记录 for (int s = EEPROM_SECTOR_COUNT-1; s >= 0; s--) { uint32_t sector_addr = EEPROM_START_ADDR + s * EEPROM_SECTOR_SIZE; uint32_t addr = sector_addr; while (addr < sector_addr + EEPROM_SECTOR_SIZE) { RecordHeader *hdr = (RecordHeader*)addr; if (hdr->valid == 0xA5A5A5A5) { // 校验 CRC if (crc16((uint8_t*)(addr + sizeof(RecordHeader)), hdr->len) == hdr->crc) { memcpy(buf, (uint8_t*)(addr + sizeof(RecordHeader)), hdr->len); *len = hdr->len; return 0; } } // 跳到下一条记录(若头部无效,则结束) if (hdr->valid != 0xA5A5A5A5 && hdr->valid != 0xFFFFFFFF) break; addr += sizeof(RecordHeader) + hdr->len; } } return -1; // 未找到 } ``` ### 3.4 启动扫描与垃圾回收 ```c void eeprom_init(void) { // 扫描所有扇区,如果发现有效记录,则记录最新位置;否则初始化第一个扇区 // 若某个扇区无效记录过多,可执行压缩:将有效记录复制到新扇区,然后擦除旧扇区 } ``` # 完整代码示例 以下为简化版完整代码(省略 CRC 实现和部分辅助函数): ```c // eeprom_emul.h #ifndef __EEPROM_EMUL_H #define __EEPROM_EMUL_H #include "stm32f4xx_hal.h" #define EEPROM_OK 0 #define EEPROM_ERROR -1 int eeprom_write(uint32_t key, uint8_t *data, uint16_t len); int eeprom_read(uint32_t key, uint8_t *buf, uint16_t *len); void eeprom_init(void); #endif ``` ```c // eeprom_emul.c #include "eeprom_emul.h" // ... 实现上述函数 ``` # 注意事项 - **扇区选择**:务必确认所选扇区未被程序代码占用,否则会覆盖程序。 - **写入对齐**:Flash 写入必须按半字或字对齐,数据长度最好为偶数。 - **掉电处理**:在写入过程中掉电,可能导致头部 valid 未写入,此时该记录视为无效,但数据可能残留,不影响正确性。 - **磨损均衡效果**:本算法将写入分散到多个扇区,但若数据量小且频繁,仍可能集中在某个扇区,可增加扇区数量或采用更复杂的均衡策略。 - **性能考虑**:每次写入需扫描扇区,耗时较长,可缓存当前写入位置以加速。 # 总结 通过日志式磨损均衡和 CRC 校验,STM32F4 的片上 Flash 可以可靠地模拟 EEPROM,满足大多数嵌入式应用需求。本文提供的代码框架可直接移植到实际项目中,开发者可根据需求调整扇区大小和数量。掉电保护策略确保了数据一致性,是工业级应用的关键。希望本文能帮助你更好地利用 STM32F4 的 Flash 资源。