mirror of
https://github.com/esphome/esphome.git
synced 2025-08-02 08:27:47 +00:00
[esp32] Add config option to execute from PSRAM (#9907)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
f761404bf6
commit
940a8b43fa
@ -1,6 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
from esphome.components.esp32 import CONF_CPU_FREQUENCY
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLOCK,
|
||||
@ -54,7 +55,7 @@ CONFIG_SCHEMA = {
|
||||
),
|
||||
cv.Optional(CONF_PSRAM): cv.All(
|
||||
cv.only_on_esp32,
|
||||
cv.requires_component("psram"),
|
||||
cv.requires_component(PSRAM_DOMAIN),
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BYTES,
|
||||
icon=ICON_COUNTER,
|
||||
|
@ -76,6 +76,7 @@ CONF_ASSERTION_LEVEL = "assertion_level"
|
||||
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
||||
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||
CONF_RELEASE = "release"
|
||||
|
||||
ASSERTION_LEVELS = {
|
||||
@ -519,32 +520,59 @@ def _detect_variant(value):
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
if not (
|
||||
pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS)
|
||||
):
|
||||
# Not specified or empty
|
||||
return config
|
||||
# Imported locally to avoid circular import issues
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
|
||||
errs = []
|
||||
full_config = fv.full_config.get()
|
||||
if pio_options := full_config[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS):
|
||||
pio_flash_size_key = "board_upload.flash_size"
|
||||
pio_partitions_key = "board_build.partitions"
|
||||
if CONF_PARTITIONS in config and pio_partitions_key in pio_options:
|
||||
raise cv.Invalid(
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32"
|
||||
)
|
||||
|
||||
)
|
||||
if pio_flash_size_key in pio_options:
|
||||
raise cv.Invalid(
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
||||
)
|
||||
|
||||
)
|
||||
if (
|
||||
config[CONF_VARIANT] != VARIANT_ESP32
|
||||
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
||||
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"{CONF_IGNORE_EFUSE_MAC_CRC} is not supported on {config[CONF_VARIANT]}"
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_IGNORE_EFUSE_MAC_CRC}' is not supported on {config[CONF_VARIANT]}",
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_IGNORE_EFUSE_MAC_CRC],
|
||||
)
|
||||
)
|
||||
if (
|
||||
config.get(CONF_FRAMEWORK, {})
|
||||
.get(CONF_ADVANCED, {})
|
||||
.get(CONF_EXECUTE_FROM_PSRAM)
|
||||
):
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32S3:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_EXECUTE_FROM_PSRAM}' is only supported on {VARIANT_ESP32S3} variant",
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_EXECUTE_FROM_PSRAM],
|
||||
)
|
||||
)
|
||||
if PSRAM_DOMAIN not in full_config:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_EXECUTE_FROM_PSRAM}' requires PSRAM to be configured",
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_EXECUTE_FROM_PSRAM],
|
||||
)
|
||||
)
|
||||
|
||||
if errs:
|
||||
raise cv.MultipleInvalid(errs)
|
||||
|
||||
return config
|
||||
|
||||
@ -627,6 +655,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
||||
): cv.boolean,
|
||||
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
@ -792,6 +821,9 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
||||
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
|
||||
if advanced.get(CONF_EXECUTE_FROM_PSRAM, False):
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_FETCH_INSTRUCTIONS", True)
|
||||
add_idf_sdkconfig_option("CONFIG_SPIRAM_RODATA", True)
|
||||
|
||||
# Apply LWIP core locking for better socket performance
|
||||
# This is already enabled by default in Arduino framework, where it provides
|
||||
|
@ -4,6 +4,7 @@ from esphome.automation import build_automation, register_action, validate_autom
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
|
||||
from esphome.components.display import Display
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AUTO_CLEAR_ENABLED,
|
||||
@ -219,7 +220,7 @@ def final_validation(configs):
|
||||
draw_rounding, config[CONF_DRAW_ROUNDING]
|
||||
)
|
||||
buffer_frac = config[CONF_BUFFER_SIZE]
|
||||
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
|
||||
if CORE.is_esp32 and buffer_frac > 0.5 and PSRAM_DOMAIN not in global_config:
|
||||
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||
for image_id in lv_images_used:
|
||||
path = global_config.get_path_for_id(image_id)[:-1]
|
||||
|
@ -25,6 +25,7 @@ from esphome.components.mipi import (
|
||||
power_of_two,
|
||||
requires_buffer,
|
||||
)
|
||||
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||
from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE
|
||||
import esphome.config_validation as cv
|
||||
from esphome.config_validation import ALLOW_EXTRA
|
||||
@ -292,7 +293,7 @@ def _final_validate(config):
|
||||
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||
config[CONF_SHOW_TEST_CARD] = True
|
||||
|
||||
if "psram" not in global_config and CONF_BUFFER_SIZE not in config:
|
||||
if PSRAM_DOMAIN not in global_config and CONF_BUFFER_SIZE not in config:
|
||||
if not requires_buffer(config):
|
||||
return config # No buffer needed, so no need to set a buffer size
|
||||
# If PSRAM is not enabled, choose a small buffer size by default
|
||||
|
@ -28,12 +28,13 @@ from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DOMAIN = "psram"
|
||||
|
||||
DEPENDENCIES = [PLATFORM_ESP32]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
psram_ns = cg.esphome_ns.namespace("psram")
|
||||
psram_ns = cg.esphome_ns.namespace(DOMAIN)
|
||||
PsramComponent = psram_ns.class_("PsramComponent", cg.Component)
|
||||
|
||||
TYPE_QUAD = "quad"
|
||||
|
@ -329,6 +329,28 @@ class ConfigValidationStep(abc.ABC):
|
||||
def run(self, result: Config) -> None: ... # noqa: E704
|
||||
|
||||
|
||||
class LoadTargetPlatformValidationStep(ConfigValidationStep):
|
||||
"""Load target platform step."""
|
||||
|
||||
def __init__(self, domain: str, conf: ConfigType):
|
||||
self.domain = domain
|
||||
self.conf = conf
|
||||
|
||||
def run(self, result: Config) -> None:
|
||||
if self.conf is None:
|
||||
result[self.domain] = self.conf = {}
|
||||
result.add_output_path([self.domain], self.domain)
|
||||
component = get_component(self.domain)
|
||||
|
||||
result[self.domain] = self.conf
|
||||
path = [self.domain]
|
||||
CORE.loaded_integrations.add(self.domain)
|
||||
|
||||
result.add_validation_step(
|
||||
SchemaValidationStep(self.domain, path, self.conf, component)
|
||||
)
|
||||
|
||||
|
||||
class LoadValidationStep(ConfigValidationStep):
|
||||
"""Load step, this step is called once for each domain config fragment.
|
||||
|
||||
@ -582,16 +604,18 @@ class MetadataValidationStep(ConfigValidationStep):
|
||||
)
|
||||
return
|
||||
for i, part_conf in enumerate(self.conf):
|
||||
path = self.path + [i]
|
||||
result.add_validation_step(
|
||||
SchemaValidationStep(
|
||||
self.domain, self.path + [i], part_conf, self.comp
|
||||
)
|
||||
SchemaValidationStep(self.domain, path, part_conf, self.comp)
|
||||
)
|
||||
result.add_validation_step(FinalValidateValidationStep(path, self.comp))
|
||||
|
||||
return
|
||||
|
||||
result.add_validation_step(
|
||||
SchemaValidationStep(self.domain, self.path, self.conf, self.comp)
|
||||
)
|
||||
result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
|
||||
|
||||
|
||||
class SchemaValidationStep(ConfigValidationStep):
|
||||
@ -628,7 +652,6 @@ class SchemaValidationStep(ConfigValidationStep):
|
||||
result.set_by_path(self.path, validated)
|
||||
|
||||
path_context.reset(token)
|
||||
result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
|
||||
|
||||
|
||||
class IDPassValidationStep(ConfigValidationStep):
|
||||
@ -909,7 +932,7 @@ def validate_config(
|
||||
|
||||
# First run platform validation steps
|
||||
result.add_validation_step(
|
||||
LoadValidationStep(target_platform, config[target_platform])
|
||||
LoadTargetPlatformValidationStep(target_platform, config[target_platform])
|
||||
)
|
||||
result.run_validation_steps()
|
||||
|
||||
|
@ -65,6 +65,7 @@ def set_core_config() -> Generator[SetCoreConfigCallable]:
|
||||
*,
|
||||
core_data: ConfigType | None = None,
|
||||
platform_data: ConfigType | None = None,
|
||||
full_config: dict[str, ConfigType] | None = None,
|
||||
) -> None:
|
||||
platform, framework = platform_framework.value
|
||||
|
||||
@ -83,7 +84,7 @@ def set_core_config() -> Generator[SetCoreConfigCallable]:
|
||||
CORE.data[platform.value] = platform_data
|
||||
|
||||
config.path_context.set([])
|
||||
final_validate.full_config.set(Config())
|
||||
final_validate.full_config.set(full_config or Config())
|
||||
|
||||
yield setter
|
||||
|
||||
|
@ -8,10 +8,13 @@ import pytest
|
||||
|
||||
from esphome.components.esp32 import VARIANTS
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import PlatformFramework
|
||||
from esphome.const import CONF_ESPHOME, PlatformFramework
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
def test_esp32_config(set_core_config) -> None:
|
||||
def test_esp32_config(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA
|
||||
@ -60,14 +63,49 @@ def test_esp32_config(set_core_config) -> None:
|
||||
r"Option 'variant' does not match selected board. @ data\['variant'\]",
|
||||
id="mismatched_board_variant_config",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"variant": "esp32s2",
|
||||
"framework": {
|
||||
"type": "esp-idf",
|
||||
"advanced": {"execute_from_psram": True},
|
||||
},
|
||||
},
|
||||
r"'execute_from_psram' is only supported on ESP32S3 variant @ data\['framework'\]\['advanced'\]\['execute_from_psram'\]",
|
||||
id="execute_from_psram_invalid_for_variant_config",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"variant": "esp32s3",
|
||||
"framework": {
|
||||
"type": "esp-idf",
|
||||
"advanced": {"execute_from_psram": True},
|
||||
},
|
||||
},
|
||||
r"'execute_from_psram' requires PSRAM to be configured @ data\['framework'\]\['advanced'\]\['execute_from_psram'\]",
|
||||
id="execute_from_psram_requires_psram_config",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"variant": "esp32s3",
|
||||
"framework": {
|
||||
"type": "esp-idf",
|
||||
"advanced": {"ignore_efuse_mac_crc": True},
|
||||
},
|
||||
},
|
||||
r"'ignore_efuse_mac_crc' is not supported on ESP32S3 @ data\['framework'\]\['advanced'\]\['ignore_efuse_mac_crc'\]",
|
||||
id="ignore_efuse_mac_crc_only_on_esp32",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_esp32_configuration_errors(
|
||||
config: Any,
|
||||
error_match: str,
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
set_core_config(PlatformFramework.ESP32_IDF, full_config={CONF_ESPHOME: {}})
|
||||
"""Test detection of invalid configuration."""
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA
|
||||
from esphome.components.esp32 import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
|
||||
|
||||
with pytest.raises(cv.Invalid, match=error_match):
|
||||
CONFIG_SCHEMA(config)
|
||||
FINAL_VALIDATE_SCHEMA(CONFIG_SCHEMA(config))
|
||||
|
@ -18,4 +18,5 @@ class SetCoreConfigCallable(Protocol):
|
||||
*,
|
||||
core_data: ConfigType | None = None,
|
||||
platform_data: ConfigType | None = None,
|
||||
full_config: dict[str, ConfigType] | None = None,
|
||||
) -> None: ...
|
||||
|
12
tests/components/esp32/test.esp32-s3-idf.yaml
Normal file
12
tests/components/esp32/test.esp32-s3-idf.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
esp32:
|
||||
variant: esp32s3
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
execute_from_psram: true
|
||||
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
logger:
|
Loading…
x
Reference in New Issue
Block a user