STM32F103RCT6的FLASH读写,我踩过的那些坑:从擦除异常到数据错位的实战复盘

张开发
2026/4/21 12:06:26 15 分钟阅读

分享文章

STM32F103RCT6的FLASH读写,我踩过的那些坑:从擦除异常到数据错位的实战复盘
STM32F103RCT6的FLASH读写实战从异常现象到精准修复的完整指南深夜调试STM32的FLASH读写功能时突然出现的HardFault让我意识到这个看似简单的功能背后藏着不少坑。作为嵌入式开发者我们都曾经历过这种时刻——明明按照手册操作却遭遇各种意外状况。本文将分享我在STM32F103RCT6上实现FLASH模拟EEPROM功能时遇到的典型问题及解决方案帮助开发者避开这些雷区。1. FLASH基础操作中的关键陷阱1.1 地址对齐问题硬件错误的罪魁祸首第一次尝试写入FLASH时我的系统立即触发了HardFault中断。经过排查发现STM32F103的FLASH写入有严格的地址对齐要求// 错误的写入方式 - 地址未对齐 uint32_t addr 0x0800F001; // 奇数地址 uint16_t data 0x1234; FLASH_ProgramHalfWord(addr, data); // 这将导致硬件错误 // 正确的写入方式 uint32_t aligned_addr 0x0800F000; // 偶数地址 FLASH_ProgramHalfWord(aligned_addr, data);关键点备忘半字(16位)写入地址必须2字节对齐字(32位)写入地址必须4字节对齐读取操作无对齐限制1.2 解锁序列被忽视的安全机制即使地址正确直接调用写入函数仍然失败。这是因为STM32的FLASH有写保护机制必须按特定顺序解锁// 正确的解锁流程 FLASH_Unlock(); // 必须先调用解锁 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 清除可能存在的错误标志 // 现在可以进行写入操作常见错误包括忘记调用FLASH_Unlock()解锁后未清除错误标志在锁定状态下尝试擦除或写入2. 跨页写入的数据完整性保障2.1 FLASH擦除特性与数据备份策略STM32的FLASH有一个重要特性只能将1从0改写不能将0改回1。这意味着在写入前必须擦除目标扇区将所有位设为1。擦除的最小单位是页STM32F103RCT6为1KB这给数据管理带来挑战。跨页写入解决方案确定目标页范围读取受影响页的原始数据到缓冲区擦除目标页合并新旧数据写回完整数据void flash_write_with_backup(uint32_t addr, uint16_t *data, uint16_t len) { uint32_t start_page addr / FLASH_PAGE_SIZE; uint32_t end_page (addr len - 1) / FLASH_PAGE_SIZE; // 备份所有受影响页的数据 for(uint32_t page start_page; page end_page; page) { uint32_t page_addr page * FLASH_PAGE_SIZE; STMFLASH_Read(page_addr, backup_buf[page-start_page], FLASH_PAGE_SIZE/2); } // 擦除所有受影响页 for(uint32_t page start_page; page end_page; page) { FLASH_ErasePage(page * FLASH_PAGE_SIZE); } // 合并数据并写入 // ... (具体实现略) }2.2 中断处理不可忽视的时序问题在FLASH操作期间发生中断可能导致写入失败。最佳实践是// 安全的FLASH操作流程 __disable_irq(); // 关闭所有中断 FLASH_Unlock(); // 执行FLASH操作 FLASH_ErasePage(...); FLASH_ProgramHalfWord(...); FLASH_Lock(); __enable_irq(); // 恢复中断中断处理注意事项FLASH操作期间应禁用所有中断操作完成后立即恢复中断使能总中断关闭时间应尽可能短3. 高级技巧与性能优化3.1 磨损均衡实现方案由于FLASH有写入次数限制通常约10万次模拟EEPROM时需要实现磨损均衡。一个简单有效的方案将FLASH划分为多个逻辑扇区维护一个写指针记录当前写入位置当当前扇区写满时切换到下一个扇区所有扇区写满后执行整体擦除#define LOGICAL_SECTORS 4 #define SECTOR_SIZE 1024 // 1KB uint32_t current_sector 0; uint32_t write_offset 0; void wear_leveling_write(uint16_t data) { if(write_offset SECTOR_SIZE/2) { // 当前扇区已满切换到下一个 current_sector (current_sector 1) % LOGICAL_SECTORS; FLASH_ErasePage(FLASH_BASE current_sector * SECTOR_SIZE); write_offset 0; } uint32_t addr FLASH_BASE current_sector * SECTOR_SIZE write_offset * 2; FLASH_ProgramHalfWord(addr, data); write_offset; }3.2 数据校验与错误恢复为确保数据完整性建议添加校验机制typedef struct { uint16_t data; uint16_t checksum; } flash_entry_t; void write_with_checksum(uint32_t addr, uint16_t data) { flash_entry_t entry; entry.data data; entry.checksum ~data; // 简单取反校验 FLASH_ProgramHalfWord(addr, entry.data); FLASH_ProgramHalfWord(addr 2, entry.checksum); } int read_with_verify(uint32_t addr, uint16_t *data) { flash_entry_t entry; entry.data STMFLASH_ReadHalfWord(addr); entry.checksum STMFLASH_ReadHalfWord(addr 2); if(entry.checksum ! ~entry.data) { return -1; // 校验失败 } *data entry.data; return 0; }4. 实战案例完整EEPROM模拟实现4.1 内存布局设计对于STM32F103RCT6256KB FLASH合理的EEPROM模拟布局区域起始地址大小用途主存储区0x0801000062KB数据存储备份区0x0801F8002KB冗余备份元数据区0x0801F0002KB存储配置信息4.2 完整API实现#define EEPROM_START_ADDR 0x08010000 #define EEPROM_SIZE (62 * 1024) #define PAGE_SIZE 1024 typedef enum { EEPROM_OK 0, EEPROM_ERROR_ADDR, EEPROM_ERROR_WRITE, EEPROM_ERROR_VERIFY } eeprom_status_t; eeprom_status_t eeprom_write(uint32_t addr, uint16_t *data, uint16_t len) { // 检查地址范围 if(addr len*2 EEPROM_SIZE) return EEPROM_ERROR_ADDR; // 计算受影响的页 uint32_t first_page addr / PAGE_SIZE; uint32_t last_page (addr len*2 - 1) / PAGE_SIZE; // 备份数据 uint16_t backup[PAGE_SIZE/2]; for(uint32_t page first_page; page last_page; page) { uint32_t page_addr EEPROM_START_ADDR page * PAGE_SIZE; STMFLASH_Read(page_addr, backup, PAGE_SIZE/2); // 擦除页 FLASH_ErasePage(page_addr); // 修改备份中的数据 uint32_t start (page first_page) ? (addr % PAGE_SIZE)/2 : 0; uint32_t end (page last_page) ? ((addr len*2 -1) % PAGE_SIZE)/2 1 : PAGE_SIZE/2; for(uint32_t i start; i end; i) { backup[i] data[(page - first_page)*PAGE_SIZE/2 i - start]; } // 写回整个页 for(uint32_t i 0; i PAGE_SIZE/2; i) { FLASH_ProgramHalfWord(page_addr i*2, backup[i]); } } return EEPROM_OK; } eeprom_status_t eeprom_read(uint32_t addr, uint16_t *data, uint16_t len) { if(addr len*2 EEPROM_SIZE) return EEPROM_ERROR_ADDR; STMFLASH_Read(EEPROM_START_ADDR addr, data, len); return EEPROM_OK; }4.3 性能优化技巧批量写入尽量减少擦除次数尽量一次性写入更多数据缓存机制在RAM中缓存频繁访问的数据延迟写入非关键数据可以累积到一定量再写入页预擦除在系统空闲时预擦除可能需要的页// 批量写入示例 void batch_write(uint16_t *data_array, uint16_t count) { uint32_t page_addr get_next_free_page(); FLASH_ErasePage(page_addr); for(int i 0; i count; i) { if((i % (PAGE_SIZE/2)) 0 i ! 0) { page_addr PAGE_SIZE; FLASH_ErasePage(page_addr); } FLASH_ProgramHalfWord(page_addr (i % (PAGE_SIZE/2))*2, data_array[i]); } }在项目后期我发现最有效的优化是合理设计数据结构尽量减少FLASH写入次数。例如将频繁变更的数据与静态数据分离对动态数据采用差分记录方式等。这些经验只能通过实际项目积累文档中很少提及。

更多文章