Compare commits

...

4 Commits

Author SHA1 Message Date
J. Nick Koston
b2e66b3a40 Add type annotations to _require_ledc_iram_validator 2026-04-13 12:28:18 -10:00
J. Nick Koston
3317eaebc7 [ledc] Move require_ledc_iram to config validation
The esp32 to_code runs at PLATFORM priority (1000) before component
to_code at COMPONENT priority (0), so the flag was set too late.
FINAL_VALIDATE_SCHEMA also runs too late for this purpose.
Move to CONFIG_SCHEMA validation to ensure it is set before esp32
processes sdkconfig options.
2026-04-13 12:26:35 -10:00
kbx81
a2fa7d36f9 fix 2026-04-13 17:22:15 -05:00
J. Nick Koston
2e027fc208 [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.
2026-04-13 11:04:29 -10:00
2 changed files with 49 additions and 13 deletions

View File

@@ -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):

View File

@@ -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,
@@ -9,6 +10,7 @@ from esphome.const import (
CONF_PHASE_ANGLE,
CONF_PIN,
)
from esphome.types import ConfigType
DEPENDENCIES = ["esp32"]
@@ -41,19 +43,29 @@ ledc_ns = cg.esphome_ns.namespace("ledc")
LEDCOutput = ledc_ns.class_("LEDCOutput", output.FloatOutput, cg.Component)
SetFrequencyAction = ledc_ns.class_("SetFrequencyAction", automation.Action)
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
cv.frequency, cv.float_range(min=0, min_included=False)
),
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
cv.Optional(CONF_PHASE_ANGLE): cv.All(
cv.angle, cv.float_range(min=0.0, max=360.0)
),
}
).extend(cv.COMPONENT_SCHEMA)
def _require_ledc_iram_validator(config: ConfigType) -> ConfigType:
"""Register LEDC IRAM requirement during config validation."""
require_ledc_iram()
return config
CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
cv.frequency, cv.float_range(min=0, min_included=False)
),
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
cv.Optional(CONF_PHASE_ANGLE): cv.All(
cv.angle, cv.float_range(min=0.0, max=360.0)
),
}
).extend(cv.COMPONENT_SCHEMA),
_require_ledc_iram_validator,
)
async def to_code(config):