diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index bab2762f00..09e09f0dc1 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -2,7 +2,11 @@ import logging import esphome.codegen as cg from esphome.components import sensor, voltage_sampler -from esphome.components.esp32 import get_esp32_variant, include_builtin_idf_component +from esphome.components.esp32 import ( + get_esp32_variant, + include_builtin_idf_component, + require_adc_oneshot_iram, +) from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC from esphome.components.zephyr import ( zephyr_add_overlay, @@ -24,6 +28,7 @@ from esphome.const import ( PlatformFramework, ) from esphome.core import CORE +from esphome.types import ConfigType from . import ( ATTENUATION_MODES, @@ -65,6 +70,13 @@ def validate_config(config): return config +def _require_adc_iram(config: ConfigType) -> ConfigType: + """Register ADC oneshot IRAM requirement during config validation.""" + if CORE.is_esp32: + require_adc_oneshot_iram() + return config + + ADCSensor = adc_ns.class_( "ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler ) @@ -95,6 +107,7 @@ CONFIG_SCHEMA = cv.All( ) .extend(cv.polling_component_schema("60s")), validate_config, + _require_adc_iram, ) CONF_ADC_CHANNEL_ID = "adc_channel_id" diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 2974028b50..7b3f9da3da 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_ADC_ONESHOT_IN_IRAM = "adc_oneshot_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_ADC_ONESHOT_IRAM_REQUIRED = "adc_oneshot_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_adc_oneshot_iram() -> None: + """Mark that ADC oneshot IRAM safety is required by a component. + + Call this from components that use the ADC oneshot driver. When flash cache is + disabled (e.g., during NVS writes by WiFi, BLE, Zigbee, or power management), + the ADC oneshot read function must be in IRAM to avoid crashes. + This sets CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM. + """ + CORE.data[KEY_ESP32][KEY_ADC_ONESHOT_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_ADC_ONESHOT_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 ADC oneshot control functions in IRAM for cache safety + # When flash cache is disabled (during NVS writes by WiFi, BLE, Zigbee, Thread, + # power management, etc.), ADC reads will crash if these functions are in flash. + # Components using ADC call require_adc_oneshot_iram() to force this. + if ( + CORE.data[KEY_ESP32].get(KEY_ADC_ONESHOT_IRAM_REQUIRED, False) + or advanced[CONF_ADC_ONESHOT_IN_IRAM] + ): + add_idf_sdkconfig_option("CONFIG_ADC_ONESHOT_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):