Skip to content

Neurobeat Loop documentation

Neurobeat Loop is an RP2354-based USB bridge. It presents a single USB composite device whose one configuration exposes four functions at once: a 16-channel audio input, a serial port, a HID device, and a vendor bulk channel, so a host can consume biopotential data through standard, driverless interfaces.

This is a developer preview. The USB design below has been verified for enumeration and on-chip memory fit; the descriptions reflect the current prototype and may change.

Overview

The bridge is a Raspberry Pi RP2354 with a USB-C connector. Its USB controller is full-speed (12 Mb/s), which sets the key constraint for the audio path. All four functions share one configuration with six interfaces and seven endpoints (plus control endpoint 0), and they're class-compliant: audio, serial and HID use the operating system's built-in drivers.

USB interface map

InterfaceFunctionEndpoints
ITF 0Audio Control (AC)
ITF 1Audio Streaming (AS)alt 0: 0 EP · alt 1: iso IN 0x81
ITF 2CDC ControlINT IN 0x82
ITF 3CDC Databulk OUT 0x03, bulk IN 0x83
ITF 4HID (kbd + mouse + joystick)INT IN 0x84
ITF 5Vendorbulk OUT 0x05, bulk IN 0x85

The HID interface collapses keyboard, mouse and joystick into three HID report IDs on one interrupt endpoint. The device uses an Interface Association Descriptor (IAD) class triplet so the host correctly groups the audio and CDC functions.

Audio format

The audio function is a UAC2 input terminal with 16 channels at 16-bit. On a full-speed link the isochronous endpoint is capped at 1023 bytes per frame, and the per-frame size is (kHz + 1) × bytes × channels. That makes 48 kHz impossible for 16×16-bit (1568 B > 1023 B), so the sample rate is the free variable:

  • 16 kHz → 544 B/frame. Chosen: a standard rate, the widest host compatibility, and the most on-chip memory headroom.
  • 32 kHz → 1056 B/frame — over the ceiling.
  • 30 kHz → 992 B/frame — fits, but non-standard.

The channel count, bit depth and all four functions are preserved; sample rate is the only parameter reduced to fit full-speed USB.

On-chip memory budget

The RP2354 USB controller has a fixed 4 KB of packet buffer (DPRAM). Each endpoint buffer rounds up to 64 bytes; bulk endpoints are double-buffered, iso/interrupt are single-buffered. Everything fits with room to spare:

EndpointTypeMax packetDPRAM (B)
0x81 audioISO544576
0x82 CDC notifINT864
0x03 CDC dataBULK64128
0x83 CDC dataBULK64128
0x84 HIDINT1664
0x05 vendorBULK64128
0x85 vendorBULK64128
Total of 3840 B pool1216 (≈32%)

Host quickstart

Confirm enumeration and the interface set:

lsusb -d 2e8a: -v   # expect one config, 6 interfaces, classes: Audio, CDC, HID, Vendor

Exercise the vendor loopback with pyusb (write to bulk OUT, read it back from bulk IN):

import usb.core, os
dev = usb.core.find(idVendor=0x2E8A)
dev.set_configuration()
payload = os.urandom(64)
dev.write(0x05, payload)          # bulk OUT
echo = dev.read(0x85, 64).tobytes()  # bulk IN
assert echo == payload, "loopback mismatch"
print("LOOPBACK OK")

On Linux, a udev rule grants user access to the vendor interface so the script runs without sudo (match on the product string and add TAG+="uaccess").

Host notes & caveats

  • The device is identified by its product string, not by a unique USB product ID — it reuses a standard Raspberry Pi PID per the usb-pid convention. Linux is unaffected; on Windows the only nuance is driver caching.
  • Audio, serial and HID need no driver install — they bind to built-in OS class drivers. Only the vendor interface uses libusb.
  • The audio stream opens only after the host's mandatory sample-rate query succeeds; enumeration and coexistence don't depend on it.