Repository navigation
feat(usb_device): generic CDC + vendor + MSC reference example; ODrive USB example moves to odrive_native #834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
cf2da34
refactor(odrive_native): move the ODrive-compatible USB device exampl…
finger563 479ce24
feat(usb_device): generic CDC + vendor/WebUSB + MSC reference example…
finger563 c082c2f
fix(usb_device): stdio for the drive README, MSC in the doc section t…
finger563 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # The following lines of boilerplate have to be in your project's CMakeLists | ||
| # in this exact order for cmake to work correctly | ||
| cmake_minimum_required(VERSION 3.20) | ||
|
|
||
| # NOTE: the IDF component manager is intentionally left ENABLED here (unlike most | ||
| # espp examples) so that it can fetch the managed `espressif/esp_tinyusb` | ||
| # dependency declared by the usb_device component's idf_component.yml. To avoid | ||
| # the component manager scanning every espp component manifest (some board | ||
| # components declare target-specific constraints that would fail on esp32s3), | ||
| # EXTRA_COMPONENT_DIRS is narrowed to just the components this example uses; the | ||
| # in-repo espp components there satisfy the `espp/*` dependencies locally (no | ||
| # example manifest / override_path needed). | ||
| include($ENV{IDF_PATH}/tools/cmake/project.cmake) | ||
|
|
||
| # add only the component directories that we want to use | ||
| set(EXTRA_COMPONENT_DIRS | ||
| "../../../components/base_component" | ||
| "../../../components/format" | ||
| "../../../components/hid-rp" | ||
| "../../../components/logger" | ||
| "../../../components/odrive_ascii" | ||
| "../../../components/odrive_native" | ||
| "../../../components/usb_device" | ||
| ) | ||
|
|
||
| set( | ||
| COMPONENTS | ||
| "main esptool_py base_component format hid-rp logger odrive_ascii odrive_native usb_device esp_tinyusb" | ||
| CACHE STRING | ||
| "List of components to include" | ||
| ) | ||
|
|
||
| project(odrive_usb_example) | ||
|
|
||
| set(CMAKE_CXX_STANDARD 20) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # ODrive-compatible USB Device Example (CDC + Vendor/WebUSB + HID) | ||
|
|
||
| This is the `odrive_native` component's USB example: a **composite** | ||
| `espp::UsbDevice` that presents an ODrive-compatible device (the ODrive ASCII | ||
| protocol over CDC, the ODrive native / Fibre protocol over the vendor interface, | ||
| and a HID gamepad), all backed by one simulated motor state, matching how a real | ||
| ODrive splits its protocols across interfaces. (It used to live in | ||
| `components/usb_device/example`; that is now the generic USB device example with | ||
| the standard espp USB services.) | ||
|
|
||
| - **CDC-ACM serial** → the **ODrive ASCII** protocol (`espp::OdriveAscii`; text, | ||
| for a terminal or the Web Serial console). | ||
| - **vendor-specific (class 0xFF, WebUSB)** → the **ODrive native / Fibre binary** | ||
| protocol (`espp::OdriveNative`) — the one `odrivetool` / the `fibre` library | ||
| auto-discover and speak over USB. | ||
| - **HID** → an animated **gamepad** input device (built with the `hid-rp` | ||
| component; visualize it with the WebHID `hid_visualizer.html`). | ||
|
|
||
| The device enumerates with an ODrive-like VID/PID (0x1209 / 0x0d32) on the native | ||
| USB port. The log console is on **UART0** (with USB-Serial-JTAG as an early-boot | ||
| secondary): on the ESP32-S3 the USB-Serial-JTAG controller and USB-OTG share the | ||
| same native USB PHY, so keeping the console on it would contend with the TinyUSB | ||
| interfaces here and reboot-loop the device. | ||
|
|
||
| Each protocol server is transport-agnostic: the CDC RX callback feeds bytes to | ||
| `OdriveAscii::process_bytes()`, the vendor RX callback feeds bytes to | ||
| `OdriveNative::process_bytes()`, and each writes its response back out the same | ||
| interface. The HID interface periodically pushes gamepad input reports. | ||
|
|
||
| <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --> | ||
| **Table of Contents** | ||
|
|
||
| - [ODrive-compatible USB Device Example (CDC + Vendor/WebUSB + HID)](#odrive-compatible-usb-device-example-cdc--vendorwebusb--hid) | ||
| - [Requirements](#requirements) | ||
| - [Build](#build) | ||
| - [Flash and Monitor](#flash-and-monitor) | ||
| - [Usage](#usage) | ||
| - [How it works](#how-it-works) | ||
|
|
||
| <!-- markdown-toc end --> | ||
|
|
||
| ## Requirements | ||
|
|
||
| - An ESP32-S3 (or -S2 / -P4) with access to the native USB-OTG pins. | ||
| - ESP-IDF installed and available in your shell. | ||
| - The IDF component manager is enabled for this example so it can fetch the | ||
| managed `espressif/esp_tinyusb` component. | ||
|
|
||
| The example's `sdkconfig.defaults` enables the CDC, vendor, and HID classes: | ||
|
|
||
| ``` | ||
| CONFIG_TINYUSB_CDC_ENABLED=y | ||
| CONFIG_TINYUSB_CDC_COUNT=1 | ||
| CONFIG_TINYUSB_VENDOR_COUNT=1 | ||
| CONFIG_TINYUSB_HID_COUNT=1 | ||
| ``` | ||
|
|
||
| ## Build | ||
|
|
||
| ```sh | ||
| cd components/odrive_native/usb_example | ||
| idf.py set-target esp32s3 | ||
| idf.py build | ||
| ``` | ||
|
|
||
| ## Flash and Monitor | ||
|
|
||
| Flash / monitor over the UART0 console (a USB-UART adapter), which is independent | ||
| of the native USB interfaces this example presents: | ||
|
|
||
| ```sh | ||
| idf.py flash monitor | ||
| ``` | ||
|
|
||
| The native USB-OTG connector will appear on the host as a new composite device: | ||
| a serial port (CDC), a vendor interface (WebUSB), and a HID gamepad, with | ||
| manufacturer "espp" and product "espp ODrive". | ||
|
|
||
| ## Usage | ||
|
|
||
| **CDC serial (ODrive ASCII):** open the CDC serial port and send ODrive ASCII | ||
| commands, e.g. from Python: | ||
|
|
||
| ```python | ||
| import serial | ||
| ser = serial.Serial('/dev/tty.usbmodemXXXX', 115200, timeout=0.5) | ||
| ser.write(b'r axis0.encoder.pos_estimate\n'); print(ser.readline()) | ||
| ser.write(b'w axis0.controller.input_pos 12.34\n'); print(ser.readline()) | ||
| ser.write(b'p 0 1.0 0.5 0.1\n'); print(ser.readline()) | ||
| ser.write(b'f 0\n'); print(ser.readline()) | ||
| ``` | ||
|
|
||
| (Writes/setpoints are silent by default — ODrive semantics; only `r`/`f` respond.) | ||
|
|
||
| **Vendor / WebUSB (ODrive native / Fibre):** the vendor interface (class 0xFF, | ||
| bulk IN + bulk OUT) speaks the ODrive native binary protocol. `odrivetool` / the | ||
| reference `fibre` library discover it over USB and read/write the endpoint tree; | ||
| or, from a Chromium-based browser, open the native-protocol WebUSB control panel | ||
| (`odrive_control_panel.html`) and connect. The BOS/WebUSB descriptors point to a | ||
| configurable landing-page URL. See `HARDWARE_TEST.md` and `odrive_usb_probe.py`. | ||
|
|
||
| **HID (gamepad):** the device also enumerates as a HID gamepad whose sticks and | ||
| buttons the firmware animates. Your OS will see a gamepad; to inspect the raw | ||
| input reports in the browser, open the WebHID `hid_visualizer.html` in Chromium | ||
| and connect. | ||
|
|
||
| ## How it works | ||
|
|
||
| - `espp::UsbDevice` installs the TinyUSB driver and builds descriptors for the | ||
| enabled CDC + vendor + HID functions, allocating interfaces / endpoints | ||
| sequentially (the S3 USB-OTG endpoint budget fits CDC + vendor + HID). | ||
| - The vendor function advertises WebUSB + MS OS 2.0 descriptors so a browser (and | ||
| Windows, via WinUSB) can bind it driverlessly. | ||
| - The CDC receive callback feeds bytes to `espp::OdriveAscii::process_bytes()` and | ||
| writes the response back out via `write_cdc()`; the vendor receive callback feeds | ||
| bytes to `espp::OdriveNative::process_bytes()` and writes back via | ||
| `write_vendor()`. Both servers share one simulated motor state. | ||
| - The HID report descriptor is built with the `hid-rp` component | ||
| (`espp::GamepadInputReport`); the main loop animates the state and pushes reports | ||
| with `write_hid_report()` when the HID interface is ready. | ||
| - The log console is on UART0, with USB-Serial-JTAG as an early-boot secondary | ||
| (see `sdkconfig.defaults.esp32s3`) — it cannot stay on USB-Serial-JTAG because | ||
| that shares the native USB PHY with the USB-OTG interfaces here. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| idf_component_register( | ||
| SRC_DIRS "." | ||
| INCLUDE_DIRS "." | ||
| REQUIRES usb_device odrive_ascii odrive_native hid-rp esp_tinyusb | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # This example uses the native USB-OTG peripheral, which is only available on the | ||
| # ESP32-S3 (also S2 / P4) -- NOT the classic ESP32. Pin the target here so a bare | ||
| # `idf.py build` (without an explicit `set-target`) does not fall back to esp32 | ||
| # and fail to compile the USB device. | ||
| CONFIG_IDF_TARGET="esp32s3" | ||
|
|
||
| # Common ESP-related | ||
| CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 | ||
| CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 | ||
|
|
||
| # Enable the TinyUSB CDC-ACM feature (provided by the managed espressif/esp_tinyusb | ||
| # component). The espp::UsbDevice CDC function drives a dedicated CDC-ACM | ||
| # interface over the native USB-OTG peripheral. | ||
| CONFIG_TINYUSB_CDC_ENABLED=y | ||
| CONFIG_TINYUSB_CDC_COUNT=1 | ||
|
|
||
| # Enable the TinyUSB vendor-specific class (this is THE key enablement for the | ||
| # vendor / WebUSB interface). esp_tinyusb gates CFG_TUD_VENDOR behind | ||
| # CONFIG_TINYUSB_VENDOR_COUNT; setting it > 0 compiles in the vendor class driver | ||
| # so espp::UsbDevice's vendor function (bInterfaceClass 0xFF + WebUSB) works. | ||
| CONFIG_TINYUSB_VENDOR_COUNT=1 | ||
|
|
||
| # Enable the TinyUSB HID class driver. esp_tinyusb gates CFG_TUD_HID behind | ||
| # CONFIG_TINYUSB_HID_COUNT; setting it > 0 compiles in the HID class driver so | ||
| # espp::UsbDevice's HID function (gamepad report descriptor from hid-rp) works. | ||
| CONFIG_TINYUSB_HID_COUNT=1 |
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.