Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/reference/hardware/esp32-p4-eth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Waveshare ESP32-P4-ETH hardware reference

Pin maps and onboard features for the **Waveshare ESP32-P4-ETH**, read from the official schematic so MoonLight work (Ethernet, audio) reads this instead of re-scraping the PDF. The board is `esp32p4rev1-eth-es8311` in firmware, `"Waveshare ESP32-P4-ETH"` in the device catalog.

**Sources**
- Product page: <https://www.waveshare.com/esp32-p4-eth.htm>
- Wiki: <http://www.waveshare.com/wiki/ESP32-P4-ETH>
- Schematic (local): `datasheet/ESP32-P4-ETH/ESP32-P4-ETH-datasheet.pdf`

Same family as the Waveshare ESP32-P4-NANO (see [gpio-usage.md § ESP32-P4](gpio-usage.md#esp32-p4) for its pin map): ESP32-P4 (RISC-V dual-core), same onboard IP101 Ethernet PHY wiring, same CSI/DSI/I2C accessory-bus convention. This board adds an onboard **ES8311** audio codec feeding an SMD mic + a 2-pin JST speaker header, which the P4-NANO lacks. The [MHC-WLED ESP32-P4 shield](mhc-wled-esp32-p4-shield.md) is a different, unrelated P4 carrier board (RS-485/line-in, no onboard codec).

## Audio (ES8311 codec)

The onboard SMD mic and speaker header connect through an **ES8311 mono codec**. The ESP is the I2S master (it drives MCLK).

| Signal | GPIO | Direction / role |
|---|---|---|
| I2S MCLK | 13 | master clock → codec (ESP is I2S master; MCLK = 256 × sample_rate) |
| I2S SCLK (BCLK) | 12 | bit clock |
| I2S LRCK (WS) | 10 | word select |
| I2S ASDOUT | 11 | **mic / ADC data: codec → ESP** (the record path) |
| I2S DSDIN | 9 | playback / DAC data: ESP → codec (speaker path, **not wired up** — out of scope, mic-only) |
| **ESP_I2C_SDA** | **7** | codec control bus (shared with the CSI/DSI accessory headers) |
| **ESP_I2C_SCL** | **8** | codec control bus |
| PA_Ctrl | 53 | speaker amp enable (not wired up — speaker is out of scope) |

- **ES8311 I2C address: `0x18`** (the hardware default; no address-select strap).
- Driven by Espressif's **`esp_codec_dev`** managed component (same driver as the ESP32-S31's ES8311), gated behind the `CONFIG_MM_P4_ES8311` Kconfig option (set only by the `esp32p4rev1-eth-es8311` firmware fragment `sdkconfig.defaults.esp32p4-es8311`) — most other P4 boards share this chip target but have no codec on the I2C bus, so the codec bring-up can't be a chip-wide default: `audioCodecType`/`audioCodecPins` are compile-time constants, and once non-`None`, `AudioService::reinit()` unconditionally probes the codec over I2C and fails the whole mic path if nothing answers.
- The codec needs **MCLK running before it answers I2C**; `AudioService::reinit()` brings up the I2S channel (which drives MCLK) before the codec I2C config, same ordering as the S31.
- **Bench-verified end to end** 2026-10-03: Ethernet link + DHCP, `I2cScanModule` ACKs the codec at 0x18 on GPIO7/8, and `AudioService` reports live mic data (non-zero level RMS, onsets, peak Hz) with `mode="local audio"`, `micMode="I2S"`, `sckPin=12`, `wsPin=10`, `sdPin=11` (the catalog entry's defaults).
- **Two bugs found and fixed in `platform_esp32_es8311.cpp`** during that bench pass (both apply to the S31's codec path too, same file, not P4-ETH-specific):
1. `esp_codec_dev`'s I2C control layer right-shifts the address it's given by one bit before handing it to the IDF `i2c_master` driver (`audio_codec_ctrl_i2c.c`: `.device_address = (i2c_cfg->addr >> 1)`) — it wants the 8-bit write-address form, not the bare 7-bit address a scan/probe uses. Passing `0x18` straight through silently targeted `0x0C`: every register write NACK'd (`ES8311: Open fail`) even though a raw `i2c_master_probe` at `0x18` ACKs cleanly (that path doesn't shift). Fixed by shifting at the one call site that needs it; `AudioCodecPins.i2cAddr` itself stays the natural 7-bit value everywhere else.
2. `esp_codec_dev_new` requires a non-null `data_if`, which the code never set (by design — `audioMicInit` owns the real I2S RX channel, not `esp_codec_dev`). Fixed with a `data_if` whose rx/tx handles stay null on purpose; its `set_fmt`/`enable` calls log harmless `NULL` warnings each re-init (`i2s_channel_get_info: input parameter 'handle' is NULL`, etc.) since `esp_codec_dev_open` discards their return value — expected noise, not a fault.
- **Mic-only path** (audio-reactive input): all pins above confirmed independently from the schematic netlist *and* the Waveshare wiki (which separately publishes the same I2C/I2S pin set). The speaker path (DSDIN + PA_Ctrl) is a separate capability, not implemented here.

## Ethernet (IP101 PHY, RMII)

On-chip EMAC → **IP101GRI** PHY → RJ45, **RMII** with a 25 MHz crystal, externally-fed 50 MHz reference clock. Same reference design as the Waveshare P4-NANO — confirmed pin-for-pin identical:

| Signal | GPIO | Source |
|---|---|---|
| MDC | 31 | schematic netlist (unambiguous two-pin net, matches the P4 pin table) |
| MDIO | 52 | schematic netlist (same) |
| PHY reset | 51 | direct schematic inspection (the flattened PDF text extraction was ambiguous here; confirmed by eye against the drawing) |
| RMII ref clock (external in) | 50 | carried over from the P4-NANO preset, consistent with the other three now-confirmed pins |

Catalog entry: `NetworkModule.ethBoard = "ESP32-P4-ETH"`, its own `kEthPresets` row in `NetworkModule.h` (not a silent alias of `"P4-NANO"`) — the pins read identical today, but a future correction to one board's preset shouldn't silently move the other.

## Other onboard features (not wired up by this branch)

From the Waveshare wiki: MIPI-CSI (2-lane, OV5647-compatible), MIPI-DSI (2-lane, 5"/7"/8"/10.1" panels), SDIO 3.0 microSD slot, USB-C (native + UART bridge), BOOT/RESET buttons. None of these conflict with the audio (GPIO 7-13) or Ethernet (GPIO 28-31/49-52) pins above.

## Free GPIO for user peripherals (LED strips)

Following the P4-NANO's own reference set (`ParallelLedDriver` default `pins="20,21,22,23,24,25,26,27"`), with this board's audio pins added to the committed set:

| Peripheral | GPIOs |
|---|---|
| Ethernet RMII (fixed silicon pads) | 28-31, 49-52 |
| ES8311 audio (I²S + I²C) | 7-13 |
| CSI/DSI accessory headers | share the I2C bus (7-8) plus their own dedicated MIPI diff-pairs |

**Clear, following the P4-NANO's documented set**: 20-27, 32-33, 39-48 (the catalog's `ParallelLedDriver` default is `pins="20,21,22,23,24,25,26,27"`, same as the P4-NANO). GPIO 20/21 also appear as spare IO on this board's CSI header (same situation as the P4-NANO, which treats them as free LED pins regardless) — only relevant if a camera module is actually plugged in. See [gpio-usage.md § ESP32-P4](gpio-usage.md#esp32-p4) for the general P4 pin map this board extends.
1 change: 1 addition & 0 deletions docs/reference/hardware/esp32-s31-coreboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The onboard electret mic (J6) and speaker connect through an **ES8311 mono codec
needs **MCLK running before it answers I2C**, and `es8311_codec_cfg.mclk_div` must be set (256, the standard ratio) or `open` fails "unable to configure sample rate". So AudioService brings up the I2S channel (which drives MCLK on GPIO52) **before** the codec I2C config.
- **Mic-only path** (audio-reactive input) needs MCLK/SCLK/LRCK + ASDOUT (record) + the I2C bus.
The speaker path (DSDIN + PA_CTRL) is output, a separate capability.
- **Two codec-bring-up bugs fixed 2026-10-03** (found bench-testing the [ESP32-P4-ETH](esp32-p4-eth.md), which shares this exact code path): `esp_codec_dev`'s I2C control layer right-shifts the address it's given by one bit, so the raw 7-bit `0x18` must be passed pre-shifted (`<<1`) — the bare 7-bit value silently targeted `0x0C` and every register write NACK'd even though a scan at `0x18` ACKs cleanly (that path doesn't shift). And `esp_codec_dev_new` requires a non-null `data_if`, which the code never set. Both are now fixed in `platform_esp32_es8311.cpp`, so a full codec-open bench pass on the S31 (not just the I2C scan referenced above) is worth re-running before the `Audio` capability stays under `planned`.

## Ethernet (YT8531 PHY, RGMII, 1 Gbps)

Expand Down
1 change: 1 addition & 0 deletions docs/reference/hardware/firmware-variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Selected by `build_esp32.py --firmware <key>`, reported by `SystemModule.firmwar
| `esp32s3-n16r8`, `esp32s3-n8r8` | S3 | WiFi and W5500 SPI Ethernet |
| `esp32p4rev1-eth` | P4 rev 1 (Waveshare ESP32-P4-NANO) | Ethernet only |
| `esp32p4rev1-eth-wifi` | P4 rev 1 | Ethernet, plus WiFi via the on-board ESP32-C6 over esp_hosted |
| `esp32p4rev1-eth-es8311` | P4 rev 1 (Waveshare ESP32-P4-ETH) | Ethernet only, plus the board's onboard ES8311 audio codec (see [esp32-p4-eth.md](esp32-p4-eth.md)) |
| `esp32p4rev3-eth`, `esp32p4rev3-eth-wifi` | P4 rev 3 silicon | As rev 1. Rev 3 is not binary-compatible with rev 1; untested, no v3 board on the bench |

## Scaling to available memory
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/hardware/gpio-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ For **LED output** specifically — the pins a WS2812-class strand data line can

**Clear on the P4-NANO:** 20-27, 32-33, 39-48 (the LED-driver default is `pins="20,21,22,23,24,25,26,27"`). Exact free set is board-specific; the NANO's is the reference. A carrier board changes what's exposed and how — the [MHC-WLED ESP32-P4 shield](mhc-wled-esp32-p4-shield.md) routes every terminal through level shifters / RS-485 transceivers / protected inputs (no bare GPIO), which is why a direct loopback jumper fails on it; its full terminal map is on that page.

**The Waveshare ESP32-P4-ETH** follows the NANO's Ethernet wiring exactly (RMII 28-31/49-52) but additionally commits **GPIO 7-13** to its onboard ES8311 audio codec (I2C 7-8 + I2S 9-13), beyond the I2C-only use the NANO makes of 7-8. Full pin map: [esp32-p4-eth.md](esp32-p4-eth.md).

## ESP32-S31

[Datasheet](https://documentation.espressif.com/esp32-s31_datasheet_en.pdf). RISC-V, Wi-Fi 6 + on-chip 1 Gbps EMAC; shares the MoonLive RISC-V backend with the P4. The bench board's full schematic pin map (Ethernet RGMII, ES8311 audio codec, SD, USB-host) is on its own page: [ESP32-S31 coreboard](esp32-s31-coreboard.md).
Expand Down
16 changes: 16 additions & 0 deletions esp32/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,20 @@ menu "MoonLight"
the same HlsDriver the desktop uses. Only the P4 has the encoder
silicon; other chips keep this off and build nothing.

config MM_P4_ES8311
bool "ESP32-P4: onboard ES8311 audio codec (Waveshare ESP32-P4-ETH)"
default n
help
Pulls the esp_codec_dev managed component and enables the ES8311
codec bring-up (I2C on GPIO7/8, I2S on GPIO9-13) for a P4 board
that actually carries the codec, e.g. the Waveshare ESP32-P4-ETH.

OFF by default: most P4 boards (the P4-NANO, the MHC-WLED shield,
the MM testbench P4's direct bench mic) have no codec on that I2C
bus, and AudioService's codec bring-up is unconditional once
audioCodecType is non-None — it would probe for an ES8311 that
nothing answers and fail the whole mic path. Only the
esp32p4rev1-eth-es8311 build sets this, via
sdkconfig.defaults.esp32p4-es8311.

endmenu
33 changes: 25 additions & 8 deletions esp32/main/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,34 @@ dependencies:
espressif/esp-dsp:
version: "^1.5.0"
# esp_codec_dev — Espressif's audio-codec driver library; we use its ES8311 part
# (es8311_codec_new) to configure the ESP32-S31 CoreBoard's onboard mic, which is
# an analog mic behind an ES8311 I2S codec (I2C addr 0x18) rather than a direct
# I2S MEMS mic. Referenced only from platform_esp32_es8311.cpp, behind the codec
# gate. The `rules` gate scopes it to the S31 — the only board with the codec —
# so other targets don't resolve a component they never compile (same pattern as
# ip101/w5500 above). A managed component outside mainline v6.0, like the P4
# esp_hosted exception (docs/how-to/building.md § ESP-IDF version).
# (es8311_codec_new) to configure a board's onboard mic where it is an analog mic
# behind an ES8311 I2S codec (I2C addr 0x18) rather than a direct I2S MEMS mic.
# Referenced only from platform_esp32_es8311.cpp, behind the codec gate. Two
# boards carry the codec today: the ESP32-S31 CoreBoard (unconditionally, it is
# the only S31 board) and the Waveshare ESP32-P4-ETH (MM_P4_ES8311, since most P4
# boards have no codec on that bus — see Kconfig.projbuild). The `rules` gate
# scopes the dependency to those two cases so other targets/builds don't resolve
# a component they never compile (same pattern as ip101/w5500 above). ONE `if`
# with `||` — NOT two separate `rules` entries: the component manager ANDs
# multiple `rules` entries together (idf_component_tools/manifest/models.py,
# OptionalRequirement.version_spec_if_meet_conditions: "AND all the `rules`"),
# so `target == esp32s31` plus a separate `$CONFIG{...}` rule would require BOTH
# at once — impossible — and silently skip the dependency on every build
# ("NOTE: Skipping optional dependency: espressif/esp_codec_dev"), which is
# exactly what happened here first (bench-caught on the P4-ETH: the codec
# answered on the I2C scan but AudioService's own bring-up failed immediately,
# "codec init failed" — the real implementation never got linked in because
# `__has_include("esp_codec_dev.h")` was false, so platform_esp32_es8311.cpp
# built its inert stub, with no managed_components/espressif__esp_codec_dev/ in
# the build dir, and the "NOTE: Skipping" line confirmed it). `||` is the
# if-clause grammar's OR (idf_component_tools/manifest/if_parser.py uses `&&`/
# `||`, not `and`/`or`), combining both conditions in the one rule that must
# match. A managed component outside mainline v6.0, like the P4 esp_hosted
# exception (docs/how-to/building.md § ESP-IDF version).
espressif/esp_codec_dev:
version: "^1.3.0"
rules:
- if: "target == esp32s31"
- if: "target == esp32s31 || $CONFIG{MM_P4_ES8311} == True"
# esp_wifi_remote + esp_hosted — WiFi for the ESP32-P4 via the on-board ESP32-C6
# co-processor over SDIO. The P4 has no native radio; these present the C6's
# radio through the standard esp_wifi_* API (API-compatible, so the WiFi seam in
Expand Down
16 changes: 16 additions & 0 deletions esp32/sdkconfig.defaults.esp32p4-es8311
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Waveshare ESP32-P4-ETH: onboard ES8311 audio codec.
# Board: https://www.waveshare.com/esp32-p4-eth.htm
# Wiki: http://www.waveshare.com/wiki/ESP32-P4-ETH
# Schematic: datasheet/ESP32-P4-ETH/ESP32-P4-ETH-datasheet.pdf
#
# Layered onto sdkconfig.defaults + sdkconfig.defaults.esp32p4rev1-eth by
# moondeck/build/build_esp32.py. The eth fragment brings the EMAC / RMII / flash /
# PSRAM / partition layout; this fragment adds the onboard codec.
#
# Sets the Kconfig symbol declared in main/Kconfig.projbuild, which
# main/idf_component.yml reads to pull the esp_codec_dev managed component, and
# which src/platform/esp32/platform_config.h reads to set audioCodecType/
# audioCodecPins to the ES8311 on I2C GPIO7/8 + I2S GPIO9-13. See MM_P4_ES8311's
# own help text for why this is a separate opt-in rather than a P4-wide default:
# most other P4 boards have no codec on that bus.
CONFIG_MM_P4_ES8311=y
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ nav:
- Firmware variants and memory: reference/hardware/firmware-variants.md
- GPIO usage per MCU: reference/hardware/gpio-usage.md
- ESP32-S31 coreboard: reference/hardware/esp32-s31-coreboard.md
- Waveshare ESP32-P4-ETH: reference/hardware/esp32-p4-eth.md
- MHC-WLED ESP32-P4 shield: reference/hardware/mhc-wled-esp32-p4-shield.md
- Light fixtures and DMX nodes: reference/hardware/light-fixtures.md
- Control surfaces: reference/hardware/control-surfaces.md
Expand Down
15 changes: 15 additions & 0 deletions moondeck/build/build_esp32.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,21 @@ def check_idf_pin(idf_path: Path) -> None:
"ships": True,
"panel_cards": True,
},
"esp32p4rev1-eth-es8311": {
"chip": "esp32p4",
"fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32p4rev1-eth",
"sdkconfig.defaults.esp32p4-es8311"],
"eth_only": True,
"description": "Waveshare ESP32-P4-ETH — Ethernet only (IP101 PHY, same "
"pins as the P4-NANO) plus the board's onboard ES8311 audio "
"codec (I2C GPIO7/8, I2S GPIO9-13), for P4 revisions 0.x/1.x "
"ONLY. No WiFi combo yet (no on-board C6 co-processor "
"question has been resolved for this board); follows the "
"same fragment-stacking pattern as esp32p4rev1-eth-wifi if "
"one is added later.",
"ships": True,
"panel_cards": True,
},
"esp32p4rev3-eth": {
"chip": "esp32p4",
"fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32p4rev1-eth",
Expand Down
63 changes: 63 additions & 0 deletions mooninstaller/deviceModels.json
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,69 @@
}
]
},
{
"name": "Waveshare ESP32-P4-ETH",
"chip": "ESP32-P4",
"firmwares": [
"esp32p4rev1-eth-es8311"
],
"image": "assets/deviceModels/esp32-p4-eth.png",
"url": "https://www.waveshare.com/esp32-p4-eth.htm",
"supported": [
"LEDs",
"Ethernet",
"Audio"
],
"planned": [
"WiFi",
"Camera",
"Speaker"
],
"modules": [
{
"type": "System",
"id": "System",
"controls": {
"deviceModel": "Waveshare ESP32-P4-ETH"
}
},
{
"type": "ParallelLedDriver",
"id": "ParallelLed",
"parent_id": "Drivers",
"controls": {
"peripheral": "Parlio",
"pins": "20,21,22,23,24,25,26,27"
}
},
{
"type": "NetworkModule",
"id": "Network",
"controls": {
"ethBoard": "ESP32-P4-ETH"
}
},
{
"type": "AudioService",
"id": "Audio",
"parent_id": "Services",
"controls": {
"mode": 2,
"sckPin": 12,
"wsPin": 10,
"sdPin": 11
}
},
{
"type": "I2cScanModule",
"id": "I2cScan",
"controls": {
"sda": 7,
"scl": 8
}
}
]
},
{
"name": "MHC-WLED ESP32-P4 shield",
"chip": "ESP32-P4",
Expand Down
7 changes: 7 additions & 0 deletions mooninstaller/firmwares.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
"ships": true,
"description": "Waveshare ESP32-P4-NANO — Ethernet + WiFi via the on-board ESP32-C6 over SDIO (esp_hosted), for P4 revisions 0.x/1.x ONLY. Boots and associates as of IDF v6.1-rc1 with CONFIG_PM_SLEEP_CLK_ICG_ENABLE=n, which sidesteps esp-idf #18759 (sleep_clock_icg_startup_init failing ESP_ERR_NO_MEM and aborting cpu_start)."
},
{
"name": "esp32p4rev1-eth-es8311",
"chip": "esp32p4",
"eth_only": true,
"ships": true,
"description": "Waveshare ESP32-P4-ETH — Ethernet only (IP101 PHY, same pins as the P4-NANO) plus the board's onboard ES8311 audio codec (I2C GPIO7/8, I2S GPIO9-13), for P4 revisions 0.x/1.x ONLY. No WiFi combo yet (no on-board C6 co-processor question has been resolved for this board); follows the same fragment-stacking pattern as esp32p4rev1-eth-wifi if one is added later."
},
{
"name": "esp32p4rev3-eth",
"chip": "esp32p4",
Expand Down
5 changes: 5 additions & 0 deletions src/core/system/NetworkModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,11 @@ class NetworkModule : public MoonModule {
{"Classic RMII (no reset)", 1, 0, 23, 18, -1, 17, false, -1, -1, -1, -1, -1, false},
// Waveshare P4-NANO and the boards following its shield pinout: IP101, the clock fed in.
{"P4-NANO", 2, 1, 31, 52, 51, 50, true, -1, -1, -1, -1, -1, false},
// Waveshare ESP32-P4-ETH: same IP101 reference wiring as the P4-NANO (MDC/MDIO confirmed
// from the schematic netlist, reset/clock confirmed by direct schematic inspection), kept
// as its own entry rather than a silent alias so a future pin correction to one board
// can't also move the other.
{"ESP32-P4-ETH", 2, 1, 31, 52, 51, 50, true, -1, -1, -1, -1, -1, false},
// The S31's 1 Gb PHY, addressed by scan rather than by a fixed address.
{"S31 CoreBoard", 4, -1, 5, 6, 7, -1, false, -1, -1, -1, -1, -1, false},
{"Custom", 0, -1, -1, -1, -1, -1, false, -1, -1, -1, -1, -1, true},
Expand Down
Loading