ESP32欠压中断触发不稳定,求代码解析与可靠性优化方案
ESP32欠压中断(BOD)数据存储问题解析与优化
问题背景
需要在ESP32断电时通过欠压中断将count和ribbonlength数据保存到Flash,但现有代码无法保证每次断电都触发中断,且代码逻辑不清晰,需解析并优化实现可靠存储。
原代码逐段解析
1. 头文件与宏定义
#include <Arduino.h> #include "esp_system.h" #include "soc/soc.h" #include "soc/cpu.h" #include "soc/rtc_cntl_reg.h" #include "driver/rtc_cntl.h" #include "esp_compiler.h" #include <EEPROM.h> typedef void (*functiontype) ( void*); #ifdef CONFIG_BROWNOUT_DET_LVL #define BROWNOUT_DET_LVL CONFIG_BROWNOUT_DET_LVL #else #define BROWNOUT_DET_LVL 5 #endif //CONFIG_BROWNOUT_DET_LVL #define CONFIG_BROWNOUT_DET_LVL_SEL_5 1 #define countaddress 507 #define ribbonaddress 509 #define ONE_BYTES 1 #define TWO_BYTES 2 #define THREE_BYTES 3
- 引入ESP32底层寄存器操作、RTC控制和EEPROM模拟库
BROWNOUT_DET_LVL定义欠压检测阈值:等级5对应约2.4V(ESP32不同等级对应电压参考官方文档)- 定义EEPROM存储地址和字节长度常量
2. 全局变量与中断处理函数
volatile int ribbonlength=0; volatile int count=0; void low_voltage_save() { byte countbytehigher = count>>8 ; byte countbytelower = count& 0xFF ; EEPROM.write(countaddress,countbytelower); EEPROM.write(countaddress+1,countbytehigher); byte bytehigher= ribbonlength>>16; byte bytemid= ribbonlength>>8; byte bytelower=ribbonlength&0xFF; EEPROM.write(ribbonaddress,bytelower); EEPROM.write(ribbonaddress+1,bytemid); EEPROM.write(ribbonaddress+2,bytehigher); EEPROM.commit(); Serial.println("Brown Out Function Triggerd"); REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_BROWN_OUT_INT_CLR); esp_cpu_stall(!xPortGetCoreID()); while(1){} }
volatile修饰变量确保中断中能正确读取最新值- 中断函数将
count(2字节)和ribbonlength(3字节)拆分写入EEPROM,再调用commit()将数据刷入Flash - 问题点:
Serial.println()会占用大量时间,欠压时电源可能不足以完成打印就断电EEPROM.commit()是耗时操作,Flash写入需要约几十毫秒,掉电快时可能执行不完esp_cpu_stall(!xPortGetCoreID())暂停非中断所在核心,但逻辑冗余,死循环无意义
3. 欠压中断初始化函数
void brownout_init() { REG_WRITE(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA /* Enable BOD */ | RTC_CNTL_BROWN_OUT_PD_RF_ENA /* Automatically power down RF */ | (2 << RTC_CNTL_BROWN_OUT_RST_WAIT_S) | (BROWNOUT_DET_LVL << RTC_CNTL_DBROWN_OUT_THRES_S)); ESP_ERROR_CHECK(rtc_isr_register((functiontype)low_voltage_save, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M) ); REG_SET_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_BROWN_OUT_INT_ENA_M); }
- 通过寄存器配置启用欠压检测(BOD),自动关闭RF模块减少功耗,设置复位等待时间为2个时钟周期,配置检测阈值
- 注册中断处理函数并开启欠压中断
- 问题点:阈值等级5可能过低,电源掉电时电压骤降,还没触发中断就已经无法维持CPU运行
4. EEPROM读取与主逻辑
int EEPROM_READ(int address,int noofbytes) { if (noofbytes== TWO_BYTES) { byte bytehigher = EEPROM.read(address+1); byte bytelower = EEPROM.read (address); return(bytehigher<<8)+bytelower; } else if (noofbytes ==THREE_BYTES) { byte bytehigher= EEPROM.read (address+2); byte bytemid= EEPROM.read (address+1); byte bytelower=EEPROM.read (address); return(bytehigher<<16)+(bytemid<<8)+bytelower; } else if (noofbytes ==ONE_BYTES) { return EEPROM.read (address); } } void incrementfun() { if(ribbonlength==100000) { ribbonlength=0; } if(count==10000) { count=0; } ribbonlength++; count++; Serial.println("current count "+String(count)+" currentlength "+String(ribbonlength)); delay(500); } void setup() { brownout_init(); Serial.begin(115200); EEPROM.begin(512); count=EEPROM_READ(countaddress,TWO_BYTES); ribbonlength=EEPROM_READ(ribbonaddress,THREE_BYTES); Serial.println("count read "+String(count)); Serial.println("ribbonlength read "+String(ribbonlength)); } void loop() { incrementfun(); }
- 从EEPROM恢复上次保存的数据,主循环模拟数据更新
触发不可靠的核心原因
- 阈值设置不合理:等级5对应电压过低,电源掉电时CPU可能在触发中断前就停止工作
- 中断操作耗时过长:串口打印、EEPROM.commit()的Flash写入操作需要时间,电源不足以支撑完成
- 电源掉电速度过快:无外部电容缓冲,电压瞬间跌落,中断还未执行就断电
- 寄存器配置遗漏:未正确配置BOD的复位延迟,导致系统提前复位而非触发中断
优化后的代码实现
关键优化点
- 调整欠压阈值到等级4(约2.5V),提前触发中断
- 移除串口打印等耗时操作
- 使用
Preferences库替代EEPROM,Flash写入更高效可靠 - 简化中断逻辑,减少操作步骤
- 增加电源缓冲电容(硬件层面,建议在VCC引脚并联100uF+10uF电容)
#include <Arduino.h> #include "esp_system.h" #include "soc/soc.h" #include "soc/cpu.h" #include "soc/rtc_cntl_reg.h" #include "driver/rtc_cntl.h" #include "Preferences.h" Preferences prefs; volatile int ribbonlength = 0; volatile int count = 0; // 欠压中断处理函数:极简操作,只做必要存储 void IRAM_ATTR low_voltage_save(void* arg) { // 关闭中断避免重复触发 REG_CLR_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_BROWN_OUT_INT_ENA_M); // 保存数据到Preferences(已提前初始化命名空间) prefs.putInt("count", count); prefs.putInt("ribbonlength", ribbonlength); // 清除中断标志 REG_WRITE(RTC_CNTL_INT_CLR_REG, RTC_CNTL_BROWN_OUT_INT_CLR); // 暂停其他核心,进入死循环等待断电 esp_cpu_stall(!xPortGetCoreID()); while(1); } void brownout_init() { // 配置BOD:等级4(~2.5V),提前触发;关闭RF;设置复位等待时间 REG_WRITE(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA | RTC_CNTL_BROWN_OUT_PD_RF_ENA | (5 << RTC_CNTL_BROWN_OUT_RST_WAIT_S) // 延长复位等待,确保存储完成 | (4 << RTC_CNTL_DBROWN_OUT_THRES_S)); // 阈值等级4,比等级5高0.1V // 注册中断处理函数,使用IRAM_ATTR确保函数在RAM中,断电时Flash不可访问 ESP_ERROR_CHECK(rtc_isr_register(low_voltage_save, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M)); // 开启欠压中断 REG_SET_BIT(RTC_CNTL_INT_ENA_REG, RTC_CNTL_BROWN_OUT_INT_ENA_M); } void incrementfun() { if(ribbonlength >= 100000) ribbonlength = 0; if(count >= 10000) count = 0; ribbonlength++; count++; Serial.printf("current count: %d, current length: %d\n", count, ribbonlength); delay(500); } void setup() { brownout_init(); Serial.begin(115200); // 初始化Preferences,命名空间为"power_save" prefs.begin("power_save", false); // 读取上次保存的数据 count = prefs.getInt("count", 0); ribbonlength = prefs.getInt("ribbonlength", 0); Serial.printf("count read: %d\n", count); Serial.printf("ribbonlength read: %d\n", ribbonlength); } void loop() { incrementfun(); }
额外注意事项
- 硬件缓冲:必须在ESP32的VCC和GND之间并联100uF电解电容+10uF陶瓷电容,延缓电源掉电速度,为中断存储争取时间
- 阈值调整:可根据实际电源情况调整阈值等级(参考ESP32官方文档,等级1~8对应不同电压)
- IRAM_ATTR:中断函数必须加
IRAM_ATTR,因为断电时Flash可能无法访问,函数需在RAM中运行 - 测试方法:用可调电源缓慢降低电压测试,不要直接拔电源(拔电源掉电速度太快,可能还是无法触发)
内容的提问来源于stack exchange,提问作者the-terminal-guy
相关产品推荐
相关产品推荐

