[psram] Require mode for S3 (#11470)

Co-authored-by: clydeps <U5yx99dok9>
This commit is contained in:
Clyde Stubbs
2025-11-04 13:38:43 +10:00
committed by GitHub
parent ce63137565
commit 758ac58343
8 changed files with 60 additions and 31 deletions

View File

@@ -4,6 +4,7 @@ from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.esp32 import add_idf_component
from esphome.components.psram import DOMAIN as psram_domain
import esphome.config_validation as cv
from esphome.const import (
CONF_BRIGHTNESS,
@@ -26,10 +27,9 @@ import esphome.final_validate as fv
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["camera"]
DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["camera", "psram"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
ESP32CameraImageData = esp32_camera_ns.struct("CameraImageData")
@@ -163,6 +163,14 @@ CONF_ON_IMAGE = "on_image"
camera_range_param = cv.int_range(min=-2, max=2)
def validate_fb_location_(value):
validator = cv.enum(ENUM_FB_LOCATION, upper=True)
if value.lower() == psram_domain:
validator = cv.All(validator, cv.requires_component(psram_domain))
return validator(value)
CONFIG_SCHEMA = cv.All(
cv.ENTITY_BASE_SCHEMA.extend(
{
@@ -236,9 +244,9 @@ CONFIG_SCHEMA = cv.All(
cv.framerate, cv.Range(min=0, max=1)
),
cv.Optional(CONF_FRAME_BUFFER_COUNT, default=1): cv.int_range(min=1, max=2),
cv.Optional(CONF_FRAME_BUFFER_LOCATION, default="PSRAM"): cv.enum(
ENUM_FB_LOCATION, upper=True
),
cv.Optional(
CONF_FRAME_BUFFER_LOCATION, default="PSRAM"
): validate_fb_location_,
cv.Optional(CONF_ON_STREAM_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(

View File

@@ -20,8 +20,7 @@ import esphome.final_validate as fv
from .const import INKPLATE_10_CUSTOM_WAVEFORMS, WAVEFORMS
DEPENDENCIES = ["i2c", "esp32"]
AUTO_LOAD = ["psram"]
DEPENDENCIES = ["i2c", "esp32", "psram"]
CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin"
CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin"

View File

@@ -1,4 +1,5 @@
import logging
import textwrap
import esphome.codegen as cg
from esphome.components.esp32 import (
@@ -104,6 +105,17 @@ def get_config_schema(config):
if not speeds:
raise cv.Invalid("PSRAM is not supported on this chip")
modes = SPIRAM_MODES[variant]
if CONF_MODE not in config and len(modes) != 1:
raise (
cv.Invalid(
textwrap.dedent(
f"""
{variant} requires PSRAM mode selection; one of {", ".join(modes)}
Selection of the wrong mode for the board will cause a runtime failure to initialise PSRAM
"""
)
)
)
return cv.Schema(
{
cv.GenerateID(): cv.declare_id(PsramComponent),

View File

@@ -26,21 +26,12 @@ from esphome.const import (
from esphome.core import CORE, HexInt
from esphome.core.entity_helpers import inherit_property_from
from esphome.external_files import download_content
from esphome.types import ConfigType
from esphome.final_validate import full_config
_LOGGER = logging.getLogger(__name__)
def AUTO_LOAD(config: ConfigType) -> list[str]:
load = ["audio"]
if (
not config
or config.get(CONF_TASK_STACK_IN_PSRAM)
or config.get(CONF_CODEC_SUPPORT_ENABLED)
):
return load + ["psram"]
return load
AUTO_LOAD = ["audio"]
CODEOWNERS = ["@kahrendt", "@synesthesiam"]
DOMAIN = "media_player"
@@ -226,12 +217,19 @@ def _validate_repeated_speaker(config):
return config
def _validate_supported_local_file(config):
def _final_validate(config):
# Default to using codec if psram is enabled
if (use_codec := config.get(CONF_CODEC_SUPPORT_ENABLED)) is None:
use_codec = psram.DOMAIN in full_config.get()
conf_id = config[CONF_ID].id
core_data = CORE.data.setdefault(DOMAIN, {conf_id: {}})
core_data[conf_id][CONF_CODEC_SUPPORT_ENABLED] = use_codec
for file_config in config.get(CONF_FILES, []):
_, media_file_type = _read_audio_file_and_type(file_config)
if str(media_file_type) == str(audio.AUDIO_FILE_TYPE_ENUM["NONE"]):
raise cv.Invalid("Unsupported local media file")
if not config[CONF_CODEC_SUPPORT_ENABLED] and str(media_file_type) != str(
if not use_codec and str(media_file_type) != str(
audio.AUDIO_FILE_TYPE_ENUM["WAV"]
):
# Only wav files are supported
@@ -290,11 +288,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_BUFFER_SIZE, default=1000000): cv.int_range(
min=4000, max=4000000
),
cv.Optional(
CONF_CODEC_SUPPORT_ENABLED, default=psram.supported()
): cv.boolean,
cv.Optional(CONF_CODEC_SUPPORT_ENABLED): cv.boolean,
cv.Optional(CONF_FILES): cv.ensure_list(MEDIA_FILE_TYPE_SCHEMA),
cv.Optional(CONF_TASK_STACK_IN_PSRAM, default=False): cv.boolean,
cv.Optional(CONF_TASK_STACK_IN_PSRAM): cv.All(
cv.boolean, cv.requires_component(psram.DOMAIN)
),
cv.Optional(CONF_VOLUME_INCREMENT, default=0.05): cv.percentage,
cv.Optional(CONF_VOLUME_INITIAL, default=0.5): cv.percentage,
cv.Optional(CONF_VOLUME_MAX, default=1.0): cv.percentage,
@@ -317,12 +315,12 @@ FINAL_VALIDATE_SCHEMA = cv.All(
},
extra=cv.ALLOW_EXTRA,
),
_validate_supported_local_file,
_final_validate,
)
async def to_code(config):
if config[CONF_CODEC_SUPPORT_ENABLED]:
if CORE.data[DOMAIN][config[CONF_ID].id][CONF_CODEC_SUPPORT_ENABLED]:
# Compile all supported audio codecs and optimize the wifi settings
cg.add_define("USE_AUDIO_FLAC_SUPPORT", True)
@@ -352,8 +350,8 @@ async def to_code(config):
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
cg.add(var.set_task_stack_in_psram(config[CONF_TASK_STACK_IN_PSRAM]))
if config[CONF_TASK_STACK_IN_PSRAM]:
if config.get(CONF_TASK_STACK_IN_PSRAM):
cg.add(var.set_task_stack_in_psram(True))
esp32.add_idf_sdkconfig_option(
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
)

View File

@@ -34,6 +34,12 @@ SUPPORTED_PSRAM_VARIANTS = [
VARIANT_ESP32S3,
VARIANT_ESP32P4,
]
SUPPORTED_PSRAM_MODES = {
VARIANT_ESP32: ["quad"],
VARIANT_ESP32S2: ["quad"],
VARIANT_ESP32S3: ["quad", "octal"],
VARIANT_ESP32P4: ["hex"],
}
@pytest.mark.parametrize(
@@ -86,7 +92,7 @@ def test_psram_configuration_valid_supported_variants(
from esphome.components.psram import CONFIG_SCHEMA, FINAL_VALIDATE_SCHEMA
# This should not raise an exception
config = CONFIG_SCHEMA({})
config = CONFIG_SCHEMA({"mode": SUPPORTED_PSRAM_MODES[variant][0]})
FINAL_VALIDATE_SCHEMA(config)
@@ -122,7 +128,7 @@ def _setup_psram_final_validation_test(
("config", "esp32_config", "expect_error", "error_match"),
[
pytest.param(
{"speed": "120MHz"},
{"mode": "quad", "speed": "120MHz"},
{"cpu_frequency": "160MHz"},
True,
r"PSRAM 120MHz requires 240MHz CPU frequency",
@@ -143,7 +149,7 @@ def _setup_psram_final_validation_test(
id="ecc_only_in_octal_mode",
),
pytest.param(
{"speed": "120MHZ"},
{"mode": "quad", "speed": "120MHZ"},
{"cpu_frequency": "240MHZ"},
False,
None,

View File

@@ -1,4 +1,7 @@
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
psram:
mode: quad
<<: !include common.yaml

View File

@@ -9,3 +9,4 @@ display:
lvgl:
psram:
mode: quad

View File

@@ -1,4 +1,6 @@
# I2C bus for camera sensor
psram:
i2c:
- id: i2c_camera_bus
sda: 25