Skip to content
MJ
← All projects
STM32FreeRTOSTinyUSBDMAC++/WinRTPCB

USB Media Controller

Custom STM32F4 composite USB device (CDC + HID consumer control) with FreeRTOS and TinyUSB, plus a C++/WinRT companion app.

Year
2024
Hardware
STM32F407

OutcomeWorking desk controller: one USB cable carries a serial debug channel and media-key input at the same time.

The problem

I wanted a physical volume/media control for my desk that behaves like a real device, not a keyboard macro hack — and I wanted to learn USB properly rather than copy a HID example.

Constraints

  • One device, two personalities. A serial port for debugging and media keys, on a single plug — which means a genuine composite USB device, not two devices on a hub.
  • No dropped input. A rotary encoder at full speed generates interrupts far faster than a naive loop can service.
  • Must just work on Windows with no driver install.

What I did

Built a composite USB device with TinyUSB on an STM32F4: CDC for serial, HID consumer control for media. Enumerating as a composite device means Windows binds both interfaces from the built-in HID/CDC class drivers — no INF file, no admin rights.

Handled input in hardware where possible. The rotary encoder is decoded on GPIO interrupts with a state-machine quadrature decoder, and buttons use hardware debouncing so the firmware never sees chatter.

Used DMA for UART so the serial channel doesn’t steal CPU from the USB stack.

Wrote a C++/WinRT companion app that talks to the CDC interface — reader/writer tasks, reconnection handling, and a settings surface.

Trade-offs

  • HID reports instead of a custom driver. A custom HID report for volume and play/pause is understood by the OS out of the box; a vendor-specific interface would have needed a driver and an admin install. Less flexibility, zero installation friction.
  • Interrupt-driven input over polling. Slightly more complex decoder, but it means the encoder never misses a detent at speed.

What I’d do differently

The first PCB revision had the encoder pull-ups misplaced. It worked — but only because the internal pull-ups masked it, and that’s precisely the kind of thing that bites in a volume build. Now I check every pull-up on the schematic against its datasheet requirement before layout.

Evidence

Rotary encoder and debounced buttons feeding a single HID report alongside a live CDC channel.
Board in enclosure.