Project 12: USB HID Device - Custom Controller

Make the RP2350 enumerate as a USB HID device and show status on the LCD.

Quick Reference

Attribute Value
Difficulty Level 4: Expert
Time Estimate 2-3 weeks
Main Programming Language C (Alternatives: Rust)
Alternative Programming Languages Rust
Coolness Level Level 4: Hardcore
Business Potential 3. The “Product” Level
Prerequisites USB basics, Project 1 display bring-up
Key Topics USB enumeration, HID descriptors, endpoint polling

1. Learning Objectives

By completing this project, you will:

  1. Implement USB descriptors for a HID device.
  2. Define HID report formats for buttons/axes.
  3. Send periodic HID reports and handle host polling.
  4. Display connection and input status on the LCD.
  5. Debug enumeration issues with logs and descriptor checks.

2. All Theory Needed (Per-Concept Breakdown)

2.1 USB Enumeration and Descriptors

Fundamentals

USB enumeration is the process where a host detects a device, reads its descriptors, and assigns it an address. Descriptors are structured data blocks that describe the device class, interfaces, endpoints, and capabilities. For HID devices, the host reads the device descriptor, configuration descriptor, interface descriptor, endpoint descriptors, and HID report descriptor. If any descriptor is malformed, the host will reject the device.

Deep Dive into the concept

When a USB device connects, the host issues a reset and requests the device descriptor at address 0. The device responds with its vendor ID (VID), product ID (PID), and class information. Next, the host requests the configuration descriptor, which includes one or more interfaces. For HID, an interface descriptor points to a HID descriptor, which in turn points to the HID report descriptor. The report descriptor defines the data format for input and output reports: buttons, axes, and their sizes. Endpoints define how data is transferred. HID devices usually use interrupt endpoints with a polling interval defined in the endpoint descriptor.

Enumeration also includes setting the device address and configuration. You must respond correctly to standard requests like GET_DESCRIPTOR, SET_ADDRESS, and SET_CONFIGURATION. Libraries like TinyUSB implement this, but you should understand the flow to debug issues. Common problems include incorrect descriptor lengths, mismatched report sizes, and invalid endpoint addresses. A deterministic test is to use a known-good descriptor template and change only one field at a time.

How this fits on projects

Descriptors are central in Section 3.2 and Section 5.10 Phase 1. They also relate to Project 10 (bare-metal register access) and Project 4 (timing-sensitive IO). Also used in: Project 10, Project 4.

Definitions & key terms

  • Descriptor -> Structured data that describes the device.
  • Enumeration -> Host discovery and configuration process.
  • VID/PID -> Vendor and Product IDs.
  • Endpoint -> Data channel between host and device.

Mental model diagram (ASCII)

Host -> GET_DESCRIPTOR -> Device
Host -> SET_ADDRESS -> Device
Host -> SET_CONFIGURATION -> Device

How it works (step-by-step)

  1. Host resets device.
  2. Host requests device descriptor.
  3. Host assigns address.
  4. Host requests configuration + HID report descriptor.
  5. Host sets configuration and begins polling.

Failure modes:

  • Wrong descriptor length -> enumeration fails.
  • Endpoint mismatch -> host ignores device.

Minimal concrete example

const tusb_desc_device_t desc_device = {
  .idVendor = 0xCafe,
  .idProduct = 0x4001,
  .bDeviceClass = 0x00,
};

Common misconceptions

  • “VID/PID can be anything.” -> Use assigned IDs or test IDs responsibly.
  • “Only report descriptor matters.” -> Device and configuration descriptors must match.

Check-your-understanding questions

  1. What is the purpose of a HID report descriptor?
  2. Why does the host need endpoint descriptors?
  3. What happens if descriptor lengths are wrong?

Check-your-understanding answers

  1. It defines the format of HID reports.
  2. It tells the host how to communicate with the device.
  3. Enumeration fails or device is rejected.

Real-world applications

  • USB keyboards, mice, controllers
  • HID-based macro pads

Where you’ll apply it

  • This project: Section 3.2, Section 5.10 Phase 1
  • Also used in: Project 10

References

  • “USB Complete” by Jan Axelson
  • USB HID specification

Key insights

USB devices succeed or fail based on descriptor correctness.

Summary

Enumeration is a strict handshake: one bad descriptor breaks everything.

Homework/Exercises to practice the concept

  1. Modify a HID report descriptor and validate it.
  2. Capture enumeration logs with a USB analyzer.
  3. Change VID/PID and observe host behavior.

Solutions to the homework/exercises

  1. Use a descriptor validator tool.
  2. Logs show GET_DESCRIPTOR requests.
  3. OS may treat the device as new hardware.

2.2 HID Reports and Polling Intervals

Fundamentals

HID reports are structured packets sent between device and host. For a game controller, a report might contain button states and axis values. The report descriptor tells the host how to interpret the bytes. The host polls the device at a fixed interval (e.g., 1 ms or 10 ms). You must send reports at or below this rate to avoid missed updates.

Deep Dive into the concept

A HID report is a compact representation of input state. For example, a report could be 1 byte for buttons and 2 bytes for X/Y axes. Each bit or byte corresponds to a usage defined in the report descriptor. The host’s USB stack polls the device on the interrupt endpoint; the polling interval is specified in the endpoint descriptor. If you send reports faster than the polling rate, they will be dropped. If you send slower, the host will reuse the last state. A deterministic approach is to send a report at each update tick, aligned to the poll interval.

You must also handle report IDs if you define multiple report types. For a simple device, use a single report ID (or none). When building a macro keypad, you can map each button to a bit. For analog axes, scale values to the defined logical range (e.g., -127 to 127). Debugging HID reports often requires a host-side tool (hid-recorder, USBlyzer) to confirm bytes. Misaligned reports cause strange behavior like stuck buttons or inverted axes.

How this fits on projects

HID report handling is used in Section 3.2 and Section 5.10 Phase 2. It also connects to Project 11 (game input design). Also used in: Project 11.

Definitions & key terms

  • HID report -> Data packet describing input state.
  • Polling interval -> Host-defined request frequency.
  • Usage -> HID-defined meaning of a report field.
  • Logical range -> Min/max values for axes.

Mental model diagram (ASCII)

Report bytes: [buttons][x][y]
Host polls every 10 ms

How it works (step-by-step)

  1. Build report structure from inputs.
  2. Pack into bytes according to descriptor.
  3. Send report when host polls.
  4. Host interprets bytes as inputs.

Failure modes:

  • Wrong report size -> host ignores input.
  • Mismatched ranges -> erratic controls.

Minimal concrete example

typedef struct {
  uint8_t buttons;
  int8_t x;
  int8_t y;
} hid_report_t;

Common misconceptions

  • “You can send reports anytime.” -> Must align with polling.
  • “Report size is flexible.” -> Must match descriptor exactly.

Check-your-understanding questions

  1. What defines the structure of a HID report?
  2. Why does polling interval matter?
  3. What happens if report size mismatches?

Check-your-understanding answers

  1. The HID report descriptor.
  2. Host only accepts input at polling intervals.
  3. Host ignores or misinterprets reports.

Real-world applications

  • Game controllers
  • Macro keyboards and control panels

Where you’ll apply it

  • This project: Section 3.2, Section 5.10 Phase 2
  • Also used in: Project 11

References

  • USB HID specification
  • TinyUSB HID examples

Key insights

HID success depends on matching report format and poll rate.

Summary

Define a clean report format and respect host polling timing.

Homework/Exercises to practice the concept

  1. Create a report descriptor for 4 buttons and 2 axes.
  2. Verify report length with a host tool.
  3. Add a second report ID for LEDs.

Solutions to the homework/exercises

  1. Use 1 byte for buttons, 2 bytes for axes.
  2. Host tool should show correct byte length.
  3. Add Report ID and update descriptor accordingly.

3. Project Specification

3.1 What You Will Build

A USB HID device that enumerates as a game controller or macro keypad. The LCD shows connection status and real-time input values.

3.2 Functional Requirements

  1. USB descriptors for HID device.
  2. HID report format with buttons and axes.
  3. Report sending at fixed poll interval.
  4. LCD status UI with connection state.

3.3 Non-Functional Requirements

  • Performance: stable reports at 10 ms interval.
  • Reliability: enumeration works on Windows, macOS, Linux.
  • Usability: easy to add/remove buttons.

3.4 Example Usage / Output

USB: Connected
Buttons: 0x03
Axes: X=12 Y=-8

3.5 Data Formats / Schemas / Protocols

  • HID report: 1 byte buttons, 2 bytes axes

3.6 Edge Cases

  • Descriptor mismatch
  • Host not providing power
  • Report size mismatch

3.7 Real World Outcome

When plugged into a PC, the device appears as a HID controller. Button presses and joystick movements are reflected in the host’s input tester, while the LCD shows live status.

3.7.1 How to Run (Copy/Paste)

cd LEARN_RP2350_LCD_DEEP_DIVE/usb_hid
mkdir -p build
cd build
cmake ..
make -j4
cp usb_hid.uf2 /Volumes/RP2350

3.7.2 Golden Path Demo (Deterministic)

  • Device enumerates within 2 seconds.
  • Host input tester shows button presses.
  • LCD shows “USB: Connected”.

3.7.3 Failure Demo (Deterministic)

  • Modify report length without updating descriptor.
  • Expected: host ignores input.
  • Fix: update descriptor to match report size.

4. Solution Architecture

4.1 High-Level Design

[Inputs] -> [HID Report Builder] -> [USB Stack] -> Host
                  |
                  -> LCD Status

4.2 Key Components

| Component | Responsibility | Key Decisions | |———–|—————-|—————| | USB stack | Enumeration + endpoints | Use TinyUSB | | Report builder | Pack buttons/axes | 1-byte buttons | | UI | LCD status | Connection + values |

4.3 Data Structures (No Full Code)

typedef struct { uint8_t buttons; int8_t x, y; } hid_report_t;

4.4 Algorithm Overview

Key Algorithm: Report Update

  1. Read buttons/joystick.
  2. Build HID report.
  3. Send report on poll event.
  4. Update LCD status.

Complexity Analysis:

  • Time: O(1) per report
  • Space: O(1)

5. Implementation Guide

5.1 Development Environment Setup

# Use TinyUSB and pico-sdk

5.2 Project Structure

usb_hid/
- src/
  - usb_descriptors.c
  - hid_report.c
  - main.c

5.3 The Core Question You’re Answering

“How do I make a microcontroller enumerate as a standard USB HID device?”

5.4 Concepts You Must Understand First

  1. USB enumeration flow
  2. HID report descriptors
  3. Endpoint polling intervals

5.5 Questions to Guide Your Design

  1. What report format best fits your inputs?
  2. What polling interval is acceptable?
  3. How will you show connection status on LCD?

5.6 Thinking Exercise

Sketch a HID report descriptor for 4 buttons and 2 axes.

5.7 The Interview Questions They’ll Ask

  1. What happens during USB enumeration?
  2. What is a HID report descriptor?
  3. Why is polling interval important?

5.8 Hints in Layers

  • Hint 1: Start with TinyUSB HID example.
  • Hint 2: Use a minimal report (buttons only) before axes.
  • Hint 3: Validate descriptors with a host tool.

5.9 Books That Will Help

| Topic | Book | Chapter | |——-|——|———| | USB basics | “USB Complete” | Ch. 1-4 | | HID | “USB Complete” | Ch. 8 |

5.10 Implementation Phases

Phase 1: Enumeration (4-5 days)

Goals: Device recognized by host. Tasks: Implement descriptors and test on PC. Checkpoint: Appears as HID device.

Phase 2: Reports (4-5 days)

Goals: Send input data. Tasks: Build report and send at interval. Checkpoint: Host input tester shows changes.

Phase 3: UI (3-4 days)

Goals: Display status on LCD. Tasks: Render connection + values. Checkpoint: LCD updates in real time.

5.11 Key Implementation Decisions

| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | USB stack | TinyUSB vs custom | TinyUSB | Faster, stable | | Report format | Small vs rich | Small | Simpler and robust |


6. Testing Strategy

6.1 Test Categories

| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Report packing | button bits | | Integration Tests | Enumeration | host recognition | | Edge Case Tests | Mismatch | descriptor errors |

6.2 Critical Test Cases

  1. Enumeration on Windows/macOS/Linux.
  2. Report size matches descriptor.
  3. Polling at 10 ms without dropped reports.

6.3 Test Data

Buttons: 0b00001111, Axes: X=127, Y=-127

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

| Pitfall | Symptom | Solution | |———|———|———-| | Descriptor length wrong | Device not recognized | Fix lengths | | Report mismatch | Host ignores input | Align sizes | | Poll interval too low | Dropped reports | Use 10 ms |

7.2 Debugging Strategies

  • Use host-side HID tools to inspect reports.
  • Enable TinyUSB debug logs.

7.3 Performance Traps

  • Sending reports faster than host polling wastes CPU.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add LED indicators for connection status.

8.2 Intermediate Extensions

  • Add output reports (host -> device LEDs).

8.3 Advanced Extensions

  • Implement composite device (HID + CDC).

9. Real-World Connections

9.1 Industry Applications

  • Custom controllers, macro pads, foot pedals
  • TinyUSB HID examples

9.3 Interview Relevance

  • USB descriptors and enumeration are advanced embedded topics.

10. Resources

10.1 Essential Reading

  • USB HID specification
  • TinyUSB documentation

10.2 Video Resources

  • USB enumeration walkthroughs

10.3 Tools & Documentation

  • USB protocol analyzer (optional)

11. Self-Assessment Checklist

11.1 Understanding

  • I can describe USB enumeration.
  • I can build a HID report descriptor.

11.2 Implementation

  • Device enumerates on multiple OSes.
  • HID reports update correctly.

11.3 Growth

  • I can explain HID design choices in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Device enumerates as HID.

Full Completion:

  • Reports update and LCD shows status.

Excellence (Going Above & Beyond):

  • Composite HID + CDC device with configuration UI.