blue-repeat/README_RELAY.md
blue baa3ef7690 3-board relay firmware: sink + I2S + dual source
Implements the only architecture that can relay iPhone audio to two BT
speakers at once (one ESP32 cannot be A2DP sink+source, and a source holds
only one link):

  iPhone ))BT)) [Board A: A2DP sink -> I2S master]
                      ==I2S bus==> [Board B: I2S slave -> A2DP source] ))BT)) JBL
                      ==I2S bus==> [Board C: I2S slave -> A2DP source] ))BT)) Cardo

- src/board_sink.cpp   : A2DP sink "BikeAudio", forwards decoded PCM to an
  I2S master bus (BCK=5, WS=25, DATA=23); follows negotiated sample rate.
- src/board_source.cpp : I2S slave (BCK=19, WS=18, DATA=22) -> A2DP source,
  target speaker via TARGET_SPEAKER build flag; pads silence on underrun.
- platformio.ini       : 3 envs (sink, source_jbl, source_cardo) sharing an
  [env] base; sources differ only by TARGET_SPEAKER. build_src_filter selects
  the per-board source file. Libs pinned as before.
- README_RELAY.md      : wiring table, I2S bus topology, flash order, pairing,
  and the speaker-sync limitation.

Replaces the single-board src/main.cpp (architecturally impossible). All
three envs build clean. Hardware flash + wiring next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 11:35:12 -04:00

85 lines
4.1 KiB
Markdown

# BikeAudio — 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.
## 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/<env>/{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 **"BikeAudio"**.
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 may lag the other by some tens of milliseconds.
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.