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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 10:32:21