# Resound — 3-board Bluetooth relay Relays iPhone audio to **two** Bluetooth speakers (JBL Charge 5 + Cardo "Tangerine EDGE") at the same time. ## Why three boards? One ESP32 **cannot** do this alone: 1. **Sink + source can't coexist.** To receive from the iPhone the ESP32 must be an A2DP *sink*; to play through a speaker it must be an A2DP *source*. The ESP32's classic-Bluetooth stack registers only one A2DP role at a time (the ESP32-A2DP library keeps a single global instance that each role overwrites). Running a sink and a source together orphans the sink — the iPhone can't even see it. 2. **A source reaches only one speaker.** An A2DP source holds a single outgoing link, so one board can drive one speaker, not two. So the job is split: one board receives, one board per speaker sends, and they pass audio between them over a short digital **I2S** wire bus. ``` iPhone ))BT)) ┌──────────────┐ I2S bus (BCK/WS/DATA + GND) │ Board A │═══════════════╦═══════════════╗ │ A2DP SINK │ ║ ║ │ I2S MASTER │ ▼ ▼ └──────────────┘ ┌────────────┐ ┌────────────┐ │ Board B │ │ Board C │ │ A2DP SOURCE│ │ A2DP SOURCE│ │ I2S SLAVE │ │ I2S SLAVE │ └─────┬──────┘ └─────┬──────┘ ))BT)) ))BT)) JBL Charge 5 Tangerine EDGE (Cardo) ``` ## Wiring Board A is the I2S **master** (it generates the clocks). Boards B and C are **slaves** that listen to A's bus in parallel. Tie the three signals from A to the matching input pins on **both** B and C, and tie **all grounds together**. | Signal | Board A (master, out) | Board B (slave, in) | Board C (slave, in) | |-------------|-----------------------|---------------------|---------------------| | Bit clock | **GPIO5** (BCK) | GPIO19 | GPIO19 | | Word select | **GPIO25** (WS/LRCK) | GPIO18 | GPIO18 | | Data | **GPIO23** (DATA out) | GPIO22 (in) | GPIO22 (in) | | Ground | **GND** | GND | GND | - A·GPIO5 → B·GPIO19 **and** C·GPIO19 - A·GPIO25 → B·GPIO18 **and** C·GPIO18 - A·GPIO23 → B·GPIO22 **and** C·GPIO22 - A·GND → B·GND **and** C·GND (mandatory — shared clock reference) Each board can be powered from its own USB/5V; only the grounds must be common. ### Sync (touch pads on each source board) JBL and Cardo have independent Bluetooth buffering, so one lags the other. Each source board has an **adjustable delay** (0–200 ms) you trim live, by ear, with two capacitive-touch pads — raise the delay on whichever speaker is *early* until they line up. The value is saved to flash (survives power-cycles). | Touch pad | Pin | Action | |-----------|-------|--------------------------------| | **+** | GPIO4 | tap = +5 ms, hold = ramp up | | **−** | GPIO27 | tap = −5 ms, hold = ramp down | Attach a short wire or a bit of foil to GPIO4 and GPIO27 on each source board and touch the end. (There is intentionally **no Wi-Fi/phone UI**: Wi-Fi + Bluetooth + the audio buffer don't fit in RAM on the classic ESP32 — it starves the Bluetooth stack — so the control is local touch.) ## Build & flash ``` pio run # builds all three pio run -e sink # Board A pio run -e source_jbl # Board B (target "JBL Charge 5") pio run -e source_cardo # Board C (target "Tangerine EDGE") ``` Flash each board with the matching environment's artifacts (`.pio/build//{bootloader,partitions,firmware}.bin`). **Power-on order:** bring up **Board A first** so the I2S bus is clocking before B and C start reading it. ## First-time pairing 1. Put the **JBL** and **Cardo** in pairing mode. 2. Power Board B and Board C — each connects to its speaker by name (auto-reconnects on later power-ups). 3. Power Board A; on the iPhone, connect to **"Resound"**. 4. Play audio — both speakers should output together. ## Known limitations - **The two speakers are not sample-synchronized.** JBL and Cardo each have their own Bluetooth buffering, so one lags the other. The per-board touch delay (above) lets you trim a fixed offset to line them up by ear; it is not continuous sample-lock, so slow drift over long sessions is possible. Fine for music/intercom; not suitable for tight stereo L/R separation. - Audio is fixed at the SBC standard **44.1 kHz / 16-bit / stereo**. - If Board A reboots, the slave boards' audio pauses until A is clocking again.