Play audio from a phone through a Tesla's speakers, over USB.
A Tesla has no aux input. It does have a USB port that accepts Tesla's own cabin microphone — the CaraokeMic — and routes whatever that microphone hears into the cabin speakers. TeslAux clones that microphone precisely enough that the car accepts it, then feeds it real audio instead of a microphone.
The result is a wired audio input: anything that can play into a USB audio output can play through the car. Wired, so unlike Bluetooth it is not resampled, not re-encoded, and not subject to codec latency.
phone ──USB──> [source board] ──I²S──> [car board] ──USB──> Tesla
a USB audio output a clone of the CaraokeMic
Two boards, because the phone and the car are both USB hosts — a device has to be a USB peripheral to each, and one USB controller cannot do both. They are joined by I²S, which carries the audio between them as plain PCM with no conversion at any point.
The source board's clock is steered to follow the phone, so the phone and the I²S link run on one clock. The only remaining clock difference — against the car — is absorbed by varying the size of each USB packet, which loses no samples at all.
Two identical RP2040-Zero boards, about $3 each. Solder four signals and a ground between them:
| source board | car board | |
|---|---|---|
| GPIO12 | → | GPIO10 (DATA) |
| GPIO11 | → | GPIO11 (BCK) |
| GPIO10 | → | GPIO12 (SHIELD) |
| GPIO9 | → | GPIO13 (LRCK) |
| GND | → | GND |
The pins are the bottom row, so the two boards can be soldered edge to edge with one rotated 180 degrees — every pair above sums to 22, which is what facing pads do. Four-inch jumpers were corrupting one sample in roughly every 1,400: the right channel replaced by its own sign bit, up to 44% of full scale in a single sample, which is audible as a pop every few minutes.
SHIELD carries no signal. The source holds it low so a driven conductor sits
between BCK and LRCK, which is where the coupling was. rp2040/src/pins.rs has
the measurement and refuses to build if the map stops being physically possible.
Ground is not optional — the boards are powered from different USB ports.
cd rp2040 && ./build-uf2.shThen hold BOOTSEL while plugging each board into a computer and copy its image
across. Use cp, not Finder: the bootloader reboots the instant the last block
lands, which Finder treats as a disk being yanked out.
# the board the phone plugs into
cp rp2040/teslamic-rp-source-ultralow.uf2 /Volumes/RPI-RP2/
# the board that plugs into the car
cp rp2040/teslamic-rp-car-elastic-lowlat.uf2 /Volumes/RPI-RP2/The phone will show a USB audio output called TeslAux Bridge. Plug the car board into the car's data port and the mic icon appears.
Each board has an onboard RGB LED: green streaming, blue waiting for
audio, red a fault. rp2040/FLASHING.md has every
image, the LED codes in full, and the diagnostic builds.
A PCM2706 USB-to-I²S bridge chip can replace the source board entirely. It is a fixed-function part with no firmware, and it recovers the host's clock in hardware rather than in a software control loop — which should be both more reliable and about 5 ms lower latency than the two-board version. That leaves one RP2040 plus a ~$10 module. The car-side firmware already supports it and follows whatever sample rate it delivers; it has not been tested yet.
The two-board rig has been measured, not just listened to: 8,352,000 frames
across three consecutive cold boots, delivered from an Android phone to a Mac
through both boards, identical to the source file, sample for sample. See rp2040/TESTING.md for
the method and tools/bitcompare.py for the tool.
Findings from the car itself, not from documentation. Several contradict what this project believed for months — the corrections are noted.
The car runs a driver written for one specific device and checks for it. Getting the audio right is not enough; get the descriptors wrong and it shows "unsupported USB microphone" and disconnects after about 60 seconds.
What has to match, verified against a real unit dumped over libusb
(see real_mic_dump.md):
- VID
0x1235, PID0x0002, and the manufacturer, product and serial strings. The serial is 40 bytes, which makes the string descriptor 162 bytes — a 128-byte control buffer silently breaks enumeration. - Four interfaces: AudioControl, AudioStreaming, and two HID interfaces.
- IF2 is a HID keyboard with the standard 65-byte boot-keyboard report descriptor. Not telemetry, as was assumed for months — the mic's button sends keystrokes.
- IF3 is an endpoint-less vendor HID, usage page
0xFF00, usage0x55AA, with an exact 36-byte report descriptor and an 8-byte Feature report reading00 01 00 03 03 00 08 00. This is the one that matters. The car writesA5 5A-framed configuration to it and validates what comes back. Cloning it byte for byte is what defeats the popup.
What does not have to match: the endpoint numbers, the audio topology (a plain
microphone → USB-streaming terminal pair works, without the real mic's feature
and selector units), bcdUSB, bcdHID, and wMaxPacketSize.
Retested after fixing two transport bugs that had been corrupting everything we sent. The earlier conclusion that the car was "48 kHz-family only" was wrong — it was our own corruption, and 44.1 kHz was simply the rate most exposed to it.
| 32 kHz, 44.1 kHz, 48 kHz, 16-bit | clean — retested after the transport fixes |
| 96 kHz, 16-bit | clean — retested |
| 192 kHz, 16-bit | plays, with a click roughly every 2 s (not diagnosed) |
| stereo | yes |
| more than 2 channels | only the first two are ever played |
| 24-bit | unverified — see below |
24-bit is an open question, not a result. It was tested in July and appeared clean at 48 and 96 kHz, but that predates the two transport bugs being found, and those July tests used a 1 kHz tone at 48 kHz — exactly one cycle per USB packet, and therefore blind to the packet corruption by construction. The same test signal also reported 48 kHz as "clean" while every packet was being mangled. So the July result carries no information either way.
t114/teslamic-48k-24bit.uf2 and teslamic-96k-24bit.uf2 are rebuilt against
the fixed firmware with a 997 Hz tone (deliberately not packet-periodic) if you
want to settle it. Note 192 kHz at 24-bit is impossible regardless: 1152 bytes
per frame against the 1023-byte full-speed isochronous limit.
It does not currently matter for the shipping firmware, which is 16-bit throughout — the PCM2706 is 16-bit only, and the RP2040 path is built around 16-bit samples.
The car accepts isochronous packets that change size from frame to frame. Verified with a build that alternates 47 and 49 samples on every frame, ~500× harsher than real clock drift, playing cleanly for ten minutes.
This is what makes the design work: the difference between our clock and the car's is absorbed by sending one more or one fewer sample when needed, so no sample is ever dropped, duplicated or resampled.
About 8 ms through both boards — USB frame quantisation, and enough buffer to cover one packet at each hop.
That is not what you will hear. The car's own audio pipeline adds roughly 100 ms, measured in July and not reachable from the device, and the phone adds 10–50 ms of its own buffering. Our share is a few percent of the total, which is worth knowing before optimising it further.
rp2040/ |
the firmware to use — both boards build from one crate |
t114/ |
the original nRF52840 prototype, where the descriptors and the transport bugs were worked out |
tools/ |
UF2 packaging, and analysis scripts for recorded audio |
RESEARCH.md |
how the CaraokeMic was reverse engineered |
real_mic_dump.md |
raw descriptor dump from a real unit |
MIT throughout — see LICENSE.
NOTICE.md records the provenance of two pieces: the T114's ST7789
driver, ported from an AGPL project and relicensed MIT here by its copyright
holder, and tools/uf2conv.py, from Microsoft's uf2 project.
"Tesla", "TeslaMic" and "CaraokeMic" are trademarks of Tesla, Inc. This project is independent and not affiliated with or endorsed by Tesla.