summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-06-29 10:16:25 +0700
committerhathach <[email protected]>2026-06-29 10:16:25 +0700
commit4b1c8d16f72bb5d8f2eb8a2e8dde35abdd2f2a88 (patch)
tree3db175aa3d96c92a44fab764dba59f473096c4b4
parent0a25cc27d7d3699536f6df01e80af3eb0423ce58 (diff)
docs: add build-doc tooling and a README for every example
Documentation tooling: - Add the `build-doc` skill and `tools/build_doc.py` wrapper for local Sphinx builds (clean / -W / open). - Enable Markdown (MyST) in conf.py and auto-collect examples/{device,host,dual}/*/README.md into a 3-level Examples nav (Examples > Device/Host/Dual > example), noting each page's source location and normalizing headings to a single H1. - Remove the stale `.claude/commands/build-doc.md`; point the AGENTS.md Documentation section at the skill. Example docs: - Add a README.md for every device/host/dual example: what it does, USB interface table, notable tusb_config.h settings, generic CMake + Make build steps, and how to try it. - Fold each *_freertos variant into its base README, noting the FreeRTOS source path and any RTOS-specific behavior. Generated docs/examples/ output is git-ignored. Builds clean with `sphinx-build -W`. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
-rw-r--r--.claude/commands/build-doc.md24
-rw-r--r--.claude/skills/build-doc/SKILL.md32
-rw-r--r--.gitignore1
-rw-r--r--AGENTS.md5
-rwxr-xr-xdocs/conf.py81
-rw-r--r--docs/index.rst8
-rw-r--r--docs/requirements.txt1
-rw-r--r--examples/device/audio_4_channel_mic/README.md56
-rw-r--r--examples/device/audio_4_channel_mic_freertos/README.md19
-rw-r--r--examples/device/audio_test/README.md54
-rw-r--r--examples/device/audio_test_freertos/README.md19
-rw-r--r--examples/device/audio_test_multi_rate/README.md55
-rw-r--r--examples/device/board_test/README.md40
-rw-r--r--examples/device/cdc_dual_ports/README.md48
-rw-r--r--examples/device/cdc_msc/README.md58
-rw-r--r--examples/device/cdc_msc_throughput/README.md70
-rw-r--r--examples/device/cdc_uac2/README.md54
-rw-r--r--examples/device/dfu/README.md68
-rw-r--r--examples/device/dfu_runtime/README.md56
-rw-r--r--examples/device/dynamic_configuration/README.md64
-rw-r--r--examples/device/hid_boot_interface/README.md47
-rw-r--r--examples/device/hid_composite/README.md54
-rw-r--r--examples/device/hid_generic_inout/README.md45
-rw-r--r--examples/device/hid_multiple_interface/README.md48
-rw-r--r--examples/device/midi2_device/README.md25
-rw-r--r--examples/device/midi_test/README.md49
-rw-r--r--examples/device/msc_dual_lun/README.md47
-rw-r--r--examples/device/mtp/README.md85
-rw-r--r--examples/device/net_lwip_webserver/README.md58
-rw-r--r--examples/device/printer_to_cdc/README.md15
-rw-r--r--examples/device/uac2_headset/README.md59
-rw-r--r--examples/device/uac2_speaker_fb/README.md57
-rw-r--r--examples/device/usbtmc/README.md53
-rw-r--r--examples/device/video_capture/README.md48
-rw-r--r--examples/device/video_capture_2ch/README.md49
-rw-r--r--examples/device/webusb_serial/README.md54
-rw-r--r--examples/dual/dynamic_switch/README.md11
-rw-r--r--examples/dual/host_hid_to_device_cdc/README.md55
-rw-r--r--examples/dual/host_info_to_device_cdc/README.md56
-rw-r--r--examples/host/bare_api/README.md47
-rw-r--r--examples/host/cdc_msc_hid/README.md59
-rw-r--r--examples/host/device_info/README.md45
-rw-r--r--examples/host/hid_controller/README.md48
-rw-r--r--examples/host/midi2_host/README.md45
-rw-r--r--examples/host/midi_rx/README.md44
-rw-r--r--examples/host/msc_file_explorer/README.md30
-rw-r--r--examples/host/msc_file_explorer_freertos/README.md105
-rwxr-xr-xtools/build_doc.py50
48 files changed, 2000 insertions, 201 deletions
diff --git a/.claude/commands/build-doc.md b/.claude/commands/build-doc.md
deleted file mode 100644
index c9ad9f539..000000000
--- a/.claude/commands/build-doc.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# build-doc
-
-Scan all example READMEs and build the Sphinx documentation.
-
-## Instructions
-
-1. Install docs dependencies:
- ```bash
- pip install -r docs/requirements.txt
- ```
-
-2. Build the docs from the repo root:
- ```bash
- sphinx-build -b html docs docs/_build
- ```
- `conf.py` automatically scans all `examples/{device,host,dual}/*/README.md`, copies them into `docs/examples/`, and regenerates `examples.rst` with the toctree.
-
-3. Use a timeout of at least 60 seconds.
-
-4. After the build completes:
- - Show the build output to the user.
- - Report total warnings and errors.
- - List which example READMEs were discovered and included.
- - If there are errors, suggest fixes.
diff --git a/.claude/skills/build-doc/SKILL.md b/.claude/skills/build-doc/SKILL.md
new file mode 100644
index 000000000..401cb31ad
--- /dev/null
+++ b/.claude/skills/build-doc/SKILL.md
@@ -0,0 +1,32 @@
+---
+name: build-doc
+description: Use when building, previewing, or testing the TinyUSB Sphinx docs locally (docs/ → HTML), chasing Sphinx warnings, understanding how example READMEs get into the docs, or regenerating the auto-generated reference files after adding a board or dependency (boards.rst, dependencies.rst, BoardPresets.json, CMakePresets.json).
+---
+
+# Build TinyUSB Docs
+
+## Build & preview
+
+```bash
+pip install -r docs/requirements.txt # one-time
+python3 tools/build_doc.py -o # build docs/_build/ and open it
+```
+
+`tools/build_doc.py` wraps `sphinx-build`: `-c` clean, `-W` fail on warnings, `-o` open. Raw form: `sphinx-build -b html docs docs/_build`.
+
+- Pages can be `.rst` or `.md` (MyST). Example `README.md`s under `examples/{device,host,dual}/*/` are **auto-collected** at build time into `docs/examples/` + `examples.rst` (both git-ignored) — add/rename an example and just rebuild; edit the source README, never the generated copies.
+- Watch the output for `WARNING:` (broken refs, missing toctree entries).
+
+## Regenerate after adding a board or dependency
+
+Run from the repo root; `docs/reference/*.rst` and the preset JSONs are **generated** — don't hand-edit.
+
+| Added | Run |
+|---|---|
+| Board (`hw/bsp/FAMILY/boards/`) | `python3 tools/gen_doc.py` + `python3 tools/gen_presets.py` |
+| Dependency (edited `tools/get_deps.py`) | `python3 tools/gen_doc.py` |
+
+- `gen_doc.py` → `docs/reference/boards.rst` + `dependencies.rst`. Needs `pandas` + `tabulate` (not in `requirements.txt`) — `pip install pandas tabulate` if it errors.
+- `gen_presets.py` → `hw/bsp/BoardPresets.json` + per-example `CMakePresets.json`.
+
+Then rebuild and `git diff` the regenerated files; commit them with the board/dep change.
diff --git a/.gitignore b/.gitignore
index 8fe09e65f..61358d118 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,6 +58,7 @@ Release
BrowseInfo
.cmake_build
README_processed.rst
+docs/examples/
.worktrees
cmake-metrics/
# Directories fetched by tools/get_deps.py - not to be committed
diff --git a/AGENTS.md b/AGENTS.md
index 25dcdd618..6bd64c377 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -127,10 +127,7 @@ cd test/unit-test && ceedling test:all # or ceedling test:test_fifo
## Documentation
-```bash
-pip install -r docs/requirements.txt
-cd docs && sphinx-build -b html . _build # ~2.5 s
-```
+Sphinx docs in `docs/` (reStructuredText `.rst` or Markdown `.md` via MyST). Use the `build-doc` skill (`.claude/skills/build-doc/SKILL.md`) to build/preview locally (`sphinx-build`) and to regenerate auto-generated files (`tools/gen_doc.py` + `tools/gen_presets.py`) after adding a board or dependency.
## Code Size Metrics
diff --git a/docs/conf.py b/docs/conf.py
index 86ddcf672..4a1a7adc2 100755
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -6,6 +6,7 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import re
+import shutil
from pathlib import Path
# -- Path setup --------------------------------------------------------------
@@ -25,6 +26,7 @@ extensions = [
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx_autodoc_typehints',
+ 'myst_parser', # Markdown (.md) support alongside reStructuredText
]
templates_path = ['_templates']
@@ -79,3 +81,82 @@ def preprocess_readme():
tgt.write_text(content, encoding='utf-8')
preprocess_readme()
+
+
+# scan example READMEs into docs/examples/ and (re)generate examples.rst
+EXAMPLE_GROUPS = ('device', 'host', 'dual')
+
+_HEADING_RE = re.compile(r'^(#{1,6})(\s.*)$')
+_FENCE_RE = re.compile(r'^\s*(```|~~~)')
+
+def _normalize_headings(text):
+ """Make each page a single Sphinx section: promote so the first heading is
+ H1 and demote any later same-or-higher heading to at least H2. Without this,
+ a README that uses flat #### headings (no H1) becomes several top-level
+ sections and each leaks into the sidebar as a separate entry."""
+ lines = text.splitlines(keepends=True)
+ headings, in_fence = [], False
+ for i, line in enumerate(lines):
+ if _FENCE_RE.match(line):
+ in_fence = not in_fence
+ elif not in_fence and _HEADING_RE.match(line):
+ headings.append(i)
+ if not headings:
+ return text
+ delta = 1 - len(_HEADING_RE.match(lines[headings[0]]).group(1))
+ for n, i in enumerate(headings):
+ m = _HEADING_RE.match(lines[i])
+ level = max(1, min(6, len(m.group(1)) + delta))
+ if n > 0:
+ level = max(level, 2)
+ lines[i] = '#' * level + m.group(2) + ('\n' if lines[i].endswith('\n') else '')
+ return ''.join(lines)
+
+def _with_location(text, rel):
+ """Insert a source-location note right after the first H1 so each rendered
+ example page shows which example directory it came from."""
+ note = f"> **Example source:** `{rel}`\n"
+ lines = text.splitlines(keepends=True)
+ for i, line in enumerate(lines):
+ if line.lstrip().startswith("# "):
+ return "".join(lines[:i + 1]) + "\n" + note + "\n" + "".join(lines[i + 1:])
+ return note + "\n" + text # no H1: prepend
+
+def generate_examples_docs():
+ """Copy every examples/{device,host,dual}/*/README.md into
+ docs/examples/<group>/<name>.md (noting its source location) and write a
+ docs/examples/<group>/index.rst landing page per group. index.rst points at
+ those group pages, giving a 3-level sidebar: Examples > Device/Host/Dual >
+ example. Output is rebuilt each run (git-ignored)."""
+ docs_dir = Path(__file__).parent
+ examples_root = docs_dir.parent / "examples"
+ out_dir = docs_dir / "examples"
+
+ # start clean so deleted/renamed examples don't leave stale pages
+ if out_dir.exists():
+ shutil.rmtree(out_dir)
+ (docs_dir / "examples.rst").unlink(missing_ok=True) # remove legacy single-file output
+
+ for group in EXAMPLE_GROUPS:
+ group_out = out_dir / group
+ group_out.mkdir(parents=True, exist_ok=True)
+
+ names = []
+ for readme in sorted((examples_root / group).glob("*/README.md")):
+ name = readme.parent.name
+ rel = f"examples/{group}/{name}"
+ content = _normalize_headings(readme.read_text(encoding='utf-8'))
+ (group_out / f"{name}.md").write_text(_with_location(content, rel), encoding='utf-8')
+ names.append(name)
+
+ # group landing page (Device / Host / Dual) with a toctree of its examples
+ heading = group.capitalize()
+ page = [f"{'*' * len(heading)}\n{heading}\n{'*' * len(heading)}\n"]
+ if names:
+ page.append(".. toctree::\n :maxdepth: 1\n")
+ page.extend(f" {name}" for name in names)
+ else:
+ page.append("No documented examples yet.")
+ (group_out / "index.rst").write_text("\n".join(page) + "\n", encoding='utf-8')
+
+generate_examples_docs()
diff --git a/docs/index.rst b/docs/index.rst
index 39d30a038..6d212d8cb 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,6 +12,14 @@
troubleshooting
.. toctree::
+ :maxdepth: 2
+ :caption: Examples
+
+ examples/device/index
+ examples/host/index
+ examples/dual/index
+
+.. toctree::
:maxdepth: 1
:caption: Project Info
diff --git a/docs/requirements.txt b/docs/requirements.txt
index ad5c89922..c94a3dd30 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,5 @@
sphinx>=5.0
furo>=2020.12.30.b24
sphinx-autodoc-typehints>=1.10
+myst-parser>=4.0
jinja2>=3.0.3
diff --git a/examples/device/audio_4_channel_mic/README.md b/examples/device/audio_4_channel_mic/README.md
new file mode 100644
index 000000000..2f0c2855d
--- /dev/null
+++ b/examples/device/audio_4_channel_mic/README.md
@@ -0,0 +1,56 @@
+# Audio 4-Channel Microphone
+
+A USB Audio Class 2.0 (UAC2) microphone that streams four channels of locally generated test audio to the host.
+
+## What it does
+
+- Enumerates as a UAC2 microphone with 4 input channels at 48 kHz, 16-bit.
+- On startup fills a 1 ms buffer with four distinct waveforms — channel 0 a sawtooth, channel 1 an inverted sawtooth, channel 2 a square wave, channel 3 a sine wave.
+- An audio task writes that buffer to the IN endpoint every 1 ms, simulating data arriving from an I2S source.
+- Handles UAC2 control requests: feature-unit mute/volume, clock-source sample-rate and clock-validity, and input-terminal connector. The endpoint uses IN flow control.
+- Blinks the on-board LED to indicate USB state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | UAC2 audio (control + streaming), 4-channel microphone input |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_AUDIO 1
+#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE 48000
+#define CFG_TUD_AUDIO_ENABLE_EP_IN 1
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 4
+#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // 16-bit
+#define CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL 1
+#define CFG_TUD_AUDIO_EP_SZ_IN TUD_AUDIO_EP_SIZE(TUD_OPT_HIGH_SPEED, CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX)
+#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/device/audio_4_channel_mic_freertos` — identical UAC2 4-channel microphone, with the device, audio (1 ms IN-endpoint writes), and LED-blink work split across FreeRTOS tasks.
+
+## Try it
+
+The device appears as a USB microphone with four channels. On Linux, list it with `arecord -l` and capture with `arecord` (for example `arecord -D hw:CARD=MicNode4Ch -c 4 -f S16_LE -r 48000 test.wav`). The included `src/plot_audio_samples.py` records and plots the generated waveforms (requires `sounddevice` and `matplotlib`).
diff --git a/examples/device/audio_4_channel_mic_freertos/README.md b/examples/device/audio_4_channel_mic_freertos/README.md
deleted file mode 100644
index a99f28bc3..000000000
--- a/examples/device/audio_4_channel_mic_freertos/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# How to build example for Esp32s3
-1. Load idf environment variables (eg. using the esp-idf alias `get_idf` if configured)
-
-2. cd into examples directory
-```
-$ cd /tinyusb/examples/device/audio_4_channel_mic_freertos
-```
-
-3. Run cmake in project directory specifying the board
-```
-$ cmake -DBOARD=espressif_s3_devkitc -B build -G Ninja .
-$ ninja.exe -C build
-```
-
-4. Flash the binary onto the esp32-s3 by copy-paste of the full command output by the esp-idf build system replacing **(PORT)** with eg. /dev/ttyUSB0
-
-eg.
-
-> /home/kaspernyhus/.espressif/python_env/idf4.4_py3.8_env/bin/python ../../../../esp-idf/components/esptool_py/esptool/esptool.py -p /dev/ttyUSB0 -b 460800 --before default_reset --after hard_reset --chip esp32s3 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x0 _build/espressif_s3_devkitc/bootloader/bootloader.bin 0x8000 _build/espressif_s3_devkitc/partition_table/partition-table.bin 0x10000 _build/espressif_s3_devkitc/audio_4_channel_mic_freertos.bin
diff --git a/examples/device/audio_test/README.md b/examples/device/audio_test/README.md
new file mode 100644
index 000000000..735999098
--- /dev/null
+++ b/examples/device/audio_test/README.md
@@ -0,0 +1,54 @@
+# Audio Test Microphone
+
+A minimal USB Audio Class 2.0 (UAC2) microphone that streams a generated test signal, useful for verifying the audio device stack end to end.
+
+## What it does
+
+- Enumerates as a UAC2 microphone with 1 input channel at 48 kHz, 16-bit.
+- An audio task fills a buffer with a continuously incrementing 16-bit ramp counter and writes it to the IN endpoint every 1 ms, simulating data from an I2S source. The counter resets when the streaming interface is closed.
+- Handles UAC2 control requests: feature-unit mute/volume, clock-source sample-rate and clock-validity, and input-terminal connector.
+- Blinks the on-board LED to indicate USB state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | UAC2 audio (control + streaming), 1-channel microphone input |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_AUDIO 1
+#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE 48000
+#define CFG_TUD_AUDIO_ENABLE_EP_IN 1
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1
+#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2 // 16-bit
+#define CFG_TUD_AUDIO_EP_SZ_IN TUD_AUDIO_EP_SIZE(TUD_OPT_HIGH_SPEED, CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE, CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX, CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX)
+#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX CFG_TUD_AUDIO_EP_SZ_IN
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/device/audio_test_freertos` — identical single-channel UAC2 test microphone, with the device, audio, and LED-blink work split across FreeRTOS tasks.
+
+## Try it
+
+The device appears as a single-channel USB microphone. On Linux, list it with `arecord -l` and capture with `arecord` (for example `arecord -D hw:CARD=MicNode -c 1 -f S16_LE -r 48000 test.wav`); the recorded samples form a rising ramp. The included `src/plot_audio_samples.py` records and plots the signal (requires `sounddevice` and `matplotlib`).
diff --git a/examples/device/audio_test_freertos/README.md b/examples/device/audio_test_freertos/README.md
deleted file mode 100644
index 9477fcd78..000000000
--- a/examples/device/audio_test_freertos/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# How to build example for Esp32s3
-1. Load idf environment variables (eg. using the esp-idf alias `get_idf` if configured)
-
-2. cd into examples directory
-```
-$ cd /tinyusb/examples/device/audio_test_freertos
-```
-
-3. Run cmake in project directory specifying the board
-```
-$ cmake -DBOARD=espressif_s3_devkitc -B build -G Ninja .
-$ ninja.exe -C build
-```
-
-4. Flash the binary onto the esp32-s3 by copy-paste of the full command output by the esp-idf build system replacing **(PORT)** with eg. /dev/ttyUSB0
-
-eg.
-
-> /home/kaspernyhus/.espressif/python_env/idf4.4_py3.8_env/bin/python ../../../../esp-idf/components/esptool_py/esptool/esptool.py -p /dev/ttyUSB0 -b 460800 --before default_reset --after hard_reset --chip esp32s3 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x0 _build/espressif_s3_devkitc/bootloader/bootloader.bin 0x8000 _build/espressif_s3_devkitc/partition_table/partition-table.bin 0x10000 _build/espressif_s3_devkitc/audio_test_freertos.bin
diff --git a/examples/device/audio_test_multi_rate/README.md b/examples/device/audio_test_multi_rate/README.md
new file mode 100644
index 000000000..c43abb824
--- /dev/null
+++ b/examples/device/audio_test_multi_rate/README.md
@@ -0,0 +1,55 @@
+# Audio Multi-Rate Microphone
+
+A single-channel USB microphone test example that supports multiple sample rates and adapts its descriptors to the negotiated USB bus speed — UAC1 at Full-Speed and UAC2 at High-Speed.
+
+## What it does
+
+- Enumerates as a single-channel microphone. The configuration descriptor returned depends on link speed: a UAC1 (Audio 1.0) configuration at Full-Speed and a UAC2 (Audio 2.0) configuration at High-Speed. A device-qualifier and other-speed-configuration descriptor are provided so it works at either speed.
+- Supports discrete sample rates of 32 kHz, 48 kHz and 96 kHz. At Full-Speed the rate is selected through the UAC1 endpoint sampling-frequency control; at High-Speed through the UAC2 clock-source frequency range.
+- Offers a 16-bit format; the High-Speed UAC2 configuration adds a second format with 24-bit samples carried in 32-bit slots, selected via the streaming interface's alternate setting.
+- An audio task generates an incrementing ramp signal sized to the current sample rate and sample width, and writes it to the IN endpoint every 1 ms. The ramp resets when the streaming interface is closed.
+- Handles both UAC1 and UAC2 control requests (mute, volume, sample frequency) dispatched on the active audio version.
+- Blinks the on-board LED to indicate USB state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | Audio (control + streaming), 1-channel microphone input — UAC1 at Full-Speed, UAC2 at High-Speed |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_AUDIO 1
+#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 96000
+#define CFG_TUD_AUDIO_FUNC_1_N_FORMATS 2
+#define CFG_TUD_AUDIO_ENABLE_EP_IN 1
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1
+#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_TX 2 // 16-bit in 16-bit slots
+#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX 16
+#define CFG_TUD_AUDIO_FUNC_1_FORMAT_2_N_BYTES_PER_SAMPLE_TX 4 // 24-bit in 32-bit slots (UAC2 only)
+#define CFG_TUD_AUDIO_FUNC_1_FORMAT_2_RESOLUTION_RX 24
+#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX TU_MAX(CFG_TUD_AUDIO10_FUNC_1_FORMAT_1_EP_SZ_IN, TU_MAX(CFG_TUD_AUDIO20_FUNC_1_FORMAT_1_EP_SZ_IN, CFG_TUD_AUDIO20_FUNC_1_FORMAT_2_EP_SZ_IN))
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+The device appears as a single-channel USB microphone. On Linux, list it with `arecord -l` and record at a chosen rate with `arecord`, e.g. `arecord -D hw:CARD=MicNode -c 1 -f S16_LE -r 96000 test.wav`, trying 32000/48000/96000 to exercise rate switching. On a High-Speed host you can also select the 24-bit format (`-f S24_3LE` / `S32_LE` depending on the host). The included `src/plot_audio_samples.py` records and plots the signal.
diff --git a/examples/device/board_test/README.md b/examples/device/board_test/README.md
new file mode 100644
index 000000000..59efe9c21
--- /dev/null
+++ b/examples/device/board_test/README.md
@@ -0,0 +1,40 @@
+# Board Test
+
+A minimal bring-up test that exercises a board's basic I/O without using the USB stack. Both the device and host stacks are disabled (`CFG_TUD_ENABLED`/`CFG_TUH_ENABLED` are `0`), so this is the first thing to run when porting to new hardware.
+
+## What it does
+
+- Blinks the on-board LED. The interval changes with the button: 1000 ms when the button is not pressed, 250 ms while it is held.
+- Prints `Hello from TinyUSB` over `stdout`/UART on each blink.
+- Echoes any character received on UART back out.
+
+(No USB class interfaces are present — this example does not enumerate as a USB device.)
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_ENABLED 0 // device stack disabled
+#define CFG_TUH_ENABLED 0 // host stack disabled
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+Flash the firmware and watch the on-board LED blink. Open the board's UART (serial console) to see `Hello from TinyUSB` printed repeatedly, and type characters to see them echoed back. Press and hold the button to speed up the blink rate.
diff --git a/examples/device/cdc_dual_ports/README.md b/examples/device/cdc_dual_ports/README.md
new file mode 100644
index 000000000..b0d1e5d97
--- /dev/null
+++ b/examples/device/cdc_dual_ports/README.md
@@ -0,0 +1,48 @@
+# CDC Dual Ports
+
+Enumerates as a USB device with two independent CDC virtual serial ports.
+
+## What it does
+
+- Exposes two CDC virtual COM ports.
+- Echoes back any data received on either port to **both** ports: the first port echoes as lower case, the second as upper case.
+- Sends a UART state notification (toggles the DSR line) on each press of the on-board button.
+- Resets the board into the bootloader when the first port is opened at 1200 bps and then disconnected (touch-1200 trigger).
+- Blinks the on-board LED: 250 ms when unmounted, 1000 ms when mounted.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial port 1) |
+| 2–3 | CDC (virtual serial port 2) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 2 // two CDC ports
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+After flashing, two serial ports appear on the host (e.g. `/dev/ttyACM0` and `/dev/ttyACM1` on Linux). Open either one in a terminal and type: characters come back lower-cased on the first port and upper-cased on the second.
diff --git a/examples/device/cdc_msc/README.md b/examples/device/cdc_msc/README.md
new file mode 100644
index 000000000..9fb3881d4
--- /dev/null
+++ b/examples/device/cdc_msc/README.md
@@ -0,0 +1,58 @@
+# CDC + MSC
+
+A composite USB device that exposes **two interfaces at once**:
+
+- **CDC** — a virtual serial port (`/dev/ttyACMx`, or a COM port on Windows) that echoes back everything it receives.
+- **MSC** — a small mass-storage disk that mounts as a removable drive.
+
+This is the canonical TinyUSB example for a multi-interface (composite) device.
+
+## What it does
+
+- **CDC:** reads incoming data and echoes it straight back. Pressing the on-board button sends a serial-state (UART) notification to the host.
+- **MSC:** presents an 8 KB FAT12 RAM disk (16 × 512-byte blocks — the smallest size Windows will mount) labelled `TinyUSB MSC`, containing a single `README.TXT`. The disk lives in RAM, so changes are not persistent.
+- **LED** shows bus state: 250 ms blink = not mounted, 1000 ms = mounted, 2500 ms = suspended.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial) |
+| 2 | MSC (mass storage) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 1
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_MSC_EP_BUFSIZE 512
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/device/cdc_msc_freertos` — identical CDC + MSC behavior, with the device, CDC, and LED-blink work split across FreeRTOS tasks.
+
+## Try it
+
+- **Serial:** open the CDC port (`screen /dev/ttyACM0`, PuTTY, …) and type — characters are echoed back.
+- **Storage:** a small `TinyUSB MSC` drive appears; open `README.TXT` to confirm it mounted.
diff --git a/examples/device/cdc_msc_throughput/README.md b/examples/device/cdc_msc_throughput/README.md
new file mode 100644
index 000000000..3de49b826
--- /dev/null
+++ b/examples/device/cdc_msc_throughput/README.md
@@ -0,0 +1,70 @@
+# CDC + MSC Throughput
+
+A deliberately minimal CDC + MSC composite device for measuring **pure USB bulk
+throughput** — the ceiling set by the USB link and the TinyUSB driver, with no
+backing storage or per-byte work in the way.
+
+## How it measures the ceiling, not storage
+
+- **MSC** advertises a 1 GiB logical disk (2 Mi × 512-byte blocks) but has no real
+ backing store. Writes are discarded; reads zero-fill only the low LBAs the host
+ scans during enumeration (partition table / GPT header) and otherwise return
+ whatever is already in the transfer buffer — so no `memset`/copy cost skews the result.
+- **CDC** drains its RX in `tud_cdc_rx_cb` and sources TX from a static zero filler,
+ so `dd` can push data in either direction over `/dev/ttyACMx`.
+
+The 1 GiB capacity lets `dd` run long enough for the rate to stabilise; high-speed peripherals show the most headroom.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial) |
+| 2 | MSC (mass storage) |
+
+## Configuration
+
+Notable `tusb_config.h` settings (tuned for throughput):
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 1
+#define CFG_TUD_MSC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 4096 : 1024) // large MSC bulk buffer
+#define CFG_TUD_CDC_RX_EPSIZE (TUD_OPT_HIGH_SPEED ? 2*512 : 2*64)
+#define CFG_TUD_CDC_TX_EPSIZE CFG_TUD_CDC_RX_EPSIZE
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 2*512 : 2*64)
+#define CFG_TUD_CDC_TX_BUFSIZE CFG_TUD_CDC_RX_BUFSIZE
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Measuring throughput (Linux)
+
+The MSC disk appears as a raw block device (e.g. `/dev/sdX`); the CDC port as `/dev/ttyACMx`.
+
+```bash
+# MSC read (device → host)
+sudo dd if=/dev/sdX of=/dev/null bs=1M count=256 iflag=direct
+
+# MSC write (host → device, discarded)
+sudo dd if=/dev/zero of=/dev/sdX bs=1M count=256 oflag=direct
+
+# CDC read (device → host)
+dd if=/dev/ttyACM0 of=/dev/null bs=64k count=4096
+```
+
+> Pick the right `/dev/sdX` carefully — writing to the wrong block device destroys data.
diff --git a/examples/device/cdc_uac2/README.md b/examples/device/cdc_uac2/README.md
index 5d120be7d..193060f10 100644
--- a/examples/device/cdc_uac2/README.md
+++ b/examples/device/cdc_uac2/README.md
@@ -1,10 +1,8 @@
-#### Composite CDC + UAC2 on Pico
+# CDC + UAC2
-This example provides a composite CDC + UAC2 device on top of a Raspberry Pi
-Pico board.
+This example provides a composite CDC + UAC2 device.
-
-#### Use Cases
+## Use Cases
- The CDC + UAC2 composite device happens to be important, especially in the
amateur radio community.
@@ -18,35 +16,55 @@ Pico board.
the amateur radio community to build (`homebrew`) radios with similar
functionality as the (expensive) commercial rigs.
- This PR is important in bridging this specific gap between the commercial
- rigs and homebrew equipment.
-
- https://digirig.net/digirig-mobile-rev-1-9/ is a digital interface for
interfacing radios (that lack an inbuilt digital interface) with computers.
Digirig Mobile works brilliantly (is OSS!) and is a big improvement over
traditional digital interfaces (like the SignaLink USB Interface). By using a
- Raspberry Pi Pico powered CDC + UAC2 composite device, we can simplify the
+ CDC + UAC2 composite device, we can simplify the
Digirig Mobile schematic, drastically reduce the manufacturing cost, and
(again) enable the homebrewers community to homebrew a modern digital interface
with ease themselves.
-#### Build Steps
+## Build Steps
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
```
-cd examples/device/cdc_uac2
-export PICO_SDK_PATH=$HOME/pico-sdk
+Make:
-cmake -DFAMILY=rp2040 pico .
+```bash
+make BOARD=raspberry_pi_pico all
+```
-cmake -DFAMILY=rp2040 -DCMAKE_BUILD_TYPE=Debug # use this for debugging
+## USB Descriptors
-make BOARD=raspberry_pi_pico all
+| Interface | Class driver |
+|-----------|--------------|
+| 0–2 | UAC2 audio (control, speaker streaming, mic streaming) |
+| 3–4 | CDC (virtual serial) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_AUDIO 1
+#define CFG_TUD_AUDIO_FUNC_1_N_FORMATS 2 // two audio formats
+#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 96000 // 48000 on Renesas RX
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
```
+## How to use
+
+After flashing, the device enumerates as both a USB sound card and a serial port:
-#### Development Notes
+- **Audio:** appears as a standard UAC2 sound card — list it with `arecord -l` / `aplay -l` on Linux, or find it in the OS sound settings. Audio is sourced/sunk by the example's `audio_task`.
+- **Serial:** the CDC port shows up as `/dev/ttyACMx` (Linux/macOS) or a COM port (Windows); open it with any terminal.
-Please try to keep this code synchronized with the `uac2_headset` example
-included in this repository.
+In the amateur-radio setup, digital-mode software (WSJT-X, fldigi, …) uses the sound card for audio and the serial port for CAT/PTT control — both over the one cable.
diff --git a/examples/device/dfu/README.md b/examples/device/dfu/README.md
new file mode 100644
index 000000000..20f8ef917
--- /dev/null
+++ b/examples/device/dfu/README.md
@@ -0,0 +1,68 @@
+# DFU
+
+A USB Device Firmware Upgrade (DFU mode) device with two firmware partitions, demonstrating download, upload and manifestation.
+
+## What it does
+
+- Exposes a DFU interface with two alternate settings, one per simulated partition: alt 0 `FLASH`, alt 1 `EEPROM`.
+- Download (host to device): prints each received byte to stdout and immediately reports flashing complete.
+- Upload (device to host): returns a fixed string per partition (`Hello world from TinyUSB DFU! - Partition 0/1`), single block only.
+- Reports per-partition poll timeouts during download: 1 ms for FLASH (alt 0), 100 ms for EEPROM (alt 1).
+- Logs manifestation, abort and detach events.
+- Provides a BOS / Microsoft OS 2.0 descriptor so Windows binds the WinUSB driver.
+- LED blink rate indicates bus state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | DFU (two alternate settings) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_DFU 1
+#define CFG_TUD_DFU_XFER_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+List the device in DFU mode:
+
+```bash
+dfu-util -l
+```
+
+Download firmware to a partition (a text file works well for this demo):
+
+```bash
+dfu-util -d cafe -a 0 -D firmware.bin # partition 0 (FLASH)
+dfu-util -d cafe -a 1 -D firmware.bin # partition 1 (EEPROM)
+```
+
+Upload from a partition back to the host:
+
+```bash
+dfu-util -d cafe -a 0 -U readback.bin
+dfu-util -d cafe -a 1 -U readback.bin
+```
+
+Downloaded bytes are echoed on the device's stdout; uploads return the partition's fixed string.
diff --git a/examples/device/dfu_runtime/README.md b/examples/device/dfu_runtime/README.md
new file mode 100644
index 000000000..5709b266c
--- /dev/null
+++ b/examples/device/dfu_runtime/README.md
@@ -0,0 +1,56 @@
+# DFU Runtime
+
+A minimal device that exposes a DFU runtime interface, advertising to the host that it can be switched into DFU (bootloader) mode.
+
+## What it does
+
+- Exposes a single DFU runtime interface alongside normal operation.
+- On a DFU_DETACH request, it does not actually reboot into a bootloader; instead it speeds up the LED blink as an indicator (this example is intentionally minimal).
+- Provides a BOS / Microsoft OS 2.0 descriptor so Windows binds the WinUSB driver.
+- LED blink rate indicates bus state (not mounted / mounted / suspended / DFU detach).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | DFU Runtime |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_DFU_RUNTIME 1
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+List the device; it appears in Runtime mode:
+
+```bash
+dfu-util -l
+```
+
+Request a switch to DFU mode:
+
+```bash
+dfu-util -e
+```
+
+This sends a DETACH request. Since the example is minimal it does not enter a real bootloader; it instead changes the LED to a fast blink as confirmation.
diff --git a/examples/device/dynamic_configuration/README.md b/examples/device/dynamic_configuration/README.md
new file mode 100644
index 000000000..b928f4f04
--- /dev/null
+++ b/examples/device/dynamic_configuration/README.md
@@ -0,0 +1,64 @@
+# Dynamic Configuration
+
+Demonstrates returning different device and configuration descriptors at enumeration time, selected by the on-board button. The button is sampled when the host requests the device descriptor, so the device enumerates as one of two completely different personalities depending on whether the button is held while plugging in.
+
+## What it does
+
+- Reads the on-board button when the host fetches the device descriptor to choose the active configuration.
+- **Button not pressed** — enumerates as CDC + MIDI:
+ - CDC echoes back received data (and appends a newline after each carriage return); prints a banner when the terminal connects.
+ - MIDI continuously plays a fixed note sequence, and drains/discards any incoming MIDI.
+- **Button pressed** — enumerates as MSC: presents a small read/write FAT12 RAM disk containing a `README.TXT` file.
+- Blinks the on-board LED to reflect bus state: 250 ms unmounted, 1000 ms mounted, 2500 ms suspended.
+
+## USB Descriptors
+
+Two alternate configurations are served, chosen by the button at enumeration.
+
+Button not pressed (CDC + MIDI):
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial) |
+| 2–3 | MIDI |
+
+Button pressed (MSC):
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | MSC (mass storage) |
+
+## Configuration
+
+Notable `tusb_config.h` settings (both personalities share one config):
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 1
+#define CFG_TUD_MIDI 1
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_MSC_EP_BUFSIZE 512
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+Plug the board in normally: a serial port and a MIDI device appear on the host. Now hold the on-board button while plugging in (or while resetting): the device instead enumerates as a removable drive with a `README.TXT` file. The two personalities use different product IDs so the host treats them as distinct devices.
diff --git a/examples/device/hid_boot_interface/README.md b/examples/device/hid_boot_interface/README.md
new file mode 100644
index 000000000..fbce10514
--- /dev/null
+++ b/examples/device/hid_boot_interface/README.md
@@ -0,0 +1,47 @@
+# HID Boot Keyboard and Mouse
+
+A composite USB HID device that exposes a boot-protocol keyboard and a boot-protocol mouse as two separate interfaces.
+
+## What it does
+
+- Presents two HID interfaces: a boot keyboard (interface 0) and a boot mouse (interface 1).
+- Polls the board button every 10 ms. While the button is held, the keyboard sends the Right Arrow keycode and the mouse moves diagonally (+5, +5); releasing the button sends an empty keyboard report.
+- Uses the HID boot protocol, so the device works even before an OS HID driver loads (e.g. in a PC BIOS/UEFI setup).
+- If the device is suspended, pressing the button issues a USB remote wakeup.
+- The LED blinks to indicate USB state (250 ms not mounted, 1000 ms mounted, 2500 ms suspended). When the host turns on Caps Lock, the LED is driven solid on via the keyboard's OUTPUT report.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | HID (boot keyboard) |
+| 1 | HID (boot mouse) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_HID 2 // boot keyboard + boot mouse
+#define CFG_TUD_HID_EP_BUFSIZE 8
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+After flashing, the board enumerates as a keyboard and a mouse. Press and hold the button: the host receives repeated Right Arrow key presses and the pointer drifts toward the bottom-right. Because it uses the boot protocol, the keyboard also works in a BIOS/UEFI menu.
diff --git a/examples/device/hid_composite/README.md b/examples/device/hid_composite/README.md
new file mode 100644
index 000000000..bfa2de63d
--- /dev/null
+++ b/examples/device/hid_composite/README.md
@@ -0,0 +1,54 @@
+# HID Composite
+
+A single USB HID interface that combines several HID functions using report IDs: keyboard, mouse, stylus pen, consumer control, and gamepad.
+
+## What it does
+
+- Presents one HID interface carrying five report types, distinguished by report ID: keyboard, mouse, stylus pen, consumer control, and gamepad.
+- Polls the board button every 10 ms and, on each cycle, sends a chain of reports (the next report is queued from `tud_hid_report_complete_cb`):
+ - Keyboard: while the button is held, sends the `A` keycode; releasing sends an empty report.
+ - Mouse: moves diagonally (+5, +5) each cycle.
+ - Stylus pen: while the button is held, reports tip-switch + in-range at position (100, 100).
+ - Consumer control: while the button is held, sends Volume Decrement; releasing sends a release report.
+ - Gamepad: while the button is held, sets the hat to Up and presses button A; releasing recenters.
+- If the device is suspended, pressing the button issues a USB remote wakeup.
+- The LED blinks to indicate USB state (250 ms not mounted, 1000 ms mounted, 2500 ms suspended). When the host turns on Caps Lock, the LED is driven solid on via the keyboard's OUTPUT report.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | HID (keyboard + mouse + stylus pen + consumer control + gamepad, composite report IDs) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_HID 1
+#define CFG_TUD_HID_EP_BUFSIZE 16
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/device/hid_composite_freertos` — the USB device and HID logic run as FreeRTOS tasks (LED via a software timer). It exposes four report types (keyboard, mouse, consumer control, gamepad) and, unlike this example, omits the stylus-pen report.
+
+## Try it
+
+After flashing, the board enumerates as a single HID device that the host recognizes as a keyboard, mouse, consumer-control, gamepad, and stylus. Press and hold the button to type `A`, decrease the volume, press gamepad button A, and report stylus contact; the mouse pointer drifts toward the bottom-right continuously.
diff --git a/examples/device/hid_generic_inout/README.md b/examples/device/hid_generic_inout/README.md
new file mode 100644
index 000000000..fc0e09712
--- /dev/null
+++ b/examples/device/hid_generic_inout/README.md
@@ -0,0 +1,45 @@
+# HID Generic In/Out
+
+A USB HID device that exchanges raw IN/OUT reports over a vendor-defined usage page, with no standard keyboard/mouse usage. It simply echoes back whatever the host sends.
+
+## What it does
+
+- Presents one HID interface built from the generic in/out report descriptor template (vendor usage page), with both an IN and an OUT endpoint.
+- Whenever the host sends data on the OUT endpoint (or via SET_REPORT), the device echoes the same bytes back to the host via `tud_hid_report`.
+- The LED blinks to indicate USB state (250 ms not mounted, 1000 ms mounted, 2500 ms suspended).
+- Because the reports carry no standard HID usage, the device is meant to be driven by a host-side tool rather than recognized as a keyboard/mouse. The example ships with `hid_test.js` (node-hid) and `hid_test.py` (Python `hid`) scripts to send and receive data.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | HID (generic in/out, raw vendor reports) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_HID 1
+#define CFG_TUD_HID_EP_BUFSIZE 64
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+After flashing, run the bundled host tool — `node hid_test.js` or `python3 hid_test.py` — to send a buffer to the device and observe the same data echoed back.
diff --git a/examples/device/hid_multiple_interface/README.md b/examples/device/hid_multiple_interface/README.md
new file mode 100644
index 000000000..f8e7aa5ed
--- /dev/null
+++ b/examples/device/hid_multiple_interface/README.md
@@ -0,0 +1,48 @@
+# HID Multiple Interfaces
+
+A composite USB device with two separate HID interfaces — one keyboard and one mouse — each with its own report descriptor, endpoint, and interface string.
+
+## What it does
+
+- Presents two independent HID interfaces: a keyboard (interface 0, "Keyboard Interface") and a mouse (interface 1, "Mouse Interface").
+- Polls the board button every 10 ms:
+ - Keyboard: while the button is held, sends the `A` keycode; releasing sends an empty report.
+ - Mouse: while the button is held, moves diagonally (+5, +5).
+- If the device is suspended, pressing the button issues a USB remote wakeup.
+- The LED blinks to indicate USB state (250 ms not mounted, 1000 ms mounted, 2500 ms suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | HID (keyboard) |
+| 1 | HID (mouse) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_HID 2 // keyboard + mouse
+#define CFG_TUD_HID_EP_BUFSIZE 8
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+After flashing, the board enumerates as two distinct HID devices, a keyboard and a mouse. Press and hold the button: the host receives repeated `A` key presses and the pointer drifts toward the bottom-right.
diff --git a/examples/device/midi2_device/README.md b/examples/device/midi2_device/README.md
index 1731dba57..3d140fce8 100644
--- a/examples/device/midi2_device/README.md
+++ b/examples/device/midi2_device/README.md
@@ -14,29 +14,40 @@ native UMP (Universal MIDI Packet) format with full MIDI 2.0 expression.
- Program Change with Bank Select
- JR Timestamps
-## USB Descriptor
+## USB Descriptors
The device exposes both USB-MIDI 1.0 (Alt Setting 0) and USB-MIDI 2.0 (Alt Setting 1)
as required by the USB-MIDI 2.0 specification. A MIDI 2.0 capable host (e.g. Windows
MIDI Services) will select Alt Setting 1 for native UMP transport. Legacy hosts use
Alt Setting 0 with automatic MIDI 1.0 fallback.
-## Hardware
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | MIDI 2.0 (audio control + MIDI streaming) |
-- Any RP2040 board with USB (e.g. Raspberry Pi Pico)
-- LED on GPIO 25: steady = playing, slow blink = waiting for host
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_MIDI2 1
+```
## Building
+CMake:
+
```bash
mkdir build && cd build
-cmake -DBOARD=raspberry_pi_pico -DPICO_SDK_FETCH_FROM_GIT=on -G Ninja ..
+cmake -DBOARD=raspberry_pi_pico ..
cmake --build .
```
-## Flashing
+Make:
-Hold BOOTSEL, connect USB, drag `midi2_device.uf2` to the RPI-RP2 drive.
+```bash
+make BOARD=raspberry_pi_pico all
+```
## Testing
diff --git a/examples/device/midi_test/README.md b/examples/device/midi_test/README.md
new file mode 100644
index 000000000..001d16bbd
--- /dev/null
+++ b/examples/device/midi_test/README.md
@@ -0,0 +1,49 @@
+# MIDI Test
+
+A USB MIDI device that continuously plays a fixed melody, useful for verifying USB MIDI enumeration and streaming.
+
+## What it does
+
+- Sends a repeating sequence of note-on / note-off messages (cable 0, channel 1) about every 286 ms.
+- Drains any incoming MIDI packets (read and discarded) so the host sender never blocks on a full input port.
+- Blinks the board LED to indicate USB state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | MIDI (Audio Control + MIDI Streaming) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_MIDI 1
+#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/device/midi_test_freertos` — identical MIDI melody playback, with the device, MIDI, and LED-blink work split across FreeRTOS tasks.
+
+## Try it
+
+The device shows up as a MIDI port. On Linux, list ports with `aseqdump -l`, then watch the incoming stream with `aseqdump -p <client:port>` (you should see a steady run of note on/off events), or route the port into a synth such as FluidSynth/qsynth to hear the melody.
diff --git a/examples/device/msc_dual_lun/README.md b/examples/device/msc_dual_lun/README.md
new file mode 100644
index 000000000..5433a8737
--- /dev/null
+++ b/examples/device/msc_dual_lun/README.md
@@ -0,0 +1,47 @@
+# MSC Dual LUN
+
+A USB Mass Storage device that presents two logical units (LUNs), each a small in-RAM FAT12 disk.
+
+## What it does
+
+- Exposes two LUNs, each a 8 KB FAT12 RAM disk containing a `README.TXT` file (`TinyUSB 0` and `TinyUSB 1` volumes).
+- Both disks are read/write by default, so host changes are written back to RAM (unless built read-only).
+- Pressing the board button marks LUN1 as "not ready" to simulate removable media being absent (e.g. SD card removed).
+- Reports a custom SCSI inquiry: vendor `TinyUSB`, product `Mass Storage`, revision `1.0`.
+- Runs under no-OS, FreeRTOS or ThreadX (USB device and LED blink as separate tasks under an RTOS).
+- LED blink rate indicates bus state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | MSC (mass storage) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_MSC 1
+#define CFG_TUD_MSC_EP_BUFSIZE 512
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+After plugging in, two removable drives appear on the host, each containing a `README.TXT`. Open or modify the files to exercise reads and writes. Press the board button to make the second drive report "not ready" (medium not present).
diff --git a/examples/device/mtp/README.md b/examples/device/mtp/README.md
new file mode 100644
index 000000000..eafbe8061
--- /dev/null
+++ b/examples/device/mtp/README.md
@@ -0,0 +1,85 @@
+# MTP
+
+A USB Media Transfer Protocol device backed by a small in-RAM filesystem that the host can browse, read from and write to.
+
+## What it does
+
+- Presents a single MTP storage preloaded with two read-only objects: `readme.txt` and `tinyusb.png`.
+- Supports core MTP operations: get device info, open/close session, get storage IDs and storage info, enumerate object handles, get object info, get object and get partial object, and a device property (device friendly name).
+- Lets the host upload one additional object (SendObjectInfo / SendObject) into a 4 KB RAM buffer and delete objects (unless built read-only).
+- Handles MTP class control requests: cancel, device reset and get device status.
+- LED blink rate indicates bus state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | MTP |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_MTP 1
+#define CFG_TUD_MTP_EP_BUFSIZE 512
+#define CFG_TUD_MTP_EP_CONTROL_BUFSIZE 16 // should be enough to hold data in MTP control request
+
+// MTP device info
+#define CFG_TUD_MTP_DEVICEINFO_EXTENSIONS "microsoft.com: 1.0; "
+#define CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS \
+ MTP_OP_GET_DEVICE_INFO, \
+ MTP_OP_OPEN_SESSION, \
+ MTP_OP_CLOSE_SESSION, \
+ MTP_OP_GET_STORAGE_IDS, \
+ MTP_OP_GET_STORAGE_INFO, \
+ MTP_OP_GET_OBJECT_HANDLES, \
+ MTP_OP_GET_OBJECT_INFO, \
+ MTP_OP_GET_OBJECT, \
+ MTP_OP_GET_PARTIAL_OBJECT, \
+ MTP_OP_DELETE_OBJECT, \
+ MTP_OP_SEND_OBJECT_INFO, \
+ MTP_OP_SEND_OBJECT, \
+ MTP_OP_RESET_DEVICE, \
+ MTP_OP_GET_DEVICE_PROP_DESC, \
+ MTP_OP_GET_DEVICE_PROP_VALUE, \
+ MTP_OP_SET_DEVICE_PROP_VALUE
+
+#define CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS \
+ MTP_EVENT_OBJECT_ADDED
+
+#define CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES \
+ MTP_DEV_PROP_DEVICE_FRIENDLY_NAME
+
+#define CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS \
+ MTP_OBJ_FORMAT_UNDEFINED, \
+ MTP_OBJ_FORMAT_ASSOCIATION, \
+ MTP_OBJ_FORMAT_TEXT, \
+ MTP_OBJ_FORMAT_PNG
+
+#define CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS \
+ MTP_OBJ_FORMAT_UNDEFINED, \
+ MTP_OBJ_FORMAT_ASSOCIATION, \
+ MTP_OBJ_FORMAT_TEXT, \
+ MTP_OBJ_FORMAT_PNG
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+The device enumerates as an MTP device and shows up in a file browser (e.g. the Files/Explorer app, or `mtp-detect` / `mtp-files` from libmtp). You should see `readme.txt` and `tinyusb.png`; you can copy a small file onto the device and delete files.
diff --git a/examples/device/net_lwip_webserver/README.md b/examples/device/net_lwip_webserver/README.md
new file mode 100644
index 000000000..b4d429100
--- /dev/null
+++ b/examples/device/net_lwip_webserver/README.md
@@ -0,0 +1,58 @@
+# Network Web Server (lwIP)
+
+A USB virtual network adapter that runs a small lwIP stack on the device, serving DHCP, DNS, and a web page over USB.
+
+## What it does
+
+- Brings up a USB network interface. Depending on the target MCU the build is either CDC-NCM (the default for most MCUs) or a dual RNDIS + CDC-ECM device that offers two configurations and lets the host pick its preferred one (Windows uses RNDIS, macOS uses CDC-ECM, Linux works with either). The NCM build also ships a BOS / Microsoft OS 2.0 descriptor so Windows auto-loads its NCM driver.
+- The device takes IP address 192.168.7.1 and runs a DHCP server (handing out 192.168.7.2–192.168.7.4), a DNS server (resolves `tiny.usb`), and an HTTP server.
+- Pressing the board button toggles the network link state (up/down) to simulate an Ethernet cable being unplugged/plugged.
+- On higher-RAM MCUs it also starts an iperf TCP server for throughput testing.
+- Blinks the board LED to indicate USB state (not mounted / mounted / suspended).
+
+## Requirements
+
+- Depends on the lwIP TCP/IP stack, fetched as a dependency (e.g. `python3 tools/get_deps.py`).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | Network (CDC-NCM, or CDC-ECM/RNDIS) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+// Network driver selection: NCM is the default; USE_ECM defaults to 0 but is
+// forced to 1 on some MCUs (LPC15xx/40xx/51uxx/54, SAMD21/SAML2x, STM32F0/F1).
+#define CFG_TUD_ECM_RNDIS USE_ECM // 0 by default -> ECM/RNDIS off
+#define CFG_TUD_NCM (1 - CFG_TUD_ECM_RNDIS) // 1 by default -> NCM on
+
+// NCM tuning (see ncm.h for performance notes)
+#define CFG_TUD_NCM_IN_NTB_MAX_SIZE 2048
+#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 4096 // 2048 on low-RAM MCUs
+#define CFG_TUD_NCM_OUT_NTB_N 1
+#define CFG_TUD_NCM_IN_NTB_N 1
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+A new USB network interface appears on the host. It is normally assigned an address in 192.168.7.x by the device's DHCP server (otherwise give the host NIC a static 192.168.7.x address), then browse to http://192.168.7.1 to load the served web page.
diff --git a/examples/device/printer_to_cdc/README.md b/examples/device/printer_to_cdc/README.md
index ecb9a678f..efba70ffb 100644
--- a/examples/device/printer_to_cdc/README.md
+++ b/examples/device/printer_to_cdc/README.md
@@ -7,13 +7,26 @@ This example demonstrates a USB composite device with a Printer class interface
This is useful for debugging printer class communication or as a reference for implementing printer class devices.
-#### USB Interfaces
+#### USB Descriptors
| Interface | Class | Description |
|-----------|-------|-------------|
| 0 | CDC ACM | Virtual serial port |
| 2 | Printer | USB Printer (bidirectional, protocol 2) |
+#### Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_PRINTER 1
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_PRINTER_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_PRINTER_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+```
+
#### How to Test
The device exposes two endpoints on the host:
diff --git a/examples/device/uac2_headset/README.md b/examples/device/uac2_headset/README.md
new file mode 100644
index 000000000..be70e4e8e
--- /dev/null
+++ b/examples/device/uac2_headset/README.md
@@ -0,0 +1,59 @@
+# USB Audio Headset
+
+A USB audio headset that combines a stereo speaker output and a mono microphone input, adapting its descriptors to the negotiated bus speed — UAC1 at Full-Speed and UAC2 at High-Speed.
+
+## What it does
+
+- Enumerates as a headset with a stereo speaker (host → device) and a mono microphone (device → host). The configuration returned depends on link speed: a UAC1 configuration at Full-Speed and a UAC2 configuration at High-Speed, with a device-qualifier and other-speed-configuration descriptor so it works at either speed.
+- Loops audio back: the audio task reads the speaker stream, mixes the two channels down to one, and writes it to the microphone IN endpoint, so whatever is played to the headset is heard back on its microphone.
+- Supports 44.1 kHz and 48 kHz, with a 16-bit format and (UAC2) a 24-bit-in-32-bit format selected by the streaming interface's alternate setting.
+- Handles UAC1 and UAC2 control requests (mute, volume, sample frequency) dispatched on the active audio version.
+- Reads the on-board button to toggle the speaker volume (0 dB / -30 dB) and notifies the host through the audio interrupt endpoint.
+- Blinks the on-board LED to indicate USB state, with a fast pattern while the speaker is streaming.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | Audio control |
+| 1 | Audio streaming — speaker output (OUT) |
+| 2 | Audio streaming — microphone input (IN) |
+
+(UAC1 at Full-Speed, UAC2 at High-Speed.)
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_AUDIO 1
+#define CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP 1
+#define CFG_TUD_AUDIO_ENABLE_EP_IN 1 // microphone
+#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 // speaker
+#define CFG_TUD_AUDIO_FUNC_1_N_FORMATS 2
+#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 48000
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 1 // mic (mono)
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2 // speaker (stereo)
+#define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_TX 16 // 16-bit
+#define CFG_TUD_AUDIO_FUNC_1_FORMAT_2_RESOLUTION_TX 24 // 24-bit in 32-bit slots (UAC2)
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+The device appears as both a USB playback (speaker) and capture (microphone) device. Play audio to the speaker (e.g. `aplay -D ...` or select it as the system output) and record from the microphone with `arecord` — you should hear the played audio looped back. Press the on-board button to toggle the volume between full scale and -30 dB and watch the host volume control follow.
diff --git a/examples/device/uac2_speaker_fb/README.md b/examples/device/uac2_speaker_fb/README.md
new file mode 100644
index 000000000..d2ddbb029
--- /dev/null
+++ b/examples/device/uac2_speaker_fb/README.md
@@ -0,0 +1,57 @@
+# USB Speaker with Feedback
+
+A stereo USB speaker (output only) that uses an explicit isochronous feedback endpoint to keep the host's sample rate in sync, adapting its descriptors to the negotiated bus speed — UAC1 at Full-Speed and UAC2 at High-Speed.
+
+## What it does
+
+- Enumerates as a stereo speaker (host → device), 16-bit. The configuration returned depends on link speed: a UAC1 configuration at Full-Speed and a UAC2 configuration at High-Speed, with a device-qualifier and other-speed-configuration descriptor so it works at either speed.
+- Declares an explicit asynchronous feedback endpoint and reports rate via the FIFO-count feedback method (`tud_audio_feedback_params_cb`), letting the host adjust how many samples it sends so the device buffer neither under- nor over-runs.
+- The audio task drains the speaker FIFO every 1 ms into a dummy buffer (the data is discarded), reading roughly one millisecond of audio per cycle with a small periodic adjustment for the 44.1 kHz and 88.2 kHz rates.
+- Supports 44.1 kHz and 48 kHz at Full-Speed, plus 88.2 kHz and 96 kHz at High-Speed (UAC2). Handles UAC1 and UAC2 control requests (mute, volume, sample frequency).
+- When `CFG_AUDIO_DEBUG` is enabled (default), adds a HID interface that reports debug telemetry — current sample rate, alternate setting, mute/volume, and FIFO size/count/average — every 1 ms.
+- Blinks the on-board LED to indicate USB state, with a fast pattern while streaming.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | UAC2 audio (control + streaming), stereo speaker output with feedback endpoint |
+| 2 | HID (vendor report) — audio debug telemetry, present only when `CFG_AUDIO_DEBUG` is enabled |
+
+(UAC1 at Full-Speed, UAC2 at High-Speed.)
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_AUDIO 1
+#define CFG_AUDIO_DEBUG 1 // adds HID debug interface
+#define CFG_TUD_HID 1 // present when CFG_AUDIO_DEBUG
+#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1 // speaker
+#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 1
+#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 2 // stereo
+#define CFG_TUD_AUDIO_FUNC_1_RESOLUTION_RX 16 // 16-bit
+#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE_FS 48000
+#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE_HS 96000
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+The device appears as a stereo USB speaker; select it as the system output or play to it with `aplay`/`speaker-test` (the audio is consumed and discarded). The feedback endpoint keeps the host's send rate matched to the device. With `CFG_AUDIO_DEBUG` enabled, `src/audio_debug.py` reads the HID telemetry stream and plots the FIFO level and sample rate.
diff --git a/examples/device/usbtmc/README.md b/examples/device/usbtmc/README.md
new file mode 100644
index 000000000..72248b29d
--- /dev/null
+++ b/examples/device/usbtmc/README.md
@@ -0,0 +1,53 @@
+# USBTMC
+
+A USB Test & Measurement Class (USBTMC) instrument with USB488/SCPI support, behaving like a simple programmable instrument.
+
+## What it does
+
+- Implements a USBTMC interface with USB488 capabilities and advertises SCPI support.
+- Responds to `*IDN?` with an identification string (`TinyUSB,ModelNumber,SerialNumber,FirmwareVer123456`).
+- Buffers other written messages and echoes them back as the query response, after a configurable delay.
+- A `delay <ms>` command adjusts that simulated response delay (0–10000 ms).
+- Maintains an IEEE-488.2 status byte (MAV/SRQ), and handles trigger, clear, and bulk abort requests.
+- Supports the USBTMC indicator-pulse request, which briefly pulses the board LED.
+- Uses an interrupt endpoint in addition to the bulk IN/OUT endpoints.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0 | USBTMC |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_USBTMC 1
+#define CFG_TUD_USBTMC_ENABLE_INT_EP 1
+#define CFG_TUD_USBTMC_ENABLE_488 1
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+The device appears as a USBTMC test-and-measurement instrument. Talk to it with a VISA/SCPI tool (for example PyVISA). The included `visaQuery.py` opens the instrument and sends `*IDN?`:
+
+```bash
+python visaQuery.py
+```
diff --git a/examples/device/video_capture/README.md b/examples/device/video_capture/README.md
new file mode 100644
index 000000000..9c16cd701
--- /dev/null
+++ b/examples/device/video_capture/README.md
@@ -0,0 +1,48 @@
+# Video Capture (UVC)
+
+A USB Video Class (UVC) camera that streams generated color-bar frames.
+
+## What it does
+
+- Presents itself as a UVC camera and streams 128x96 video at 10 fps.
+- By default generates YUY2 (uncompressed) color-bar frames into a RAM frame buffer; build options select alternatives: `CFG_EXAMPLE_VIDEO_READONLY` streams fixed MJPEG images from flash (or YUY2 with `CFG_EXAMPLE_VIDEO_DISABLE_MJPEG`), and `CFG_EXAMPLE_VIDEO_BUFFERLESS` fills the payload on the fly.
+- Uses an isochronous streaming endpoint by default (bulk if `CFG_TUD_VIDEO_STREAMING_BULK` is set).
+- Adopts the frame interval requested by the host at stream commit.
+- Blinks the board LED to indicate USB state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | UVC video (control + streaming) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_VIDEO 1
+#define CFG_TUD_VIDEO_STREAMING 1
+#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256
+#define CFG_TUD_VIDEO_STREAMING_BULK 0 // 0 = isochronous, 1 = bulk streaming
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+The device appears as a webcam. On Linux, find it with `v4l2-ctl --list-devices` and view it with `ffplay /dev/videoN` or any camera app. You should see scrolling color bars.
diff --git a/examples/device/video_capture_2ch/README.md b/examples/device/video_capture_2ch/README.md
new file mode 100644
index 000000000..f55253157
--- /dev/null
+++ b/examples/device/video_capture_2ch/README.md
@@ -0,0 +1,49 @@
+# Dual-Channel Video Capture (UVC)
+
+A USB Video Class (UVC) device that exposes two independent video streaming functions on a single device.
+
+## What it does
+
+- Presents two UVC camera functions, both 128x96 at 10 fps.
+- Stream 0 sends YUY2 (uncompressed) color bars; stream 1 sends MJPEG color-bar frames.
+- Uses bulk streaming endpoints (`CFG_TUD_VIDEO_STREAMING_BULK`).
+- Adopts the frame interval requested by the host at stream commit, per stream.
+- Blinks the board LED to indicate USB state (not mounted / mounted / suspended).
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | UVC video (control + streaming) — YUY2 |
+| 2–3 | UVC video (control + streaming) — MJPEG |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_VIDEO 2 // 2 video control interfaces
+#define CFG_TUD_VIDEO_STREAMING 2 // 2 video streaming interfaces
+#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE 256
+#define CFG_TUD_VIDEO_STREAMING_BULK 1 // bulk streaming endpoints
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+Two webcam devices appear on the host. On Linux, list them with `v4l2-ctl --list-devices` and open each with `ffplay /dev/videoN` or a camera app — one shows YUY2 color bars, the other MJPEG color bars.
diff --git a/examples/device/webusb_serial/README.md b/examples/device/webusb_serial/README.md
new file mode 100644
index 000000000..5ca70f909
--- /dev/null
+++ b/examples/device/webusb_serial/README.md
@@ -0,0 +1,54 @@
+# WebUSB Serial
+
+A WebUSB device that acts as a web serial bridge, reachable directly from a WebUSB-capable browser (e.g. Chrome). It also exposes a standard CDC virtual serial port, and bridges traffic between the two.
+
+## What it does
+
+- Exposes a Vendor (WebUSB) interface plus a CDC virtual serial port.
+- Echoes any data received on either the WebUSB or the CDC interface back to **both** of them.
+- Serves a WebUSB landing-page URL (`example.tinyusb.org/webusb-serial/index.html`) via a BOS descriptor, so a supporting browser can offer to open the page.
+- Provides a Microsoft OS 2.0 descriptor so Windows auto-binds the WinUSB driver to the WebUSB interface.
+- Treats a vendor control request as a connect/disconnect signal from the browser; lights the on-board LED solid while the web serial is connected.
+- Blinks the on-board LED to reflect bus state: 250 ms unmounted, 1000 ms mounted, 2500 ms suspended.
+
+## USB Descriptors
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial) |
+| 2 | Vendor (WebUSB) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_VENDOR 1
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## Try it
+
+After flashing, open the landing page (`https://example.tinyusb.org/webusb-serial/index.html`) in a WebUSB-capable browser such as Chrome, click **Connect**, and select the device — the on-board LED lights solid once connected. Characters typed in the web page are echoed back, and are also mirrored to the CDC serial port (e.g. `/dev/ttyACM0`) and vice versa.
+
+On Linux/macOS you may need to install the udev rules from `examples/device/99-tinyusb.rules` for the browser to access the device.
diff --git a/examples/dual/dynamic_switch/README.md b/examples/dual/dynamic_switch/README.md
index 034787f18..94b161480 100644
--- a/examples/dual/dynamic_switch/README.md
+++ b/examples/dual/dynamic_switch/README.md
@@ -9,6 +9,17 @@ This example demonstrates TinyUSB's dual-role capability by allowing runtime swi
- **Host Mode**: Enumerates connected USB devices and prints device information
- **Dynamic switching**: Deinitializes the current stack and reinitializes in the new mode
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1)
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+```
+
## Usage
1. **Build and flash** the example to your board
diff --git a/examples/dual/host_hid_to_device_cdc/README.md b/examples/dual/host_hid_to_device_cdc/README.md
new file mode 100644
index 000000000..dfa0d75af
--- /dev/null
+++ b/examples/dual/host_hid_to_device_cdc/README.md
@@ -0,0 +1,55 @@
+# Dual: USB HID Host → CDC Device
+
+A dual-role bridge: the board acts as a USB host on one root-hub port and a USB device on another at the same time, forwarding input from a connected HID keyboard/mouse to the host PC as text over a virtual serial port.
+
+## What it does
+
+- Host side: enumerates a connected USB HID device (boot keyboard and/or mouse), including devices behind a hub, and requests its interrupt reports.
+- Device side: presents a single CDC (virtual serial) interface to the PC.
+- Data flow: incoming keyboard reports are converted from HID keycodes to ASCII (with shift handling) and written to the CDC serial port; mouse reports are formatted as `[addr] LMR x y wheel` text lines. HID mount/unmount events are also announced over the CDC port. Data received from the PC on the CDC port is read and discarded (LED control is a TODO).
+
+## USB Descriptors
+
+(DEVICE-side interfaces only)
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1)
+#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX)
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+```
+
+## Requirements
+
+The board needs two usable USB ports: one acting as host (for the HID device) and one acting as device (to the PC).
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+1. Plug a USB keyboard or mouse into the board's host port.
+2. Connect the board's device port to the PC and open the CDC serial port it enumerates (e.g. `/dev/ttyACMx` on Linux, a COM port on Windows).
+3. Type on the keyboard: the characters appear in the serial terminal. Move/click the mouse: lines like `[1] L-- 3 -2 0` appear. Connecting or removing a HID device prints a mount/unmount message.
diff --git a/examples/dual/host_info_to_device_cdc/README.md b/examples/dual/host_info_to_device_cdc/README.md
new file mode 100644
index 000000000..10f1dd0c2
--- /dev/null
+++ b/examples/dual/host_info_to_device_cdc/README.md
@@ -0,0 +1,56 @@
+# Dual: USB Host Device-Info → CDC Device
+
+A dual-role bridge: the board acts as a USB host on one root-hub port and a USB device on another at the same time, printing information about any device plugged into the host port out to the PC over a virtual serial port.
+
+## What it does
+
+- Host side: enumerates any USB device attached to the host port (including devices behind a hub) and captures its device descriptor during enumeration.
+- Device side: presents a single CDC (virtual serial) interface to the PC.
+- Data flow: when a device is mounted on the host port, the board reads its descriptor fields (VID/PID, bcdUSB, class/subclass/protocol, max packet size, configuration count) plus the manufacturer, product, and serial-number string descriptors (UTF-16 converted to UTF-8) and prints them as formatted text to the CDC serial port. Mount and unmount events are also reported. Optionally runs the host/device/main work as FreeRTOS tasks.
+
+## USB Descriptors
+
+(DEVICE-side interfaces only)
+
+| Interface | Class driver |
+|-----------|--------------|
+| 0–1 | CDC (virtual serial) |
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUD_CDC 1
+#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
+#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 256)
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1)
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+```
+
+## Requirements
+
+The board needs two usable USB ports: one acting as host (for the device to inspect) and one acting as device (to the PC).
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+1. Connect the board's device port to the PC and open the CDC serial port it enumerates (e.g. `/dev/ttyACMx` on Linux, a COM port on Windows).
+2. Plug any USB device into the board's host port.
+3. The serial terminal shows a `mounted device N` line followed by a full device-descriptor dump (IDs, strings, and field values). Removing the device prints an `unmounted device N` line.
diff --git a/examples/host/bare_api/README.md b/examples/host/bare_api/README.md
new file mode 100644
index 000000000..1f1341198
--- /dev/null
+++ b/examples/host/bare_api/README.md
@@ -0,0 +1,47 @@
+# Host: Bare API
+
+A USB host example that drives the TinyUSB host stack through its low-level API directly, without the higher-level class-driver application layer.
+
+## What it does
+
+- Enumerates any attached device and, on mount, fetches and prints the full device descriptor (VID/PID, USB version, class, max packet size, etc.) over the debug UART.
+- Reads and prints the manufacturer, product, and serial-number string descriptors (UTF-16 to UTF-8).
+- Fetches and walks the configuration descriptor with a small hand-written parser.
+- For any HID interface found, opens its interrupt IN endpoint with the raw endpoint API (`tuh_edpt_open` / `tuh_edpt_xfer`) and continuously prints the incoming HID reports as raw hex bytes.
+- Blinks the board LED once per second.
+
+## Requirements
+
+The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+#define CFG_TUH_API_EDPT_XFER 1
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+Plug any USB device (a HID keyboard/mouse works well) into the board's USB host port. On attach you should see `Device attached` followed by the full device/string/configuration descriptor dump on the debug UART. If the device exposes a HID interface, its interrupt-IN reports are printed as hex as they arrive (e.g. when you press a key or move the mouse).
diff --git a/examples/host/cdc_msc_hid/README.md b/examples/host/cdc_msc_hid/README.md
new file mode 100644
index 000000000..b59c17678
--- /dev/null
+++ b/examples/host/cdc_msc_hid/README.md
@@ -0,0 +1,59 @@
+# Host: CDC/MSC/HID
+
+A USB host example that enumerates CDC serial, Mass Storage, and HID devices and reports their activity over the debug UART.
+
+## What it does
+
+- CDC (`CFG_TUH_CDC`): bidirectionally bridges the debug console and the attached CDC serial device — bytes typed on the debug UART are written to the device, and bytes received from the device are echoed back to the UART. Also supports common USB-serial adapters (FTDI, CP210x, CH34x, PL2303). On mount it prints the interface info and line coding (set to 115200 8N1 on enumeration).
+- MSC (`CFG_TUH_MSC`): on mount, issues a SCSI Inquiry and prints the drive's vendor/product/revision strings and its capacity (block count, block size, total MB).
+- HID (`CFG_TUH_HID`): receives reports and prints keyboard keystrokes as ASCII, mouse button state and cursor movement, and any other (generic) report as raw hex.
+- Blinks the board LED once per second.
+
+## Requirements
+
+The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_CDC 1
+#define CFG_TUH_CDC_FTDI 1
+#define CFG_TUH_CDC_CP210X 1
+#define CFG_TUH_CDC_CH34X 1
+#define CFG_TUH_CDC_PL2303 1
+#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX)
+#define CFG_TUH_MSC 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_HID_EPIN_BUFSIZE 64
+#define CFG_TUH_HID_EPOUT_BUFSIZE 64
+#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM (CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS)
+#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 }
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/host/cdc_msc_hid_freertos` — identical CDC/MSC/HID host behavior, running the host stack in a FreeRTOS task with a software-timer LED.
+
+## How to use
+
+Plug a USB keyboard, mouse, flash drive, or serial device into the board's USB host port (a USB hub also works, so several can be connected at once). Watch the debug UART: keystrokes and mouse movement appear as you use the input devices, an attached flash drive prints its inquiry/size info, and for a serial device you can type into the debug console to forward characters to it and see its output echoed back.
diff --git a/examples/host/device_info/README.md b/examples/host/device_info/README.md
new file mode 100644
index 000000000..79c359548
--- /dev/null
+++ b/examples/host/device_info/README.md
@@ -0,0 +1,45 @@
+# Host: Device Info
+
+A USB host example that enumerates any attached device and prints its descriptor information over the debug UART. No class drivers are enabled — only the hub driver, so it works with any kind of device.
+
+## What it does
+
+- On mount of any device, fetches and prints the full device descriptor: VID/PID, USB version, device class/subclass/protocol, max packet size, bcdDevice, and number of configurations.
+- Reads and prints the manufacturer, product, and serial-number string descriptors (UTF-16 to UTF-8), falling back to a placeholder serial when none is present.
+- Blinks the board LED, with a faster pattern while no device is mounted.
+- Builds on either the bare main loop or a FreeRTOS task, depending on `CFG_TUSB_OS`.
+
+## Requirements
+
+The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_ENUMERATION_BUFSIZE 256
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+Plug any USB device into the board's USB host port. The debug UART prints the device's `ID vvvv:pppp`, serial number, and the decoded device descriptor (a hub lets you attach several devices, each printed in turn). Unplugging the device prints a removal message.
diff --git a/examples/host/hid_controller/README.md b/examples/host/hid_controller/README.md
new file mode 100644
index 000000000..861f89c5e
--- /dev/null
+++ b/examples/host/hid_controller/README.md
@@ -0,0 +1,48 @@
+# Host: HID Controller
+
+A USB host example that reads a USB game controller / gamepad (a HID device) and prints its inputs over the debug UART.
+
+## What it does
+
+- Enumerates HID devices (`CFG_TUH_HID`) and, on mount, prints the device's VID/PID.
+- Decodes reports from explicitly supported controllers — Sony DualShock 4 and a few compatible PS4 pads (Hori FC4, Hori PS4 Mini, ASW GG xrd) — printing the joystick axes (x, y, z, rz), D-pad direction, and pressed buttons (Square/Cross/Circle/Triangle, L1/R1/L2/R2, Share/Option/L3/R3, PS, touchpad click). Output is only printed when the report changes meaningfully.
+- Sends a periodic rumble output report back to the DualShock 4, with the motor intensities driven by the L2/R2 analog triggers.
+- Blinks the board LED once per second.
+
+Note: events are only shown for the explicitly supported controllers above; other HID devices enumerate but their reports are not decoded.
+
+## Requirements
+
+The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 0
+#define CFG_TUH_HID (3*CFG_TUH_DEVICE_MAX)
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_HID_EP_BUFSIZE 64
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+Plug a supported USB game controller (e.g. a Sony DualShock 4) into the board's USB host port. Move the sticks and press buttons — the changes are printed on the debug UART, and squeezing the L2/R2 triggers makes the controller rumble.
diff --git a/examples/host/midi2_host/README.md b/examples/host/midi2_host/README.md
new file mode 100644
index 000000000..e05bc4f0a
--- /dev/null
+++ b/examples/host/midi2_host/README.md
@@ -0,0 +1,45 @@
+# Host: MIDI 2.0
+
+A minimal USB host example that receives MIDI from a connected USB-MIDI device and prints it over the debug UART, using the USB-MIDI 2.0 host driver (UMP).
+
+## What it does
+
+- Enumerates USB-MIDI devices (`CFG_TUH_MIDI2`) and, on mount, prints the negotiated protocol (MIDI 1.0 / MIDI 2.0) and the number of RX/TX cables.
+- Receives Universal MIDI Packets (UMP) and prints each one, decoding common Channel Voice messages — Note On/Off, Control Change, Program Change, Channel Pressure, Pitch Bend — for both MIDI 2.0 and MIDI 1.0 message types, and falling back to a raw hex dump for anything else.
+
+## Requirements
+
+The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_MIDI2 CFG_TUH_DEVICE_MAX
+#define CFG_TUH_MIDI2_RX_BUFSIZE 512
+#define CFG_TUH_MIDI2_TX_BUFSIZE 512
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+Plug a USB-MIDI device (keyboard, controller, or interface) into the board's USB host port. On attach the debug UART prints the mount/descriptor info; then playing notes or moving controls on the device prints the decoded MIDI messages.
diff --git a/examples/host/midi_rx/README.md b/examples/host/midi_rx/README.md
new file mode 100644
index 000000000..fe29f538e
--- /dev/null
+++ b/examples/host/midi_rx/README.md
@@ -0,0 +1,44 @@
+# Host: MIDI Receive
+
+A USB host example that receives MIDI from a connected USB-MIDI device and prints the incoming bytes over the debug UART.
+
+## What it does
+
+- Enumerates USB-MIDI devices (`CFG_TUH_MIDI`) and, on mount, prints the interface index, device address, and the number of RX/TX cables.
+- On each received MIDI packet, reads the stream and prints the cable number followed by the raw MIDI bytes as hex.
+- Blinks the board LED once per second.
+
+## Requirements
+
+The board must support USB host mode (provide VBUS to the connected device); some boards need an external USB-A port / host adapter.
+
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_MIDI CFG_TUH_DEVICE_MAX
+```
+
+## Building
+
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
+
+```bash
+make BOARD=raspberry_pi_pico all
+```
+
+## How to use
+
+Plug a USB-MIDI device (keyboard, controller, or interface) into the board's USB host port. On attach the debug UART prints the mount info; then playing notes or moving controls prints lines like `Cable 0 rx: 90 3C 7F` with the raw MIDI bytes.
diff --git a/examples/host/msc_file_explorer/README.md b/examples/host/msc_file_explorer/README.md
index e220bedea..b80ca8f6c 100644
--- a/examples/host/msc_file_explorer/README.md
+++ b/examples/host/msc_file_explorer/README.md
@@ -27,16 +27,38 @@ CLI is presented over the board's serial console.
| mv | `mv <src> <dest>` | Rename/move a file or directory |
| rm | `rm <file>` | Remove a file |
+## Configuration
+
+Notable `tusb_config.h` settings:
+
+```c
+#define CFG_TUH_ENABLED 1
+#define CFG_TUH_HUB 1
+#define CFG_TUH_MSC 1
+#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
+#define CFG_TUH_MSC_MAXLUN 4
+```
+
## Build
-Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)):
+CMake:
+
+```bash
+mkdir build && cd build
+cmake -DBOARD=raspberry_pi_pico ..
+cmake --build .
+```
+
+Make:
```bash
-# Example: build for Raspberry Pi Pico
-cmake -B build -DBOARD=raspberry_pi_pico -DFAMILY=rp2040 examples/host/msc_file_explorer
-cmake --build build
+make BOARD=raspberry_pi_pico all
```
+## FreeRTOS variant
+
+A FreeRTOS build is in `examples/host/msc_file_explorer_freertos` — identical MSC FatFS file-explorer CLI, running the host stack and CLI as FreeRTOS tasks.
+
## Usage
1. Flash the firmware to your board.
diff --git a/examples/host/msc_file_explorer_freertos/README.md b/examples/host/msc_file_explorer_freertos/README.md
deleted file mode 100644
index 4ee6b96fa..000000000
--- a/examples/host/msc_file_explorer_freertos/README.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# MSC File Explorer (FreeRTOS)
-
-This host example implements an interactive command-line file browser for USB Mass Storage devices.
-When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like
-CLI is presented over the board's serial console.
-
-## Features
-
-- Automatic mount/unmount of USB storage devices
-- FAT12/16/32 filesystem support via FatFS
-- Interactive CLI with command history
-- Read speed benchmarking with `dd`
-- Support for up to 4 simultaneous USB storage devices (via hub)
-
-## Supported Commands
-
-| Command | Usage | Description |
-|---------|--------------------|------------------------------------------------------|
-| help | `help` | Print list of available commands |
-| cat | `cat <file>` | Print file contents to the console |
-| cd | `cd <dir>` | Change current working directory |
-| cp | `cp <src> <dest>` | Copy a file |
-| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) |
-| ls | `ls [dir]` | List directory contents |
-| pwd | `pwd` | Print current working directory |
-| mkdir | `mkdir <dir>` | Create a directory |
-| mv | `mv <src> <dest>` | Rename/move a file or directory |
-| rm | `rm <file>` | Remove a file |
-
-## Build
-
-Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)):
-
-```bash
-# Example: build for STM32F407 Discovery board
-cmake -B build -DBOARD=stm32f407disco -GNinja examples/host/msc_file_explorer_freertos
-cmake --build build
-```
-
-## Usage
-
-1. Flash the firmware to your board.
-2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud.
-3. Plug a USB flash drive into the board's USB host port.
-4. The device is auto-mounted and the prompt appears:
-
-```
-TinyUSB MSC File Explorer Example
-
-Device connected
- Vendor : Kingston
- Product : DataTraveler 2.0
- Rev : 1.0
- Capacity: 1.9 GB
-
-0:/> _
-```
-
-### Browsing Files
-
-```
-0:/> ls
-----a 1234 readme.txt
-d---- 0 photos
-d---- 0 docs
-
-0:/> cd photos
-0:/photos> ls
-----a 520432 vacation.jpg
-----a 312088 family.png
-
-0:/> cat readme.txt
-Hello from USB drive!
-```
-
-### Copying and Moving Files
-
-```
-0:/> cp readme.txt backup.txt
-0:/> mv backup.txt docs/backup.txt
-```
-
-### Measuring Read Speed
-
-```
-0:/> dd
-Reading 1024 sectors...
- Data speed: 823 KB/s
-```
-
-### Multiple Devices
-
-When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to
-navigate between them:
-
-```
-0:/> cd 1:
-1:/> ls
-```
-
-## Testing
-
-Build-time validation follows the standard TinyUSB host example flow. Runtime behavior should be
-verified on hardware by attaching an MSC device and exercising CLI commands such as `ls`, `pwd`,
-and `dd`.
diff --git a/tools/build_doc.py b/tools/build_doc.py
new file mode 100755
index 000000000..50e52452a
--- /dev/null
+++ b/tools/build_doc.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+"""Build the TinyUSB Sphinx documentation locally.
+
+Thin wrapper around `sphinx-build` so a manual doc build is one command.
+`conf.py` auto-collects example READMEs, so no extra steps are needed.
+
+ python3 tools/build_doc.py # build docs/_build/
+ python3 tools/build_doc.py -c -W -o # clean, fail on warnings, open result
+"""
+import argparse
+import shutil
+import subprocess
+import sys
+import webbrowser
+from pathlib import Path
+
+TOP = Path(__file__).parent.parent.resolve()
+DOCS = TOP / "docs"
+BUILD = DOCS / "_build"
+
+
+def main():
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ p.add_argument("-c", "--clean", action="store_true", help="remove docs/_build first")
+ p.add_argument("-W", "--strict", action="store_true", help="treat warnings as errors")
+ p.add_argument("-o", "--open", action="store_true", help="open the built docs in a browser")
+ args = p.parse_args()
+
+ if args.clean and BUILD.exists():
+ shutil.rmtree(BUILD)
+
+ cmd = ["sphinx-build", "-b", "html"]
+ if args.strict:
+ cmd.append("-W")
+ cmd += [str(DOCS), str(BUILD)]
+
+ print("+", " ".join(cmd))
+ rc = subprocess.call(cmd)
+ if rc != 0:
+ return rc
+
+ index = BUILD / "index.html"
+ print(f"\nDocs built: {index}")
+ if args.open:
+ webbrowser.open(index.as_uri())
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())