Merge branch 'feature/usb_device/hid' into 'master'

usb: Add HID device example

Closes IDFGH-7637, IDFGH-5054, IDF-581, and IDFGH-6013

See merge request espressif/esp-idf!19177
This commit is contained in:
Tomas Rezucha 2022-07-27 21:18:50 +08:00
commit 5bd6696557
14 changed files with 324 additions and 63 deletions

View File

@ -107,7 +107,7 @@ menu "TinyUSB Stack"
Name of the MSC device.
config TINYUSB_DESC_HID_STRING
depends on TINYUSB_HID_ENABLED
depends on TINYUSB_HID_COUNT > 0
string "HID Device String"
default "Espressif HID Device"
help
@ -175,6 +175,14 @@ menu "TinyUSB Stack"
help
Enable TinyUSB MIDI feature.
endmenu # "MIDI"
endif # TINYUSB
menu "Human Interface Device Class (HID)"
config TINYUSB_HID_COUNT
int "TinyUSB HID interfaces count"
default 0
range 0 4
help
Setting value greater than 0 will enable TinyUSB HID feature.
endmenu # "HID Device Class (HID)"
endif # TINYUSB
endmenu # "TinyUSB Stack"

View File

@ -11,7 +11,7 @@ extern "C" {
#endif
#define USB_ESPRESSIF_VID 0x303A
#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 8
#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 7
typedef enum{
TINYUSB_USBDEV_0,

View File

@ -41,8 +41,8 @@ extern "C" {
# define CONFIG_TINYUSB_MSC_ENABLED 0
#endif
#ifndef CONFIG_TINYUSB_HID_ENABLED
# define CONFIG_TINYUSB_HID_ENABLED 0
#ifndef CONFIG_TINYUSB_HID_COUNT
# define CONFIG_TINYUSB_HID_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_MIDI_ENABLED
@ -82,9 +82,6 @@ extern "C" {
// MSC Buffer size of Device Mass storage
#define CFG_TUD_MSC_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_BUFSIZE CONFIG_TINYUSB_HID_BUFSIZE
#define CFG_TUD_MIDI_EP_BUFSIZE 64
#define CFG_TUD_MIDI_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
#define CFG_TUD_MIDI_RX_BUFSIZE 64
@ -97,7 +94,7 @@ extern "C" {
#define CFG_TUD_CDC 0
#endif
#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED
#define CFG_TUD_HID CONFIG_TINYUSB_HID_ENABLED
#define CFG_TUD_HID CONFIG_TINYUSB_HID_COUNT
#define CFG_TUD_MIDI CONFIG_TINYUSB_MIDI_ENABLED
#define CFG_TUD_CUSTOM_CLASS CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED

View File

@ -33,12 +33,12 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
.otg_mode = USB_OTG_MODE_DEVICE,
};
usb_phy_gpio_conf_t gpio_conf = {
.vp_io_num = USBPHY_VP_NUM,
.vm_io_num = USBPHY_VM_NUM,
.rcv_io_num = USBPHY_RCV_NUM,
.oen_io_num = USBPHY_OEN_NUM,
.vpo_io_num = USBPHY_VPO_NUM,
.vmo_io_num = USBPHY_VMO_NUM,
.vp_io_num = USBPHY_VP_NUM,
.vm_io_num = USBPHY_VM_NUM,
.rcv_io_num = USBPHY_RCV_NUM,
.oen_io_num = USBPHY_OEN_NUM,
.vpo_io_num = USBPHY_VPO_NUM,
.vmo_io_num = USBPHY_VMO_NUM,
};
if (config->external_phy) {
phy_conf.target = USB_PHY_TARGET_EXT;
@ -48,6 +48,10 @@ esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
}
ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed");
#if (CONFIG_TINYUSB_HID_COUNT > 0)
// For HID device, configuration descriptor must be provided
ESP_RETURN_ON_FALSE(config->configuration_descriptor, ESP_ERR_INVALID_ARG, TAG, "Configuration descriptor must be provided for HID device");
#endif
dev_descriptor = config->device_descriptor ? config->device_descriptor : &descriptor_dev_kconfig;
string_descriptor = config->string_descriptor ? config->string_descriptor : descriptor_str_kconfig;
cfg_descriptor = config->configuration_descriptor ? config->configuration_descriptor : descriptor_cfg_kconfig;

View File

@ -56,8 +56,7 @@ tusb_desc_strarray_device_t descriptor_str_tinyusb = {
"123456", // 3: Serials, should use chip ID
"TinyUSB CDC", // 4: CDC Interface
"TinyUSB MSC", // 5: MSC Interface
"TinyUSB HID", // 6: HID
"TinyUSB MIDI" // 7: MIDI
"TinyUSB MIDI" // 6: MIDI
};
/* End of TinyUSB default */
@ -121,28 +120,14 @@ tusb_desc_strarray_device_t descriptor_str_kconfig = {
"",
#endif
#if CONFIG_TINYUSB_HID_ENABLED
CONFIG_TINYUSB_DESC_HID_STRING // 6: HIDs
#else
"",
#endif
#if CONFIG_TINYUSB_MIDI_ENABLED
CONFIG_TINYUSB_DESC_MIDI_STRING // 7: MIDI
CONFIG_TINYUSB_DESC_MIDI_STRING // 6: MIDI
#else
"",
#endif
};
//------------- HID Report Descriptor -------------//
#if CFG_TUD_HID
enum {
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE
};
#endif
//------------- Configuration Descriptor -------------//
enum {
#if CFG_TUD_CDC
@ -159,10 +144,6 @@ enum {
ITF_NUM_MSC,
#endif
#if CFG_TUD_HID
ITF_NUM_HID,
#endif
#if CFG_TUD_MIDI
ITF_NUM_MIDI,
ITF_NUM_MIDI_STREAMING,
@ -175,7 +156,6 @@ enum {
TUSB_DESC_TOTAL_LEN = TUD_CONFIG_DESC_LEN +
CFG_TUD_CDC * TUD_CDC_DESC_LEN +
CFG_TUD_MSC * TUD_MSC_DESC_LEN +
CFG_TUD_HID * TUD_HID_DESC_LEN +
CFG_TUD_MIDI * TUD_MIDI_DESC_LEN
};
@ -197,24 +177,13 @@ enum {
EPNUM_MSC,
#endif
#if CFG_TUD_HID
EPNUM_HID,
#endif
#if CFG_TUD_MIDI
EPNUM_MIDI,
#endif
};
#if CFG_TUD_HID //HID Report Descriptor
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD), ),
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_MOUSE), )
};
#endif
uint8_t const descriptor_cfg_kconfig[] = {
// interface count, string index, total length, attribute, power in mA
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
#if CFG_TUD_CDC
@ -232,14 +201,9 @@ uint8_t const descriptor_cfg_kconfig[] = {
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC, 0x80 | EPNUM_MSC, 64), // highspeed 512
#endif
#if CFG_TUD_HID
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_HID, 6, HID_PROTOCOL_NONE, sizeof(desc_hid_report), 0x80 | EPNUM_HID, 16, 10)
#endif
#if CFG_TUD_MIDI
// Interface number, string index, EP Out & EP In address, EP size
TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 7, EPNUM_MIDI, 0x80 | EPNUM_MIDI, 64) // highspeed 512
TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, 6, EPNUM_MIDI, 0x80 | EPNUM_MIDI, 64) // highspeed 512
#endif
};

View File

@ -28,7 +28,13 @@ void app_main(void)
/* Setting TinyUSB up */
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t tusb_cfg = { 0 }; // the configuration uses default values
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false, // In the most cases you need to use a `false` value
.configuration_descriptor = NULL,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
tinyusb_config_cdcacm_t acm_cfg = { 0 }; // the configuration uses default values

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five 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.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tusb_hid)

View File

@ -0,0 +1,80 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# TinyUSB Human Interface Device Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
Human interface devices (HID) are one of the most common USB devices, it is implemented in various devices such as keyboards, mice, game controllers, sensors and alphanumeric display devices.
In this example, we implement USB keyboard and mouse.
Upon connection to USB host (PC), the example application will sent 'key a/A pressed & released' events and move mouse in a square trajectory. To send these HID reports again, press the BOOT button, that is present on most ESP development boards (GPIO0).
As a USB stack, a TinyUSB component is used.
## How to use example
### Hardware Required
Any ESP board that have USB-OTG supported.
#### Pin Assignment
_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments).
Boot signal (GPIO0) is used to send HID reports to USB host.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
After the flashing you should see the output at idf monitor:
```
I (290) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (310) example: USB initialization
I (310) tusb_desc:
┌─────────────────────────────────┐
│ USB Device Descriptor Summary │
├───────────────────┬─────────────┤
│bDeviceClass │ 0 │
├───────────────────┼─────────────┤
│bDeviceSubClass │ 0 │
├───────────────────┼─────────────┤
│bDeviceProtocol │ 0 │
├───────────────────┼─────────────┤
│bMaxPacketSize0 │ 64 │
├───────────────────┼─────────────┤
│idVendor │ 0x303a │
├───────────────────┼─────────────┤
│idProduct │ 0x4004 │
├───────────────────┼─────────────┤
│bcdDevice │ 0x100 │
├───────────────────┼─────────────┤
│iManufacturer │ 0x1 │
├───────────────────┼─────────────┤
│iProduct │ 0x2 │
├───────────────────┼─────────────┤
│iSerialNumber │ 0x3 │
├───────────────────┼─────────────┤
│bNumConfigurations │ 0x1 │
└───────────────────┴─────────────┘
I (480) TinyUSB: TinyUSB Driver installed
I (480) example: USB initialization DONE
I (2490) example: Sending Keyboard report
I (3040) example: Sending Mouse report
```

View File

@ -0,0 +1,5 @@
idf_component_register(
SRCS "tusb_hid_example_main.c"
INCLUDE_DIRS "."
REQUIRES driver tinyusb
)

View File

@ -0,0 +1,175 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdlib.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "class/hid/hid_device.h"
#include "driver/gpio.h"
#define APP_BUTTON (GPIO_NUM_0) // Use BOOT signal by default
static const char *TAG = "example";
/************* TinyUSB descriptors ****************/
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN)
/**
* @brief HID report descriptor
*
* In this example we implement Keyboard + Mouse HID device,
* so we must define both report descriptors
*/
const uint8_t hid_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) ),
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) )
};
/**
* @brief Configuration descriptor
*
* This is a simple configuration descriptor that defines 1 configuration and 1 HID interface
*/
static const uint8_t hid_configuration_descriptor[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(0, 0, false, sizeof(hid_report_descriptor), 0x81, 16, 10),
};
/********* TinyUSB HID callbacks ***************/
// Invoked when received GET HID REPORT DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
// We use only one interface and one HID report descriptor, so we can ignore parameter 'instance'
return hid_report_descriptor;
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
}
/********* Application ***************/
typedef enum {
MOUSE_DIR_RIGHT,
MOUSE_DIR_DOWN,
MOUSE_DIR_LEFT,
MOUSE_DIR_UP,
MOUSE_DIR_MAX,
} mouse_dir_t;
#define DISTANCE_MAX 125
#define DELTA_SCALAR 5
static void mouse_draw_square_next_delta(int8_t *delta_x_ret, int8_t *delta_y_ret)
{
static mouse_dir_t cur_dir = MOUSE_DIR_RIGHT;
static uint32_t distance = 0;
// Calculate next delta
if (cur_dir == MOUSE_DIR_RIGHT) {
*delta_x_ret = DELTA_SCALAR;
*delta_y_ret = 0;
} else if (cur_dir == MOUSE_DIR_DOWN) {
*delta_x_ret = 0;
*delta_y_ret = DELTA_SCALAR;
} else if (cur_dir == MOUSE_DIR_LEFT) {
*delta_x_ret = -DELTA_SCALAR;
*delta_y_ret = 0;
} else if (cur_dir == MOUSE_DIR_UP) {
*delta_x_ret = 0;
*delta_y_ret = -DELTA_SCALAR;
}
// Update cumulative distance for current direction
distance += DELTA_SCALAR;
// Check if we need to change direction
if (distance >= DISTANCE_MAX) {
distance = 0;
cur_dir++;
if (cur_dir == MOUSE_DIR_MAX) {
cur_dir = 0;
}
}
}
static void app_send_hid_demo(void)
{
// Keyboard output: Send key 'a/A' pressed and released
ESP_LOGI(TAG, "Sending Keyboard report");
uint8_t keycode[6] = {HID_KEY_A};
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, keycode);
vTaskDelay(pdMS_TO_TICKS(50));
tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
// Mouse output: Move mouse cursor in square trajectory
ESP_LOGI(TAG, "Sending Mouse report");
int8_t delta_x;
int8_t delta_y;
for (int i = 0; i < (DISTANCE_MAX / DELTA_SCALAR) * 4; i++) {
// Get the next x and y delta in the draw square pattern
mouse_draw_square_next_delta(&delta_x, &delta_y);
tud_hid_mouse_report(HID_ITF_PROTOCOL_MOUSE, 0x00, delta_x, delta_y, 0, 0);
vTaskDelay(pdMS_TO_TICKS(20));
}
}
void app_main(void)
{
// Initialize button that will trigger HID reports
const gpio_config_t boot_button_config = {
.pin_bit_mask = BIT64(APP_BUTTON),
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_up_en = true,
.pull_down_en = false,
};
ESP_ERROR_CHECK(gpio_config(&boot_button_config));
ESP_LOGI(TAG, "USB initialization");
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
.configuration_descriptor = hid_configuration_descriptor,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
while (1) {
if (tud_mounted()) {
static bool send_hid_data = true;
if (send_hid_data) {
app_send_hid_demo();
}
send_hid_data = !gpio_get_level(APP_BUTTON);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}

View File

@ -0,0 +1,5 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_TINYUSB=y
CONFIG_TINYUSB_HID_COUNT=1

View File

@ -80,7 +80,8 @@ void app_main(void)
tinyusb_config_t const tusb_cfg = {
.device_descriptor = NULL, // If device_descriptor is NULL, tinyusb_driver_install() will use Kconfig
.string_descriptor = NULL,
.external_phy = false // In the most cases you need to use a `false` value
.external_phy = false,
.configuration_descriptor = NULL,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));

View File

@ -47,18 +47,20 @@ void app_main(void)
"012-345", // 3: Serials, should use chip ID
};
tinyusb_config_t tusb_cfg = {
const tinyusb_config_t tusb_cfg = {
.descriptor = &my_descriptor,
.string_descriptor = my_string_descriptor,
.external_phy = false // In the most cases you need to use a `false` value
.external_phy = false,
.configuration_descriptor = NULL,
};
#else
tinyusb_config_t tusb_cfg = {
.descriptor = NULL,
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false // In the most cases you need to use a `false` value
.external_phy = false,
.configuration_descriptor = NULL,
};
#endif

View File

@ -44,7 +44,13 @@ void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
void app_main(void)
{
ESP_LOGI(TAG, "USB initialization");
const tinyusb_config_t tusb_cfg = {}; // the configuration using default values
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
.configuration_descriptor = NULL,
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
tinyusb_config_cdcacm_t acm_cfg = {