An issue was introduced in version 3.2.0 of the ESP32 Arduino board support package that causes MAX3421E examples to not compile. To compile the code for this project, downgrade to a previous version of the board support package (ex. 3.1.3).
The code below reads keypresses from a USB keyboard plugged into the USB Host FeatherWing and displays the corresponding keycodes on the TFT display of the Feather ESP32-S2.
First, install the necessary libraries in the Arduino IDE:
- Adafruit GFX Library
- Adafruit ST7789 TFT Library
- TinyUSB for Adafruit ESP32-S2
// SPDX-FileCopyrightText: 2024 John Park for Adafruit Industries // // SPDX-License-Identifier: MIT /********************************************************************* Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! MIT license, check LICENSE for more information Copyright (c) 2024 John Park for Adafruit Industries Copyright (c) 2019 Ha Thach for Adafruit Industries All text above, and the splash screen below must be included in any redistribution *********************************************************************/ /* Keyboard HID Keycode Reporter * - Device runs on native usb controller (roothub port0) * - esp32-s2 TFT Feather : using MAX3421e controller featherwing * - SPI instance, CS pin, INT pin are correctly configured in usbh_helper.h */ // USBHost is defined in usbh_helper.h #include "usbh_helper.h" #include <Adafruit_GFX.h> #include <Adafruit_ST7789.h> #include <Fonts/FreeMono18pt7b.h> #include <Fonts/FreeMono12pt7b.h> Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); void setup() { Serial.begin(115200); // turn on backlight pinMode(TFT_BACKLITE, OUTPUT); digitalWrite(TFT_BACKLITE, HIGH); // turn on the TFT / I2C power supply pinMode(TFT_I2C_POWER, OUTPUT); digitalWrite(TFT_I2C_POWER, HIGH); delay(10); // initialize TFT tft.init(135, 240); // Init ST7789 240x135 tft.setRotation(3); tft.fillScreen(ST77XX_BLACK); tft.setFont(&FreeMono18pt7b); tft.setTextWrap(true); tft.fillScreen(ST77XX_BLACK); tft.setCursor(0, 20); tft.setTextColor(ST77XX_GREEN); tft.setTextSize(1); tft.println("HIDreporter"); // init host stack on controller (rhport) 1 USBHost.begin(1); // while ( !Serial ) delay(10); // wait for native usb Serial.println("TinyUSB Dual: HID Device Reporter"); } void loop() { USBHost.task(); Serial.flush(); } extern "C" { // Invoked when device with hid interface is mounted // Report descriptor is also available for use. // tuh_hid_parse_report_descriptor() can be used to parse common/simple enough // descriptor. Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, // it will be skipped therefore report_desc = NULL, desc_len = 0 void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { (void) desc_report; (void) desc_len; uint16_t vid, pid; tuh_vid_pid_get(dev_addr, &vid, &pid); Serial.printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); Serial.printf("VID = %04x, PID = %04x\r\n", vid, pid); tft.fillRect(0, 34, 240, 80, ST77XX_BLACK); tft.setFont(&FreeMono12pt7b); tft.setCursor(0, 50); tft.printf("VID=%04x,PID=%04x\r\n", vid, pid); if (!tuh_hid_receive_report(dev_addr, instance)) { Serial.printf("Error: cannot request to receive report\r\n"); } } // Invoked when device with hid interface is un-mounted void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { Serial.printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); tft.fillRect(0, 34, 240, 140, ST77XX_BLACK); tft.setFont(&FreeMono12pt7b); tft.setTextColor(ST77XX_YELLOW); tft.setCursor(0, 50); tft.printf("-- unmounted --"); tft.setTextColor(ST77XX_GREEN); } // Invoked when received report from device via interrupt endpoint void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { Serial.printf("HIDreport : "); tft.fillRect(0, 64, 240, 80, ST77XX_BLACK); tft.setCursor(0, 88); tft.setFont(&FreeMono18pt7b); for (uint16_t i = 0; i < len; i++) { Serial.printf("0x%02X ", report[i]); tft.printf("%02X ", report[i]); } Serial.println(); // continue to request to receive report if (!tuh_hid_receive_report(dev_addr, instance)) { Serial.printf("Error: cannot request to receive report\r\n"); } } } // extern C
This code initializes the Feather ESP32-S2's TFT screen, mounts a USB HID device (keyboard), and displays any keycodes received from the device.
Code Explainer
Include Libraries
-
usbh_helper.h
: This header contains helper functions and configurations to interface with the USB Host controller and handle HID (Human Interface Devices) like a keyboard. -
Adafruit_GFX
andAdafruit_ST7789
: These libraries are used to control the TFT display.Adafruit_GFX
provides generic graphics functions, whileAdafruit_ST7789
is specific to the ST7789 TFT driver used by the Feather ESP32-S2 TFT. -
Fonts: Two different fonts (
FreeMono18pt7b
andFreeMono12pt7b
) are used for displaying text on the TFT screen in different sizes.
#include "usbh_helper.h" #include <Adafruit_GFX.h> #include <Adafruit_ST7789.h> #include <Fonts/FreeMono18pt7b.h> #include <Fonts/FreeMono12pt7b.h>
Initialize Display
This line initializes the TFT object, specifying the control pins: TFT_CS
(chip select), TFT_DC
(data/command), and TFT_RST
(reset). These pins are defined in usbh_helper.h
based on the hardware configuration of the Feather ESP32-S2 TFT.
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
Setup
-
Serial.begin(115200)
: Initializes serial communication at 115200 baud, useful for debugging via the serial monitor. -
pinMode
anddigitalWrite
: These lines control the TFT's backlight and power. The backlight is turned on by settingTFT_BACKLITE
to HIGH, and the I2C power (which powers the screen) is enabled by settingTFT_I2C_POWER
to HIGH. -
tft.init(135, 240)
: Initializes the TFT display with a resolution of 135x240 pixels. -
tft.setRotation(3)
: Rotates the display to the correct orientation. -
tft.fillScreen(ST77XX_BLACK)
: Clears the screen by filling it with black. - Text Setup: The display is prepared to print "HIDreporter" at the top of the screen using a green color and a specific font.
-
USBHost.begin(1)
: Initializes the USB Host on the first port (port 1). This is necessary to allow the Feather ESP32-S2 to act as a USB host and handle devices like keyboards. -
Serial.println
: Outputs a message to the serial monitor indicating that the HID reporter has started.
void setup() { Serial.begin(115200); pinMode(TFT_BACKLITE, OUTPUT); digitalWrite(TFT_BACKLITE, HIGH); pinMode(TFT_I2C_POWER, OUTPUT); digitalWrite(TFT_I2C_POWER, HIGH); delay(10); tft.init(135, 240); tft.setRotation(3); tft.fillScreen(ST77XX_BLACK); tft.setFont(&FreeMono18pt7b); tft.setCursor(0, 20); tft.setTextColor(ST77XX_GREEN); tft.setTextSize(1); tft.println("HIDreporter"); USBHost.begin(1); Serial.println("TinyUSB HID Device Reporter"); }
Main Loop
-
USBHost.task()
: Continuously runs the USB Host task, which manages communication with connected USB devices (e.g., keyboards). It handles detection, setup, and communication with the device. -
Serial.flush()
: Ensures all serial output has been transmitted. This is mainly used for debugging purposes.
void loop() { USBHost.task(); Serial.flush(); }
HID Callbacks
These functions are defined as extern "C" since they're invoked by the USB host stack when certain events (like mounting or unmounting a USB device) occur.
HID Device Mounted
- This function is called when a USB HID device (e.g., a keyboard) is mounted (connected to the USB Host).
-
tuh_vid_pid_get
: Retrieves the Vendor ID (VID) and Product ID (PID) of the connected USB device and displays this information on the TFT screen and serial monitor. -
tft.fillRect
: Clears a portion of the screen to make room for new text. -
tuh_hid_receive_report
: Requests to receive a report from the HID device. A report contains data from the device (e.g., key presses from the keyboard).
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { uint16_t vid, pid; tuh_vid_pid_get(dev_addr, &vid, &pid); tft.fillRect(0, 34, 240, 80, ST77XX_BLACK); tft.setFont(&FreeMono12pt7b); tft.setCursor(0, 50); tft.printf("VID=%04x,PID=%04x\r\n", vid, pid); if (!tuh_hid_receive_report(dev_addr, instance)) { Serial.printf("Error: cannot request to receive report\r\n"); } }
HID Device Unmounted
- This function is called when a USB HID device is unmounted (disconnected).
- It clears part of the screen and displays the message "-- unmounted --" in yellow to indicate that the device is no longer connected.
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { tft.fillRect(0, 34, 240, 140, ST77XX_BLACK); tft.setFont(&FreeMono12pt7b); tft.setTextColor(ST77XX_YELLOW); tft.setCursor(0, 50); tft.printf("-- unmounted --"); tft.setTextColor(ST77XX_GREEN); }
HID Report Received
- This function is invoked when a new report (keycode) is received from the connected HID device.
- It loops through the bytes in the report and prints the hexadecimal values on both the TFT display and the serial monitor.
-
tuh_hid_receive_report
is called again at the end to continuously request new reports from the HID device.
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { tft.fillRect(0, 64, 240, 80, ST77XX_BLACK); tft.setCursor(0, 88); tft.setFont(&FreeMono18pt7b); for (uint16_t i = 0; i < len; i++) { tft.printf("%02X ", report[i]); } if (!tuh_hid_receive_report(dev_addr, instance)) { Serial.printf("Error: cannot request to receive report\r\n"); } }
Page last edited April 01, 2025
Text editor powered by tinymce.