usb_host: hid example

This commit is contained in:
Roman Leonov 2022-12-01 13:44:20 +01:00
parent e603c9b04d
commit 4a7a6bf35b
20 changed files with 2941 additions and 0 deletions

View File

@ -0,0 +1,8 @@
# 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.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
get_filename_component(ProjectId ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" ProjectId ${ProjectId})
project(${ProjectId})

View File

@ -0,0 +1,70 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB HID Class example
This example implements a basic USB Host HID Class Driver, and demonstrates how to use the driver to communicate with USB HID devices (such as Keyboard and Mouse or both) on the ESP32-S2/S3. Currently, the example only supports the HID boot protocol which should be present on most USB Mouse and Keyboards. The example will continuously scan for the connection of any HID Mouse or Keyboard, and attempt to fetch HID reports from those devices once connected. To quit the example (and shut down the HID driver), users can GPIO0 to low (i.e., pressing the "Boot" button on most ESP dev kits).
### Hardware Required
* Development board with USB capable ESP SoC (ESP32-S2/ESP32-S3)
* A USB cable for Power supply and programming
* USB OTG Cable
### Common Pin Assignments
If your board doesn't have a USB A connector connected to the dedicated GPIOs,
you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
```
ESP BOARD USB CONNECTOR (type A)
--
| || VCC
[GPIO19] ------> | || D-
[GPIO20] ------> | || D+
| || GND
--
```
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
The example serial output will be the following:
```
I (195) example: HID HOST example
I (35955) example: Interface number 0, protocol Mouse
I (35955) example: Interface number 1, protocol Keyboard
X: 000016 Y: -00083 | |o|
|Q|T| | | | |
X: 000016 Y: -00083 |o| |
| |1|3|5| | |
```
Where every keyboard key printed as char symbol if it is possible and a Hex value for any other key.
#### Keyboard report description
```
|Q|T| | | | |
| | | | | |
| | | | | +----------------- Key 5 Char symbol
| | | | +------------------- Key 4 Char symbol
| | | +--------------------- Key 3 Char symbol
| | +----------------------- Key 2 Char symbol
| +------------------------- Key 1 Char symbol
+--------------------------- Key 0 Char symbol
```
#### Mouse report description
```
X: -00343 Y: 000183 | |o|
| | | |
| | | +- Right mouse button pressed status ("o" - pressed, " " - not pressed)
| | +--- Left mouse button pressed status ("o" - pressed, " " - not pressed)
| +---------- Y relative coordinate of the cursor
+----------------------- X relative coordinate of the cursor
```

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "hid_host.c"
PRIV_REQUIRES usb
INCLUDE_DIRS "include")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HID Subclass
*
* @see 4.2 Subclass, p.8 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_SUBCLASS_NO_SUBCLASS = 0x00,
HID_SUBCLASS_BOOT_INTERFACE = 0x01
} __attribute__((packed)) hid_subclass_t;
/**
* @brief HID Protocols
*
* @see 4.3 Protocols, p.9 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_PROTOCOL_NONE = 0x00,
HID_PROTOCOL_KEYBOARD = 0x01,
HID_PROTOCOL_MOUSE = 0x02,
HID_PROTOCOL_MAX
} __attribute__((packed)) hid_protocol_t;
/**
* @brief HID Descriptor
*
* @see 6.2.1 HID Descriptor, p.22 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
uint8_t bLength; // Numeric expression that is the total size of the HID descriptor
uint8_t bDescriptorType; // Constant name specifying type of HID descriptor
uint16_t bcdHID; // Numeric expression identifying the HIDClass Specification release
int8_t bCountryCode; // Numeric expression identifying country code of the localized hardware
uint8_t bNumDescriptors; // Numeric expression specifying the number of class descriptors (always at least one i.e. Report descriptor.)
uint8_t bReportDescriptorType; // Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants
uint16_t wReportDescriptorLength; // Numeric expression that is the total size of the Report descriptor
// Optional descriptors may follow further
} __attribute__((packed)) hid_descriptor_t;
/**
* @brief HID Country Codes
*
* @see 6.2.1 HID Descriptor, p.23 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_COUNTRY_CODE_NOT_SUPPORTED = 0x00,
HID_COUNTRY_CODE_ARABIC = 0x01,
HID_COUNTRY_CODE_BELGIAN = 0x02,
HID_COUNTRY_CODE_CANADIAN_BILINGUAL = 0x03,
HID_COUNTRY_CODE_CANADIAN_FRENCH = 0x04,
HID_COUNTRY_CODE_CZECH = 0x05,
HID_COUNTRY_CODE_DANISH = 0x06,
HID_COUNTRY_CODE_FINNISH = 0x07,
HID_COUNTRY_CODE_FRENCH = 0x08,
HID_COUNTRY_CODE_GERMAN = 0x09,
HID_COUNTRY_CODE_GREEK = 0x0A,
HID_COUNTRY_CODE_HEBREW = 0x0B,
HID_COUNTRY_CODE_HUNGARY = 0x0C,
HID_COUNTRY_CODE_ISO = 0x0D,
HID_COUNTRY_CODE_ITALIAN = 0x0E,
HID_COUNTRY_CODE_JAPAN = 0x0F,
HID_COUNTRY_CODE_KOREAN = 0x10,
HID_COUNTRY_CODE_LATIN_AMERICAN = 0x11,
HID_COUNTRY_CODE_NETHERLANDS = 0x12,
HID_COUNTRY_CODE_NORWEGIAN = 0x13,
HID_COUNTRY_CODE_PERSIAN = 0x14,
HID_COUNTRY_CODE_POLAND = 0x15,
HID_COUNTRY_CODE_PORTUGUESE = 0x16,
HID_COUNTRY_CODE_RUSSIA = 0x17,
HID_COUNTRY_CODE_SLOVAKIA = 0x18,
HID_COUNTRY_CODE_SPANISH = 0x19,
HID_COUNTRY_CODE_SWEDISH = 0x1A,
HID_COUNTRY_CODE_SWISS_F = 0x1B,
HID_COUNTRY_CODE_SWISS_G = 0x1C,
HID_COUNTRY_CODE_SWITZERLAND = 0x1D,
HID_COUNTRY_CODE_TAIWAN = 0x1E,
HID_COUNTRY_CODE_TURKISH_Q = 0x1F,
HID_COUNTRY_CODE_UK = 0x20,
HID_COUNTRY_CODE_US = 0x21,
HID_COUNTRY_CODE_YUGOSLAVIA = 0x22,
HID_COUNTRY_CODE_TURKISH_F = 0x23
} __attribute__((packed)) hid_country_code_t;
/**
* @brief HID Class Descriptor Types
*
* @see 7.1, p.49 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_CLASS_DESCRIPTOR_TYPE_HID = 0x21,
HID_CLASS_DESCRIPTOR_TYPE_REPORT = 0x22,
HID_CLASS_DESCRIPTOR_TYPE_PHYSICAL = 0x23
} __attribute__((packed)) hid_class_descritpor_type_t;
/**
* @brief HID Class-Specific Requests
*
* @see 7.2, p.50 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_CLASS_SPECIFIC_REQ_GET_REPORT = 0x01,
HID_CLASS_SPECIFIC_REQ_GET_IDLE = 0x02,
HID_CLASS_SPECIFIC_REQ_GET_PROTOCOL = 0x03,
HID_CLASS_SPECIFIC_REQ_SET_REPORT = 0x09,
HID_CLASS_SPECIFIC_REQ_SET_IDLE = 0x0A,
HID_CLASS_SPECIFIC_REQ_SET_PROTOCOL = 0x0B
} __attribute__((packed)) hid_class_specific_req_t;
/**
* @brief HID Report Types
*
* @see 7.2.1, p.51 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_REPORT_TYPE_INPUT = 0x01,
HID_REPORT_TYPE_OUTPUT = 0x02,
HID_REPORT_TYPE_FEATURE = 0x03,
} __attribute__((packed)) hid_report_type_t;
/**
* @brief HID Report protocol
*
* @see 7.2.5/7.2.6, p.54 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_REPORT_PROTOCOL_BOOT = 0x00,
HID_REPORT_PROTOCOL_REPORT = 0x01,
HID_REPORT_PROTOCOL_MAX
} __attribute__((packed)) hid_report_protocol_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <wchar.h>
#include <stdint.h>
#include "esp_err.h"
#include <freertos/FreeRTOS.h>
#include "hid.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct hid_host_device *hid_host_device_handle_t; /**< Handle to a HID */
typedef struct hid_interface *hid_host_interface_handle_t; /**< Handle to a particular HID interface */
/**
* @brief HID Interface input report callback
*
* @param[in] data Pointer to buffer containing input report data
* @param[in] length Data length
*/
typedef void (*hid_input_report_cb_t)(const uint8_t *const data, const int length);
/**
* @brief USB HID event containing event type and associated device handle
*/
typedef struct {
enum {
HID_DEVICE_CONNECTED, /**< HID device has been connected to the system.*/
HID_DEVICE_DISCONNECTED, /**< HID device has been disconnected from the system.*/
} event;
union {
uint8_t address; /**< Address of connected HID device.*/
hid_host_device_handle_t handle; /**< HID device handle to disconnected device.*/
} device;
} hid_host_event_t;
/**
* @brief USB HID interface event for application logic
*/
typedef enum {
HID_DEVICE_INTERFACE_INIT = 0x00, /**< Interface available for application logic */
HID_DEVICE_INTERFACE_CLAIM, /**< Interface was claimed */
HID_DEVICE_INTERFACE_RELEASE, /**< Interface was released */
HID_DEVICE_INTERFACE_TRANSFER_ERROR /**< Interface transfer error occurred */
} hid_interface_event_t;
/**
* @brief USB HID Host interface event containing event type and associated parameters
*/
typedef struct {
hid_interface_event_t event; /**< USB HID Interface event */
struct interface_event_param {
uint8_t dev_addr; /**< USB Device address */
uint8_t num; /**< USB Interface number */
hid_protocol_t proto; /**< USB HID Interface protocol */
} interface;
} hid_host_interface_event_t;
/**
* @brief USB HID host event callback.
*
* @param[in] event mass storage event
*/
typedef void (*hid_host_event_cb_t)(const hid_host_event_t *event, void *arg);
typedef void (*hid_host_interface_event_cb_t)(const hid_host_interface_event_t *event, void *arg);
/**
* @brief HID configuration structure.
*/
typedef struct {
bool create_background_task; /**< When set to true, background task handling USB events is created.
Otherwise user has to periodically call hid_host_handle_events function */
size_t task_priority; /**< Task priority of created background task */
size_t stack_size; /**< Stack size of created background task */
BaseType_t core_id; /**< Select core on which background task will run or tskNO_AFFINITY */
hid_host_event_cb_t callback; /**< Callback invoked when HID event occurs. Must not be NULL. */
void *callback_arg; /**< User provided argument passed to callback */
} hid_host_driver_config_t;
typedef struct {
uint8_t dev_addr;
hid_host_interface_event_cb_t iface_event_cb; /**< Callback invoked when HID event occurs. Must not be NULL. */
void *iface_event_arg; /**< User provided argument passed to callback */
} hid_host_device_config_t;
typedef struct {
hid_protocol_t proto;
hid_input_report_cb_t callback;
} hid_host_interface_config_t;
/**
* @brief Install USB Host HID Class driver
*
* @param[in] config configuration structure HID to create
* @return esp_err_r
*/
esp_err_t hid_host_install(const hid_host_driver_config_t *config);
/**
* @brief Uninstall HID Class driver
* @return esp_err_t
*/
esp_err_t hid_host_uninstall(void);
/**
* @brief HID Host USB event handler
*
* If HID Host install was made with create_background_task=false configuration,
* application needs to handle USB Host events itself.
* Do not used id HID host install was made with create_background_task=true configuration
*
* @param[in] arg Pointer to handler argument
* @return none
*/
void hid_host_event_handler_task(void *arg);
/**
* @brief Initialization of HID device.
*
* @param[in] hid_host_dev_config Pointer to HID Host device configuration structure
* @param[out] hid_device_handle Pointer to HID device handle
* @return esp_err_t
*/
esp_err_t hid_host_install_device(const hid_host_device_config_t *hid_host_dev_config,
hid_host_device_handle_t *hid_device_handle);
/**
* @brief Deinitialization of HID device.
*
* @param[in] hid_device_handle Device handle obtained from hid_host_install_device function
* @return esp_err_t
*/
esp_err_t hid_host_uninstall_device(hid_host_device_handle_t hid_device_handle);
/**
* @brief Print configuration descriptor.
*
* @param[in] hid_device_handle Device handle obtained from hid_host_install_device function
* @return esp_err_t
*/
esp_err_t hid_host_print_descriptors(hid_host_device_handle_t hid_device_handle);
/**
* @brief USB HID Interface claim
*
* @param[in] iface_config Pointer to Interface configuration structure
* @param[out] iface_handle Pointer to Interface handle
* @param[in] esp_err_t
*/
esp_err_t hid_host_claim_interface(const hid_host_interface_config_t *iface_config,
hid_host_interface_handle_t *iface_handle);
/**
* @brief USB HID Interface release
*
* @param[in] iface_handle Interface handle obtained from hid_host_claim_interface function
* @param[in] esp_err_t
*/
esp_err_t hid_host_release_interface(hid_host_interface_handle_t iface_handle);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -0,0 +1,292 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------ HID usage keys ---------------------------------------------------------------
/**
* @brief HID Keys
*
*/
typedef enum {
HID_KEY_NO_PRESS = 0x00,
HID_KEY_ROLLOVER = 0x01,
HID_KEY_POST_FAIL = 0x02,
HID_KEY_ERROR_UNDEFINED = 0x03,
HID_KEY_A = 0x04,
HID_KEY_B = 0x05,
HID_KEY_C = 0x06,
HID_KEY_D = 0x07,
HID_KEY_E = 0x08,
HID_KEY_F = 0x09,
HID_KEY_G = 0x0A,
HID_KEY_H = 0x0B,
HID_KEY_I = 0x0C,
HID_KEY_J = 0x0D,
HID_KEY_K = 0x0E,
HID_KEY_L = 0x0F,
HID_KEY_M = 0x10,
HID_KEY_N = 0x11,
HID_KEY_O = 0x12,
HID_KEY_P = 0x13,
HID_KEY_Q = 0x14,
HID_KEY_R = 0x15,
HID_KEY_S = 0x16,
HID_KEY_T = 0x17,
HID_KEY_U = 0x18,
HID_KEY_V = 0x19,
HID_KEY_W = 0x1A,
HID_KEY_X = 0x1B,
HID_KEY_Y = 0x1C,
HID_KEY_Z = 0x1D,
HID_KEY_1 = 0x1E,
HID_KEY_2 = 0x1F,
HID_KEY_3 = 0x20,
HID_KEY_4 = 0x21,
HID_KEY_5 = 0x22,
HID_KEY_6 = 0x23,
HID_KEY_7 = 0x24,
HID_KEY_8 = 0x25,
HID_KEY_9 = 0x26,
HID_KEY_0 = 0x27,
HID_KEY_ENTER = 0x28,
HID_KEY_ESC = 0x29,
HID_KEY_DEL = 0x2A,
HID_KEY_TAB = 0x2B,
HID_KEY_SPACE = 0x2C,
HID_KEY_MINUS = 0x2D,
HID_KEY_EQUAL = 0x2E,
HID_KEY_OPEN_BRACKET = 0x2F,
HID_KEY_CLOSE_BRACKET = 0x30,
HID_KEY_BACK_SLASH = 0x31,
HID_KEY_SHARP = 0x32,
HID_KEY_COLON = 0x33,
HID_KEY_QUOTE = 0x34,
HID_KEY_TILDE = 0x35,
HID_KEY_LESS = 0x36,
HID_KEY_GREATER = 0x37,
HID_KEY_SLASH = 0x38,
HID_KEY_CAPS_LOCK = 0x39,
HID_KEY_F1 = 0x3A,
HID_KEY_F2 = 0x3B,
HID_KEY_F3 = 0x3C,
HID_KEY_F4 = 0x3D,
HID_KEY_F5 = 0x3E,
HID_KEY_F6 = 0x3F,
HID_KEY_F7 = 0x40,
HID_KEY_F8 = 0x41,
HID_KEY_F9 = 0x42,
HID_KEY_F10 = 0x43,
HID_KEY_F11 = 0x44,
HID_KEY_F12 = 0x45,
HID_KEY_PRINT_SCREEN = 0x46,
HID_KEY_SCROLL_LOCK = 0x47,
HID_KEY_PAUSE = 0x48,
HID_KEY_INSERT = 0x49,
HID_KEY_HOME = 0x4A,
HID_KEY_PAGEUP = 0x4B,
HID_KEY_DELETE = 0x4C,
HID_KEY_END = 0x4D,
HID_KEY_PAGEDOWN = 0x4E,
HID_KEY_RIGHT = 0x4F,
HID_KEY_LEFT = 0x50,
HID_KEY_DOWN = 0x51,
HID_KEY_UP = 0x52,
HID_KEY_NUM_LOCK = 0x53,
HID_KEY_KEYPAD_DIV = 0x54,
HID_KEY_KEYPAD_MUL = 0x55,
HID_KEY_KEYPAD_SUB = 0x56,
HID_KEY_KEYPAD_ADD = 0x57,
HID_KEY_KEYPAD_ENTER = 0x58,
HID_KEY_KEYPAD_1 = 0x59,
HID_KEY_KEYPAD_2 = 0x5A,
HID_KEY_KEYPAD_3 = 0x5B,
HID_KEY_KEYPAD_4 = 0x5C,
HID_KEY_KEYPAD_5 = 0x5D,
HID_KEY_KEYPAD_6 = 0x5E,
HID_KEY_KEYPAD_7 = 0x5F,
HID_KEY_KEYPAD_8 = 0x60,
HID_KEY_KEYPAD_9 = 0x61,
HID_KEY_KEYPAD_0 = 0x62,
HID_KEY_KEYPAD_DELETE = 0x63,
HID_KEY_KEYPAD_SLASH = 0x64,
HID_KEY_APPLICATION = 0x65,
HID_KEY_POWER = 0x66,
HID_KEY_KEYPAD_EQUAL = 0x67,
HID_KEY_F13 = 0x68,
HID_KEY_F14 = 0x69,
HID_KEY_F15 = 0x6A,
HID_KEY_F16 = 0x6B,
HID_KEY_F17 = 0x6C,
HID_KEY_F18 = 0x6D,
HID_KEY_F19 = 0x6E,
HID_KEY_F20 = 0x6F,
HID_KEY_F21 = 0x70,
HID_KEY_F22 = 0x71,
HID_KEY_F23 = 0x72,
HID_KEY_F24 = 0x73,
HID_KEY_EXECUTE = 0x74,
HID_KEY_HELP = 0x75,
HID_KEY_MENU = 0x76,
HID_KEY_SELECT = 0x77,
HID_KEY_STOP = 0x78,
HID_KEY_AGAIN = 0x79,
HID_KEY_UNDO = 0x7A,
HID_KEY_CUT = 0x7B,
HID_KEY_COPY = 0x7C,
HID_KEY_PASTE = 0x7D,
HID_KEY_FIND = 0x7E,
HID_KEY_MUTE = 0x7F,
HID_KEY_VOLUME_UP = 0x80,
HID_KEY_VOLUME_DOWN = 0x81,
HID_KEY_LOCKING_CAPS_LOCK = 0x82,
HID_KEY_LOCKING_NUM_LOCK = 0x83,
HID_KEY_LOCKING_SCROLL_LOCK = 0x84,
HID_KEY_KEYPAD_COMMA = 0x85,
HID_KEY_KEYPAD_EQUAL_SIGN = 0x86,
HID_KEY_INTERNATIONAL_1 = 0x87,
HID_KEY_INTERNATIONAL_2 = 0x88,
HID_KEY_INTERNATIONAL_3 = 0x89,
HID_KEY_INTERNATIONAL_4 = 0x8A,
HID_KEY_INTERNATIONAL_5 = 0x8B,
HID_KEY_INTERNATIONAL_6 = 0x8C,
HID_KEY_INTERNATIONAL_7 = 0x8D,
HID_KEY_INTERNATIONAL_8 = 0x8E,
HID_KEY_INTERNATIONAL_9 = 0x8F,
HID_KEY_LANG_1 = 0x90,
HID_KEY_LANG_2 = 0x91,
HID_KEY_LANG_3 = 0x92,
HID_KEY_LANG_4 = 0x93,
HID_KEY_LANG_5 = 0x94,
HID_KEY_LANG_6 = 0x95,
HID_KEY_LANG_7 = 0x96,
HID_KEY_LANG_8 = 0x97,
HID_KEY_LANG_9 = 0x98,
HID_KEY_ALTERNATE_ERASE = 0x99,
HID_KEY_SYSREQ = 0x9A,
HID_KEY_CANCEL = 0x9B,
HID_KEY_CLEAR = 0x9C,
HID_KEY_PRIOR = 0x9D,
HID_KEY_RETURN = 0x9E,
HID_KEY_SEPARATOR = 0x9F,
HID_KEY_OUT = 0xA0,
HID_KEY_OPER = 0xA1,
HID_KEY_CLEAR_AGAIN = 0xA2,
HID_KEY_CRSEL = 0xA3,
HID_KEY_EXSEL = 0xA4,
HID_KEY_KEYPAD_00 = 0xB0,
HID_KEY_KEYPAD_000 = 0xB1,
HID_KEY_THOUSANDS_SEPARATOR = 0xB2,
HID_KEY_DECIMAL_SEPARATOR = 0xB3,
HID_KEY_CURRENCY_UNIT = 0xB4,
HID_KEY_CURRENCY_SUB_UNIT = 0xB5,
HID_KEY_KEYPAD_OPEN_PARENTHESIS = 0xB6,
HID_KEY_KEYPAD_CLOSE_PARENTHESIS = 0xB7,
HID_KEY_KEYPAD_OPEN_BRACE = 0xB8,
HID_KEY_KEYPAD_CLOSE_BRACE = 0xB9,
HID_KEY_KEYPAD_TAB = 0xBA,
HID_KEY_KEYPAD_BACKSPACE = 0xBB,
HID_KEY_KEYPAD_A = 0xBC,
HID_KEY_KEYPAD_B = 0xBD,
HID_KEY_KEYPAD_C = 0xBE,
HID_KEY_KEYPAD_D = 0xBF,
HID_KEY_KEYPAD_E = 0xC0,
HID_KEY_KEYPAD_F = 0xC1,
HID_KEY_KEYPAD_XOR = 0xC2,
HID_KEY_KEYPAD_CARET = 0xC3,
HID_KEY_KEYPAD_PERCENT = 0xC4,
HID_KEY_KEYPAD_LESSER = 0xC5,
HID_KEY_KEYPAD_GREATER = 0xC6,
HID_KEY_KEYPAD_AND = 0xC7,
HID_KEY_KEYPAD_LOGICAL_AND = 0xC8,
HID_KEY_KEYPAD_OR = 0xC9,
HID_KEY_KEYPAD_LOGICAL_OR = 0xCA,
HID_KEY_KEYPAD_COLON = 0xCB,
HID_KEY_KEYPAD_SHARP = 0xCC,
HID_KEY_KEYPAD_SPACE = 0xCD,
HID_KEY_KEYPAD_AT = 0xCE,
HID_KEY_KEYPAD_BANG = 0xCF,
HID_KEY_KEYPAD_MEMORY_STORE = 0xD0,
HID_KEY_KEYPAD_MEMORY_RECALL = 0xD1,
HID_KEY_KEYPAD_MEMORY_CLEAD = 0xD2,
HID_KEY_KEYPAD_MEMORY_ADD = 0xD3,
HID_KEY_KEYPAD_MEMORY_SUBSTRACT = 0xD4,
HID_KEY_KEYPAD_MEMORY_MULTIPLY = 0xD5,
HID_KEY_KEYPAD_MEMORY_DIVIDE = 0xD6,
HID_KEY_KEYPAD_SIGN = 0xD7,
HID_KEY_KEYPAD_CLEAR = 0xD8,
HID_KEY_KEYPAD_CLEAR_ENTRY = 0xD9,
HID_KEY_KEYPAD_BINARY = 0xDA,
HID_KEY_KEYPAD_OCTAL = 0xDB,
HID_KEY_KEYPAD_DECIMAL = 0xDC,
HID_KEY_KEYPAD_HEXADECIMAL = 0xDD,
HID_KEY_LEFT_CONTROL = 0xE0,
HID_KEY_LEFT_SHIFT = 0xE1,
HID_KEY_LEFT_ALT = 0xE2,
HID_KEY_LEFT_GUI = 0xE3,
HID_KEY_RIGHT_CONTROL = 0xE0,
HID_KEY_RIGHT_SHIFT = 0xE1,
HID_KEY_RIGHT_ALT = 0xE2,
HID_KEY_RIGHT_GUI = 0xE3
} __attribute__((packed)) hid_key_t;
// Modifier bit mask
#define HID_LEFT_CONTROL (1 << 0)
#define HID_LEFT_SHIFT (1 << 1)
#define HID_LEFT_ALT (1 << 2)
#define HID_LEFT_GUI (1 << 3)
#define HID_RIGHT_CONTROL (1 << 4)
#define HID_RIGHT_SHIFT (1 << 5)
#define HID_RIGHT_ALT (1 << 6)
#define HID_RIGHT_GUI (1 << 7)
/**
* @brief HID Keyboard Key number for Boot Interface
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_KEYBOARD_KEY_NUMBER0 = 0,
HID_KEYBOARD_KEY_NUMBER1,
HID_KEYBOARD_KEY_NUMBER2,
HID_KEYBOARD_KEY_NUMBER3,
HID_KEYBOARD_KEY_NUMBER4,
HID_KEYBOARD_KEY_NUMBER5,
HID_KEYBOARD_KEY_MAX,
} hid_keyboard_key_number_t;
/**
* @brief HID Keyboard Input Report for Boot Interfaces
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
union {
struct {
uint8_t left_ctr: 1;
uint8_t left_shift: 1;
uint8_t left_alt: 1;
uint8_t left_gui: 1;
uint8_t rigth_ctr: 1;
uint8_t right_shift: 1;
uint8_t right_alt: 1;
uint8_t right_gui: 1;
};
uint8_t val;
} modifier;
uint8_t reserved;
uint8_t key[HID_KEYBOARD_KEY_MAX];
} __attribute__((packed)) hid_keyboard_input_report_boot_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HID Mouse Input Report for Boot Interfaces
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
union {
struct {
uint8_t button1: 1;
uint8_t button2: 1;
uint8_t button3: 1;
uint8_t reserved: 5;
};
uint8_t val;
} buttons;
int8_t x_displacement;
int8_t y_displacement;
} __attribute__((packed)) hid_mouse_input_report_boot_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -0,0 +1,5 @@
#This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_app_hid_basic)

View File

@ -0,0 +1,3 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |

View File

@ -0,0 +1,6 @@
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRC_DIRS "." "../../../"
INCLUDE_DIRS "." "../../../include" ${CMAKE_CURRENT_BINARY_DIR}
REQUIRES usb unity esp_common
WHOLE_ARCHIVE)

View File

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "unity_test_utils.h"
#include "esp_err.h"
#include "hid_host.h"
#include "test_hid_basic.h"
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks();
}
void app_main(void)
{
// ____ ___ ___________________ __ __
// | | \/ _____/\______ \ _/ |_ ____ _______/ |_
// | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\.
// | | / / \ | | \ | | \ ___/ \___ \ | |
// |______/ /_______ / |______ / |__| \___ >____ > |__|
// \/ \/ \/ \/
printf(" ____ ___ ___________________ __ __ \r\n");
printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n");
printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n");
printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n");
printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n");
printf(" \\/ \\/ \\/ \\/ \r\n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(20);
unity_run_menu();
}

View File

@ -0,0 +1,379 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "unity_test_utils.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/semphr.h"
#include "usb/usb_host.h"
#include "hid_host.h"
#include "hid_usage_keyboard.h"
#include "hid_usage_mouse.h"
#include "test_hid_basic.h"
typedef enum {
HOST_NO_CLIENT = 0x1,
HOST_ALL_FREE = 0x2,
DEVICE_CONNECTED = 0x4,
DEVICE_DISCONNECTED = 0x8,
DEVICE_ADDRESS_MASK = 0xFF0,
} test_app_event_t;
static EventGroupHandle_t test_usb_flags;
hid_host_device_handle_t test_hid_device = NULL;
TaskHandle_t test_usb_event_task_handle;
SemaphoreHandle_t test_semaphore;
// ----------------------- Private -------------------------
/**
* @brief USB HID Host event callback. Handle such event as device connection and removing
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_event_callback(const hid_host_event_t *event, void *arg)
{
if (event->event == HID_DEVICE_CONNECTED) {
// Obtained USB device address is placed after application events
xEventGroupSetBits(test_usb_flags, DEVICE_CONNECTED | (event->device.address << 4));
} else if (event->event == HID_DEVICE_DISCONNECTED) {
xEventGroupSetBits(test_usb_flags, DEVICE_DISCONNECTED);
}
}
/**
* @brief USB HID Host interface callback
*
* @param[in] event HID Host interface event
* @param[in] arg Pointer to arguments, does not used
*/
static void test_hid_host_interface_event_callback(const hid_host_interface_event_t *event, void *arg)
{
switch (event->event) {
case HID_DEVICE_INTERFACE_INIT:
printf("Found Interface number %d, protocol %s\n",
event->interface.num,
(event->interface.proto == HID_PROTOCOL_KEYBOARD)
? "Keyboard"
: "Mouse");
break;
case HID_DEVICE_INTERFACE_TRANSFER_ERROR:
case HID_DEVICE_INTERFACE_CLAIM:
case HID_DEVICE_INTERFACE_RELEASE:
default:
// ... do nothing here for now
break;
}
}
/**
* @brief Handle common USB host library events
*
* @param[in] args Pointer to arguments, does not used
*/
static void test_handle_usb_events(void *args)
{
while (1) {
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// Release devices once all clients has deregistered
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
usb_host_device_free_all();
xEventGroupSetBits(test_usb_flags, HOST_NO_CLIENT);
}
// Give ready_to_uninstall_usb semaphore to indicate that USB Host library
// can be deinitialized, and terminate this task.
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
xEventGroupSetBits(test_usb_flags, HOST_ALL_FREE);
}
}
vTaskDelete(NULL);
}
/**
* @brief Create global semaphore
*/
static void test_create_semaphore(void)
{
test_semaphore = xSemaphoreCreateBinary();
}
/**
* @brief Deletes global semaphore
*/
static void test_delete_semaphore(void)
{
vSemaphoreDelete(test_semaphore);
}
/**
* @brief HID Keyboard report callback
*
* Keyboard report length == size of keyboard boot report
*
*/
static void test_hid_host_keyboard_report_callback(const uint8_t *const data, const int length)
{
printf("Keyboard report length %d\n", length);
TEST_ASSERT_EQUAL(sizeof(hid_keyboard_input_report_boot_t), length);
xSemaphoreGive(test_semaphore);
}
/**
* @brief HID Mouse report callback
*
* Mouse report length >= size of mouse boot report
*
*/
static void test_hid_host_mouse_report_callback(const uint8_t *const data, const int length)
{
printf("Mouse report length %d\n", length);
TEST_ASSERT_GREATER_OR_EQUAL(sizeof(hid_mouse_input_report_boot_t), length);
xSemaphoreGive(test_semaphore);
}
/**
* @brief Waits global semaphore for timeout
*/
static bool test_wait_semaphore_timeout(unsigned int ms)
{
return ( xSemaphoreTake(test_semaphore, pdMS_TO_TICKS(ms)) )
? true
: false;
}
/**
* @brief HID Keyboard report length test
*
* - Claim Keyboard interface
* - Wait Keyboard interface report callback for 5 sec
* - Release Keyboard interface
*/
static void test_hid_keyboard_report_length(void)
{
hid_host_interface_handle_t keyboard_handle;
hid_host_interface_config_t keyboard_iface_config = {
.proto = HID_PROTOCOL_KEYBOARD,
.callback = test_hid_host_keyboard_report_callback,
};
// claim keyboard interface
TEST_ASSERT_EQUAL(ESP_OK, hid_host_claim_interface(&keyboard_iface_config, &keyboard_handle) );
// wait report
printf("Please, press any keyboard key ...\n");
fflush(stdout);
test_wait_semaphore_timeout(5000);
// release keyboard interface
TEST_ASSERT_EQUAL(ESP_OK, hid_host_release_interface(keyboard_handle) );
}
/**
* @brief HID Mouse report length test
*
* - Claim Mouse interface
* - Wait Mouse interface report callback for 5 sec
* - Release Mouse interface
*/
static void test_hid_mouse_report_length(void)
{
hid_host_interface_handle_t mouse_handle;
hid_host_interface_config_t mouse_iface_config = {
.proto = HID_PROTOCOL_MOUSE,
.callback = test_hid_host_mouse_report_callback,
};
// claim mouse interface
TEST_ASSERT_EQUAL(ESP_OK, hid_host_claim_interface(&mouse_iface_config, &mouse_handle) );
// wait report
printf("Please, move mouse or press any button ...\n");
fflush(stdout);
test_wait_semaphore_timeout(5000);
// release mouse interface
TEST_ASSERT_EQUAL(ESP_OK, hid_host_release_interface(mouse_handle) );
}
// ----------------------- Public -------------------------
/**
* @brief Setups HID
*
* - Create events handle
* - Create usb_events task
* - Install USB Host driver
* - Install HID Host driver
*/
void test_hid_setup(void)
{
test_usb_flags = xEventGroupCreate();
const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 };
TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config) );
xTaskCreate(test_handle_usb_events, "usb_events", 4096, NULL, 2, &test_usb_event_task_handle);
// hid host driver config
const hid_host_driver_config_t hid_host_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.callback = test_hid_host_event_callback,
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_install(&hid_host_config) );
}
/**
* @brief Waits HID connection and install
*
* - Wait events: DEVICE_CONNECTED, DEVICE_ADDRESS_MASK
* - On DEVICE_ADDRESS_MASK install HID Host device driver and return handle
*
* @return hid_host_device_handle_t HID Device handle
*/
hid_host_device_handle_t test_hid_wait_connection_and_install(void)
{
TEST_ASSERT_NULL(test_hid_device);
printf("Please, insert HID device ...\n");
fflush(stdout);
EventBits_t event = xEventGroupWaitBits(test_usb_flags,
DEVICE_CONNECTED | DEVICE_ADDRESS_MASK,
pdTRUE,
pdFALSE,
portMAX_DELAY);
if (event & DEVICE_CONNECTED) {
xEventGroupClearBits(test_usb_flags, DEVICE_CONNECTED);
}
if (event & DEVICE_ADDRESS_MASK) {
xEventGroupClearBits(test_usb_flags, DEVICE_ADDRESS_MASK);
const hid_host_device_config_t hid_host_device_config = {
.dev_addr = (event & DEVICE_ADDRESS_MASK) >> 4,
.iface_event_cb = test_hid_host_interface_event_callback,
.iface_event_arg = NULL,
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_install_device(&hid_host_device_config, &test_hid_device) );
}
return test_hid_device;
}
/**
* @brief Test HID Wait device removal
*
* - Wait events: DEVICE_DISCONNECTED
* - On DEVICE_DISCONNECTED proceed
*/
void test_hid_wait_for_removal(void)
{
printf("Please, remove HID device ...\n");
fflush(stdout);
EventBits_t event = xEventGroupWaitBits(test_usb_flags,
DEVICE_DISCONNECTED,
pdTRUE,
pdFALSE,
portMAX_DELAY);
if (event & DEVICE_DISCONNECTED) {
xEventGroupClearBits(test_usb_flags, DEVICE_DISCONNECTED);
}
}
/**
* @brief Uninstalls HID device by handle obtained from test_hid_wait_connection_and_install()
*
* - Wait events: DEVICE_DISCONNECTED
* - On DEVICE_DISCONNECTED proceed
*/
void test_hid_uninstall_hid_device(hid_host_device_handle_t hid_device_handle)
{
TEST_ASSERT_EQUAL(ESP_OK, hid_host_uninstall_device(test_hid_device) );
}
/**
* @brief Teardowns HID
*
* - Uninstall HID Host driver
* - Uninstall USB Host driver
* - Delete usb_events task
* - Delete events handle
*/
void test_hid_teardown(void)
{
TEST_ASSERT_EQUAL(ESP_OK, hid_host_uninstall() );
TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall() );
vTaskDelete(test_usb_event_task_handle);
vEventGroupDelete(test_usb_flags);
}
// ------------------------- HID Test ------------------------------------------
static void test_setup_hid_basic(void)
{
test_hid_setup();
test_create_semaphore();
}
static void test_teardown_hid_basic(void)
{
test_delete_semaphore();
test_hid_teardown();
//Short delay to allow task to be cleaned up
vTaskDelay(pdMS_TO_TICKS(10));
test_hid_device = NULL;
}
TEST_CASE("(HID Host) Memory leakage basic", "[auto][hid_host]")
{
test_setup_hid_basic();
test_teardown_hid_basic();
vTaskDelay(20);
}
TEST_CASE("(HID Host) Memory leakage with HID device", "[hid_host]")
{
test_setup_hid_basic();
test_hid_device = test_hid_wait_connection_and_install();
test_hid_wait_for_removal();
test_hid_uninstall_hid_device(test_hid_device);
test_teardown_hid_basic();
vTaskDelay(20);
}
TEST_CASE("(HID Host) HID Keyboard report length", "[hid_host]")
{
test_setup_hid_basic();
test_hid_device = test_hid_wait_connection_and_install();
test_hid_keyboard_report_length();
test_hid_wait_for_removal();
test_hid_uninstall_hid_device(test_hid_device);
test_teardown_hid_basic();
vTaskDelay(20);
}
TEST_CASE("(HID Host) HID Mouse report length", "[hid_host]")
{
test_setup_hid_basic();
test_hid_device = test_hid_wait_connection_and_install();
test_hid_mouse_report_length();
test_hid_wait_for_removal();
test_hid_uninstall_hid_device(test_hid_device);
test_teardown_hid_basic();
vTaskDelay(20);
}

View File

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "hid_host.h"
#ifdef __cplusplus
extern "C" {
#endif
extern hid_host_device_handle_t test_hid_device;
// ------------------------ HID Test -------------------------------------------
void test_hid_setup(void);
hid_host_device_handle_t test_hid_wait_connection_and_install(void);
void test_hid_wait_for_removal(void);
void test_hid_uninstall_hid_device(hid_host_device_handle_t hid_device_handle);
void test_hid_teardown(void);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "unity_test_utils.h"
#include "hid_host.h"
#define TEST_HID_ERR_HANDLING_CASES (3)
static void test_install_hid_driver_without_config(void);
static void test_install_hid_driver_with_wrong_config(void);
static void test_claim_interface_without_driver(void);
void (*test_hid_err_handling_case[TEST_HID_ERR_HANDLING_CASES])(void) = {
test_install_hid_driver_without_config,
test_install_hid_driver_with_wrong_config,
test_claim_interface_without_driver
};
// ----------------------- Private -------------------------
/**
* @brief USB HID Host event callback. Handle such event as device connection and removing
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_event_callback_stub(const hid_host_event_t *event, void *arg)
{
if (event->event == HID_DEVICE_CONNECTED) {
// Device connected
} else if (event->event == HID_DEVICE_DISCONNECTED) {
// Device disconnected
}
}
// Install HID driver without USB Host and without configuration
static void test_install_hid_driver_without_config(void)
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(NULL));
}
// Install HID driver without USB Host and with configuration
static void test_install_hid_driver_with_wrong_config(void)
{
const hid_host_driver_config_t hid_host_config_callback_null = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = NULL, /* error expected */
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_callback_null));
const hid_host_driver_config_t hid_host_config_stack_size_null = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 0, /* error expected */
.core_id = 0,
.callback = NULL,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_stack_size_null));
const hid_host_driver_config_t hid_host_config_task_priority_null = {
.create_background_task = true,
.task_priority = 0,/* error expected */
.stack_size = 4096,
.core_id = 0,
.callback = NULL,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_task_priority_null));
const hid_host_driver_config_t hid_host_config_correct = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config_correct));
}
// Open device without installed driver
static void test_claim_interface_without_driver(void)
{
hid_host_interface_handle_t keyboard_handle;
hid_host_interface_config_t keyboard_iface_config_wrong_proto1 = {
.proto = HID_PROTOCOL_NONE,
.callback = NULL,
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
hid_host_claim_interface(&keyboard_iface_config_wrong_proto1, &keyboard_handle));
hid_host_interface_config_t keyboard_iface_config_wrong_proto2 = {
.proto = HID_PROTOCOL_MAX,
.callback = NULL,
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
hid_host_claim_interface(&keyboard_iface_config_wrong_proto2, &keyboard_handle));
/*
hid_host_interface_config_t keyboard_iface_config = {
.proto = HID_PROTOCOL_KEYBOARD,
.callback = NULL,
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE,
hid_host_claim_interface(&keyboard_iface_config, &keyboard_handle));
*/
}
// ----------------------- Public --------------------------
/**
* @brief HID
*
* There are multiple erroneous scenarios checked in this test:
*
* -# Install HID driver without USB Host
* -# Open device without installed driver
* -# Uninstall driver before installing it
* -# Open non-existent device
* -# Open the same device twice
* -# Uninstall driver with open devices
*/
TEST_CASE("(HID Host) Error handling", "[auto][hid_host]")
{
for (int i = 0; i < TEST_HID_ERR_HANDLING_CASES; i++) {
(*test_hid_err_handling_case[i])();
}
}

View File

@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "unity_test_utils.h"
#include "hid_host.h"
#include "test_hid_basic.h"
// ----------------------- Private -------------------------
/**
* @brief Test HID Uninstall USB driver with device inserted
*
* - Wait events: DEVICE_DISCONNECTED
* - On DEVICE_DISCONNECTED proceed
*/
void test_hid_usb_uninstall(void)
{
printf("HID device remain inserted, uninstall device ... ");
test_hid_uninstall_hid_device(test_hid_device);
}
// ----------------------- Public --------------------------
// ------------------------- USB Test ------------------------------------------
static void test_setup_hid_usb_driver(void)
{
test_hid_setup();
}
static void test_teardown_hid_usb_driver(void)
{
test_hid_teardown();
//Short delay to allow task to be cleaned up
vTaskDelay(pdMS_TO_TICKS(10));
test_hid_device = NULL;
}
TEST_CASE("(HID Host) USB uninstall (dev present)", "[hid_host]")
{
test_setup_hid_usb_driver();
test_hid_device = test_hid_wait_connection_and_install();
test_hid_usb_uninstall();
test_teardown_hid_usb_driver();
vTaskDelay(20);
}

View File

@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
def test_hid_basic(dut: IdfDut) -> None:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('[auto]')
dut.expect_unity_test_output()

View File

@ -0,0 +1,3 @@
CONFIG_ESP_TASK_WDT=n
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "hid_host_example.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,391 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_err.h"
#include "esp_log.h"
#include "usb/usb_host.h"
#include "errno.h"
#include "driver/gpio.h"
#include "hid_host.h"
#include "hid_usage_keyboard.h"
#include "hid_usage_mouse.h"
#define APP_QUIT_PIN GPIO_NUM_0
#define APP_QUIT_PIN_POLL_MS 500
#define READY_TO_UNINSTALL (HOST_NO_CLIENT | HOST_ALL_FREE)
typedef enum {
HOST_NO_CLIENT = 0x1,
HOST_ALL_FREE = 0x2,
DEVICE_CONNECTED = 0x4,
DEVICE_DISCONNECTED = 0x8,
DEVICE_ADDRESS_MASK = 0xFF0,
} app_event_t;
#define USB_EVENTS_TO_WAIT (DEVICE_CONNECTED | DEVICE_ADDRESS_MASK | DEVICE_DISCONNECTED)
static const char *TAG = "example";
static EventGroupHandle_t usb_flags;
static bool hid_device_connected = false;
hid_host_interface_handle_t keyboard_handle = NULL;
hid_host_interface_handle_t mouse_handle = NULL;
const char *modifier_char_name[8] = {
"LEFT_CONTROL",
"LEFT_SHIFT",
"LEFT_ALT",
"LEFT_GUI",
"RIGHT_CONTROL",
"RIGHT_SHIFT",
"RIGHT_ALT",
"RIGHT_GUI"
};
/**
* @brief Makes new line depending on report output protocol type
*
* @param[in] proto Current protocol to output
*/
static void hid_trigger_new_line_output(hid_protocol_t proto)
{
static hid_protocol_t prev_proto_output = HID_PROTOCOL_NONE;
if (prev_proto_output != proto) {
prev_proto_output = proto;
printf("\r\n");
fflush(stdout);
}
}
/**
* @brief HID Keyboard modifier verification function. Verify and print debug information about modifier has been pressed
*
* @param[in] modifier
*/
static inline void hid_keyboard_modifier_pressed(uint8_t modifier)
{
// verify bit mask
for (uint8_t i = 0; i < (sizeof(uint8_t) << 3); i++) {
if ((modifier >> i) & 0x01) {
ESP_LOGD(TAG, "Modifier Pressed: %s", modifier_char_name[i]);
}
}
}
/**
* @brief HID Keyboard modifier verification for capitalization application (right or left shift)
*
* @param[in] modifier
* @return true Modifier was pressed (left or right shift)
* @return false Modifier was not pressed (left or right shift)
*
*/
static inline bool hid_keyboard_is_modifier_capital(uint8_t modifier)
{
if ((modifier && HID_LEFT_SHIFT) ||
(modifier && HID_RIGHT_SHIFT)) {
return true;
}
return false;
}
/**
* @brief HID Keyboard get char symbol from key code
*
* @param[in] modifier Keyboard modifier data
* @param[in] key_code Keyboard key code
*/
static inline char hid_keyboard_get_char(uint8_t modifier, uint8_t key_code)
{
uint8_t key_char = (hid_keyboard_is_modifier_capital(modifier)) ? 'A' : 'a';
// Handle only char key pressed
if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_Z)) {
key_char += (key_code - HID_KEY_A);
} else if ((key_code >= HID_KEY_1) && (key_code <= HID_KEY_9)) {
key_char = '1' + (key_code - HID_KEY_1);
} else if (key_code == HID_KEY_0) {
key_char = '0';
} else {
// All other key pressed
key_char = 0x00;
}
return key_char;
}
/**
* @brief USB HID Host Keyboard Interface report callback handler
*
* @param[in] data Pointer to input report data buffer
* @param[in] length Length of input report data buffer
*/
static void hid_host_keyboard_report_callback(const uint8_t *const data, const int length)
{
bool keys_state_changed = false;
hid_keyboard_input_report_boot_t *kb_report = (hid_keyboard_input_report_boot_t *)data;
if (kb_report->modifier.val != 0) {
hid_keyboard_modifier_pressed(kb_report->modifier.val);
}
static uint8_t keys[HID_KEYBOARD_KEY_MAX] = { 0 };
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
if (kb_report->key[i] != keys[i]) {
keys_state_changed = true;
if (kb_report->key[i] != 0) {
keys[i] = hid_keyboard_get_char(kb_report->modifier.val, kb_report->key[i]);
} else {
keys[i] = 0x00;
}
}
}
if (keys_state_changed) {
hid_trigger_new_line_output(HID_PROTOCOL_KEYBOARD);
printf("|");
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
printf("%c|", keys[i] ? keys[i] : ' ');
}
printf("\r");
fflush(stdout);
}
}
/**
* @brief USB HID Host Mouse Interface report callback handler
*
* @param[in] data Pointer to input report data buffer
* @param[in] length Length of input report data buffer
*/
static void hid_host_mouse_report_callback(const uint8_t *const data, const int length)
{
hid_mouse_input_report_boot_t *mouse_report = (hid_mouse_input_report_boot_t *)data;
// First 3 bytes are mandated by HID specification
if (length < sizeof(hid_mouse_input_report_boot_t)) {
ESP_LOGE(TAG, "Mouse Boot report length (%d) error", length);
return;
}
static int x_pos = 0;
static int y_pos = 0;
// Calculate absolute position from displacement
x_pos += mouse_report->x_displacement;
y_pos += mouse_report->y_displacement;
hid_trigger_new_line_output(HID_PROTOCOL_MOUSE);
printf("X: %06d\tY: %06d\t|%c|%c|\r",
x_pos, y_pos,
(mouse_report->buttons.button1 ? 'o' : ' '),
(mouse_report->buttons.button2 ? 'o' : ' '));
fflush(stdout);
}
/**
* @brief USB HID Host event callback. Handle such event as device connection and removing
*
* @param[in] event HID device event
* @param[in] arg Pointer to arguments, does not used
*/
static void hid_host_event_callback(const hid_host_event_t *event, void *arg)
{
if (event->event == HID_DEVICE_CONNECTED) {
// Obtained USB device address is placed after application events
xEventGroupSetBits(usb_flags, DEVICE_CONNECTED | (event->device.address << 4));
} else if (event->event == HID_DEVICE_DISCONNECTED) {
xEventGroupSetBits(usb_flags, DEVICE_DISCONNECTED);
}
}
/**
* @brief USB HID Host interface callback
*
* @param[in] event HID interface event
* @param[in] arg Pointer to arguments, does not used
*/
static void hid_host_interface_event_callback(const hid_host_interface_event_t *event, void *arg)
{
switch (event->event) {
case HID_DEVICE_INTERFACE_INIT:
ESP_LOGI(TAG, "Interface number %d, protocol %s",
event->interface.num,
(event->interface.proto == HID_PROTOCOL_KEYBOARD)
? "Keyboard"
: "Mouse");
if (event->interface.proto == HID_PROTOCOL_KEYBOARD) {
const hid_host_interface_config_t hid_keyboard_config = {
.proto = HID_PROTOCOL_KEYBOARD,
.callback = hid_host_keyboard_report_callback,
};
hid_host_claim_interface(&hid_keyboard_config, &keyboard_handle);
}
if (event->interface.proto == HID_PROTOCOL_MOUSE) {
const hid_host_interface_config_t hid_mouse_config = {
.proto = HID_PROTOCOL_MOUSE,
.callback = hid_host_mouse_report_callback,
};
hid_host_claim_interface(&hid_mouse_config, &mouse_handle);
}
break;
case HID_DEVICE_INTERFACE_TRANSFER_ERROR:
ESP_LOGI(TAG, "Interface number %d, transfer error",
event->interface.num);
break;
case HID_DEVICE_INTERFACE_CLAIM:
case HID_DEVICE_INTERFACE_RELEASE:
// ... do nothing here for now
break;
default:
ESP_LOGI(TAG, "%s Unhandled event %X, Interface number %d",
__FUNCTION__,
event->event,
event->interface.num);
break;
}
}
/**
* @brief Handle common USB host library events
*
* @param[in] args Pointer to arguments, does not used
*/
static void handle_usb_events(void *args)
{
while (1) {
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// Release devices once all clients has deregistered
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
usb_host_device_free_all();
xEventGroupSetBits(usb_flags, HOST_NO_CLIENT);
}
// Give ready_to_uninstall_usb semaphore to indicate that USB Host library
// can be deinitialized, and terminate this task.
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
xEventGroupSetBits(usb_flags, HOST_ALL_FREE);
}
}
vTaskDelete(NULL);
}
static bool wait_for_event(EventBits_t event, TickType_t timeout)
{
return xEventGroupWaitBits(usb_flags, event, pdTRUE, pdTRUE, timeout) & event;
}
void app_main(void)
{
TaskHandle_t usb_events_task_handle;
hid_host_device_handle_t hid_device;
BaseType_t task_created;
const gpio_config_t input_pin = {
.pin_bit_mask = BIT64(APP_QUIT_PIN),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
ESP_ERROR_CHECK( gpio_config(&input_pin) );
ESP_LOGI(TAG, "HID HOST example");
usb_flags = xEventGroupCreate();
assert(usb_flags);
const usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1
};
ESP_ERROR_CHECK( usb_host_install(&host_config) );
task_created = xTaskCreate(handle_usb_events, "usb_events", 4096, NULL, 2, &usb_events_task_handle);
assert(task_created);
// hid host driver config
const hid_host_driver_config_t hid_host_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = hid_host_event_callback,
.callback_arg = NULL
};
ESP_ERROR_CHECK( hid_host_install(&hid_host_config) );
do {
EventBits_t event = xEventGroupWaitBits(usb_flags, USB_EVENTS_TO_WAIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(APP_QUIT_PIN_POLL_MS));
if (event & DEVICE_CONNECTED) {
xEventGroupClearBits(usb_flags, DEVICE_CONNECTED);
hid_device_connected = true;
}
if (event & DEVICE_ADDRESS_MASK) {
xEventGroupClearBits(usb_flags, DEVICE_ADDRESS_MASK);
const hid_host_device_config_t hid_host_device_config = {
.dev_addr = (event & DEVICE_ADDRESS_MASK) >> 4,
.iface_event_cb = hid_host_interface_event_callback,
.iface_event_arg = NULL,
};
ESP_ERROR_CHECK( hid_host_install_device(&hid_host_device_config, &hid_device) );
}
if (event & DEVICE_DISCONNECTED) {
xEventGroupClearBits(usb_flags, DEVICE_DISCONNECTED);
hid_host_release_interface(keyboard_handle);
hid_host_release_interface(mouse_handle);
ESP_ERROR_CHECK( hid_host_uninstall_device(hid_device) );
hid_device_connected = false;
}
} while (gpio_get_level(APP_QUIT_PIN) != 0);
if (hid_device_connected) {
ESP_LOGI(TAG, "Uninitializing HID Device");
hid_host_release_interface(keyboard_handle);
hid_host_release_interface(mouse_handle);
ESP_ERROR_CHECK( hid_host_uninstall_device(hid_device) );
hid_device_connected = false;
}
ESP_LOGI(TAG, "Uninitializing USB");
ESP_ERROR_CHECK( hid_host_uninstall() );
wait_for_event(READY_TO_UNINSTALL, portMAX_DELAY);
ESP_ERROR_CHECK( usb_host_uninstall() );
vTaskDelete(usb_events_task_handle);
vEventGroupDelete(usb_flags);
ESP_LOGI(TAG, "Done");
}