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
|
import esphome.codegen as cg
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.components.esp32 import CONF_CPU_FREQUENCY
|
from esphome.components.esp32 import CONF_CPU_FREQUENCY
|
||||||
|
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BLOCK,
|
CONF_BLOCK,
|
||||||
@ -54,7 +55,7 @@ CONFIG_SCHEMA = {
|
|||||||
),
|
),
|
||||||
cv.Optional(CONF_PSRAM): cv.All(
|
cv.Optional(CONF_PSRAM): cv.All(
|
||||||
cv.only_on_esp32,
|
cv.only_on_esp32,
|
||||||
cv.requires_component("psram"),
|
cv.requires_component(PSRAM_DOMAIN),
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_BYTES,
|
unit_of_measurement=UNIT_BYTES,
|
||||||
icon=ICON_COUNTER,
|
icon=ICON_COUNTER,
|
||||||
|
@ -76,6 +76,7 @@ CONF_ASSERTION_LEVEL = "assertion_level"
|
|||||||
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
||||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
||||||
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||||
|
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||||
CONF_RELEASE = "release"
|
CONF_RELEASE = "release"
|
||||||
|
|
||||||
ASSERTION_LEVELS = {
|
ASSERTION_LEVELS = {
|
||||||
@ -519,32 +520,59 @@ def _detect_variant(value):
|
|||||||
|
|
||||||
|
|
||||||
def final_validate(config):
|
def final_validate(config):
|
||||||
if not (
|
# Imported locally to avoid circular import issues
|
||||||
pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS)
|
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||||
):
|
|
||||||
# Not specified or empty
|
|
||||||
return config
|
|
||||||
|
|
||||||
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(
|
|
||||||
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(
|
|
||||||
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
errs.append(
|
||||||
|
cv.Invalid(
|
||||||
|
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
|
||||||
|
)
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
config[CONF_VARIANT] != VARIANT_ESP32
|
config[CONF_VARIANT] != VARIANT_ESP32
|
||||||
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
and CONF_ADVANCED in (conf_fw := config[CONF_FRAMEWORK])
|
||||||
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
and CONF_IGNORE_EFUSE_MAC_CRC in conf_fw[CONF_ADVANCED]
|
||||||
):
|
):
|
||||||
raise cv.Invalid(
|
errs.append(
|
||||||
f"{CONF_IGNORE_EFUSE_MAC_CRC} is not supported on {config[CONF_VARIANT]}"
|
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
|
return config
|
||||||
|
|
||||||
@ -627,6 +655,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
|||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
CONF_ENABLE_LWIP_CHECK_THREAD_SAFETY, default=True
|
||||||
): cv.boolean,
|
): cv.boolean,
|
||||||
|
cv.Optional(CONF_EXECUTE_FROM_PSRAM): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
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)
|
add_idf_sdkconfig_option("CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES", False)
|
||||||
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
if not advanced.get(CONF_ENABLE_LWIP_BRIDGE_INTERFACE, False):
|
||||||
add_idf_sdkconfig_option("CONFIG_LWIP_BRIDGEIF_MAX_PORTS", 0)
|
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
|
# Apply LWIP core locking for better socket performance
|
||||||
# This is already enabled by default in Arduino framework, where it provides
|
# 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
|
import esphome.codegen as cg
|
||||||
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
|
from esphome.components.const import CONF_COLOR_DEPTH, CONF_DRAW_ROUNDING
|
||||||
from esphome.components.display import Display
|
from esphome.components.display import Display
|
||||||
|
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_AUTO_CLEAR_ENABLED,
|
CONF_AUTO_CLEAR_ENABLED,
|
||||||
@ -219,7 +220,7 @@ def final_validation(configs):
|
|||||||
draw_rounding, config[CONF_DRAW_ROUNDING]
|
draw_rounding, config[CONF_DRAW_ROUNDING]
|
||||||
)
|
)
|
||||||
buffer_frac = config[CONF_BUFFER_SIZE]
|
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")
|
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")
|
||||||
for image_id in lv_images_used:
|
for image_id in lv_images_used:
|
||||||
path = global_config.get_path_for_id(image_id)[:-1]
|
path = global_config.get_path_for_id(image_id)[:-1]
|
||||||
|
@ -25,6 +25,7 @@ from esphome.components.mipi import (
|
|||||||
power_of_two,
|
power_of_two,
|
||||||
requires_buffer,
|
requires_buffer,
|
||||||
)
|
)
|
||||||
|
from esphome.components.psram import DOMAIN as PSRAM_DOMAIN
|
||||||
from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE
|
from esphome.components.spi import TYPE_OCTAL, TYPE_QUAD, TYPE_SINGLE
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.config_validation import ALLOW_EXTRA
|
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
|
# If no drawing methods are configured, and LVGL is not enabled, show a test card
|
||||||
config[CONF_SHOW_TEST_CARD] = True
|
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):
|
if not requires_buffer(config):
|
||||||
return config # No buffer needed, so no need to set a buffer size
|
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
|
# 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
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
DOMAIN = "psram"
|
||||||
|
|
||||||
DEPENDENCIES = [PLATFORM_ESP32]
|
DEPENDENCIES = [PLATFORM_ESP32]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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)
|
PsramComponent = psram_ns.class_("PsramComponent", cg.Component)
|
||||||
|
|
||||||
TYPE_QUAD = "quad"
|
TYPE_QUAD = "quad"
|
||||||
|
@ -329,6 +329,28 @@ class ConfigValidationStep(abc.ABC):
|
|||||||
def run(self, result: Config) -> None: ... # noqa: E704
|
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):
|
class LoadValidationStep(ConfigValidationStep):
|
||||||
"""Load step, this step is called once for each domain config fragment.
|
"""Load step, this step is called once for each domain config fragment.
|
||||||
|
|
||||||
@ -582,16 +604,18 @@ class MetadataValidationStep(ConfigValidationStep):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
for i, part_conf in enumerate(self.conf):
|
for i, part_conf in enumerate(self.conf):
|
||||||
|
path = self.path + [i]
|
||||||
result.add_validation_step(
|
result.add_validation_step(
|
||||||
SchemaValidationStep(
|
SchemaValidationStep(self.domain, path, part_conf, self.comp)
|
||||||
self.domain, self.path + [i], part_conf, self.comp
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
result.add_validation_step(FinalValidateValidationStep(path, self.comp))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
result.add_validation_step(
|
result.add_validation_step(
|
||||||
SchemaValidationStep(self.domain, self.path, self.conf, self.comp)
|
SchemaValidationStep(self.domain, self.path, self.conf, self.comp)
|
||||||
)
|
)
|
||||||
|
result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
|
||||||
|
|
||||||
|
|
||||||
class SchemaValidationStep(ConfigValidationStep):
|
class SchemaValidationStep(ConfigValidationStep):
|
||||||
@ -628,7 +652,6 @@ class SchemaValidationStep(ConfigValidationStep):
|
|||||||
result.set_by_path(self.path, validated)
|
result.set_by_path(self.path, validated)
|
||||||
|
|
||||||
path_context.reset(token)
|
path_context.reset(token)
|
||||||
result.add_validation_step(FinalValidateValidationStep(self.path, self.comp))
|
|
||||||
|
|
||||||
|
|
||||||
class IDPassValidationStep(ConfigValidationStep):
|
class IDPassValidationStep(ConfigValidationStep):
|
||||||
@ -909,7 +932,7 @@ def validate_config(
|
|||||||
|
|
||||||
# First run platform validation steps
|
# First run platform validation steps
|
||||||
result.add_validation_step(
|
result.add_validation_step(
|
||||||
LoadValidationStep(target_platform, config[target_platform])
|
LoadTargetPlatformValidationStep(target_platform, config[target_platform])
|
||||||
)
|
)
|
||||||
result.run_validation_steps()
|
result.run_validation_steps()
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ def set_core_config() -> Generator[SetCoreConfigCallable]:
|
|||||||
*,
|
*,
|
||||||
core_data: ConfigType | None = None,
|
core_data: ConfigType | None = None,
|
||||||
platform_data: ConfigType | None = None,
|
platform_data: ConfigType | None = None,
|
||||||
|
full_config: dict[str, ConfigType] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
platform, framework = platform_framework.value
|
platform, framework = platform_framework.value
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ def set_core_config() -> Generator[SetCoreConfigCallable]:
|
|||||||
CORE.data[platform.value] = platform_data
|
CORE.data[platform.value] = platform_data
|
||||||
|
|
||||||
config.path_context.set([])
|
config.path_context.set([])
|
||||||
final_validate.full_config.set(Config())
|
final_validate.full_config.set(full_config or Config())
|
||||||
|
|
||||||
yield setter
|
yield setter
|
||||||
|
|
||||||
|
@ -8,10 +8,13 @@ import pytest
|
|||||||
|
|
||||||
from esphome.components.esp32 import VARIANTS
|
from esphome.components.esp32 import VARIANTS
|
||||||
import esphome.config_validation as cv
|
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)
|
set_core_config(PlatformFramework.ESP32_IDF)
|
||||||
|
|
||||||
from esphome.components.esp32 import CONFIG_SCHEMA
|
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'\]",
|
r"Option 'variant' does not match selected board. @ data\['variant'\]",
|
||||||
id="mismatched_board_variant_config",
|
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(
|
def test_esp32_configuration_errors(
|
||||||
config: Any,
|
config: Any,
|
||||||
error_match: str,
|
error_match: str,
|
||||||
|
set_core_config: SetCoreConfigCallable,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
set_core_config(PlatformFramework.ESP32_IDF, full_config={CONF_ESPHOME: {}})
|
||||||
"""Test detection of invalid configuration."""
|
"""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):
|
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,
|
core_data: ConfigType | None = None,
|
||||||
platform_data: ConfigType | None = None,
|
platform_data: ConfigType | None = None,
|
||||||
|
full_config: dict[str, ConfigType] | None = 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