Skip to content
Open
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
46 changes: 46 additions & 0 deletions .github/workflows/build-jc-esp32p4-m3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Build JC ESP32-P4 M3 firmware

on:
workflow_dispatch:
pull_request:
branches:
- main
push:
branches:
- agent/jc-esp32p4-m3-audio

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Build firmware
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: v6.1-beta1
target: esp32p4
path: esp32
command: python ../moondeck/build/build_esp32.py --firmware esp32p4-jc-m3-eth --release jc-m3-test

- name: Stage flash package
run: |
set -euo pipefail
B=build/esp32-esp32p4-jc-m3-eth
mkdir -p dist/bootloader dist/partition_table
cp "$B/projectMM.bin" dist/
cp "$B/bootloader/bootloader.bin" dist/bootloader/
cp "$B/partition_table/partition-table.bin" dist/partition_table/
cp "$B/ota_data_initial.bin" dist/
cp "$B/flasher_args.json" dist/
cp "$B/flash_args" dist/

- uses: actions/upload-artifact@v4
with:
name: projectMM-esp32p4-jc-m3-eth
path: dist/
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ Three distinct things, kept distinct in the vocabulary:
- **deviceModel** — the whole assembled product, identified by its catalog name (`Olimex ESP32-Gateway Rev G`). This is *which hardware this is*. It is distinct from **`deviceName`**, *which individual unit this is* (per-unit identity the user sets — see [§ Device name](#device-name-one-identity-every-network-name-derives-from-it)); a **device** (the umbrella term) has a `deviceName` and a `deviceModel`.
- **board** — the bare PCB *only*. The word survives in its literal sense: **on-board** LED, **on-board** peripherals, board-soldered pins — things physically *on the PCB*. (A deviceModel is a board plus whatever is wired onto it.)

**Firmware** is the compiled binary: chip target plus which radios/peripherals/sdkconfig fragments are included. Today's variants: `esp32` (classic, WiFi **and** RMII Ethernet in one binary — Ethernet comes up only when a PHY is present, pins/PHY per deviceModel), `esp32-eth` (classic, Ethernet only, WiFi excluded), `esp32-16mb` (classic with 16 MB flash, WiFi + Ethernet), `esp32s3-n16r8` / `esp32s3-n8r8` (S3 with WiFi + W5500 SPI Ethernet), `esp32p4-eth` (Waveshare ESP32-P4-NANO, Ethernet only), `esp32p4-eth-wifi` (the same P4 hardware with WiFi via its on-board ESP32-C6 over esp_hosted). Each chip's firmware carries the Ethernet *driver(s)* it can host (RMII EMAC for classic/P4, W5500 SPI for S3); which PHY/pins a deviceModel uses is runtime config. Selected by `build_esp32.py --firmware <key>`, reported by `SystemModule.firmware`, used as the contract target key in scenarios.
**Firmware** is the compiled binary: chip target plus which radios/peripherals/sdkconfig fragments are included. Today's variants: `esp32` (classic, WiFi **and** RMII Ethernet in one binary; Ethernet comes up only when a PHY is present, pins/PHY per deviceModel), `esp32-eth` (classic, Ethernet only, WiFi excluded), `esp32-16mb` (classic with 16 MB flash, WiFi + Ethernet), `esp32s3-n16r8` / `esp32s3-n8r8` (S3 with WiFi + W5500 SPI Ethernet), `esp32p4-eth` (Waveshare ESP32-P4-NANO, Ethernet only), `esp32p4-jc-m3-eth` (JC-ESP32P4-M3-DEV, Ethernet + ES8311 codec), `esp32p4-eth-wifi` (the P4-NANO with WiFi via its on-board ESP32-C6 over esp_hosted), and `esp32s31` (S31 CoreBoard with WiFi, RGMII Ethernet, and ES8311 codec). Each chip's firmware carries the Ethernet *driver(s)* it can host (RMII EMAC for classic/P4, W5500 SPI for S3); which PHY/pins a deviceModel uses is runtime config. Selected by `build_esp32.py --firmware <key>`, reported by SystemModule's `firmware` control, used as the contract target key in scenarios.

**deviceModel** is the physical hardware: chip + PCB + on-board peripherals (PHY, USB-serial, PSRAM, antenna), identified by its product name. Examples: `Olimex ESP32-Gateway Rev G`, `LOLIN D32`, `Generic ESP32 Dev`. A unit cannot identify its own deviceModel (no readable PCB ID on classic ESP32), so MoonDeck deduces it from the firmware where unambiguous (`esp32-eth*` ⇒ Olimex) and otherwise lets the user pick. It is stored on the unit as SystemModule's `deviceModel` Text control (display-only in the UI; HTTP `/api/control` writes still apply). MoonDeck mirrors the picked / deduced value to the unit via `POST /api/control` after each discover and after every dropdown change. The catalog of valid deviceModels lives at [web-installer/deviceModels.json](../web-installer/deviceModels.json), shared between MoonDeck and the web installer: MoonDeck reads it for its dropdown and HTTP push (plain REST on the LAN); the web installer reads it for its picker and pushes the whole entry — deviceModel plus every module/control — over serial during provisioning as REST ops (**"Improv = REST over serial"**, the `APPLY_OP` vendor RPC; see [ImprovProvisioningModule.md](moonmodules/core/moxygen/ImprovProvisioningModule.md)). Pushing over serial sidesteps the mixed-content block that stops an HTTPS installer page from POSTing to an `http://` device; an already-running device is re-configured via MoonDeck on the LAN.

Expand Down
Binary file added docs/assets/deviceModels/jc-esp32p4-m3-dev.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions docs/history/plans/Plan-20260726 - JC ESP32-P4 M3 audio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# JC ESP32-P4 M3 audio

## Goal

Create a dedicated firmware and device-model configuration for the JC-ESP32P4-M3-DEV so its onboard ES8311 microphone feeds AudioService and the audio-reactive effects reliably.

## Hardware contract

- ES8311 control address: `0x18`.
- I2C: SDA GPIO7, SCL GPIO8.
- I2S: BCLK GPIO12, WS GPIO10, MCLK GPIO13.
- Mic data defaults to GPIO48 from the published schematic. GPIO11 remains testable through AudioService's live `sdPin` control because the supplied ESPHome example disagrees with the schematic.
- Default sample rate: 22,050 Hz.

## Implementation

1. Add an `esp32p4-jc-m3-eth` firmware variant based on the existing P4 configuration.
2. Extend the existing Espressif `esp_codec_dev` ES8311 platform path to the JC P4 target.
3. Configure record-only ES8311 operation with a 256× MCLK ratio and a 32-bit I2S word matching the receive channel.
4. Add the JC device model with its audio pins so audio-reactive effects can consume the local source.
5. Add a hardware reference page and focused manifest/configuration tests.
6. Build the desktop target, run focused checks, and build the JC firmware.

## Validation boundary

The build and host-side checks prove the configuration and code paths. The product owner verifies real capture on the JC board, first with GPIO48 and then GPIO11 if GPIO48 produces silence.
51 changes: 51 additions & 0 deletions docs/reference/jc-esp32p4-m3-dev.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# JC-ESP32P4-M3-DEV hardware reference

Audio and network wiring for the Guition **JC-ESP32P4-M3-DEV**. The dedicated projectMM firmware is `esp32p4-jc-m3-eth`.

![JC-ESP32P4-M3-DEV](../assets/deviceModels/jc-esp32p4-m3-dev.jpg)

## Sources

- [Community hardware repository](https://github.com/p1ngb4ck/unofficial_guition_esp32p4_repo/tree/main/JC-ESP32P4-M3-Dev)
- [ES8311 product brief](https://www.everest-semi.com/pdf/ES8311%20PB.pdf)
- [Espressif `esp_codec_dev`](https://components.espressif.com/components/espressif/esp_codec_dev)

## Audio

The onboard analog microphone connects to an ES8311 mono codec. The P4 is the I2S master and drives MCLK, BCLK, and WS. The codec control bus responds at `0x18` with SDA GPIO7 and SCL GPIO8.

| Signal | GPIO | Role |
|---|---:|---|
| I2C SDA | 7 | ES8311 register control |
| I2C SCL | 8 | ES8311 register control |
| I2S WS / LRCLK | 10 | word select |
| I2S BCLK | 12 | bit clock |
| I2S MCLK | 13 | master clock, 256 × sample rate |
| I2S ADC data | 48 | codec microphone data to the P4, schematic mapping |
| I2S DAC data | 9 | P4 playback data to the codec |
| PA control | 53 | speaker amplifier enable |

The supplied ESPHome example assigns microphone data to GPIO11 and uses 16-bit samples. The published schematic routes the codec's `ES7210_SDOUT` net to GPIO48. ProjectMM defaults `sdPin` to GPIO48 because it follows the schematic, while the AudioService control stays editable live so GPIO11 can be tested without reflashing.

The codec path explicitly selects its analog microphone input and converts the scanned 7-bit address (`0x18`) to the 8-bit wire-address form expected by `esp_codec_dev`. The codec and receive channel use a 32-bit standard-I2S word. This keeps the ES8311 ADC result aligned with the `int32_t` sample path used by AudioService. The default 22,050 Hz sample rate yields a Nyquist frequency of 11,025 Hz and keeps a 512-sample analysis block near 23 ms.

## Ethernet

The board uses the P4 on-chip EMAC with an IP101-compatible RMII PHY, matching the common P4 Ethernet configuration:

| Signal | GPIO |
|---|---:|
| MDC | 31 |
| MDIO | 52 |
| PHY reset / power | 51 |
| RMII reference clock input | 50 |

The device-model entry supplies these runtime values to NetworkModule.

## Hardware validation

1. Flash `esp32p4-jc-m3-eth` and apply the `JC-ESP32P4-M3-DEV` device model.
2. Confirm Audio reports no codec initialization error.
3. Play music near the onboard microphone and inspect `level RMS`, `peakHz`, and an audio-reactive effect.
4. If the readings stay silent, change Audio `sdPin` from 48 to 11. The I2S channel rebuilds immediately.
5. To recheck the control bus, disable or remove Audio before running I2cScan because the active codec owns I2C port 0.
8 changes: 8 additions & 0 deletions esp32/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,12 @@ menu "projectMM"
path on an eth-only build (link up but no IP). Only the WiFi build
(esp32p4-eth-wifi) sets this, via sdkconfig.defaults.esp32p4-eth-wifi.

config MM_JC_P4_M3
bool "JC-ESP32P4-M3-DEV: include the on-board ES8311 microphone codec"
default n
help
Selects the JC board's ES8311 control wiring and pulls the
esp_codec_dev managed component. The dedicated esp32p4-jc-m3-eth
firmware sets this via sdkconfig.defaults.esp32p4-jc-m3.

endmenu
22 changes: 12 additions & 10 deletions esp32/main/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,20 @@ dependencies:
# only from platform_esp32_i2s.cpp's audioFft.
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/building.md § ESP-IDF version).
# esp_codec_dev: Espressif's audio-codec driver library; we use its ES8311
# part (es8311_codec_new) for the S31 CoreBoard and the dedicated
# JC-ESP32P4-M3-DEV firmware. Both put an analog mic behind an ES8311 at 0x18.
# Keep the validated minor line stable: its high-level device API requires a
# data interface, while ProjectMM intentionally uses the codec interface
# directly because its I2S channel is owned by the platform audio path.
# `matches` is the component-manager OR form: include the dependency for the
# S31 target or when the JC firmware's Kconfig flag is set, while other targets
# avoid resolving a component they never compile.
espressif/esp_codec_dev:
version: "^1.3.0"
rules:
version: "~1.6.2"
matches:
- if: "target == esp32s31"
- if: "$CONFIG{MM_JC_P4_M3} == 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
6 changes: 6 additions & 0 deletions esp32/sdkconfig.defaults.esp32p4-jc-m3
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# JC-ESP32P4-M3-DEV board features layered onto the common P4 configuration.
#
# The board has an ES8311 codec at I2C address 0x18. The Kconfig symbol selects
# its fixed control/MCLK wiring in platform_config.h and includes Espressif's
# esp_codec_dev managed component through main/idf_component.yml.
CONFIG_MM_JC_P4_M3=y
9 changes: 9 additions & 0 deletions moondeck/build/build_esp32.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ def check_idf_pin(idf_path: Path) -> None:
"WiFi-less fallback; esp32p4-eth-wifi adds the C6 radio.",
"ships": True,
},
"esp32p4-jc-m3-eth": {
"chip": "esp32p4",
"fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32p4-eth",
"sdkconfig.defaults.esp32p4-jc-m3"],
"eth_only": True,
"description": "JC-ESP32P4-M3-DEV with Ethernet + on-board ES8311 microphone "
"(I2C 0x18, dedicated codec configuration).",
"ships": True,
},
"esp32p4-eth-wifi": {
"chip": "esp32p4",
"fragments": ["sdkconfig.defaults", "sdkconfig.defaults.esp32p4-eth",
Expand Down
17 changes: 9 additions & 8 deletions src/platform/esp32/platform_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,26 +141,27 @@ constexpr bool hasI2sMic = false;


// Some boards put the mic behind an I2S audio codec configured over I2C (vs a
// direct I2S MEMS mic). The codec type + its control pins are a fixed board
// property, so they live here per-target (like ethConfigDefault), not as
// AudioService controls — the I2S data pins (ws/sd/sck) stay user controls.
// direct I2S MEMS mic). The codec type + its control pins are fixed by the
// firmware's hardware target, so they live here (like ethConfigDefault), not as
// AudioService controls. The I2S data pins (ws/sd/sck) stay user controls.
// `audioCodecInit` (platform.h) consumes these; CodecType is neutral so a second
// codec is just another enum value + a backend branch.
enum class CodecType : uint8_t { None = 0, Es8311 = 1 };
struct AudioCodecPins {
uint16_t i2cSda;
uint16_t i2cScl;
uint16_t mclk; // I2S master clock the codec needs (separate from BCLK/WS)
uint8_t i2cAddr; // codec I2C address (ES8311 default 0x18)
uint8_t i2cAddr; // 7-bit codec I2C address (ES8311 default 0x18)
};

// Default None; the ESP32-S31 Function-CoreBoard has an ES8311 (addr 0x18, I2C
// SDA on GPIO51 / SCL on GPIO50, MCLK on GPIO52 — bench-confirmed by I2C scan; the
// schematic net labels read SDA/SCL the other way round. See
// docs/reference/esp32-s31-coreboard.md.).
// The ESP32-S31 Function-CoreBoard and the dedicated JC-ESP32P4-M3-DEV firmware
// carry an ES8311. Other firmware variants keep the direct-mic/no-codec path.
#ifdef CONFIG_IDF_TARGET_ESP32S31
constexpr CodecType audioCodecType = CodecType::Es8311;
constexpr AudioCodecPins audioCodecPins = { /*sda*/ 51, /*scl*/ 50, /*mclk*/ 52, /*addr*/ 0x18 };
#elif defined(CONFIG_MM_JC_P4_M3)
constexpr CodecType audioCodecType = CodecType::Es8311;
constexpr AudioCodecPins audioCodecPins = { /*sda*/ 7, /*scl*/ 8, /*mclk*/ 13, /*addr*/ 0x18 };
#else
constexpr CodecType audioCodecType = CodecType::None;
constexpr AudioCodecPins audioCodecPins = { 0, 0, 0, 0 };
Expand Down
Loading