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

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恢复上次保存的数据,主循环模拟数据更新

触发不可靠的核心原因

  1. 阈值设置不合理:等级5对应电压过低,电源掉电时CPU可能在触发中断前就停止工作
  2. 中断操作耗时过长:串口打印、EEPROM.commit()的Flash写入操作需要时间,电源不足以支撑完成
  3. 电源掉电速度过快:无外部电容缓冲,电压瞬间跌落,中断还未执行就断电
  4. 寄存器配置遗漏:未正确配置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();
}

额外注意事项

  1. 硬件缓冲:必须在ESP32的VCC和GND之间并联100uF电解电容+10uF陶瓷电容,延缓电源掉电速度,为中断存储争取时间
  2. 阈值调整:可根据实际电源情况调整阈值等级(参考ESP32官方文档,等级1~8对应不同电压)
  3. IRAM_ATTR:中断函数必须加IRAM_ATTR,因为断电时Flash可能无法访问,函数需在RAM中运行
  4. 测试方法:用可调电源缓慢降低电压测试,不要直接拔电源(拔电源掉电速度太快,可能还是无法触发)

内容的提问来源于stack exchange,提问作者the-terminal-guy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 08:55:01