mirror of
https://github.com/esphome/esphome.git
synced 2026-10-10 21:39:06 +00:00
[esp32] Honor sdkconfig CPU frequency options (#20158)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
865863abf5
commit
ff96530e4d
@@ -551,9 +551,88 @@ assert all(variant in CPU_FREQUENCIES for variant in VARIANTS)
|
||||
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
|
||||
|
||||
|
||||
_SDKCONFIG_CPU_FREQUENCY_OPTION = "CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ"
|
||||
_SDKCONFIG_CPU_FREQUENCY_PREFIX = f"{_SDKCONFIG_CPU_FREQUENCY_OPTION}_"
|
||||
# Older IDF versions named the CPU frequency options after the chip
|
||||
_LEGACY_SDKCONFIG_CPU_FREQUENCY_VARIANTS = frozenset(
|
||||
{VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3}
|
||||
)
|
||||
|
||||
|
||||
def _get_sdkconfig_cpu_frequencies(
|
||||
sdkconfig_options: dict[str, str], variant: str
|
||||
) -> tuple[set[int], set[int]]:
|
||||
"""Return the CPU frequencies in MHz that sdkconfig_options selects and disables."""
|
||||
# (integer option, choice option prefix) pairs that can carry a frequency
|
||||
keys = [(_SDKCONFIG_CPU_FREQUENCY_OPTION, _SDKCONFIG_CPU_FREQUENCY_PREFIX)]
|
||||
if variant in _LEGACY_SDKCONFIG_CPU_FREQUENCY_VARIANTS:
|
||||
keys.append(
|
||||
(
|
||||
f"CONFIG_{variant}_DEFAULT_CPU_FREQ_MHZ",
|
||||
f"CONFIG_{variant}_DEFAULT_CPU_FREQ_",
|
||||
)
|
||||
)
|
||||
selected: set[int] = set()
|
||||
disabled: set[int] = set()
|
||||
for integer_key, choice_prefix in keys:
|
||||
if (value := sdkconfig_options.get(integer_key)) is not None:
|
||||
try:
|
||||
selected.add(int(value))
|
||||
except ValueError as err:
|
||||
raise cv.Invalid(
|
||||
f"{integer_key} must be an integer MHz value",
|
||||
path=[CONF_FRAMEWORK, CONF_SDKCONFIG_OPTIONS],
|
||||
) from err
|
||||
for name, value in sdkconfig_options.items():
|
||||
mhz = name.removeprefix(choice_prefix)
|
||||
if mhz == name or not mhz.isdigit():
|
||||
continue
|
||||
if value.lower() == "y":
|
||||
selected.add(int(mhz))
|
||||
elif value.lower() == "n":
|
||||
disabled.add(int(mhz))
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
f"{name} must be y or n",
|
||||
path=[CONF_FRAMEWORK, CONF_SDKCONFIG_OPTIONS],
|
||||
)
|
||||
return selected, disabled
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
cpu_frequency = config.get(CONF_CPU_FREQUENCY, None)
|
||||
cpu_frequency = config.get(CONF_CPU_FREQUENCY)
|
||||
variant = config[CONF_VARIANT]
|
||||
sdkconfig_path = [CONF_FRAMEWORK, CONF_SDKCONFIG_OPTIONS]
|
||||
selected, disabled = _get_sdkconfig_cpu_frequencies(
|
||||
config[CONF_FRAMEWORK][CONF_SDKCONFIG_OPTIONS], variant
|
||||
)
|
||||
if len(selected) > 1:
|
||||
conflicting = ", ".join(f"{mhz}MHz" for mhz in sorted(selected))
|
||||
raise cv.Invalid(
|
||||
f"sdkconfig_options contains conflicting CPU frequencies ({conflicting})",
|
||||
path=sdkconfig_path,
|
||||
)
|
||||
sdkconfig_cpu_frequency = f"{selected.pop()}MHZ" if selected else None
|
||||
if cpu_frequency is None and sdkconfig_cpu_frequency is not None:
|
||||
if sdkconfig_cpu_frequency not in CPU_FREQUENCIES[variant]:
|
||||
raise cv.Invalid(
|
||||
f"sdkconfig_options selects {sdkconfig_cpu_frequency}, which {variant} does not support",
|
||||
path=sdkconfig_path,
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"sdkconfig_options contains a CPU frequency setting; using %s. "
|
||||
"Set 'esp32.cpu_frequency' to configure it directly.",
|
||||
sdkconfig_cpu_frequency,
|
||||
)
|
||||
cpu_frequency = sdkconfig_cpu_frequency
|
||||
elif (
|
||||
sdkconfig_cpu_frequency is not None and sdkconfig_cpu_frequency != cpu_frequency
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"esp32.cpu_frequency ({cpu_frequency}) conflicts with sdkconfig_options "
|
||||
f"({sdkconfig_cpu_frequency})",
|
||||
path=sdkconfig_path,
|
||||
)
|
||||
# if not specified in config, default to the maximum supported frequency
|
||||
# (ESP32-P4 engineering samples are limited to 360MHz, non-engineering can do 400MHz)
|
||||
if cpu_frequency is None:
|
||||
@@ -562,12 +641,17 @@ def set_core_data(config):
|
||||
cpu_frequency = "360MHZ"
|
||||
else:
|
||||
cpu_frequency = choices[-1]
|
||||
config[CONF_CPU_FREQUENCY] = cpu_frequency
|
||||
elif cpu_frequency not in CPU_FREQUENCIES[variant]:
|
||||
raise cv.Invalid(
|
||||
f"Invalid CPU frequency '{cpu_frequency}' for {config[CONF_VARIANT]}",
|
||||
path=[CONF_CPU_FREQUENCY],
|
||||
)
|
||||
if int(cpu_frequency[:-3]) in disabled:
|
||||
raise cv.Invalid(
|
||||
f"sdkconfig_options disables the selected CPU frequency ({cpu_frequency})",
|
||||
path=sdkconfig_path,
|
||||
)
|
||||
config[CONF_CPU_FREQUENCY] = cpu_frequency
|
||||
|
||||
if variant == VARIANT_ESP32P4 and cpu_frequency == "400MHZ":
|
||||
_LOGGER.warning(
|
||||
@@ -3138,7 +3222,7 @@ async def to_code(config):
|
||||
|
||||
# Set default CPU frequency
|
||||
add_idf_sdkconfig_option(
|
||||
f"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_{config[CONF_CPU_FREQUENCY][:-3]}", True
|
||||
f"{_SDKCONFIG_CPU_FREQUENCY_PREFIX}{config[CONF_CPU_FREQUENCY][:-3]}", True
|
||||
)
|
||||
|
||||
# Apply LWIP optimization settings
|
||||
|
||||
@@ -111,6 +111,245 @@ def test_esp32_config(
|
||||
assert VARIANT_FRIENDLY[variant].lower() in config["board"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"sdkconfig_options",
|
||||
"cpu_frequency",
|
||||
"expected_frequency",
|
||||
"warns",
|
||||
"variant",
|
||||
"framework_type",
|
||||
),
|
||||
[
|
||||
pytest.param(
|
||||
{
|
||||
"CONFIG_ESP32_DEFAULT_CPU_FREQ_160": "y",
|
||||
"CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ": "160",
|
||||
},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="legacy-frequency-options-with-default",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32_DEFAULT_CPU_FREQ_160": "y"},
|
||||
"160MHz",
|
||||
"160MHZ",
|
||||
False,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="explicit-cpu-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ": "160"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="legacy-scalar-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32_DEFAULT_CPU_FREQ_160": "y"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="legacy-frequency-choice",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160": "y"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="current-idf-frequency-choice",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160": "n"},
|
||||
None,
|
||||
"240MHZ",
|
||||
False,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="disabled-frequency-choice",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ_240": "n"},
|
||||
"240MHz",
|
||||
"240MHZ",
|
||||
False,
|
||||
VARIANT_ESP32,
|
||||
"esp-idf",
|
||||
id="ignore-unrecognized-legacy-disabled-choice",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ": "160"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32C3,
|
||||
"esp-idf",
|
||||
id="current-scalar-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ": "160"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32S2,
|
||||
"esp-idf",
|
||||
id="s2-legacy-scalar-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160": "y"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32S3,
|
||||
"esp-idf",
|
||||
id="s3-legacy-choice-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP32C3_DEFAULT_CPU_FREQ_80": "y"},
|
||||
None,
|
||||
"80MHZ",
|
||||
True,
|
||||
VARIANT_ESP32C3,
|
||||
"esp-idf",
|
||||
id="c3-legacy-choice-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ": "160"},
|
||||
None,
|
||||
"160MHZ",
|
||||
True,
|
||||
VARIANT_ESP32,
|
||||
"arduino",
|
||||
id="arduino-framework-current-scalar-frequency",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_sdkconfig_cpu_frequency_is_honored(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
sdkconfig_options: dict[str, str],
|
||||
cpu_frequency: str | None,
|
||||
expected_frequency: str,
|
||||
warns: bool,
|
||||
variant: str,
|
||||
framework_type: str,
|
||||
) -> None:
|
||||
"""SDK config frequency settings are honored and recommend the native config key."""
|
||||
platform_framework = (
|
||||
PlatformFramework.ESP32_ARDUINO
|
||||
if framework_type == "arduino"
|
||||
else PlatformFramework.ESP32_IDF
|
||||
)
|
||||
set_core_config(platform_framework)
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA
|
||||
|
||||
config: dict[str, Any] = {
|
||||
"variant": variant,
|
||||
"framework": {
|
||||
"type": framework_type,
|
||||
"sdkconfig_options": sdkconfig_options,
|
||||
},
|
||||
}
|
||||
if cpu_frequency is not None:
|
||||
config["cpu_frequency"] = cpu_frequency
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
config = CONFIG_SCHEMA(config)
|
||||
|
||||
assert ("CPU frequency setting" in caplog.text) is warns
|
||||
assert config["cpu_frequency"] == expected_frequency
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("variant", "cpu_frequency", "sdkconfig_options", "error_match"),
|
||||
[
|
||||
pytest.param(
|
||||
VARIANT_ESP32C3,
|
||||
None,
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ": "fast"},
|
||||
"must be an integer MHz value",
|
||||
id="integer-option-not-a-number",
|
||||
),
|
||||
pytest.param(
|
||||
VARIANT_ESP32C3,
|
||||
None,
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240": "y"},
|
||||
"ESP32C3 does not support",
|
||||
id="unsupported-frequency",
|
||||
),
|
||||
pytest.param(
|
||||
VARIANT_ESP32C3,
|
||||
None,
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160": "yes"},
|
||||
"must be y or n",
|
||||
id="choice-option-not-y-or-n",
|
||||
),
|
||||
pytest.param(
|
||||
VARIANT_ESP32C3,
|
||||
None,
|
||||
{
|
||||
"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160": "y",
|
||||
"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240": "y",
|
||||
},
|
||||
r"conflicting CPU frequencies \(160MHz, 240MHz\)",
|
||||
id="two-frequencies-selected",
|
||||
),
|
||||
pytest.param(
|
||||
VARIANT_ESP32,
|
||||
"160MHz",
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240": "y"},
|
||||
"conflicts with sdkconfig_options",
|
||||
id="conflicts-with-explicit-value",
|
||||
),
|
||||
pytest.param(
|
||||
VARIANT_ESP32,
|
||||
None,
|
||||
{"CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240": "n"},
|
||||
"disables the selected CPU frequency",
|
||||
id="default-frequency-disabled",
|
||||
),
|
||||
pytest.param(
|
||||
VARIANT_ESP32,
|
||||
"160MHz",
|
||||
{"CONFIG_ESP32_DEFAULT_CPU_FREQ_160": "n"},
|
||||
"disables the selected CPU frequency",
|
||||
id="explicit-frequency-disabled-by-legacy-option",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_sdkconfig_cpu_frequency_errors(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
variant: str,
|
||||
cpu_frequency: str | None,
|
||||
sdkconfig_options: dict[str, str],
|
||||
error_match: str,
|
||||
) -> None:
|
||||
"""Bad, conflicting or disabled sdkconfig frequencies fail at validation."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA
|
||||
|
||||
config: dict[str, Any] = {
|
||||
"variant": variant,
|
||||
"framework": {"type": "esp-idf", "sdkconfig_options": sdkconfig_options},
|
||||
}
|
||||
if cpu_frequency is not None:
|
||||
config["cpu_frequency"] = cpu_frequency
|
||||
|
||||
with pytest.raises(cv.Invalid, match=error_match) as exc_info:
|
||||
CONFIG_SCHEMA(config)
|
||||
assert exc_info.value.path == ["framework", "sdkconfig_options"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config_toolchain", "expected"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user