# usb-device and a device-side USB stack for microcontrollers in Rust

> usb-device is a Rust framework for the device side of USB on microcontrollers, where a UsbDevice object composes USB classes and a bus driver so that an HID or serial port implementation can be shared across hardware families. The interesting design is the split between the UsbClass trait, the UsbBus trait and the vendor HAL crates, and the interesting constraint is that the stack is explicitly experimental and that a high speed enumeration feature flag is more about test compliance than real support.

**rust-embedded-community/usb-device** — Experimental device-side USB framework for microcontrollers in Rust.

- Repository: https://github.com/rust-embedded-community/usb-device
- Stars: 531 · Forks: 87
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/rust-embedded-community-usb-device

## The three traits that make up the stack

usb-device is a USB stack for embedded devices in Rust, and the description is deliberately modest. It is an experimental device-side USB framework for microcontrollers.

The documentation explains the design through three objects. The UsbDevice object represents a composite USB device and is the most important object for application implementors. It combines a number of UsbClasses, either custom ones or pre-existing ones provided by other crates, together with a UsbBus device driver to implement the USB device.

The UsbClass trait is the extension point for functionality. It can be used to implement USB classes such as a HID device or a serial port, and an implementation may also use a custom class if the required functionality is not covered by a standard class.

The UsbBus trait is the hardware end. It is intended to be implemented by device-specific crates to provide a driver for each device's USB peripheral.

That three-way split is the whole architecture, and it is the reason the project can cover a lot of silicon without a lot of code. The class implementations are hardware independent, the bus driver is hardware specific but contains no application logic, and the application only ever talks to UsbDevice. A HID keyboard written against the stack runs on an atsam4, an imxrt or a stm32 without modification, because the only hardware-specific part is the bus driver underneath.

The primary language is Rust, the licence is MIT, and the crate is published as usb-device with the keywords no-std, embedded and usb, and the Cargo.toml sets the edition to 2018. The author is Matti Virkkunen.

The recent releases are v0.3.2 from 2024-03-06, v0.3.1 from 2023-11-15 and v0.3.0 from 2023-11-13. The last push was on 2026-06-19, so the repository is being worked on while the version line has not moved past 0.3.2, and the default branch is master.

## The hardware driver crates, and what each covers

The vendor list is the practical scope of the project, and it is longer than the name of any one microcontroller family would suggest.

There is atsam4, a device driver for the atsam4e and atsam4s microcontrollers, with examples in a separate repository. The documentation notes that while it is not expressly supported with this crate, atsam3s and atsam55g could also be supported with a similar code base, which is a statement about how portable the driver pattern is rather than a promise.

There is atsamd, for the samd21 and samd51 families, with an example for the Adafruit itsybitsy_m4 board. There is imxrt-usbd, for the NXP i.MX RT microcontrollers, with examples for i.MX RT boards such as the Teensy 4 maintained with the driver. There is rp2040-hal, for the Raspberry Pi RP2040, with examples in the various boards crates. There is stm32-usbd, covering multiple STM32 families, where the examples live in each individual HAL crate that implements the USB peripheral.

And there is musb, for the Mentor USB, USB2.0 IP that is widely used in microcontrollers and SoCs from vendors like TI, MediaTek, Puya and Allwinner, with examples in the py32-hal repository.

The musb entry is the one that widens the scope most, because a licensed USB PHY IP is exactly what a silicon vendor integrates when they do not build their own controller, so a single driver reaching several vendors' SoCs is more valuable than the length of the list suggests.

The examples being scattered across the HAL crates rather than centralised here is a deliberate arrangement. The device driver knows about the peripheral registers of a specific family, so it belongs with that family, and the shared stack stays hardware independent.

## The class crates, and where the maturity boundary sits

The class implementations are the part an application actually consumes, and the readme is candid about which of them are ready.

There are two HID implementations, usbd-hid and usbd-human-interface-device, both from outside the project's own organisation. There is usbd-serial, a CDC-ACM serial port class, and usbd-midi, a MIDI class, both from the rust-embedded-community organisation. There is usbd-dfu for device firmware upgrade, and usbd-picotool-reset, a class for the picotool reset interface, which is the mechanism the Raspberry Pi uses to reset the board from a USB host.

Two of them are marked experimental in the list itself. usbd-storage is a mass storage port class and usbd-audio is an audio class, and both are labelled experimental.

The mass storage class in particular is the one to think about, because mass storage is a class where a wrong implementation causes visible problems on the host side. It is experimental for a reason, and the label is the useful information.

The other thing worth noticing is who owns what. The two HID crates and the DFU and picotool-reset crates come from other authors, while the serial and MIDI classes are maintained inside the project. That split tells you where the project's own engineering effort has gone, which for a stack whose job is to make a peripheral look like a device is exactly the serial port and MIDI.

There is also an Others section, and it has one entry with a lot of weight in it. The Embassy project has an async USB stack, embassy-usb, and the readme points at it as another implementation of USB in Rust.

## Adding the crate to a firmware project

The manifest is where the real constraints of the stack show up, and it is worth reading before you write any code.

```toml
[dependencies]
defmt = { version = "1", optional = true }
portable-atomic = { version = "1.2.0", default-features = false }
heapless = ">=0.8, <=0.9"
log = { version = "0.4", default-features = false, optional = true}
```

Only two of those are mandatory, and both are there for a reason. heapless gives the stack its fixed-capacity collections, which is what keeps the endpoint buffers and descriptors off the heap in a no_std target, and portable-atomic supplies atomics on cores that lack them natively. defmt and log are both optional, so a project chooses a logging format, defmt for the defmt ecosystem or the standard log facade for everything else, or neither if the firmware does not log at all.

The keywords in the manifest are no-std, embedded and usb, and the edition is 2018, which is a hint about the audience: this is a crate for firmware that has to fit in a few tens of kilobytes of RAM, not for a Linux or desktop target.

The version to depend on is 0.3.2, the same version as the latest release, and the crate is added the ordinary way as a Cargo dependency by name, with the hardware support coming from the separate HAL crate for your family. The split means your Cargo.toml carries two dependencies rather than one, the stack and the bus driver, and the bus driver is where the choice between vendor families is expressed.

## Dependencies, no_std and the high speed caveat

The dependency list in Cargo.toml is short and says a lot about the constraints the stack works under.

defmt is an optional dependency, and that is the defmt logging framework, which is the format-aware alternative to a serial println for firmware. log at 0.4 is also optional, so a project can pick either the standard logging facade or defmt, or neither. heapless is a required dependency at 0.8 or 0.9, and heapless is the fixed-capacity collections crate, which is the mechanism that keeps the stack allocation free. portable-atomic is there for the atomics on targets without native support.

Taken together, that is a no_std stack: the keywords say no-std, and heapless is the enforcement. A USB device on a microcontroller has a fixed amount of RAM and the endpoint buffers have to come from somewhere known at compile time, which is why the class implementations are written against heapless rather than Vec.

The one feature flag deserves attention. test-class-high-speed is empty, and the comments above it explain why: it is for using larger endpoint buffers for high speed operation, with the default being full speed. But the note is explicit that usb-device does not truly support high speed enumeration yet, so setting this will make TestClass only compliant with high speed mode, and it may still manage to be enumerated as a full speed device with invalid descriptors.

So the flag is a test-compatibility switch, not a feature. That is a good name for it to have, because a flag called high-speed with no other explanation would be a trap, and here the explanation is in the manifest.

The test setup is also visible. There is a test target named test_class_host with harness = false, pointing at tests/test_class_host/main.rs, and the dev-dependencies include rusb at 0.9.1 and rand. rusb is the host-side USB library, so the integration test drives the device from a host machine, and the harness is disabled because the test is a program rather than a set of unit test functions.

## Where usb-device is the wrong choice

Three boundaries follow from the design and from the readme's own framing.

The first is the word experimental in the project description, and it should be taken seriously rather than as a disclaimer. The 0.3.x version line has not moved since March 2024, and the repository is being touched without a release. For a firmware stack whose failure mode is a device that enumerates incorrectly on a host, that combination says the API is still moving and the coverage of real hardware is partial.

The second is the async question. This stack is synchronous, and the readme's own Others section points at the Embassy project's embassy-usb as the async implementation. If your firmware is built on the Embassy executor, which a good deal of modern embedded Rust is, then choosing usb-device means writing your own async adapter on top of a blocking stack, or choosing Embassy instead. The decision is really about your async runtime rather than about USB.

The third is the hardware list, which is a real constraint rather than a growing list. The bus driver has to exist for your peripheral, and while the six vendor crates cover a lot of the popular choices, a project on a less common SoC either finds a musb-compatible IP or has to write the driver itself, which means understanding the peripheral's register interface and the USB spec's device-side requirements in detail.

The alternative to compare against is a vendor SDK. Most microcontroller vendors ship a USB device stack in C, with the descriptors and endpoint handling already done and validated on their own silicon. The case for usb-device is Rust, type safety, and the ability to share a class implementation across families. The case against is that you are building against a 0.x framework rather than the vendor's supported code, and the vendor stack is what their hardware teams test against.

## Conclusion

usb-device is a good fit for a Rust firmware project that needs to expose a USB peripheral on a supported microcontroller family and wants the HID, CDC-ACM, MIDI or DFU class implementations to be reusable rather than rewritten per board. It is a poor fit if you need an async USB stack, since the readme points to the Embassy project's embassy-usb for that, and a poor fit if your silicon is not in the vendor list, because the bus driver is the part that has to be written for the peripheral. Before adopting, read the feature flags in Cargo.toml, because the test-class-high-speed option is a test compliance switch rather than real high speed support, and check the class crates you need, since two of them are marked experimental.

## FAQ

### What is the usb-device crate?

It is an experimental device-side USB stack for embedded devices written in Rust, providing a no-std framework where a UsbDevice object composes USB classes and a bus driver. The crate is published as usb-device under the MIT licence with the author Matti Virkkunen.

### Which microcontrollers does usb-device support?

Through separate driver crates: atsam4 for atsam4e and atsam4s, atsamd for samd21 and samd51, imxrt-usbd for the NXP i.MX RT family including the Teensy 4, rp2040-hal for the Raspberry Pi RP2040, stm32-usbd for multiple STM32 families, and musb for the Mentor USB2.0 IP used by TI, MediaTek, Puya and Allwinner devices.

### Which USB classes are available for usb-device?

Two HID implementations, usbd-hid and usbd-human-interface-device, a CDC-ACM serial port class in usbd-serial, MIDI in usbd-midi, device firmware upgrade in usbd-dfu, picotool reset in usbd-picotool-reset, and two marked experimental, usbd-storage for mass storage and usbd-audio for audio.

### Does usb-device support high speed USB?

Not really. The test-class-high-speed feature flag is for using larger endpoint buffers, and the manifest notes that usb-device does not truly support high speed enumeration yet, so setting it only makes TestClass compliant with high speed mode and the descriptors may still be invalid.

### What dependencies does usb-device have?

heapless at 0.8 or 0.9 is required, portable-atomic at 1.2.0 is required, and defmt and log are both optional, so a project can use either logging framework or neither. The dev-dependencies are rusb and rand, and there is a test target named test_class_host with the test harness disabled.

### Is there an async alternative to usb-device?

Yes. The readme lists other implementations of USB in Rust and points at the Embassy project's embassy-usb, which has an async USB stack. That is the relevant choice if your firmware runs on the Embassy executor.

## Sources

- [Issues](https://github.com/rust-embedded-community/usb-device/issues)
- [License: MIT](https://github.com/rust-embedded-community/usb-device/blob/master/LICENSE)
- [README](https://github.com/rust-embedded-community/usb-device/blob/master/README.md)
- [Releases](https://github.com/rust-embedded-community/usb-device/releases)
- [rust-embedded-community/usb-device on GitHub](https://github.com/rust-embedded-community/usb-device)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rust-embedded-community-usb-device
