From 6423f9832f6ecb0ef31c25a36bf8f4e008f2c651 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 16 Mar 2026 14:54:20 -1000 Subject: [PATCH] [esp32] Add sram1_as_iram option for +40KB IRAM on original ESP32 Add a new `sram1_as_iram` option under `esp32 > framework > advanced` that enables CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM, reclaiming 40KB of SRAM1 memory as additional IRAM on the original ESP32. This requires a bootloader from ESP-IDF v5.1 or later. USB flashing updates the bootloader automatically, but OTA does not. At boot, ESPHome now detects the bootloader version and suggests enabling this option when the bootloader is compatible. --- esphome/components/esp32/__init__.py | 19 +++++++++++++++++ esphome/core/application.cpp | 31 ++++++++++++++++++++++++---- esphome/core/defines.h | 1 + 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 18178c83ff..ef5ddad8a4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -96,6 +96,7 @@ CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert" CONF_EXECUTE_FROM_PSRAM = "execute_from_psram" CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision" CONF_RELEASE = "release" +CONF_SRAM1_AS_IRAM = "sram1_as_iram" ARDUINO_FRAMEWORK_NAME = "framework-arduinoespressif32" ARDUINO_FRAMEWORK_PKG = f"pioarduino/{ARDUINO_FRAMEWORK_NAME}" @@ -882,6 +883,13 @@ def final_validate(config): path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION], ) ) + if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_SRAM1_AS_IRAM]: + errs.append( + cv.Invalid( + f"'{CONF_SRAM1_AS_IRAM}' is only supported on {VARIANT_ESP32}", + path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_SRAM1_AS_IRAM], + ) + ) if ( config[CONF_VARIANT] != VARIANT_ESP32P4 and config.get(CONF_ENGINEERING_SAMPLE) is not None @@ -1129,6 +1137,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of( *ESP32_CHIP_REVISIONS ), + cv.Optional(CONF_SRAM1_AS_IRAM, default=False): cv.boolean, # DHCP server is needed for WiFi AP mode. When WiFi component is used, # it will handle disabling DHCP server when AP is not configured. # Default to false (disabled) when WiFi is not used. @@ -1595,6 +1604,16 @@ async def to_code(config): for rev, flag in ESP32_CHIP_REVISIONS.items(): add_idf_sdkconfig_option(flag, rev == min_rev) cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET") + + # Use SRAM1 region as IRAM on ESP32 (original) variant + # This provides an additional 40KB of IRAM by using SRAM1 memory that was previously + # reserved for bootloader DRAM. Requires a bootloader from ESP-IDF v5.1 or later. + # WARNING: If the device has an old bootloader (pre-v5.1), the app will fail to boot. + # A USB flash will update the bootloader automatically. OTA updates do not. + # See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/performance/ram-usage.html + if variant == VARIANT_ESP32 and conf[CONF_ADVANCED][CONF_SRAM1_AS_IRAM]: + add_idf_sdkconfig_option("CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM", True) + cg.add_define("USE_ESP32_SRAM1_AS_IRAM") add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv") diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 3a9e825e04..d36e90500d 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -9,6 +9,13 @@ #endif #ifdef USE_ESP32 #include +#if defined(USE_ESP32_FRAMEWORK_ESP_IDF) +#include +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +#include +#include +#endif +#endif #endif #ifdef USE_LWIP_FAST_SELECT #include "esphome/core/lwip_fast_select.h" @@ -237,15 +244,31 @@ void Application::process_dump_config_() { esp_chip_info(&chip_info); ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100, chip_info.revision % 100, chip_info.cores); + static const char *const ESP32_ADVANCED_PATH = "under esp32 > framework > advanced"; #if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET) // Suggest optimization for chips that don't need the PSRAM cache workaround if (chip_info.revision >= 300) { #ifdef USE_PSRAM - ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to save ~10KB IRAM", chip_info.revision / 100, - chip_info.revision % 100); + ESP_LOGW(TAG, "Chip rev >= 3.0 detected. Set minimum_chip_revision: \"%d.%d\" %s to save ~10KB IRAM", + chip_info.revision / 100, chip_info.revision % 100, ESP32_ADVANCED_PATH); #else - ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100, - chip_info.revision % 100); + ESP_LOGW(TAG, "Chip rev >= 3.0 detected. Set minimum_chip_revision: \"%d.%d\" %s to reduce binary size", + chip_info.revision / 100, chip_info.revision % 100, ESP32_ADVANCED_PATH); +#endif + } +#endif +#if defined(USE_ESP32_FRAMEWORK_ESP_IDF) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) + { + // esp_bootloader_desc_t is available in ESP-IDF >= 5.2; if readable the bootloader is modern + esp_bootloader_desc_t boot_desc; + if (esp_ota_get_bootloader_description(nullptr, &boot_desc) != ESP_OK) { + ESP_LOGW(TAG, "Bootloader too old for OTA rollback and SRAM1 as IRAM (+40KB). " + "Flash via USB once to update the bootloader"); + } +#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_SRAM1_AS_IRAM) + else { + ESP_LOGW(TAG, "Bootloader supports SRAM1 as IRAM (+40KB). Set sram1_as_iram: true %s", ESP32_ADVANCED_PATH); + } #endif } #endif diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 390ac8ddd7..57a9d7a159 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -200,6 +200,7 @@ #define USE_ESPHOME_TASK_LOG_BUFFER #define USE_OTA_ROLLBACK #define USE_ESP32_MIN_CHIP_REVISION_SET +#define USE_ESP32_SRAM1_AS_IRAM #define USE_BLUETOOTH_PROXY #define BLUETOOTH_PROXY_MAX_CONNECTIONS 3