diff --git a/firmware/README.md b/firmware/README.md index 9ccc7d1..0075cf1 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -12,7 +12,7 @@ For the *what* and *why* (architecture, MQTT contract, operating modes, signal c | --- | --- | | Cargo build + flash | ✅ working | | GPIO input (button G39) | ✅ working — debounced, active-low | -| I2S TX (16-bit / 44.1 kHz / stereo, Philips) | ✅ working into onboard NS4168 | +| I2S TX (16-bit / 44.1 kHz / stereo, Philips) | ✅ working into external MAX98357A | | Continuous pink noise generator (Paul Kellet IIR) | ✅ xorshift32 white → pink filter, volume-scaled | | Button state machine (short / long / double) | ✅ working | | NVS persistence (volume + direction + playing) | ✅ working | @@ -20,7 +20,7 @@ For the *what* and *why* (architecture, MQTT contract, operating modes, signal c | WiFi | ✅ working — STA, hostname `nightstand-`, auto-reconnect | | MQTT client + HA discovery | ✅ working — LWT, retained discovery on (re)connect, `cmd/play`/`cmd/volume`/`cmd/update` subscribed | | OTA updates | ✅ working — `esp_https_ota` chunked download with HA progress bar, two-slot rollback | -| Hardware: external MAX98357A + 1314 speaker | ❌ amps in transit; using onboard for now | +| Hardware: external MAX98357A + GRS 3FR-4 speaker | ✅ assembled on breadboard | ## Module layout @@ -195,7 +195,7 @@ new_std_tx(i2s, config, bclk, dout, mclk, ws) That's **`dout` before `ws`**, not `bclk → ws → dout` as you'd guess from reading "Bclk, Ws, Data" in audio convention. Getting them swapped produces: - "Static" instead of a tone (data on the LRCK line, clock on the data line) - A persistent quiet squeal during silence (WS clock ticking on the data input) -- The NS4168 amp running warm (sustained switching, no real audio output) +- The amp running warm (sustained switching, no real audio output) The compiler accepts both orderings since the pin types are generic. Always check the signature against the version of `esp-idf-hal` you're on. Symptom: garbage output where you'd expect tone. @@ -223,10 +223,6 @@ The ESP-IDF Kconfig docs imply the path is relative to your project. In an esp-i Despite working fine at the protocol level, `esp_https_ota` validates the URL scheme and refuses plain HTTP unless `CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y` is in sdkconfig. With the flag missing, `esp_https_ota_begin` returns immediately with an error and never opens a socket — webserver logs show no GET requests. The flag is set in `sdkconfig.defaults`. -### 8. The onboard NS4168 + tiny speaker is a prototype-only path - -We're using the I2S pins that drive the built-in amp (G19 BCLK, G22 DOUT, G33 LRCK) for hello-world. The plan ([signal-chain.md](../reference/signal-chain.md)) is to switch to an external MAX98357A on G21/G26/G32 once amps arrive. Don't run sustained white noise on the built-in speaker — short test bursts only. The thermal warning on this is real (we briefly demonstrated it via the swapped-pins bug above). - ## Layout ``` @@ -248,18 +244,18 @@ firmware/ └── .embuild/ # embuild-managed ESP-IDF clone (gitignored) ``` -## Hardware-pinout cheat sheet (Atom Echo, prototype config) +## Hardware-pinout cheat sheet (Atom Echo) | Pin | Function (this firmware) | Notes | | --- | --- | --- | | G39 | Button input | Active-low, on-board pull-up, input-only pin | -| G19 | I2S BCLK → NS4168 | Onboard amp, prototype-only | -| G22 | I2S DOUT → NS4168 | Onboard amp data | -| G33 | I2S WS/LRCK → NS4168 | Onboard amp word select | -| G27 | SK6812 RGB LED data | Not used yet | -| G26 | GROVE yellow / future I2S BCLK to MAX98357A | Free | -| G32 | GROVE white / future I2S WS to MAX98357A | Free | -| G21 | Side header / future I2S DOUT to MAX98357A | Free | +| G26 | I2S BCLK → MAX98357A | GROVE yellow | +| G21 | I2S DOUT → MAX98357A | Side header | +| G32 | I2S WS/LRCK → MAX98357A | GROVE white | +| G27 | SK6812 RGB LED data | On-board, top-face button cap | +| G19 | (reserved — onboard NS4168 BCLK) | Driven silent: no I2S routed here | +| G22 | (reserved — onboard NS4168 DOUT) | Driven silent: no I2S routed here | +| G33 | (reserved — onboard NS4168 LRCK / mic DATA) | Driven silent: no I2S routed here | | G25 | Side header / spare | Free | Authoritative pinout: [`../reference/atom-echo/pinmap.md`](../reference/atom-echo/pinmap.md). diff --git a/firmware/src/audio.rs b/firmware/src/audio.rs index 001acf1..6ca8c42 100644 --- a/firmware/src/audio.rs +++ b/firmware/src/audio.rs @@ -36,19 +36,20 @@ const STACK_SIZE: usize = 16 * 1024; const FADE_DURATION_S: f32 = 0.7; const FADE_STEP_PER_SAMPLE: f32 = 100.0 / (FADE_DURATION_S * SAMPLE_RATE as f32); -/// Construct the I2S driver for the onboard NS4168 amp. +/// Construct the I2S driver for the external MAX98357A amp. /// -/// Pin assignment matches the M5Stack Atom Echo schematic: -/// BCLK = G19, DOUT = G22, WS/LRCK = G33, no MCLK. +/// Pin assignment per `reference/signal-chain.md`: +/// BCLK = G26 (GROVE yellow), DOUT = G21 (side header), +/// WS/LRCK = G32 (GROVE white), no MCLK. /// /// **Argument order to `new_std_tx` is `(bclk, dout, mclk, ws)`** — not /// the audio-convention `(bclk, ws, dout)` you'd guess. Getting it wrong /// produces static instead of sound; see firmware/README.md gotcha #3. -pub fn make_onboard_i2s( +pub fn make_i2s( i2s0: esp_idf_svc::hal::i2s::I2S0, - bclk: esp_idf_svc::hal::gpio::Gpio19, - dout: esp_idf_svc::hal::gpio::Gpio22, - ws: esp_idf_svc::hal::gpio::Gpio33, + bclk: esp_idf_svc::hal::gpio::Gpio26, + dout: esp_idf_svc::hal::gpio::Gpio21, + ws: esp_idf_svc::hal::gpio::Gpio32, ) -> Result> { let config = StdConfig::new( Config::default(), diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 5e6a8ee..d5a4958 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -71,13 +71,11 @@ fn main() -> Result<()> { // will refresh this once it knows whether we landed online or offline. let _ = led_tx.send(LedSignal::Net(NetStatus::Connecting)); - // Audio task: I2S to the onboard NS4168 (prototype). Will move to the - // external MAX98357A on G21/G26/G32 once amps arrive. - let i2s = audio::make_onboard_i2s( + let i2s = audio::make_i2s( peripherals.i2s0, - pins.gpio19, // BCLK - pins.gpio22, // DOUT - pins.gpio33, // WS / LRCK + pins.gpio26, // BCLK + pins.gpio21, // DOUT + pins.gpio32, // WS / LRCK )?; let _audio_handle = audio::spawn( i2s,