Wemos D1 Mini ESP8266 IRAM占用过高 求优化方案
问题背景
原基于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占用优化
调整编译选项,将非关键函数移至Flash
ESP8266默认会把大量库函数加载到IRAM,可在platformio.ini中添加编译参数,强制将普通函数放到Flash(irom):build_flags = -D ICACHE_FLASH_ATTR -mtext-section-literals注意:中断服务函数、必须实时运行的函数需保留在IRAM,可给这类函数添加
ICACHE_RAM_ATTR修饰符。替换高IRAM占用的库
ESP_Mail_Client这类全功能邮件库IRAM占用较高,可考虑改用轻量SMTP实现,或者仅在发送邮件时动态初始化相关对象,避免全局实例化持续占用IRAM。优化全局对象初始化
代码中全局实例化的LocalWiFi、GetTime等对象,若无需持续驻留内存,可改为在函数内部(如SendEmail)动态创建,使用后释放,减少静态IRAM占用。
DRAM占用优化
用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);
- 修改
精简全局变量
将零散的全局数组(如OffDelayPrevMillis、oneSecondPrevMillis)封装为结构体,减少全局变量数量,或改为函数局部变量(仅保留必要的状态变量)。合并定时器逻辑
目前每个定时器都用数组存储,可将机器的定时器状态合并为一个结构体数组,减少重复内存占用。
通用优化
- 开启编译器优化
在platformio.ini中添加-Os参数,开启代码优化,减少代码和内存占用:build_flags = -Os - 清理冗余代码与库
检查自定义头文件(如creditials.h、localWifi.h),移除未使用的函数和变量;若ESP8266内置时间功能可替代TimeLib,则移除该库。
内容的提问来源于stack exchange,提问作者Shardin

