From 2e027fc20807a108b41d90384acbeb46fba1cae4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2026 11:04:29 -1000 Subject: [PATCH] [ledc] Place LEDC control functions in IRAM for cache safety When flash cache is disabled during background flash operations (NVS writes by WiFi, BLE, Zigbee, Thread, power management, etc.), the LEDC control functions will crash if they are in flash. This places the LEDC control functions in IRAM by setting CONFIG_LEDC_CTRL_FUNC_IN_IRAM when the LEDC component is used. Adds require_ledc_iram() helper and ledc_in_iram advanced config option. --- esphome/components/esp32/__init__.py | 24 ++++++++++++++++++++++++ esphome/components/ledc/output.py | 2 ++ 2 files changed, 26 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 2974028b50..195295e5a6 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1058,6 +1058,7 @@ CONF_DISABLE_MBEDTLS_PEER_CERT = "disable_mbedtls_peer_cert" CONF_DISABLE_MBEDTLS_PKCS7 = "disable_mbedtls_pkcs7" CONF_DISABLE_REGI2C_IN_IRAM = "disable_regi2c_in_iram" CONF_DISABLE_FATFS = "disable_fatfs" +CONF_LEDC_IN_IRAM = "ledc_in_iram" # VFS requirement tracking # Components that need VFS features can call require_vfs_*() functions @@ -1071,6 +1072,7 @@ KEY_MBEDTLS_PEER_CERT_REQUIRED = "mbedtls_peer_cert_required" KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required" KEY_FATFS_REQUIRED = "fatfs_required" KEY_MBEDTLS_SHA512_REQUIRED = "mbedtls_sha512_required" +KEY_LEDC_IRAM_REQUIRED = "ledc_iram_required" def require_vfs_select() -> None: @@ -1168,6 +1170,17 @@ def require_fatfs() -> None: CORE.data[KEY_ESP32][KEY_FATFS_REQUIRED] = True +def require_ledc_iram() -> None: + """Mark that LEDC IRAM safety is required by a component. + + Call this from components that use the LEDC (PWM) driver. When flash cache is + disabled (e.g., during NVS writes by WiFi, BLE, Zigbee, or power management), + the LEDC control functions must be in IRAM to avoid crashes. + This sets CONFIG_LEDC_CTRL_FUNC_IN_IRAM. + """ + CORE.data[KEY_ESP32][KEY_LEDC_IRAM_REQUIRED] = True + + def _parse_idf_component(value: str) -> ConfigType: """Parse IDF component shorthand syntax like 'owner/component^version'""" # Match operator followed by version-like string (digit or *) @@ -1268,6 +1281,7 @@ FRAMEWORK_SCHEMA = cv.Schema( cv.Optional(CONF_DISABLE_MBEDTLS_PEER_CERT, default=True): cv.boolean, cv.Optional(CONF_DISABLE_MBEDTLS_PKCS7, default=True): cv.boolean, cv.Optional(CONF_DISABLE_REGI2C_IN_IRAM, default=True): cv.boolean, + cv.Optional(CONF_LEDC_IN_IRAM, default=False): cv.boolean, cv.Optional(CONF_DISABLE_FATFS, default=True): cv.boolean, } ), @@ -2068,6 +2082,16 @@ async def to_code(config): if advanced[CONF_DISABLE_REGI2C_IN_IRAM]: add_idf_sdkconfig_option("CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM", False) + # Place LEDC control functions in IRAM for cache safety + # When flash cache is disabled (during NVS writes by WiFi, BLE, Zigbee, Thread, + # power management, etc.), LEDC operations will crash if these functions are in flash. + # Components using LEDC call require_ledc_iram() to force this. + if ( + CORE.data[KEY_ESP32].get(KEY_LEDC_IRAM_REQUIRED, False) + or advanced[CONF_LEDC_IN_IRAM] + ): + add_idf_sdkconfig_option("CONFIG_LEDC_CTRL_FUNC_IN_IRAM", True) + # Disable FATFS support # Components that need FATFS (SD card, etc.) can call require_fatfs() if CORE.data[KEY_ESP32].get(KEY_FATFS_REQUIRED, False): diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 95df1fba23..e82795fa4c 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -1,6 +1,7 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import output +from esphome.components.esp32 import require_ledc_iram import esphome.config_validation as cv from esphome.const import ( CONF_CHANNEL, @@ -57,6 +58,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( async def to_code(config): + require_ledc_iram() gpio = await cg.gpio_pin_expression(config[CONF_PIN]) var = cg.new_Pvariable(config[CONF_ID], gpio) await cg.register_component(var, config)