Why Your USB Serial Data Looks Like Garbage (And How to Fix It)

When you’re wiring together fast embedded systems (like an ESP32 feeding data into a Teensy 4.1) it’s easy to assume USB will “just work.” After all, USB is fast. Way faster than UART, right?

That assumption is exactly what led to a subtle (and very common) bug: perfectly timed data turning into unreadable garbage.

Let’s walk through what happened, why it happens, and the one-line fix that makes everything click.

The Setup

The goal was straightforward:

  • An ESP32 streams data (eventually from an RPLidar C1)
  • Output goes over USB via a CP2102
  • A Teensy 4.1 reads that data using its USB host port

The ESP32 test code was a simple counter printed once per second at 460800 baud.

On the PC? Everything looked perfect.

On the Teensy? Total nonsense:

�U��M�E��e��E��E��%�A��������...

Yet the Teensy was receiving data at the correct timing intervals. That’s the key clue.

The Misconception: “USB Is Just a Fast Pass-Through”

At first glance, the reasoning makes sense:

  • USB runs at megabits per second (12 Mbps or more)
  • UART is much slower (hundreds of kilobits)
  • Therefore, USB should just carry the data without caring about baud rate

But that’s not how USB serial adapters work.

What’s Really Happening

The ESP32 dev board doesn’t expose raw UART over USB. Instead, it uses a USB-to-serial bridge chip (like the CP2102).

That chip sits between two worlds:

  • USB side (host ↔ CP2102): always runs at USB speeds
  • UART side (CP2102 ↔ ESP32): runs at a configured baud rate

That baud rate must be set by the USB host.

When you use the Arduino Serial Monitor, selecting “460800 baud” isn’t just cosmetic—it sends a USB control message telling the CP2102:

“Talk to the ESP32 at 460800 baud.”

So when you unplug from your PC and plug into the Teensy, the Teensy becomes the host that has to configure that chip.

The Root Cause

From the original code:

USBSerial userial(myusb);

void setup() {
  Serial.begin(460800); // Debug output
  myusb.begin();
}

The mistake is subtle:

  • Serial.begin(460800) sets the Teensy’s USB device port (to your PC)
  • It does nothing for userial (the USB host connection)

So the CP2102 defaults to 115200 baud, while the ESP32 is sending at 460800 baud.

That mismatch = corrupted data.

The Fix (One Line)

Add this:

userial.begin(460800);

That’s it.

Now both sides agree on the UART speed, and the garbage disappears.

Why This Confuses So Many People

This issue trips people up because there are three layers involved:

  1. USB transport (fast, fixed speed)
  2. USB-to-UART bridge (configurable)
  3. UART link (baud-dependent)

The important takeaway:

USB speed and UART baud rate are completely independent.

Even though data travels over USB at megabits per second, the bridge chip still needs to know how fast to talk to the microcontroller.

A Helpful Mental Model

Think of the CP2102 like a translator:

  • USB side: speaks “fast digital packets”
  • UART side: speaks “timed serial bits”

Setting the baud rate is like telling the translator how fast to speak on the UART side.

If you don’t tell it? It guesses (usually 115200). And that guess is often wrong.

Bonus Insight: Native USB vs USB-to-Serial

This distinction also explains something subtle:

  • On boards with native USB (like Teensy), baud rate often doesn’t matter for Serial
  • On boards using USB-to-serial chips (like many ESP32 dev boards), baud rate is critical

That’s why your Teensy debug output worked fine regardless of settings while the ESP32 link didn’t.

Final Result

After fixing the baud rate on the host side, the system behaved exactly as expected:

A: 302  D: 9365
A: 312  D: 2652
A: 312  D: 2844
...

Clean, structured data from the LiDAR without any corruption.

Key Takeaways

  • USB serial ≠ raw USB data stream
  • USB-to-UART bridges require host-side baud configuration
  • Serial.begin() and userial.begin() control completely different interfaces
  • If you see “garbage data,” check baud mismatch first

Closing Thought

This is one of those bugs that feels like signal integrity or timing trouble—but turns out to be configuration.

Once you understand where the baud rate actually lives (hint: not just on the transmitting device), a whole class of “mystery corruption” problems becomes easy to diagnose.

Teensy 4.1

DEV-16771
$31.50