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

Wemos D1 Mini ESP8266 IRAM占用过高 求优化方案

ESP8266(Wemos D1 Mini)IRAM/DRAM占用过高问题优化方案

问题背景

原基于ATMEGA328P开发的程序,迁移到Wemos D1 Mini ESP8266(VSCode PlatformIO环境)后,出现静态RAM占用异常:

  • IRAM(iram1_0_seg)占用率达84.85%
  • DRAM(dram0_0_seg)占用率达41.35%

内存统计信息:

Memory Stats:
Memory region         Used Size  Region Size  %age Used
dport0_0_seg:          0 GB         16 B      0.00%
 dram0_0_seg:       33872 B        80 KB     41.35%
 irom0_0_seg:      492260 B    1044464 B     47.13%
**     iram1_0_seg:       27804 B        32 KB     84.85%**
Retrieving maximum program size .pio\build\d1_mini\firmware.elf
Checking size .pio\build\d1_mini\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [====      ]  41.3% (used 33812 bytes from 81920 bytes)
Flash: [=====     ]  50.4% (used 526682 bytes from 1044464 bytes)
Building .pio\build\d1_mini\firmware.bin

曾尝试用PROGMEM迁移数据至程序内存,但因多数数据为String类型,编译器不支持,现寻求优化技巧。

程序代码

#ifndef Arduino_h
  #include <Arduino.h>
#endif

#ifndef WiFi_h
  #include <ESP8266WiFi.h>
#endif

#ifndef ESP_MAIL_CLIENT_H
  #include <ESP_Mail_Client.h>
#endif

#ifndef creditials_h
  #include <creditials.h>
#endif

#ifndef localWifi_h
  #include <localWifi.h>
#endif

#ifndef getTime_h
  #include <getTime.h>
#endif

#ifndef _Time_h
  #include <TimeLib.h>
#endif

#ifndef WIFIUDP_H
  #include <WiFiUdp.h>    
#endif

#ifndef global_h
  #include <global.h>
#endif

#ifndef TimFlsh_h
  #include "TimerFlasher.h"
#endif
  

// instantate methods
GetTime    M_getTime;
LocalWiFi  M_WiFi;
L_Global   M_Global;
TimFlsh    M_TimFlsh;


// global variable  
long unsigned millisSeconds = 1000; 
int  cnt = 0;
int  machHasRun                     [2]  = {_Off,  _Off };         //indicates that a mach as started
bool machAllReadyOn                 [2]  = {false, false};
int  whichMachCase                  [2]  = {  0,      0 };

/*
D3 INPUT: Washer running, pulled low when on.
D4 INPUT: Dryer running, pulled low when on.
D5 OUTPUT: pull low to turn on Washer ON LED.
D6 OUTPUT: pull low to turn on Dryer ON LED.
D7 OUTPUT: pull low to turn on Send E-mail LED.
*/

int  machOnIO                       [2]  = { D3,     D4 };         // mach on / off IO
int  machLEDIO                      [2]  = { D5,     D6 };         // LED on/off IO
int  emailLED                            =   D7;
bool wait                           [2]  = {false, false};

long unsigned OffDelayPrevMillis    [2]  = {0,0};
long unsigned OffDelayInterval      [2]  = {60 * millisSeconds, 60 * millisSeconds};

long unsigned EmailDelayPrevMillis  [2]  = {0,0};
long unsigned EmailDelayInterval    [2]  = {30 * millisSeconds, 30 * millisSeconds};
bool oneSecond                      [2]  = {true, true};
long unsigned oneSecondPrevMillis   [2]  = {0,0};
long unsigned oneSecondInterval     [2]  = {1 * millisSeconds, 1 * millisSeconds};


bool EmailOK;

//forward declare
bool SendEmail         (int cnt);

void setup() 
{
  pinMode(machOnIO[WASHER],  INPUT_PULLUP);   //washer on/off
  pinMode(machOnIO[DRYER],   INPUT_PULLUP);   //dryer on/off
  pinMode(machLEDIO[WASHER], OUTPUT);         //washer power LED
  pinMode(machLEDIO[DRYER],  OUTPUT);         // dryer LED
  pinMode(emailLED,          OUTPUT);         // sending e-mail indiacator
  

#if TEST
    pinMode(BUILTIN_LED,       OUTPUT);
      Serial.begin(9600);
    Serial.println("Setup complete:");
    digitalWrite(LED_BUILTIN,I_Off);   // turn off on board led
#endif

}  //end setup

//==========================================================================================================================
// main loop A. loop through machines to see if running
//           b. loop on case for each machine
//      case 1. start cycle, set machine to running, when detect off, prepare to delay
//      case 2. delay off of machine cycle to assure this is not a user interrupt (door opened), 
//              return to case 1 if starts running again before delay expires
//      case 3. machine confirmed off, send an e-mail.  if email fails stay in case 3, send again after delay, try 3 times
//      return to A
//==========================================================================================================================
void loop()
{  // check if a machine is running

  for (int loop = WASHER; loop <= DRYER; loop++ )            // Washer = 0, Dryer = 1
  {
    oneSecond[loop] = (M_TimFlsh.oneSecondDelay(loop, oneSecond, oneSecondPrevMillis, oneSecondInterval));  
    if (digitalRead(machOnIO[loop]) == I_On  and machAllReadyOn[loop] == false )  // machine On and not detected before
    {
      digitalWrite(machLEDIO[loop], I_On);                  // turn on proper cycle LED
      whichMachCase[loop] = START_CYCLE;                    // set switch/case
      #if TEST
        if(loop == WASHER){digitalWrite(BUILTIN_LED,I_On);}        
      #endif
    } //end if
  } // end for

  //============================================================================================
  for (int loop = WASHER; loop <= DRYER; loop++ )             // check all machines
  {
        //What to do 
      //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
      switch(whichMachCase[loop])
      {
        //---------------------------------------------------------------------------------------
        case START_CYCLE:  // check for end of cycle or user invocked stop
        {
          #if TEST
            Serial.println("Case START_CYCLE " + M_Global.name[loop]);
          #endif

          machAllReadyOn[loop] = true;                        // set proper machine to on
          if(digitalRead(machOnIO[loop]) == HIGH)             // ck mach off IO
          {
            OffDelayPrevMillis[loop] = millis();             // set start time for current machine for the off delay
            whichMachCase[loop] = DELAYOFF;                     // move to next case
          }
          break;
        } // END CASE Start_cycle
        
        //-------------------------------------------------------------------------------------------
        case DELAYOFF:  // make sure cyce is over and not just a user interruption "off delay timer"
        {
          #if TEST
            Serial.println("Case DELAYOFF " + M_Global.name[loop]);
          #endif

          if(M_TimFlsh.ckOffDelayTime(loop, OffDelayPrevMillis[loop], OffDelayInterval[loop]))                      // true when timed out
          {
            digitalWrite(machLEDIO[loop], I_Off);       // turn off cycle LED
            #if TEST
              digitalWrite(BUILTIN_LED,I_Off);
            #endif
            wait[loop] = false;
            whichMachCase[loop] = SENDMAIL;             // move to next case
          }
          else if (oneSecond[loop])
          { // waiting for timeout
            machAllReadyOn[loop] = false;
            M_TimFlsh.flashLED(machLEDIO[loop]);
          }
          break;
        } // END CASE DelayOFf

        //-----------------------------------------------------------------------------------
        case SENDMAIL:     // time to send the e-mail
        {
          #if TEST
            Serial.println("Case SENDEMAIL " + M_Global.name[loop]);
          #endif

          if(wait[loop])                                        // prior e-mail failed, wait to send a replacment
          {
            {wait[loop] = M_TimFlsh.ckEmailDelay(loop, EmailDelayPrevMillis[loop], OffDelayInterval[loop]);} 
          }
          else 
          {
            digitalWrite(emailLED, I_On);
            EmailOK = SendEmail(loop);
            EmailDelayPrevMillis[loop] = millis();              // set start time for e-mail delay
            if (EmailOK)
            {
                machAllReadyOn[loop] = false; 
                wait[loop] = false;
                whichMachCase[loop]  = 0;
                digitalWrite(emailLED, I_Off);
            }
              else if (!EmailOK)
            {
                M_TimFlsh.fastBlinkLED(emailLED);
                wait[loop] = true;
            }
          }
        } // END CASE SendEmail          
        //-----------------------------------------------------------------------------------------------
        default:
        {
          break;
        } // END DEFAULT
        //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  
      } //END SWITCH
    //====================================================================================================  
  }  // END FOR LOOP
} // end loop()

//==================================================================
// Send e-mail and get the status
//==================================================================
  bool SendEmail(int cnt)
  {
    bool EmailFail = false;
    int WiFiOK = M_WiFi.connect_WiFi();
    if (WiFiOK == WL_CONNECTED)
    {
      String Dtmsg  = M_getTime.NST_Time();
      String Emsg   = M_Global.msg[cnt];
      bool EmailOK  = M_WiFi.send_Email(Dtmsg, Emsg, "ESP8266 machine monitoring", cnt);
      if(EmailOK)
      {
        #if TEST
          Serial.println("Email OK");
        #endif
        M_WiFi.discon_Wifi();
        return EmailOK;
      }  //end  if emailOK
      else
      {
        M_WiFi.discon_Wifi();
        #if TEST
          Serial.println("Email Failed");
        #endif
        return EmailFail;
      } // end else emailok   
    }
     #if TEST
      Serial.println("Email Failed");
    #endif
    return EmailFail;
  }

优化技巧

IRAM占用优化

  1. 调整编译选项,将非关键函数移至Flash
    ESP8266默认会把大量库函数加载到IRAM,可在platformio.ini中添加编译参数,强制将普通函数放到Flash(irom):

    build_flags =
      -D ICACHE_FLASH_ATTR
      -mtext-section-literals
    

    注意:中断服务函数、必须实时运行的函数需保留在IRAM,可给这类函数添加ICACHE_RAM_ATTR修饰符。

  2. 替换高IRAM占用的库
    ESP_Mail_Client这类全功能邮件库IRAM占用较高,可考虑改用轻量SMTP实现,或者仅在发送邮件时动态初始化相关对象,避免全局实例化持续占用IRAM。

  3. 优化全局对象初始化
    代码中全局实例化的LocalWiFi、GetTime等对象,若无需持续驻留内存,可改为在函数内部(如SendEmail)动态创建,使用后释放,减少静态IRAM占用。

DRAM占用优化

  1. 用char数组替代String类型
    String会动态分配DRAM且易产生内存碎片,建议全部替换为固定大小的char[]:

    • 修改NST_Time()函数,使其直接写入预定义的char数组,而非返回String;
    • 全局字符串常量(如邮件主题)用const char[] PROGMEM定义,存储到Flash,使用时通过strcpy_P读取:
      const char email_subject[] PROGMEM = "ESP8266 machine monitoring";
      char subject_buf[64];
      strcpy_P(subject_buf, email_subject);
      
  2. 精简全局变量
    将零散的全局数组(如OffDelayPrevMillis、oneSecondPrevMillis)封装为结构体,减少全局变量数量,或改为函数局部变量(仅保留必要的状态变量)。

  3. 合并定时器逻辑
    目前每个定时器都用数组存储,可将机器的定时器状态合并为一个结构体数组,减少重复内存占用。

通用优化

  • 开启编译器优化
    在platformio.ini中添加-Os参数,开启代码优化,减少代码和内存占用:
    build_flags =
      -Os
    
  • 清理冗余代码与库
    检查自定义头文件(如creditials.h、localWifi.h),移除未使用的函数和变量;若ESP8266内置时间功能可替代TimeLib,则移除该库。

内容的提问来源于stack exchange,提问作者Shardin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 04:57:06