[esp32] Fix S31 reserved pins (#18915)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Clyde Stubbs
2026-08-31 12:38:20 +00:00
committed by GitHub
co-authored by Claude Sonnet 5 Copilot Autofix powered by AI
parent 5e58b312e2
commit 2890afe0e5
3 changed files with 38 additions and 16 deletions
+8 -5
View File
@@ -5,11 +5,10 @@ import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
from esphome.pins import check_strapping_pin
# Per the ESP32-S31 datasheet, the SPI flash and PSRAM interfaces use
# dedicated package pins (SPICS/SPIQ/SPIWP/SPIHD/SPICLK/SPID) outside the
# GPIO matrix, so no GPIOs are reserved for them. GPIO29 and GPIO41 do not
# exist on this chip (SOC_GPIO_VALID_GPIO_MASK excludes them).
# https://documentation.espressif.com/esp32-s31_datasheet_en.html
# Per the ESP32-S31 IDF DOCS and datasheet:
# https://docs.espressif.com/projects/esp-idf/en/v6.1/esp32s31/api-reference/peripherals/gpio.html
# https://documentation.espressif.com/esp32-s31_datasheet_en.pdf
_ESP32S31_SPI_FLASH_PINS: set[int] = {26, 27, 28, 30, 31, 32}
_ESP32S31_INVALID_PINS: set[int] = {29, 41}
# GPIO60/GPIO61 set the boot mode; GPIO37 selects the JTAG signal source;
# GPIO36 sets the VDD_SPI voltage.
@@ -25,6 +24,10 @@ def esp32_s31_validate_gpio_pin(value: int) -> int:
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-61)")
if value in _ESP32S31_INVALID_PINS:
raise cv.Invalid(f"GPIO{value} does not exist on ESP32-S31.")
if value in _ESP32S31_SPI_FLASH_PINS:
raise cv.Invalid(
f"GPIO{value} is reserved for the SPI flash interface on ESP32-S31 and cannot be used."
)
return value
+15 -5
View File
@@ -1274,8 +1274,9 @@ def test_esp32_s31_gpio_validation(
set_core_config: SetCoreConfigCallable,
caplog: pytest.LogCaptureFixture,
) -> None:
"""S31: flash uses dedicated pins so GPIO27-33 are normal pins, GPIO29 and
GPIO41 do not exist, and GPIO36 is a strapping pin."""
"""S31: GPIO26-28/30-32 are reserved for the SPI flash interface, GPIO29
and GPIO41 do not exist, GPIO33 is a normal pin, and GPIO36 is a
strapping pin."""
from esphome.components.esp32.const import VARIANT_ESP32S31
from esphome.components.esp32.gpio import validate_supports
from esphome.const import CONF_INPUT, CONF_MODE, CONF_OPEN_DRAIN, CONF_OUTPUT
@@ -1286,9 +1287,18 @@ def test_esp32_s31_gpio_validation(
input_mode = {CONF_INPUT: True, CONF_OUTPUT: False, CONF_OPEN_DRAIN: False}
# Previously reserved for the flash interface, which uses dedicated pins
for num in (27, 28, 31, 32, 33):
pin = {CONF_NUMBER: num, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
# Not reserved; a normal GPIO
pin = {CONF_NUMBER: 33, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
assert validate_gpio_pin(pin)[CONF_NUMBER] == 33
# Reserved for the SPI flash interface, but can be bypassed with
# ignore_pin_validation_error
for num in (26, 27, 28, 30, 31, 32):
with pytest.raises(cv.Invalid, match=f"GPIO{num} is reserved"):
validate_gpio_pin(
{CONF_NUMBER: num, CONF_IGNORE_PIN_VALIDATION_ERROR: False}
)
pin = {CONF_NUMBER: num, CONF_IGNORE_PIN_VALIDATION_ERROR: True}
assert validate_gpio_pin(pin)[CONF_NUMBER] == num
for num in (29, 41):
@@ -144,17 +144,22 @@ def test_metadata_records_rotation(
@pytest.mark.parametrize(
("variant", "board"),
("variant", "board", "model"),
[
(VARIANT_ESP32S3, "esp32-s3-devkitc-1"),
(VARIANT_ESP32P4, "esp32-p4-evboard"),
# ESP32-8048S070 is a real Sunton board wired for ESP32-S3 (e.g. its
# default de_pin is GPIO41, which doesn't exist on S31), so it is
# only meaningful as a config on that variant.
(VARIANT_ESP32S3, "esp32-s3-devkitc-1", "ESP32-8048S070"),
# P4 and S31 use the pin-agnostic CUSTOM model so this only checks
# that the chip itself is accepted, independent of board wiring.
(VARIANT_ESP32P4, "esp32-p4-evboard", "CUSTOM"),
# No dedicated board is registered for ESP32-S31 yet; an unknown board
# name simply skips per-board pin validation.
(VARIANT_ESP32S31, "esp32-s31-devkitc"),
(VARIANT_ESP32S31, "esp32-s31-devkitc", "CUSTOM"),
],
)
def test_configuration_succeeds_on_supported_variants(
variant: str, board: str, set_core_config: SetCoreConfigCallable
variant: str, board: str, model: str, set_core_config: SetCoreConfigCallable
) -> None:
"""mipi_rgb requires a chip with an RGB LCD peripheral: S3, P4 or S31."""
set_core_config(
@@ -164,7 +169,11 @@ def test_configuration_succeeds_on_supported_variants(
from esphome.components.mipi_rgb.display import CONFIG_SCHEMA
CONFIG_SCHEMA({"model": "ESP32-8048S070", "data_pins": DATA_PINS, "pclk_pin": 21})
config = {"model": model, "data_pins": DATA_PINS, "pclk_pin": 21}
if model == "CUSTOM":
config[CONF_INIT_SEQUENCE] = [[0xA0, 0x01]]
config[CONF_DIMENSIONS] = {CONF_WIDTH: 480, CONF_HEIGHT: 480}
CONFIG_SCHEMA(config)
def test_only_on_variant_rejects_unsupported_variant(