ESP8266不使用EEPROM库直接写入PROGMEM及内存地址确定问题
问题分析与解决方案
为什么你的代码会导致无限重启
你的代码直接通过指针给PROGMEM地址赋值,这是完全错误的:
- PROGMEM所在的**程序闪存(Flash)**并非RAM,不能直接通过指针写入,AVR架构需要遵循特定的闪存写入流程(解锁、擦除页、写入数据)才能操作。
- 你写入的区域是主程序的闪存段,直接覆盖会破坏程序代码,导致芯片重启后无法正常执行,陷入循环。
不使用EEPROM库操作闪存的正确方式
1. 确定安全的闪存地址
要找到不损害主程序的存储区域,需遵循以下方法:
- 查阅芯片数据手册(如ATmega328P),定位用户可用的独立闪存区域,或选择程序区末尾未被编译代码占用的空间。
- 也可通过修改链接脚本预留空间:在Arduino IDE或PlatformIO中,修改芯片对应的链接脚本,指定一段不被程序编译占用的闪存地址范围。
- 注意:EEPROM是独立的非易失存储区,和PROGMEM的程序闪存不是同一区域;若要使用闪存而非EEPROM,必须避开主程序、引导加载器等关键区域,否则会导致芯片变砖。
2. 逐字节写入闪存的正确流程(以AVR芯片为例)
AVR芯片的闪存写入必须遵循硬件规则:
- 必须先擦除整个闪存页(不同芯片页大小不同,如ATmega328P为128字节),无法单独擦除单个字节。
- 写入前需解锁闪存写入保护,完成后需恢复程序区的读写权限。
示例代码(针对ATmega328P):
#include <avr/io.h> #include <avr/pgmspace.h> #include <string.h> // 定义预留的闪存起始地址(需确保此地址不在程序占用范围内,根据芯片手册调整) #define RESERVED_FLASH_START 0x1E00 // 擦除指定闪存页 void flash_page_erase(uint32_t address) { while (bit_is_set(SPMCSR, SPMEN)); // 等待上一次操作完成 boot_page_erase(address); // 触发页擦除 while (bit_is_set(SPMCSR, SPMEN)); // 等待擦除完成 } // 写入16位数据到闪存 void flash_write_word(uint32_t address, uint16_t data) { while (bit_is_set(SPMCSR, SPMEN)); boot_page_fill(address, data); // 填充页缓存 boot_page_write(address); // 写入闪存 while (bit_is_set(SPMCSR, SPMEN)); boot_rww_enable(); // 重新启用程序区读写权限 } // 逐字节写入闪存(实际按页擦除后,以16位为单位写入) void flash_write_bytes(uint32_t start_addr, const uint8_t* data, uint16_t len) { flash_page_erase(start_addr); // 先擦除目标页 for (uint16_t i = 0; i < len; i += 2) { uint16_t word = data[i]; if (i + 1 < len) { word |= (data[i + 1] << 8); // 拼接两个字节为16位数据 } flash_write_word(start_addr + i, word); } } void setup() { Serial.begin(74880); // 读取预留区域原有数据 uint8_t read_buf[sizeof("Line example in PROGMEM")]; uint16_t data_len = sizeof("Line example in PROGMEM"); for (int i = 0; i < data_len; i++) { read_buf[i] = pgm_read_byte(RESERVED_FLASH_START + i); } Serial.print("Original data: "); Serial.println((char*)read_buf); // 准备写入全'#'数据 uint8_t write_buf[data_len]; memset(write_buf, '#', data_len); // 写入闪存 flash_write_bytes(RESERVED_FLASH_START, write_buf, data_len); // 重新读取验证 memset(read_buf, 0, data_len); for (int i = 0; i < data_len; i++) { read_buf[i] = pgm_read_byte(RESERVED_FLASH_START + i); } Serial.print("Written data: "); Serial.println((char*)read_buf); } void loop() {}
关键注意事项
- 闪存写入次数有限(通常约10000次),频繁写入会加速芯片老化。
- 必须严格确认写入地址的安全性,一旦覆盖主程序或引导区,芯片将无法正常启动。
- 上述代码仅适用于AVR架构芯片(如ATmega系列),ESP32等其他架构的闪存操作流程完全不同,需参考对应芯片的官方文档。
内容的提问来源于stack exchange,提问作者Dima
相关产品推荐
相关产品推荐

