TinyUSB is an open-source cross-platform USB Host/Device stack for embedded systems, designed to be memory-safe with no dynamic allocation and thread-safe with all interrupt events deferred to non-ISR task functions.
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
- Keep TinyUSB memory-safe: avoid dynamic allocation, defer ISR work to task context, and follow C99 with two-space indentation/no tabs.
- Match file organization: core stack under
src, MCU/BSP support inhw/{mcu,bsp}, examples underexamples/{device,host,dual}, docs indocs, tests undertest/{unit-test,fuzz,hil}. - Use descriptive snake_case for helpers, reserve
tud_/tuh_for public APIs,TU_for macros, and keep headers self-contained with#if CFG_TUSB_MCUguards where needed. - Prefer
.clang-formatfor C/C++ formatting, runpre-commit run --all-filesbefore submitting, and document board/HIL coverage when applicable. - Commit in imperative mood, keep changes scoped, and supply PRs with linked issues plus test/build evidence.
- Install ARM GCC toolchain:
sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi - Fetch core dependencies:
python3 tools/get_deps.py-- takes <1 second. NEVER CANCEL. - For specific board families:
python3 tools/get_deps.py FAMILY_NAME(e.g., rp2040, stm32f4), orpython3 tools/get_deps.py -b BOARD_NAME - Dependencies are cached in
lib/andhw/mcu/directories - For Espressif boards, initialize the ESP-IDF environment before any build/flash/monitor command:
. $HOME/code/esp-idf/export.sh
Choose ONE of these approaches: Option 1: Individual Example with CMake and Ninja (RECOMMENDED)
cd examples/device/cdc_msc
mkdir -p build && cd build
cmake -DBOARD=raspberry_pi_pico -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel ..
cmake --build .-- takes 1-2 seconds. NEVER CANCEL. Set timeout to 5+ minutes.
Option 2: All Examples for a Board
different folder than Option 1
cd examples/
mkdir -p build && cd build
cmake -DBOARD=raspberry_pi_pico -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel ..
cmake --build .-- takes 15-20 seconds, may have some objcopy failures that are non-critical. NEVER CANCEL. Set timeout to 30+ minutes.
Option 3: Individual Example with Make
cd examples/device/cdc_msc
make BOARD=raspberry_pi_pico all-- takes 2-3 seconds. NEVER CANCEL. Set timeout to 5+ minutes.
Option 4: Espressif Example with ESP-IDF
Only ESP-IDF-enabled examples are supported for Espressif boards. Use FreeRTOS examples such as examples/device/cdc_msc_freertos
that contain idf_component_register() support.
. $HOME/code/esp-idf/export.sh
cd examples/device/cdc_msc_freertos
idf.py -DBOARD=espressif_s3_devkitc buildUse -DBOARD=... with any supported board under hw/bsp/espressif/boards/. NEVER CANCEL. Set timeout to 10+ minutes.
- Debug build:
- CMake:
-DCMAKE_BUILD_TYPE=Debug - Make:
DEBUG=1
- CMake:
- With logging:
- CMake:
-DLOG=2 - Make:
LOG=2
- CMake:
- With RTT logger:
- CMake:
-DLOG=2 -DLOGGER=rtt - Make:
LOG=2 LOGGER=rtt
- CMake:
- RootHub port selection:
- CMake:
-DRHPORT_DEVICE=1 - Make:
RHPORT_DEVICE=1
- CMake:
- Port speed:
- CMake:
-DRHPORT_DEVICE_SPEED=OPT_MODE_FULL_SPEED - Make:
RHPORT_DEVICE_SPEED=OPT_MODE_FULL_SPEED
- CMake:
- Flash with JLink:
- CMake:
ninja cdc_msc-jlink - Make:
make BOARD=raspberry_pi_pico flash-jlink
- CMake:
- Flash with OpenOCD:
- CMake:
ninja cdc_msc-openocd - Make:
make BOARD=raspberry_pi_pico flash-openocd
- CMake:
- Generate UF2:
- CMake:
ninja cdc_msc-uf2 - Make:
make BOARD=raspberry_pi_pico all uf2
- CMake:
- List all targets (CMake/Ninja):
ninja -t targets - Espressif flash:
- Run
. $HOME/code/esp-idf/export.sh cd examples/device/cdc_msc_freertosidf.py -DBOARD=espressif_s3_devkitc flash
- Run
- Espressif serial monitor / chip log output:
- Run
. $HOME/code/esp-idf/export.sh cd examples/device/cdc_msc_freertosidf.py -DBOARD=espressif_s3_devkitc monitor
- Run
Look up the board's JLINK_DEVICE and OPENOCD_OPTION from hw/bsp/*/boards/*/board.cmake (or board.mk).
Terminal 1 – start the GDB server:
JLinkGDBServer -device stm32h743xi -if SWD -speed 4000 \
-port 2331 -swoport 2332 -telnetport 2333 -noguiTerminal 2 – connect GDB:
arm-none-eabi-gdb /tmp/build/firmware.elf
(gdb) target remote :2331
(gdb) monitor reset halt
(gdb) load
(gdb) continueTo break on entry instead of running immediately:
(gdb) monitor reset halt
(gdb) load
(gdb) break main
(gdb) continueTerminal 1 – start the GDB server:
openocd -f interface/stlink.cfg -f target/stm32h7x.cfg
# or with J-Link probe:
openocd -f interface/jlink.cfg -f target/stm32h7x.cfgFor rp2040/rp2350 with a CMSIS-DAP probe (e.g. Picoprobe, debugprobe):
openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000"
# or for rp2350:
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"For boards that define OPENOCD_OPTION in board.cmake, use those options directly:
openocd $(cat hw/bsp/FAMILY/boards/BOARD/board.cmake | grep OPENOCD_OPTION | ...)Terminal 2 – connect GDB (OpenOCD default port is 3333):
arm-none-eabi-gdb /tmp/build/firmware.elf
(gdb) target remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) continue- Build with RTT logging enabled (example):
cd examples/device/cdc_msc && make BOARD=stm32h743eval LOG=2 LOGGER=rtt all - Flash with J-Link:
cd examples/device/cdc_msc && make BOARD=stm32h743eval LOG=2 LOGGER=rtt flash-jlink - Launch GDB server with RTT port (keep this running in terminal 1):
JLinkGDBServer -device stm32h743xi -if SWD -speed 4000 -port 2331 -swoport 2332 -telnetport 2333 -RTTTelnetPort 19021 -nogui - Read RTT output (terminal 2):
JLinkRTTClient - Capture RTT to file (optional):
JLinkRTTClient | tee rtt.log - For non-interactive capture:
timeout 20s JLinkRTTClient > rtt.log
- Install Ceedling:
sudo gem install ceedling - Run all unit tests:
cd test/unit-test && ceedlingorcd test/unit-test && ceedling test:all-- takes 4 seconds. NEVER CANCEL. Set timeout to 10+ minutes. - Run specific test:
cd test/unit-test && ceedling test:test_fifo - Tests use Unity framework with CMock for mocking
-B examplesmeansexamplesis the parent folder that contains multi-board build outputs such asexamples/cmake-build-BOARD_NAME/...- Select config file before running HIL tests:
- if GitHub Actions self-hosted runner service is running, use
tinyusb.json - otherwise use
local.json - example:
HIL_CONFIG=$( (systemctl list-units --type=service --state=running 2>/dev/null; systemctl --user list-units --type=service --state=running 2>/dev/null) | grep -q 'actions\.runner' && echo tinyusb.json || echo local.json )
- if GitHub Actions self-hosted runner service is running, use
- Run tests on actual hardware, one of following ways:
- test a specific board
python test/hil/hil_test.py -b BOARD_NAME -B examples $HIL_CONFIG - test all boards in config
python test/hil/hil_test.py -B examples $HIL_CONFIG
- test a specific board
- In case of error, enabled verbose mode with
-vflag for detailed logs. Also try to observe script output, and try to modify hil_test.py (temporarily) to add more debug prints to pinpoint the issue. - Requires pre-built (all) examples for target boards (see Build Examples section 2)
take 2-5 minutes. NEVER CANCEL. Set timeout to 20+ minutes.
- Install requirements:
pip install -r docs/requirements.txt - Build docs:
cd docs && sphinx-build -b html . _build-- takes 2-3 seconds. NEVER CANCEL. Set timeout to 10+ minutes.
Generate and compare code size metrics to evaluate the impact of changes. This is the most common workflow when making code changes — use it to verify size impact before committing.
Quick single-board metrics (preferred for iterative development):
rm -rf cmake-build
python3 tools/build.py -b raspberry_pi_pico --target all --target tinyusb_metrics
python3 tools/metrics.py combine -j -m -f tinyusb/src cmake-build/cmake-build-*/metrics.jsonThis builds all examples for one board and produces metrics.json + metrics.md. Takes ~30 seconds.
NEVER CANCEL. Set timeout to 10+ minutes.
Comparing with master (before/after workflow):
- On master: build and save baseline
rm -rf cmake-build python3 tools/build.py -b raspberry_pi_pico --target all --target tinyusb_metrics python3 tools/metrics.py combine -j -m -f tinyusb/src cmake-build/cmake-build-*/metrics.json mv metrics.json metrics_master.json - Switch to your branch: rebuild
rm -rf cmake-build python3 tools/build.py -b raspberry_pi_pico --target all --target tinyusb_metrics python3 tools/metrics.py combine -j -m -f tinyusb/src cmake-build/cmake-build-*/metrics.json - Compare:
python3 tools/metrics.py compare -m -f tinyusb/src metrics_master.json metrics.jsonProducesmetrics_compare.mdshowing size differences.
Full CI metrics (all arm-gcc families, for thorough validation):
rm -rf cmake-build
FAMILIES=$(python3 .github/workflows/ci_set_matrix.py | python3 -c "import sys,json; d=json.load(sys.stdin); print(' '.join(d.get('arm-gcc',[])))")
python3 tools/build.py --one-first --target all --target tinyusb_metrics $FAMILIES
python3 tools/metrics.py combine -j -m -f tinyusb/src cmake-build/cmake-build-*/metrics.jsonBuilds the first board of each family. Takes 2-4 minutes. NEVER CANCEL. Set timeout to 10+ minutes.
- Format code:
clang-format -i path/to/file.c(uses.clang-formatconfig) - Check spelling:
pip install codespell && codespell(uses.codespellrcconfig) - Pre-commit hooks validate unit tests and code quality automatically
- Analyze whole project:
pvs-studio-analyzer analyze -f examples/cmake-build-raspberry_pi_pico/compile_commands.json -R .PVS-Studio/.pvsconfig -o pvs-report.log -j12 --dump-files --misra-cpp-version 2008 --misra-c-version 2023 --use-old-parser
- Analyze specific source files:
pvs-studio-analyzer analyze -f examples/cmake-build-raspberry_pi_pico/compile_commands.json -R .PVS-Studio/.pvsconfig -S path/to/file.c -o pvs-report.log -j12 --dump-files --misra-cpp-version 2008 --misra-c-version 2023 --use-old-parser
- Multiple specific files:
pvs-studio-analyzer analyze -f examples/cmake-build-raspberry_pi_pico/compile_commands.json -R .PVS-Studio/.pvsconfig -S src/file1.c -S src/file2.c -o pvs-report.log -j12 --dump-files --misra-cpp-version 2008 --misra-c-version 2023 --use-old-parser
- Requires
compile_commands.jsonin the build directory (generated by CMake with-DCMAKE_EXPORT_COMPILE_COMMANDS=ON) - Use
-foption to specify path tocompile_commands.json - Use
-R .PVS-Studio/.pvsconfigto specify rule configuration file - Use
-j12for parallel analysis with 12 threads --dump-filessaves preprocessed files for debugging--misra-c-version 2023enables MISRA C:2023 checks--misra-cpp-version 2008enables MISRA C++:2008 checks--use-old-parseruses legacy parser for compatibility- Analysis takes ~10-30 seconds depending on project size. Set timeout to 5+ minutes.
- View results:
plog-converter -a GA:1,2 -t errorfile pvs-report.logor open in PVS-Studio GUI
- Pre-commit validation (RECOMMENDED):
pre-commit run --all-files- Install pre-commit:
pip install pre-commit && pre-commit install - Runs all quality checks, unit tests, spell checking, and formatting
- Takes 10-15 seconds. NEVER CANCEL. Set timeout to 15+ minutes.
- Install pre-commit:
- Build validation: Build at least one board with all example that exercises your changes, see Build Examples section (option 2)
- Run unit tests relevant to touched modules; add fuzz/HIL coverage when modifying parsers or protocol state machines.
- Device examples: Cannot be fully tested without real hardware, but must build successfully
- Unit tests: Exercise core stack functionality - ALL tests must pass
- Build system: Must be able to build examples for multiple board families
- STM32F4:
stm32f407disco- no external SDK required, good for testing - RP2040:
raspberry_pi_pico- requires Pico SDK, commonly used - Other families: Check
hw/bsp/FAMILY/boards/for available boards
DO NOT commit files automatically - only modify files and let the maintainer review before committing.
- Bump the release version variable at the top of
tools/make_release.py. - Execute
python3 tools/make_release.pyto refresh:src/tusb_option.h(version defines)repository.yml(version mapping)library.json(PlatformIO version)sonar-project.properties(SonarQube version)docs/reference/boards.rst(generated board documentation)hw/bsp/BoardPresets.json(CMake presets)
- Generate release notes for
docs/info/changelog.rst:- Get commit list:
git log <last-release-tag>..HEAD --oneline - Visit GitHub PRs for merged pull requests to understand context and gather details
- Use GitHub tools to search/read PRs:
github-mcp-server-list_pull_requests,github-mcp-server-pull_request_read - Extract key changes, API modifications, bug fixes, and new features from PR descriptions
- Add new changelog entry following the existing format:
- Version heading with equals underline (e.g.,
0.20.0followed by======) - Release date in italics (e.g.,
*November 19, 2024*) - Major sections: General, API Changes, Controller Driver (DCD & HCD), Device Stack, Host Stack, Testing
- Use bullet lists with descriptive categorization
- Reference function names, config macros, and file paths using RST inline code (double backticks)
- Include meaningful descriptions, not just commit messages
- Version heading with equals underline (e.g.,
- Get commit list:
- Validation before commit:
- Run unit tests:
cd test/unit-test && ceedling test:all - Build at least one example:
cd examples/device/cdc_msc && make BOARD=stm32f407disco all - Verify changed files look correct:
git diff --stat
- Run unit tests:
- Leave files unstaged for maintainer to review, modify if needed, and commit with message:
Bump version to X.Y.Z - After maintainer commits: Create annotated tag with
git tag -a vX.Y.Z -m "Release X.Y.Z" - Push commit and tag:
git push origin <branch> && git push origin vX.Y.Z - Create GitHub release from the tag with changelog content
├── src/ # Core TinyUSB stack
│ ├── class/ # USB device classes (CDC, HID, MSC, Audio, etc.)
│ ├── portable/ # MCU-specific drivers (organized by vendor)
│ ├── device/ # USB device stack core
│ ├── host/ # USB host stack core
│ └── common/ # Shared utilities (FIFO, etc.)
├── examples/ # Example applications
│ ├── device/ # Device examples (cdc_msc, hid_generic, etc.)
│ ├── host/ # Host examples
│ └── dual/ # Dual-role examples
├── hw/bsp/ # Board Support Packages
│ └── FAMILY/boards/ # Board-specific configurations
├── test/unit-test/ # Unit tests using Ceedling
├── tools/ # Build and utility scripts
└── docs/ # Sphinx documentation
- Dependency fetch: <1 second
- Single example build: 1-3 seconds
- Unit tests: ~4 seconds
- Documentation build: ~2.5 seconds
- Full board examples: 15-20 seconds
- Toolchain installation: 2-5 minutes (one-time)
tools/get_deps.py: Manages dependencies for MCU familiestools/build.py: Builds multiple examples, supports make/cmakesrc/tusb.h: Main TinyUSB header filesrc/tusb_config.h: Configuration templateexamples/device/cdc_msc/: Most commonly used example for testingtest/unit-test/project.yml: Ceedling test configuration
- Look in
$HOME/Documents/Calibre Libraryfor all MCU reference manuals, datasheets and board schematics.
- Missing compiler: Install
gcc-arm-none-eabipackage - Missing dependencies: Run
python3 tools/get_deps.py FAMILY - Board not found: Check
hw/bsp/FAMILY/boards/for valid board names - objcopy errors: Often non-critical in full builds, try individual example builds
- CDC (Serial):
src/class/cdc/- Virtual serial port - HID:
src/class/hid/- Human Interface Device (keyboard, mouse, etc.) - MSC:
src/class/msc/- Mass Storage Class (USB drive) - Audio:
src/class/audio/- USB Audio Class - Each class has device (
*_device.c) and host (*_host.c) implementations
- STM32: Largest support (F0, F1, F2, F3, F4, F7, G0, G4, H7, L4, U5, etc.)
- Raspberry Pi: RP2040, RP2350 with PIO-USB host support
- NXP: iMXRT, Kinetis, LPC families
- Microchip: SAM D/E/G/L families
- Check
hw/bsp/for complete list anddocs/reference/boards.rstfor details
- Use C99 standard
- Memory-safe: no dynamic allocation
- Thread-safe: defer all interrupt events to non-ISR task functions
- 2-space indentation, no tabs
- Use snake_case for variables/functions
- Use UPPER_CASE for macros and constants
- Follow existing variable naming patterns in files you're modifying
- Include proper header comments with MIT license
- Add descriptive comments for non-obvious functions
- When including headers, group in order: C stdlib, tusb common, drivers, classes
- Always check return values from functions that can fail
- Use TU_ASSERT() for error checking with return statements
- Follow the existing code patterns in the files you're modifying
Remember: TinyUSB is designed for embedded systems - builds are fast, tests are focused, and the codebase is optimized for resource-constrained environments.