ATmega328P EEPROM写入失败求助:写入后读取值始终为0xFF
问题详情
我使用以下代码对ATmega328P的EEPROM执行写入操作:
eeprom_write_byte(address, (U8_T) 0);
写入函数实现:
void eeprom_write_byte(void* address, U8_T data) { cli(); /* 等待上一次写入完成 */ while (TRUE == EECR.bits.EEPE) { } /* 设置擦除后写入模式 */ EECR.bits.EEPM0 = FALSE; EECR.bits.EEPM1 = FALSE; /* 配置写入参数 */ EEAR.byte = (U8_T) address; EEDR.byte = data; /* 使能主写入 */ EECR.bits.EEMPE = TRUE; /* 启动写入 */ EECR.bits.EEPE = TRUE; sei(); }
寄存器地址映射如下:
SECTIONS { EEAR = 0x41; EEDR = 0x40; EECR = 0x3F; ... }
我有个疑问:EEMPE和EEPE是否必须在4个时钟周期内先后置位?
读取EEPROM的代码如下:
U8_T eeprom_read_byte(void* address) { cli(); /* 等待上一次写入完成 */ while (TRUE == EECR.bits.EEPE) { } /* 配置读取参数 */ EEAR.byte = (U8_T) address; /* 启动读取 */ EECR.bits.EERE = TRUE; sei(); return EEDR.byte; }
但读取结果始终是0xFF(EEPROM擦除后的默认值)。 datasheet明确EEPM0和EEPM1置0时为擦除后写入模式,但写入操作完全未生效。我试过用-O2编译无效果,-O3会导致链接失败,且航空领域开发禁止编译优化,必须在无优化环境下解决问题。
寄存器定义如下:
/** EEPROM控制寄存器 */ typedef union { struct { /** EEPROM读取使能 */ VBOOL_T EERE : 1; /** EEPROM写入使能 */ VBOOL_T EEPE : 1; /** EEPROM主写入使能 */ VBOOL_T EEMPE : 1; /** EEPROM就绪中断使能 */ VBOOL_T EERIE : 1; /** EEPROM模式位 */ VBOOL_T EEPM0 : 1; VBOOL_T EEPM1 : 1; VBOOL_T Reserved6 : 1; VBOOL_T Reserved7 : 1; } bits; VU8_T byte; } EECR_T; /** EEPROM控制寄存器 */ extern volatile EECR_T EECR;
可能原因及修复方案
1. EEAR地址赋值不完整
ATmega328P的EEPROM是1KB容量,地址为16位(0x000~0x3FF),EEAR由EEARL(0x41)和EEARH(0x42)两个8位寄存器组成。你的代码仅将address强制转换为U8_T赋值给EEAR.byte(对应EEARL),高8位EEARH未被正确设置,导致写入地址与预期不符,读取自然得不到目标数据。
修复:
按16位完整地址赋值:
uint16_t eeprom_addr = (uint16_t)address; EEARL = (uint8_t)(eeprom_addr & 0xFF); EEARH = (uint8_t)(eeprom_addr >> 8);
2. 位字段顺序与硬件寄存器不匹配
ATmega328P的EECR寄存器位定义为:bit0=EERE、bit1=EEPE、bit2=EEMPE、bit3=EERIE、bit4=EEPM0、bit5=EEPM1。若编译器默认位字段从高到低排列(bit7对应结构体第一个成员),则你通过EECR.bits操作的是错误寄存器位,写入序列完全无效。
验证与修复:
改用字节位操作替代位字段,测试是否生效:
// 设置擦除后写入模式 EECR.byte &= ~((1 << 4) | (1 << 5)); // EEPM0=bit4,EEPM1=bit5 // 执行写入序列 EECR.byte |= (1 << 2); // 置位EEMPE(bit2) EECR.byte |= (1 << 1); // 置位EEPE(bit1)
若此操作生效,需调整结构体位成员顺序,或统一使用字节位操作方式。
3. EEMPE与EEPE的时序不满足要求
是的,根据datasheet,EEMPE置位后必须在4个时钟周期内置位EEPE,否则EEMPE会自动清零,写入不触发。
无优化编译时,位字段操作可能生成多条指令(读字节→修改位→写回),导致两个置位操作间隔超过4个时钟周期。
修复:
使用直接字节操作保证时序紧凑:
// 连续执行置位操作,确保时序符合要求 EECR.byte |= (1 << 2); EECR.byte |= (1 << 1);
4. 电源或时钟异常
ATmega328P的EEPROM写入对电源电压和时钟频率有要求(如5V供电时最低4.5V,时钟需满足时序规范),若电源不稳定、电压不足,或时钟配置错误,都会导致写入失败。
验证:
检查电源是否稳定在器件要求范围内,确认时钟配置(内部RC校准、外部晶振工作状态)符合EEPROM操作时序。
内容的提问来源于stack exchange,提问作者Matt Murphy

