diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index c49fc89fbd..0c1ea316f2 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -592,6 +592,9 @@ CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size" KEY_VFS_SELECT_REQUIRED = "vfs_select_required" KEY_VFS_DIR_REQUIRED = "vfs_dir_required" +# Ring buffer IRAM requirement tracking +KEY_RINGBUF_IN_IRAM = "ringbuf_in_iram" + def require_vfs_select() -> None: """Mark that VFS select support is required by a component. @@ -611,6 +614,17 @@ def require_vfs_dir() -> None: CORE.data[KEY_VFS_DIR_REQUIRED] = True +def enable_ringbuf_in_iram() -> None: + """Keep ring buffer functions in IRAM instead of moving them to flash. + + Call this from components that use esphome/core/ring_buffer.cpp and need + the ring buffer functions to remain in IRAM for performance reasons + (e.g., voice assistants, audio components). + This prevents CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH from being enabled. + """ + CORE.data[KEY_RINGBUF_IN_IRAM] = True + + def _parse_idf_component(value: str) -> ConfigType: """Parse IDF component shorthand syntax like 'owner/component^version'""" if "^" not in value: @@ -1021,6 +1035,18 @@ async def to_code(config): # IDF 6.0+: this is the default, option no longer exists add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True) + # Place ring buffer functions into flash instead of IRAM by default + # This saves IRAM but may impact performance for audio/voice components. + # Components that need ring buffer in IRAM call enable_ringbuf_in_iram(). + # In ESP-IDF 6.0 flash placement becomes the default. + if CORE.data.get(KEY_RINGBUF_IN_IRAM, False): + # Component requires ring buffer in IRAM for performance + # IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n + add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False) + else: + # No component needs it - place in flash to save IRAM + add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True) + # Setup watchdog add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 907429ee0e..0e7c3a5937 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -1,6 +1,10 @@ from esphome import pins import esphome.codegen as cg -from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant +from esphome.components.esp32 import ( + add_idf_sdkconfig_option, + enable_ringbuf_in_iram, + get_esp32_variant, +) from esphome.components.esp32.const import ( VARIANT_ESP32, VARIANT_ESP32C3, @@ -274,6 +278,9 @@ async def to_code(config): # Helps avoid callbacks being skipped due to processor load add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True) + # Keep ring buffer functions in IRAM for audio performance + enable_ringbuf_in_iram() + cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN])) if CONF_I2S_BCLK_PIN in config: cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN])) diff --git a/esphome/components/micro_wake_word/__init__.py b/esphome/components/micro_wake_word/__init__.py index 575fb97799..aab7fcd5cb 100644 --- a/esphome/components/micro_wake_word/__init__.py +++ b/esphome/components/micro_wake_word/__init__.py @@ -448,6 +448,9 @@ async def to_code(config): # The inference task queues detection events that need immediate processing socket.require_wake_loop_threadsafe() + # Keep ring buffer functions in IRAM for audio performance + esp32.enable_ringbuf_in_iram() + mic_source = await microphone.microphone_source_to_code(config[CONF_MICROPHONE]) cg.add(var.set_microphone_source(mic_source))