# BLE-MIDI add-on: EFM8 ↔ nRF52 custom framed protocol Plan for adding Bluetooth MIDI to the QuNexus by attaching a BLE-MIDI module 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. ## Module selection **Chosen: Seeed/Raytac MDBT50Q — nRF52840, 1 MB flash / 256 kB RAM, BT 5.4, ~15.5 × 10.5 × 2.05 mm, certified.** SWD is broken out (**pin 51 = SWDIO, pin 53 = SWDCLK**), it ships **blank**, so we SWD-flash our own BLE-MIDI + framed-protocol firmware onto it. The nRF52840 has the most BLE-MIDI reference code of any nRF52 (Nordic nRF5 SDK BLE-MIDI example, Adafruit Bluefruit MIDI mode) — we lift the BLE-MIDI GATT layer and add our framed-protocol UART handler on top. Battery-acceptable (system-off ~1.5 µA, BLE-connected a few mA avg). Wiring uses only 6 of its 61 pads: VDD, GND, UART TX, UART RX, SWDIO, SWDCLK. > **Antenna variant — watch the SKU.** The Seeed **MDBT50Q-U1M** is the > **u.FL** variant: **no onboard antenna**, just a u.FL jack → needs an > **external 2.4 GHz antenna on a u.FL pigtail** routed to a board edge > inside the case (keep-out ~3–5 mm at the antenna end). If you'd rather > avoid the external antenna, get the **chip-antenna variant Raytac > MDBT50Q-1MEN** (same module, onboard chip antenna, no u.FL). Same SWD > pins, same firmware. Prefer the chip-antenna SKU unless the u.FL + > external antenna is deliberately wanted for edge placement. **Smaller fallback (if case space is tight): Raytac MDBT42Q-512KV2 — nRF52832, 512 kB / 64 kB, ~6.5 × 7.1 × 1.6 mm, onboard chip antenna.** Same SWD-exposed/blank profile, lowest power, smallest fit, and it *does* have the integrated antenna — but less headroom and less ready-made BLE-MIDI code than the 52840. ### Hard selection rules (do not re-evaluate against these) - **Must expose SWD and be blank/reflashable.** We load our own BLE-MIDI firmware; a sealed module is unusable. - **Reject AT-command / "transparent serial" modules** (e.g. Raytac MDBT42T-AT / WRL-25466, nRF52805). They advertise the **Nordic UART Service** (`6E400001-…`), not **BLE-MIDI** (`03B80E5A-…`). iOS/macOS/DAWs scan for the MIDI UUID and will not present an NUS device as a MIDI source. Their AT command set cannot change the advertised service. The MDBT42T-AT also exposes **no SWD** (19 pads, none SWDIO/SWCLK), so it can't be reflashed either — doubly unsuitable. - **Reject SPI-only radio modules that need a host to run the BLE stack** (e.g. Raspberry Pi Radio Module 2 / RMC20452T, Infineon CYW43439). The BLE stack (BTstack) runs on an RP2040 host over a bespoke gSPI; the module cannot talk to the EFM8 directly (no SPI on the EFM8, and it can't run BTstack). That makes a 3-chip build (EFM8 + RP2040 + radio), and the Wi-Fi combo chip is power-hungry — wrong for battery-later. (A Pico W is the cheap bench-prototype instantiation of that architecture, but it is not the final-build path.) ## 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.