diff --git a/BLE_MIDI_PROTOCOL.md b/BLE_MIDI_PROTOCOL.md new file mode 100644 index 0000000..55a3a6e --- /dev/null +++ b/BLE_MIDI_PROTOCOL.md @@ -0,0 +1,100 @@ +# BLE-MIDI add-on: EFM8 ↔ nRF52 custom framed protocol + +Plan for adding Bluetooth MIDI to the QuNexus by attaching a BLE-MIDI +module (bare **nRF52832**, chosen for lowest power / battery-later) to the +EFM8's dormant **UART1** — the same pair wired to the empty MIDI-expander +jack (**P1.2 / P1.3**, pins 44 / 43). UART1 is never enabled in stock v2.2.1 +firmware (no `XBR2` write, no `SBUF1`/`SCON1` use), so we reclaim it for BLE +without disturbing the DIN-MIDI UART0 or the P2/P3/P4 scan bus. + +See [[EFM8UB20_PINOUT.md]] for the pin map and the port-config block this +modifies. + +## Transport: frame everything (SLIP-style) + +MIDI bytes and control commands share one UART, so **both** are carried in +framed packets. Escaping means any MIDI byte value (0x00–0xFF) is safe in a +payload — no reliance on "undefined MIDI bytes," no collision with real +MIDI status/system bytes. + +``` +END = 0xC0 ESC = 0xDB (ESC_END = 0xDC, ESC_ESC = 0xDD) + +Frame on wire: END CMD LEN payload[0..LEN-1] CRC8 END + - CMD, LEN, payload, CRC8 are byte-stuffed: + 0xC0 -> ESC 0xDC , 0xDB -> ESC 0xDD + - LEN = payload byte count (0..255) + - CRC8 = computed over (CMD, LEN, payload) *unstuffed* + - Receiver collects bytes between END markers, un-stuffs, checks CRC, parses. +``` + +Baud: **31250 8N1** (matches MIDI; frames are short, throughput is ample). + +## Command set + +``` +EFM8 -> nRF52 + 0x01 MIDI_TX payload = MIDI bytes to send over BLE-MIDI + 0x10 ENTER_PAIRING (len 0) start fast advertising / pairing + 0x11 DISCONNECT (len 0) + 0x12 CLEAR_BONDS (len 0) wipe stored bonds (force re-pair) + 0x13 GET_STATUS (len 0) + +nRF52 -> EFM8 + 0x02 MIDI_RX payload = MIDI bytes received over BLE-MIDI (-> inject into router) + 0x20 STATUS payload = [state, bonded_peers, rssi] + state: 0=disconnected 1=advertising 2=connected 3=pairing + 0x21 EVENT payload = [event_code] (connected / disconnected / bond_added / error) +``` + +`MIDI_TX`/`MIDI_RX` are just frames whose CMD carries MIDI bytes; the nRF52 +routes by CMD — `MIDI_TX` → BLE-MIDI TX, control CMDs → BLE stack actions. +BLE crypto/bonding is handled **entirely by the nRF52**; the EFM8 only +triggers pairing and reads status. + +## Data flow on the EFM8 side + +- **Outgoing MIDI:** hook the existing MIDI-send path (same place DIN MIDI + out is fed) to also emit a `MIDI_TX` frame on UART1 — a straight mirror, + exactly like the USB-1→CV stub mirrors into the CV ring. +- **Incoming MIDI:** UART1 RX ISR parses frames; on `MIDI_RX`, inject the + bytes into the router/event buffer — same injection mechanism as the + USB-1→CV stub (which injects at `0x8126`). +- **Pairing control:** a key combo (detected in the existing key-scan path) + → emit `ENTER_PAIRING` / `CLEAR_BONDS` / `DISCONNECT`. +- **Status:** on `STATUS`/`EVENT` frames, set a state byte that the P2 scan + ISR reads to drive the LED matrix (blink = pairing, steady = connected, + off = disconnected). + +## EFM8 firmware pieces (asm patches in `firmware.asm`, verified by `roundtrip.py`) + +1. **UART1 init:** `XBR2 |= 0x01`, `P1MDOUT 0x3F → 0x37` (RX open-drain), + SCON1, Timer3 baud @31250. +2. **UART1 ISR:** small frame RX state machine (collect → un-stuff → CRC → + dispatch by CMD). A few bytes of state + a small ring buffer. +3. **Frame TX helper** (stuff + CRC + END-wrap) used by the mirror and the + control commands. +4. **MIDI mirror hook + MIDI inject** (reuse the USB-1→CV injection pattern). +5. **Key-combo detect → control frame.** +6. **LED status** state variable read by the P2 scan ISR. + +The nRF52 side (BLE-MIDI peripheral + this frame protocol) is a separate +firmware project — spec'd here, not written in this repo. + +## Phasing (keeps each step verifiable) + +1. **Phase 1 — transport + MIDI:** UART1 + frame RX/TX + `MIDI_TX`/`MIDI_RX` + mirror/inject. BLE-MIDI works end-to-end (auto-pair, Model A). Smallest + useful mod; proves the UART/framing on real hardware. +2. **Phase 2 — control:** add the control commands + key-combo + LED status + (Model B pairing). All on top of the Phase-1 frame layer, no rework. + +Both phases are asm patches in `firmware.asm` that round-trip byte-identical +against a known-good image (same workflow as the USB-1→CV patch). + +## Safety constraint (hard rule for any firmware mod) + +The bootloader region **0x0000–0x23FF must NOT be erased**; only +**0x2400–0xF806** is the app region; **no mass erase should ever be +performed.** Firmware is read-only unless explicitly asked to write. See +`RECOVERY.md` for the C2 flash reader/patcher constraints. \ No newline at end of file