rp-usb-console
When developing for the Raspberry Pi Pico, you typically have two choices: debugging with a hardware probe or deploying programs directly via USB. The latter is quick and convenient, but it doesn’t allow runtime communication with your application. Embassy’s built-in usb-logger lets you stream logs over USB, yet it lacks a way to send commands back (such as rebooting into BOOTSEL mode). rp-usb-console fills this gap by providing a lightweight, zero-heap solution for both logging and command exchange over USB, designed specifically for the RP2040 and the Embassy async framework. Initially, I created this library for my own use.
Quick start

Command list
- "/LT": set global loglevel to TRACE
- "/LD": set global loglevel to DEBUG
- "/LI": set global loglevel to INFO
- "/LW": set global loglevel to WARN
- "/LE": set global loglevel to ERROR
- "/LO": set global loglevel to OFF
- "/LM <module_filter>,": Set specific loglevel for modules containing module filter
- "BS": Reboot RP2040 to Boot Select (to deploy applications)
- "RS": Reset RP2040
All command must be ended with /r or /n or both.
Features
- Zero-heap logging: Fixed-size 255-byte log message buffers with no dynamic allocation
- USB CDC ACM interface: Standard USB serial communication
- Non-blocking operation: Log messages are dropped if the channel is full to maintain real-time behavior
- Packet fragmentation: Large messages are split into 64-byte USB packets for reliable transmission
- Bidirectional communication: Both logging output and line-buffered command input over USB
- Configurable command sink: Forward commands to your own channel or disable forwarding entirely
- Embassy integration: Designed specifically for Embassy executor and RP2040
Design Goals
- Predictable real-time behavior: No blocking on full channels
- Memory efficiency: Fixed buffers, no heap allocation
- Reliability: Fragmented transmission reduces host latency issues
- Safety: Single-core RP2040 assumptions with proper synchronization
Usage
Add this to your Cargo.toml:
[]
= "0.1.0"
Basic usage example:
use Spawner;
use Channel;
use CriticalSectionRawMutex;
use info;
use USB_READ_BUFFER_SIZE;
// Create a channel for receiving commands from USB
static COMMAND_CHANNEL: = new;
async
async
API Reference
start()
Initialize the USB CDC interface and spawn necessary tasks.
Parameters:
spawner: Embassy task spawnerlevel: Log level filter (e.g.,LevelFilter::Info)usb_peripheral: RP2040 USB peripheralcommand_sender: Optional channel sender for receiving line-buffered USB commands (Nonedisables forwarding)
LogMessage
Fixed-size message buffer with USB packet fragmentation support.
- Size: 255 bytes maximum
- Truncation: Messages exceeding capacity are truncated with ellipsis
- Fragmentation: Automatically split into 64-byte USB packets
Implementation Details
Memory Management
- No heap allocation: All buffers are statically allocated
- Fixed message size: 255 bytes per log message
- Channel capacity: 4 pending log messages maximum
USB Protocol
- Packet size: 64 bytes per USB transfer
- Fragmentation: Large messages split across multiple packets
- Connection handling: Automatic reconnection support
Threading Model
- Single-core: Designed for RP2040's single-core architecture
- Embassy tasks: Three main tasks handle USB device, TX, and RX operations
- Non-blocking: Log operations never block the caller
Limitations
- Messages longer than 255 bytes are truncated
- Full log channels result in dropped messages
- Currently uses CRLF line endings
- No built-in timestamp support
Future Improvements
- Backpressure metrics (dropped message counter)
- Configurable line ending options (CRLF/LF)
- Optional timestamp formatting
- Extended formatting features
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.