From 0fb31726f69b304581caec41614a080774feda8a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 23 Mar 2026 13:39:29 -1000 Subject: [PATCH] [esp32] Add sram1_as_iram option and bootloader version detection (#14874) --- esphome/components/esp32/__init__.py | 19 ++++++++ esphome/core/application.cpp | 50 ++++++++++++++++++---- esphome/core/defines.h | 1 + tests/components/esp32/test.esp32-idf.yaml | 1 + 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index f85f13fe73..1ecc270fd1 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -97,6 +97,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" CONF_SUBTYPE = "subtype" ARDUINO_FRAMEWORK_NAME = "framework-arduinoespressif32" @@ -884,6 +885,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 @@ -1131,6 +1139,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. @@ -1655,6 +1664,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 c020a8ed58..ce15aed1e2 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -9,6 +9,8 @@ #endif #ifdef USE_ESP32 #include +#include +#include #endif #ifdef USE_LWIP_FAST_SELECT #include "esphome/core/lwip_fast_select.h" @@ -167,19 +169,49 @@ 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); -#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); -#else - ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100, - chip_info.revision % 100); +#if defined(USE_ESP32_VARIANT_ESP32) && (!defined(USE_ESP32_MIN_CHIP_REVISION_SET) || !defined(USE_ESP32_SRAM1_AS_IRAM)) + static const char *const ESP32_ADVANCED_PATH = "under esp32 > framework > advanced"; #endif +#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, "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, "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 + { + // esp_bootloader_desc_t is available in ESP-IDF >= 5.2; if readable the bootloader is modern. + // + // Design decision: We intentionally do NOT mention sram1_as_iram when the bootloader is too old. + // Enabling sram1_as_iram with an old bootloader causes a hard brick (device fails to boot, + // requires USB reflash to recover). Users don't always read warnings carefully, so we only + // suggest the option once we've confirmed the bootloader can handle it. In practice this + // means a user with an old bootloader may need to flash twice: once via USB to update the + // bootloader (they'll see the suggestion on next boot), then OTA with sram1_as_iram: true. + // Two flashes is a better outcome than a bricked device. + esp_bootloader_desc_t boot_desc; + if (esp_ota_get_bootloader_description(nullptr, &boot_desc) != ESP_OK) { +#ifdef USE_ESP32_VARIANT_ESP32 + ESP_LOGW(TAG, "Bootloader too old for OTA rollback and SRAM1 as IRAM (+40KB). " + "Flash via USB once to update the bootloader"); +#else + ESP_LOGW(TAG, "Bootloader too old for OTA rollback. Flash via USB once to update the bootloader"); #endif + } +#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 // USE_ESP32 } this->components_[this->dump_config_at_]->call_dump_config_(); diff --git a/esphome/core/defines.h b/esphome/core/defines.h index f437e30a95..996818c2e6 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -202,6 +202,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 diff --git a/tests/components/esp32/test.esp32-idf.yaml b/tests/components/esp32/test.esp32-idf.yaml index da85aa3b0f..b999f23e1c 100644 --- a/tests/components/esp32/test.esp32-idf.yaml +++ b/tests/components/esp32/test.esp32-idf.yaml @@ -19,6 +19,7 @@ esp32: disable_mbedtls_pkcs7: true disable_regi2c_in_iram: true disable_fatfs: true + sram1_as_iram: true wifi: ssid: MySSID