tab5_example

Example of the component espp/usb_host v1.3.5
# USB HID Host on the M5Stack Tab5 (HID device viewer)

Turns the Tab5 into a USB HID host and shows what the attached device sends,
on the screen. Every HID interface the host opens is decoded from its own
report descriptor: a **keyboard** lights up its keys on a virtual keyboard, a
**mouse** moves a dot around a pad (with its wheel and buttons), a **gamepad**
drives two stick pads, a d-pad and button indicators, and a 3Dconnexion
**SpaceMouse** (SpaceNavigator, SpaceMouse Compact / Wireless / Pro, ...) is
decoded into its six axes and buttons. Anything else shows its raw Input
reports.

It is a bench tool for `espp::UsbHost` and the `hid-rp` runtime report
decoders, for evaluating a SpaceMouse as a 6-DoF input, and a starting point
for a Tab5 firmware that takes USB input.


## What it shows

- **Device card**: connection state (grey = none, green = SpaceMouse, blue =
  keyboard / mouse / gamepad, orange = other HID), product / manufacturer,
  VID:PID, and one line per opened interface with what it was recognised as. A
  composite device (a keyboard with a mouse interface, or a wireless receiver
  carrying a keyboard, a mouse and a gamepad) lists all of them and shows a
  panel for each.
- **SpaceMouse**: six centered bars, `Tx Ty Tz` (translation, blue) and
  `Rx Ry Rz` (rotation, orange), with the raw counts (`-350..350` on a
  SpaceNavigator), and one indicator per button.
- **Keyboard**: a virtual US keyboard whose pressed keys and modifiers light
  up. The decoder reads the keyboard's own report layout, so boot-style
  keyboards (modifier byte + six key slots) and NKRO bitmaps both work, with no
  protocol switch.
- **Mouse**: a pad with a dot that follows the accumulated motion (clamped at
  the edges), a wheel bar with its accumulated count, and `L R M 4 5` button
  indicators.
- **Gamepad**: left and right stick pads with the raw stick values, the d-pad,
  the four face buttons by position (labelled with the Xbox letters for those
  positions: `A` south, `B` east, `X` west, `Y` north, whatever the pad calls
  them), and `L1 R1 L2 R2 L3 R3 Select Start Home`. Per-controller layout
  quirks are looked up by VID:PID in `hid-rp`.
- **Last report**: the newest Input report's bytes (from any interface) and
  the report rate.

The panel column scrolls by touch when more panels are attached than fit the
screen (a receiver with all three kinds, in landscape).

`main/spacemouse_decoder.*` routes each raw SpaceMouse report by its id byte
to the matching `espp::SpaceMouse*InputReport` class from `hid-rp` (1 =
translation, 2 = rotation, 3 = buttons; the 12-byte combined report some
newer firmware sends on id 1 is handled too). The keyboard, mouse and gamepad
decoders are `hid-rp`'s `espp::hid_rp::ReportMap` and the `KeyboardDecoder` /
`MouseDecoder` / `GamepadDecoder` built on it (`hid-rp-report-map.hpp`).

## Hardware

- M5Stack Tab5. The USB-A jack is on the ESP32-P4's high-speed USB-OTG
  controller (the USB Host Library's default on the P4) and the BSP switches
  its 5 V through an IO expander (`set_usb_a_power()`).
- The console (`idf.py monitor`) stays on the USB-C port: that is the other
  (full-speed) controller, with USB-Serial-JTAG, so both work at once.
- A wireless device works through its USB receiver, and a hub works too: hub
  support is on in the example's `sdkconfig.defaults`, and the example runs the
  root port full-speed-only so the full-speed HID devices behind a hub never
  need the hub's transaction translator, which ESP-IDF's hub driver does not
  implement.
- A device plugged straight into the jack enumerates whether it was attached
  at power-up or hot-plugged. The example keeps the jack's 5 V off until the
  host is listening and waits 500 ms before powering the root port (both in
  menuconfig: **USB Host Tab5 Example Configuration**).
- Known limitation, cause not established: a device that is already behind a
  hub when the board boots can fail its first descriptor read
  (`ENUM: CHECK_SHORT_DEV_DESC FAILED`), after which the hub driver disables
  that hub port (it resets a port once). The same device enumerates when
  hot-plugged. Longer port power-good and reset-recovery delays did not help.
  If a device behind a hub is missing after boot, unplug and re-plug it.
- Seen during development, cause not established: with `idf.py monitor`
  attached to the USB-C port, a device on the jack sometimes stalled in
  enumeration, and never with the monitor detached. The firmware does not touch
  the USB-C controller, and other Tab5 firmware runs a console on USB-C with a
  device on USB-A continuously without stalls, so this is more likely the
  monitor's DTR/RTS handling (which resets the chip and can disturb the
  USB-Serial-JTAG PHY) than the hardware. If you hit it, try a terminal that
  leaves DTR/RTS alone, or a UART adapter.

## Build & flash

```sh
idf.py set-target esp32p4
idf.py -p <PORT> flash monitor
```

Built with the component manager on (the `usb` host library and `usb_host_hid`
class driver come from the registry, as in the plain `usb_host` example).

## Notes

- SpaceMouse axis signs follow the device: on a SpaceNavigator `Tx` + is
  right, `Ty` + is toward the user, `Tz` + is down (pushing the cap), and the
  rotations follow the right-hand rule about those axes. Check them against
  the bars before mapping to a robot frame. A SpaceMouse reports a zeroed
  translation + rotation pair when released, so the bars return to center on
  their own.
- Gamepad stick values are normalised to `-32767..32767` with Y growing
  downwards (the HID convention); a pad whose Y grows upwards is corrected by
  its VID:PID quirk.
- One physical device is shown at a time. A hub can carry several; while one
  device is on the card, the interfaces of any other USB address are not
  opened (logged as "not opening ... another device is being shown"), so its
  reports cannot mix into the panels. Re-plug it once the shown device is gone.
- A device that enumerates but is not opened as HID shows as "N USB device(s)
  enumerated, none opened as HID"; call `host.print_usb_devices()` (or set the
  `hid-host` log tag to debug with `CONFIG_LOG_MAXIMUM_LEVEL_DEBUG`) to see its
  interfaces.
- A SpaceMouse is recognised by 3Dconnexion's vendor id (`0x256F`), or by the
  product ids of the early Logitech-branded SpaceNavigator / SpaceExplorer /
  SpacePilot (`0x046D:C62x`), before the descriptor is looked at: its six axes
  and buttons would otherwise pass for a gamepad.

To create a project from this example, run:

idf.py create-project-from-example "espp/usb_host=1.3.5:tab5_example"

or download archive (~23.58 KB)