diff --git a/examples/peripherals/usb/host/hid/CMakeLists.txt b/examples/peripherals/usb/host/hid/CMakeLists.txt new file mode 100644 index 00000000000..6b4e5b0df42 --- /dev/null +++ b/examples/peripherals/usb/host/hid/CMakeLists.txt @@ -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}) diff --git a/examples/peripherals/usb/host/hid/README.md b/examples/peripherals/usb/host/hid/README.md new file mode 100644 index 00000000000..1799c75d969 --- /dev/null +++ b/examples/peripherals/usb/host/hid/README.md @@ -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 +``` diff --git a/examples/peripherals/usb/host/hid/components/hid/CMakeLists.txt b/examples/peripherals/usb/host/hid/components/hid/CMakeLists.txt new file mode 100644 index 00000000000..1819d1439f4 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "hid_host.c" + PRIV_REQUIRES usb + INCLUDE_DIRS "include") diff --git a/examples/peripherals/usb/host/hid/components/hid/hid_host.c b/examples/peripherals/usb/host/hid/components/hid/hid_host.c new file mode 100644 index 00000000000..f0a2459d406 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/hid_host.c @@ -0,0 +1,1124 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "usb/usb_host.h" + +#include "hid_host.h" + +// HID spinlock +static portMUX_TYPE hid_lock = portMUX_INITIALIZER_UNLOCKED; +#define HID_ENTER_CRITICAL() portENTER_CRITICAL(&hid_lock) +#define HID_EXIT_CRITICAL() portEXIT_CRITICAL(&hid_lock) + +// HID verification macroses +#define HID_GOTO_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if(!(exp)) { \ + HID_EXIT_CRITICAL(); \ + ret = err; \ + goto fail; \ + } \ + } while(0) + +#define HID_RETURN_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if(!(exp)) { \ + HID_EXIT_CRITICAL(); \ + return err; \ + } \ + } while(0) + +#define HID_GOTO_ON_ERROR(exp) ESP_GOTO_ON_ERROR(exp, fail, TAG, "") + +#define HID_GOTO_ON_FALSE(exp, err) ESP_GOTO_ON_FALSE( (exp), err, fail, TAG, "" ) + +#define HID_RETURN_ON_ERROR(exp) ESP_RETURN_ON_ERROR((exp), TAG, "") + +#define HID_RETURN_ON_FALSE(exp, err) ESP_RETURN_ON_FALSE( (exp), (err), TAG, "") + +#define HID_RETURN_ON_INVALID_ARG(exp) ESP_RETURN_ON_FALSE((exp) != NULL, ESP_ERR_INVALID_ARG, TAG, "") + +static const char *TAG = "hid-host"; + +/** + * @brief HID Interface structure in device to interact with. After HID device opening keeps the interface configuration + * + */ +typedef struct hid_interface { + STAILQ_ENTRY(hid_interface) tailq_entry; + uint8_t dev_addr; /**< USB device address */ + hid_protocol_t proto; /**< HID Interface protocol */ + hid_host_device_handle_t dev; /**< Parent USB HID device */ + uint8_t num; /**< Interface number */ + uint8_t ep; /**< Interrupt IN EP number */ + uint16_t ep_mps; /**< Interrupt IN max size */ + uint8_t country_code; /**< Country code */ + uint16_t report_size; /**< Size of Report */ + uint8_t *report; /**< Pointer to HID Report */ + usb_transfer_t *in_xfer; /**< Pointer to IN transfer buffer */ + hid_input_report_cb_t report_cb; /**< Input report callback */ + hid_host_interface_event_cb_t user_cb; /**< Interface application callback */ + void *user_cb_arg; /**< Interface application callback arg */ + bool ready; /**< Interface ready for claiming */ +} hid_iface_t; + +/** + * @brief HID Device structure. + * + */ +typedef struct hid_host_device { + STAILQ_ENTRY(hid_host_device) tailq_entry; /**< HID device queue */ + SemaphoreHandle_t device_busy; /**< HID device main mutex */ + SemaphoreHandle_t ctrl_xfer_done; /**< Control transfer semaphore */ + usb_transfer_t *ctrl_xfer; /**< Pointer to control transfer buffer */ + usb_device_handle_t dev_hdl; /**< USB device handle */ +} hid_device_t; + +/** + * @brief HID driver default context + * + * This context is created during HID Host install. + */ +typedef struct { + usb_host_client_handle_t client_handle; /**< Client task handle */ + hid_host_event_cb_t user_cb; /**< User application callback */ + void *user_arg; /**< User application callback args */ + SemaphoreHandle_t all_events_handled; /**< Events handler semaphore */ + volatile bool end_client_event_handling; /**< Client event handling flag */ +} hid_driver_t; + +static hid_driver_t *s_hid_driver; /**< Internal pointer to HID driver */ + +STAILQ_HEAD(devices, hid_host_device) devices_tailq; +STAILQ_HEAD(interfaces, hid_interface) ifaces_tailq; + +// ----------------------- Private ------------------------- + +/** + * @brief Get Next Interface descriptor + * + * @param[in] desc Pointer to Descriptor buffer (usually starts from Configuration descriptor) + * @param[in] len Total length + * @param[in] offset Pointer to offset DEscriptor buffer + * @return const usb_standard_desc_t* Pointer to Interface descriptor + */ +static inline const usb_standard_desc_t *next_interface_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset) +{ + return usb_parse_next_descriptor_of_type(desc, len, USB_W_VALUE_DT_INTERFACE, (int *)offset); +} + +/** + * @brief Get Next HID descriptor + * + * @param[in] desc Pointer to the Descriptor buffer + * @param[in] len Total length + * @param[in] offset Pointer to offset in descriptor buffer + * @return const usb_standard_desc_t* Pointer to HID descriptor + */ +static inline const usb_standard_desc_t *next_hid_descriptor(const usb_standard_desc_t *desc, size_t len, size_t *offset) +{ + return usb_parse_next_descriptor_of_type(desc, len, HID_CLASS_DESCRIPTOR_TYPE_HID, (int *)offset); +} + +/** + * @brief Get Next Endpoint descriptor + * + * @param[in] desc Pointer to Descriptor buffer + * @param[in] len Total length + * @param[in] offset Pointer to offset in descriptor buffer + * @return const usb_standard_desc_t* Pointer to Endpoint descriptor + */ +static inline const usb_standard_desc_t *next_endpoint_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset) +{ + return usb_parse_next_descriptor_of_type(desc, len, USB_B_DESCRIPTOR_TYPE_ENDPOINT, (int *)offset); +} + +/** + * @brief USB Event handler + * + * Handle all USB related events such as USB host (usbh) events or hub events from USB hardware + * + * @param[in] arg Argument, does not used + */ +static void event_handler_task(void *arg) +{ + while (1) { + /* Here wee need a timeout 50 ms to handle end_client_event_handling flag + * during situation when all devices were removed and it is time to remove + * and destroy everything. + */ + usb_host_client_handle_events(s_hid_driver->client_handle, pdMS_TO_TICKS(50)); + + if (s_hid_driver->end_client_event_handling) { + break; + } + } + usb_host_client_unblock(s_hid_driver->client_handle); + ESP_ERROR_CHECK( usb_host_client_deregister(s_hid_driver->client_handle) ); + xSemaphoreGive(s_hid_driver->all_events_handled); + vTaskDelete(NULL); +} + +/** + * @brief Return HID device in devices list by USB device handle + * + * @param[in] usb_device_handle_t USB device handle + * @return hid_device_t Pointer to device, NULL if device not present + */ +static hid_device_t *get_hid_device_by_handle(usb_device_handle_t usb_handle) +{ + hid_host_device_handle_t device = NULL; + + HID_ENTER_CRITICAL(); + STAILQ_FOREACH(device, &devices_tailq, tailq_entry) { + if (usb_handle == device->dev_hdl) { + HID_EXIT_CRITICAL(); + return device; + } + } + HID_EXIT_CRITICAL(); + return NULL; +} + +/** + * @brief Get HID Interface pointer by Endpoint address + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] ep_addr Endpoint address + * @return hid_iface_t Pointer to HID Interface configuration structure + */ +static hid_iface_t *get_interface_by_ep(hid_device_t *hid_device, uint8_t ep_addr) +{ + hid_iface_t *interface = NULL; + + HID_ENTER_CRITICAL(); + STAILQ_FOREACH(interface, &ifaces_tailq, tailq_entry) { + if (ep_addr == interface->ep) { + HID_EXIT_CRITICAL(); + return interface; + } + } + + HID_EXIT_CRITICAL(); + return NULL; +} + +/** + * @brief Get Interface structure from the RAM list + * + * @param[in] proto HID Protocol + * @return hid_iface_t Pointer to an interface structure + */ +static hid_iface_t *get_interface_by_proto(hid_protocol_t proto) +{ + hid_iface_t *interface = NULL; + + HID_ENTER_CRITICAL(); + STAILQ_FOREACH(interface, &ifaces_tailq, tailq_entry) { + if (proto == interface->proto) { + HID_EXIT_CRITICAL(); + return interface; + } + } + + HID_EXIT_CRITICAL(); + return NULL; +} + +/** + * @brief Verify claim/release status of the particular interface + * + * @param[in] proto HID Interface protocol + * @return true Interface is claimed by application logic + * @return false Interface is released + */ +static bool is_interface_claimed(hid_protocol_t proto) +{ + hid_iface_t *interface = get_interface_by_proto(proto); + + if (interface) { + return (interface->report_cb != NULL); + } + + return false; +} + +/** + * @brief Get Interface Descriptor + * + * @param[in] config_desc Pointer to Configuration Descriptor + * @param[in] offset Offset + * @param[in] proto Interface Protocol field to search, or set to `HID_PROTOCOL_NONE` to ignore + * @return esp_err_t + */ +static const usb_intf_desc_t *get_interface_desc_by_proto(const usb_config_desc_t *config_desc, + size_t *offset, + hid_protocol_t proto) +{ + size_t total_length = config_desc->wTotalLength; + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc; + + next_desc = next_interface_desc(next_desc, total_length, offset); + + while ( next_desc ) { + + const usb_intf_desc_t *ifc_desc = (const usb_intf_desc_t *)next_desc; + + if ( ifc_desc->bInterfaceClass == USB_CLASS_HID && + ifc_desc->bInterfaceSubClass == HID_SUBCLASS_BOOT_INTERFACE) { + + if (proto != HID_PROTOCOL_NONE) { + if (ifc_desc->bInterfaceProtocol == proto) { + return ifc_desc; + } + } else { + return ifc_desc; + } + + } + + next_desc = next_interface_desc(next_desc, total_length, offset); + }; + return NULL; +} + +/** + * @brief Interface user callback function. + * + * @param[in] iface Pointer to Interface structure + * @param[in] event HID Interface event + */ +static void interface_user_callback(hid_iface_t *iface, hid_interface_event_t event) +{ + if (!iface->user_cb) { + return; + } + + const hid_host_interface_event_t iface_event = { + .event = event, + .interface.dev_addr = iface->dev_addr, + .interface.num = iface->num, + .interface.proto = iface->proto + }; + + iface->user_cb(&iface_event, iface->user_cb_arg); +} + +/** + * @brief Add interface in a list + * + * @param[in] dev_config Pointer to device config structure + * @param[in] hid_device Pointer to HID device structure + * @param[in] iface_desc Pointer to an Interface descriptor + * @param[in] hid_desc Pointer to an HID device descriptor + * @param[in] ep_desc Pointer to an EP descriptor + * @return esp_err_t + */ +static esp_err_t add_interface_to_list(const hid_host_device_config_t *dev_config, + hid_device_t *hid_device, + const usb_intf_desc_t *iface_desc, + const hid_descriptor_t *hid_desc, + const usb_ep_desc_t *ep_desc) +{ + hid_iface_t *iface = calloc(1, sizeof(hid_iface_t)); + + HID_RETURN_ON_FALSE(iface, ESP_ERR_NO_MEM); + + iface->dev_addr = dev_config->dev_addr; + + if (iface_desc) { + iface->proto = iface_desc->bInterfaceProtocol; + iface->num = iface_desc->bInterfaceNumber; + } + + if (hid_desc) { + iface->country_code = hid_desc->bCountryCode; + iface->report_size = hid_desc->wReportDescriptorLength; + } + + // EP IN && INT Type + if (ep_desc) { + if ( (ep_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) && + (ep_desc->bmAttributes & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) ) { + iface->ep = ep_desc->bEndpointAddress; + iface->ep_mps = USB_EP_DESC_GET_MPS(ep_desc); + } else { + ESP_LOGE(TAG, "HID device does not have INT EP"); + } + } + + if (iface_desc && hid_desc && ep_desc) { + iface->dev = hid_device; + iface->ready = true; + } + + iface->user_cb = dev_config->iface_event_cb; + iface->user_cb_arg = dev_config->iface_event_arg; + + HID_ENTER_CRITICAL(); + STAILQ_INSERT_TAIL(&ifaces_tailq, iface, tailq_entry); + HID_EXIT_CRITICAL(); + + interface_user_callback(iface, HID_DEVICE_INTERFACE_INIT); + + return ESP_OK; +} + +/** + * @brief Search HID class descriptor in USB device + * + * @param[in] dev_addr USB device physical address + * @return true USB device contain HID Interface + * @return false USB does not contain HID Interface + */ +static bool is_hid_device(uint8_t dev_addr) +{ + bool is_hid_device = false; + usb_device_handle_t device; + const usb_config_desc_t *config_desc; + size_t offset = 0; + + if ( usb_host_device_open(s_hid_driver->client_handle, dev_addr, &device) == ESP_OK) { + if ( usb_host_get_active_config_descriptor(device, &config_desc) == ESP_OK ) { + if (get_interface_desc_by_proto(config_desc, &offset, HID_PROTOCOL_NONE)) { + is_hid_device = true; + } + } + usb_host_device_close(s_hid_driver->client_handle, device); + } + + if (is_hid_device) { + ESP_LOGD(TAG, "Found HID Interface, USB addr %d", dev_addr); + } else { + ESP_LOGW(TAG, "Device at addr %d has no HID Interface", dev_addr); + } + + return is_hid_device; +} + +/** + * @brief Create a list of available interfaces in RAM + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] dev_addr USB device physical address + * @param[in] sub_class USB HID SubClass value + * @return esp_err_t + */ +static esp_err_t create_interface_list(hid_device_t *hid_device, + const hid_host_device_config_t *dev_config, + uint8_t sub_class) +{ + const usb_config_desc_t *config_desc = NULL; + const usb_intf_desc_t *iface_desc = NULL; + const hid_descriptor_t *hid_desc = NULL; + const usb_ep_desc_t *ep_desc = NULL; + size_t offset = 0; + + HID_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(hid_device->dev_hdl, &config_desc)); + + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc; + size_t total_length = config_desc->wTotalLength; + + next_desc = next_interface_desc(next_desc, total_length, &offset); + + while ( next_desc ) { + iface_desc = (const usb_intf_desc_t *)next_desc; + hid_desc = NULL; + ep_desc = NULL; + + if ( iface_desc->bInterfaceClass == USB_CLASS_HID && + iface_desc->bInterfaceSubClass == sub_class) { + // HID descriptor (extract Report size) + next_desc = next_hid_descriptor(next_desc, total_length, &offset); + if (next_desc) { + hid_desc = (const hid_descriptor_t *)next_desc; + } + // HID mouse and keyboard contain one INT descriptor + next_desc = next_endpoint_desc(next_desc, total_length, &offset); + if (next_desc) { + ep_desc = (const usb_ep_desc_t *)next_desc; + } + + HID_RETURN_ON_ERROR( add_interface_to_list(dev_config, + hid_device, + iface_desc, + hid_desc, + ep_desc) ); + } + next_desc = next_interface_desc(next_desc, total_length, &offset); + }; + + return ESP_OK; +} + +/** + * @brief Destroy a list of available interfaces in RAM + * + * @return esp_err_t + */ +static esp_err_t destroy_interface_list(void) +{ + hid_iface_t *interface = NULL; + + for (int proto = HID_PROTOCOL_KEYBOARD; proto < HID_PROTOCOL_MAX; proto++) { + + interface = get_interface_by_proto(proto); + + if (interface) { + HID_ENTER_CRITICAL(); + interface->ready = false; + STAILQ_REMOVE(&ifaces_tailq, interface, hid_interface, tailq_entry); + HID_EXIT_CRITICAL(); + free(interface); + } + } + + return ESP_OK; +} + +/** + * @brief USB Host Client's event callback + * + * @param[in] event Client event message + * @param[in] arg Argument, does not used + */ +static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg) +{ + if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) { + if (is_hid_device(event->new_dev.address)) { + const hid_host_event_t hid_event = { + .event = HID_DEVICE_CONNECTED, + .device.address = event->new_dev.address, + }; + s_hid_driver->user_cb(&hid_event, s_hid_driver->user_arg); + } + } else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) { + hid_device_t *hid_device = get_hid_device_by_handle(event->dev_gone.dev_hdl); + if (hid_device) { + const hid_host_event_t msc_event = { + .event = HID_DEVICE_DISCONNECTED, + .device.handle = hid_device, + }; + s_hid_driver->user_cb(&msc_event, s_hid_driver->user_arg); + } + } +} + +/** + * @brief HID Host prepare interface transfer + * + * @param[in] iface Pointer to Interface structure, + * @param[in] callback Input report data callback + * @return esp_err_t + */ +static esp_err_t hid_host_interface_prepare_transfer(hid_iface_t *iface) +{ + HID_RETURN_ON_ERROR( usb_host_interface_claim( s_hid_driver->client_handle, + iface->dev->dev_hdl, + iface->num, 0) ); + + HID_RETURN_ON_ERROR( usb_host_transfer_alloc(iface->ep_mps, 0, &iface->in_xfer) ); + + return ESP_OK; +} + +/** + * @brief Disable active interface + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] iface Pointer to Interface structure + * @return esp_err_t + */ +static esp_err_t hid_host_disable_interface(hid_device_t *hid_device, hid_iface_t *iface) +{ + HID_RETURN_ON_INVALID_ARG(hid_device); + HID_RETURN_ON_INVALID_ARG(iface); + + if (!iface->ready) { + return ESP_ERR_INVALID_STATE; + } + + HID_RETURN_ON_ERROR( usb_host_endpoint_halt(hid_device->dev_hdl, iface->ep) ); + HID_RETURN_ON_ERROR( usb_host_endpoint_flush(hid_device->dev_hdl, iface->ep) ); + usb_host_endpoint_clear(hid_device->dev_hdl, iface->ep); + + return ESP_OK; +} + +/** + * @brief HID transfer complete status verification + * + * @param[in] transfer Pointer to USB transfer struct + * @return true Transfer completed successfully + * @return false Transfer completed with error + */ +static bool transfer_complete_status_verify(usb_transfer_t *transfer) +{ + bool completed = false; + + assert(transfer); + + switch (transfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + completed = true; + break; + case USB_TRANSFER_STATUS_NO_DEVICE: // User is notified about device disconnection from usb_event_cb + case USB_TRANSFER_STATUS_CANCELED: + break; + case USB_TRANSFER_STATUS_ERROR: + case USB_TRANSFER_STATUS_TIMED_OUT: + case USB_TRANSFER_STATUS_STALL: + case USB_TRANSFER_STATUS_OVERFLOW: + case USB_TRANSFER_STATUS_SKIPPED: + default: + // Transfer was not completed or cancelled by user. + ESP_LOGE(TAG, "Transfer failed, status %d", transfer->status); + } + return completed; +} + +/** + * @brief HID IN Transfer complete callback + * + * @param[in] transfer Pointer to transfer data structure + */ +static void in_xfer_done(usb_transfer_t *in_xfer) +{ + assert(in_xfer); + + hid_device_t *hid_device = (hid_device_t *)in_xfer->context; + hid_iface_t *iface = get_interface_by_ep(hid_device, in_xfer->bEndpointAddress); + + assert(iface); + + if (!transfer_complete_status_verify(in_xfer)) { + interface_user_callback(iface, HID_DEVICE_INTERFACE_TRANSFER_ERROR); + return; + } + + if (iface->report_cb) { + iface->report_cb(in_xfer->data_buffer, in_xfer->actual_num_bytes); + } + + usb_host_transfer_submit(in_xfer); +} + +static void hid_device_release(hid_device_t *hid_device) +{ + xSemaphoreGive(hid_device->device_busy); +} + +static esp_err_t hid_device_claim(hid_device_t *hid_device, uint32_t timeout_ms) +{ + return ( xSemaphoreTake(hid_device->device_busy, pdMS_TO_TICKS(timeout_ms)) + ? ESP_OK + : ESP_ERR_TIMEOUT ); +} + +static esp_err_t hid_device_wait_ready(hid_device_t *hid_device, uint32_t timeout_ms) +{ + HID_RETURN_ON_ERROR( hid_device_claim(hid_device, timeout_ms) ); + hid_device_release(hid_device); + return ESP_OK; +} + +/** + * @brief HID Control transfer complete callback + * + * @param[in] ctrl_xfer Pointer to transfer data structure + */ +static void ctrl_xfer_done(usb_transfer_t *ctrl_xfer) +{ + assert(ctrl_xfer); + hid_device_t *hid_device = (hid_device_t *)ctrl_xfer->context; + xSemaphoreGive(hid_device->ctrl_xfer_done); +} + +/** + * @brief HID control transfer synchronous. + * + * @note Passes interface and endpoint descriptors to obtain: + + * - interface number, IN endpoint, OUT endpoint, max. packet size + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] ctrl_xfer Pointer to the Transfer structure + * @param[in] len Number of bytes to transfer + * @param[in] timeout_ms Timeout in ms + * @return esp_err_t + */ +esp_err_t hid_control_transfer(hid_device_t *hid_device, + usb_transfer_t *ctrl_xfer, + size_t len, + uint32_t timeout_ms) +{ + ctrl_xfer->device_handle = hid_device->dev_hdl; + ctrl_xfer->callback = ctrl_xfer_done; + ctrl_xfer->context = hid_device; + ctrl_xfer->bEndpointAddress = 0; + ctrl_xfer->timeout_ms = timeout_ms; + ctrl_xfer->num_bytes = len; + + HID_RETURN_ON_ERROR( usb_host_transfer_submit_control(s_hid_driver->client_handle, ctrl_xfer)); + + BaseType_t received = xSemaphoreTake(hid_device->ctrl_xfer_done, pdMS_TO_TICKS(ctrl_xfer->timeout_ms)); + + if (received != pdTRUE) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + ESP_LOGE(TAG, "Control Transfer Timeout"); + + HID_RETURN_ON_ERROR( usb_host_endpoint_halt(hid_device->dev_hdl, ctrl_xfer->bEndpointAddress) ); + HID_RETURN_ON_ERROR( usb_host_endpoint_flush(hid_device->dev_hdl, ctrl_xfer->bEndpointAddress) ); + usb_host_endpoint_clear(hid_device->dev_hdl, ctrl_xfer->bEndpointAddress); + return ESP_ERR_TIMEOUT; + } + + ESP_LOG_BUFFER_HEXDUMP(TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_DEBUG); + + return ESP_OK; +} + +/** + * @brief HID class specific request Set + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] bRequest Value to fill bRequest filed in request + * @param[in] wValue Value to fill wValue filed in request + * @param[in] wIndex Value to fill wIndex filed in request + * @param[in] wLength Value to fill wLength filed in request + * @return esp_err_t + */ +static esp_err_t hid_class_request_set(hid_device_t *hid_device, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + uint16_t wLength) +{ + esp_err_t ret; + usb_transfer_t *ctrl_xfer = hid_device->ctrl_xfer; + HID_RETURN_ON_INVALID_ARG(hid_device); + HID_RETURN_ON_INVALID_ARG(hid_device->ctrl_xfer); + + HID_RETURN_ON_ERROR( hid_device_wait_ready(hid_device, 5000 /* timeout */) ); + HID_RETURN_ON_ERROR( hid_device_claim(hid_device, 5000 /* timeout */) ); + + usb_setup_packet_t *setup = (usb_setup_packet_t *)ctrl_xfer->data_buffer; + setup->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | + USB_BM_REQUEST_TYPE_TYPE_CLASS | + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + setup->bRequest = bRequest; + setup->wValue = wValue; + setup->wIndex = wIndex; + setup->wLength = wLength; + + ret = hid_control_transfer(hid_device, ctrl_xfer, USB_SETUP_PACKET_SIZE, 5000 /* timeout */); + + hid_device_release(hid_device); + + return ret; +} + +/** + * @brief HID class specific request Get + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] bRequest Value to fill bRequest filed in request + * @param[in] wValue Value to fill wValue filed in request + * @param[in] wIndex Value to fill wIndex filed in request + * @param[in] data Pointer to date buffer to put received data, should be not less than length + * @param[in] length Length of data to receive + * @return esp_err_t + */ +static esp_err_t hid_class_request_get(hid_device_t *hid_device, + uint8_t bRequest, + uint16_t wValue, + uint16_t wIndex, + uint8_t *data, + uint32_t length) +{ + esp_err_t ret; + HID_RETURN_ON_INVALID_ARG(hid_device); + HID_RETURN_ON_INVALID_ARG(hid_device->ctrl_xfer); + HID_RETURN_ON_INVALID_ARG(data); + + usb_transfer_t *ctrl_xfer = hid_device->ctrl_xfer; + + HID_RETURN_ON_ERROR( hid_device_wait_ready(hid_device, 5000 /* timeout */) ); + HID_RETURN_ON_ERROR( hid_device_claim(hid_device, 5000 /* timeout */) ); + + usb_setup_packet_t *setup = (usb_setup_packet_t *)ctrl_xfer->data_buffer; + + setup->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | + USB_BM_REQUEST_TYPE_TYPE_CLASS | + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + setup->bRequest = bRequest; + setup->wValue = wValue; + setup->wIndex = wIndex; + setup->wLength = length; + + ret = hid_control_transfer(hid_device, ctrl_xfer, USB_SETUP_PACKET_SIZE + length, 5000 /* timeout */); + + if (ESP_OK == ret) { + ctrl_xfer->actual_num_bytes -= USB_SETUP_PACKET_SIZE; + + if (ctrl_xfer->actual_num_bytes <= length) { + memcpy(data, ctrl_xfer->data_buffer + USB_SETUP_PACKET_SIZE, ctrl_xfer->actual_num_bytes); + } + } + + hid_device_release(hid_device); + + return ret; +} + +/** + * @brief HID class specific request SET IDLE + * + * @param[in] iface Pointer to HID Interface configuration structure + * @param[in] duration 0 (zero) for the indefinite duration, non-zero, then a fixed duration used. + * @param[in] report_id If 0 (zero) the idle rate applies to all input reports generated by the device, otherwise ReportID + * @return esp_err_t + */ +static esp_err_t hid_class_request_set_idle(hid_iface_t *iface, + uint8_t duration, + uint8_t report_id) +{ + HID_RETURN_ON_INVALID_ARG(iface); + + return hid_class_request_set(iface->dev, + HID_CLASS_SPECIFIC_REQ_SET_IDLE, + (duration << 8) | report_id, + iface->num, 0); +} + +/** + * @brief HID class specific request SET PROTOCOL + * + * @param[in] iface Pointer to HID Interface configuration structure + * @param[in] protocol HID report protocol (boot or report) + * @return esp_err_t + */ +static esp_err_t hid_class_request_set_protocol(hid_iface_t *iface, + hid_report_protocol_t protocol) +{ + HID_RETURN_ON_INVALID_ARG(iface); + + return hid_class_request_set(iface->dev, + HID_CLASS_SPECIFIC_REQ_SET_PROTOCOL, + protocol, + iface->num, 0); +} + +/** + * @brief HID class specific request GET PROTOCOL + * + * @param[in] iface Pointer to HID Interface configuration structure + * @param[in] protocol Pointer to HID report protocol (boot or report) of device + * @return esp_err_t + */ +static esp_err_t hid_class_request_get_protocol(hid_iface_t *iface, + hid_report_protocol_t *protocol) +{ + uint8_t tmp[1] = { 0xff }; + + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(protocol); + + hid_class_request_get(iface->dev, HID_CLASS_SPECIFIC_REQ_GET_PROTOCOL, 0, iface->num, tmp, 1); + *protocol = (hid_report_protocol_t) tmp[0]; + return ESP_OK; +} + +/** + * @brief HID Host Interface start transfer + * + * @param[in] iface Pointer to HID Interface configuration structure + * @param[in] timeout Timeout in ms + * @return esp_err_t + */ +static esp_err_t hid_host_iface_start_transfer(hid_iface_t *iface, + const uint32_t timeout) +{ + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(iface->in_xfer); + HID_RETURN_ON_INVALID_ARG(iface->dev); + + // prepare transfer + iface->in_xfer->device_handle = iface->dev->dev_hdl; + iface->in_xfer->callback = in_xfer_done; + iface->in_xfer->context = iface->dev; + iface->in_xfer->timeout_ms = timeout; + iface->in_xfer->bEndpointAddress = iface->ep; + iface->in_xfer->num_bytes = iface->ep_mps; + + // start data transfer + return usb_host_transfer_submit(iface->in_xfer); +} + +// ----------------------- Public -------------------------- + +esp_err_t hid_host_install(const hid_host_driver_config_t *config) +{ + esp_err_t ret; + HID_RETURN_ON_INVALID_ARG(config); + HID_RETURN_ON_INVALID_ARG(config->callback); + if ( config->create_background_task ) { + HID_RETURN_ON_FALSE(config->stack_size != 0, ESP_ERR_INVALID_ARG); + HID_RETURN_ON_FALSE(config->task_priority != 0, ESP_ERR_INVALID_ARG); + } + HID_RETURN_ON_FALSE(!s_hid_driver, ESP_ERR_INVALID_STATE); + + hid_driver_t *driver = calloc(1, sizeof(hid_driver_t)); + HID_RETURN_ON_FALSE(driver, ESP_ERR_NO_MEM); + driver->user_cb = config->callback; + driver->user_arg = config->callback_arg; + + usb_host_client_config_t client_config = { + .async.client_event_callback = client_event_cb, + .async.callback_arg = NULL, + .max_num_event_msg = 10, + }; + + driver->end_client_event_handling = false; + driver->all_events_handled = xSemaphoreCreateBinary(); + HID_GOTO_ON_FALSE(driver->all_events_handled, ESP_ERR_NO_MEM); + + HID_GOTO_ON_ERROR( usb_host_client_register(&client_config, &driver->client_handle) ); + + HID_ENTER_CRITICAL(); + HID_GOTO_ON_FALSE_CRITICAL(!s_hid_driver, ESP_ERR_INVALID_STATE); + s_hid_driver = driver; + STAILQ_INIT(&devices_tailq); + STAILQ_INIT(&ifaces_tailq); + HID_EXIT_CRITICAL(); + + if (config->create_background_task) { + BaseType_t task_created = xTaskCreatePinnedToCore( + event_handler_task, + "USB HID HOST", + config->stack_size, + NULL, + config->task_priority, + NULL, + config->core_id); + HID_GOTO_ON_FALSE(task_created, ESP_ERR_NO_MEM); + } + + return ESP_OK; + +fail: + s_hid_driver = NULL; + if (driver->client_handle) { + usb_host_client_deregister(driver->client_handle); + } + if (driver->all_events_handled) { + vSemaphoreDelete(driver->all_events_handled); + } + free(driver); + return ret; +} + +esp_err_t hid_host_uninstall(void) +{ + // Make sure hid driver is installed, + // not being uninstalled from other task + // and no hid device is registered + HID_ENTER_CRITICAL(); + HID_RETURN_ON_FALSE_CRITICAL( s_hid_driver != NULL, ESP_ERR_INVALID_STATE ); + HID_RETURN_ON_FALSE_CRITICAL( !s_hid_driver->end_client_event_handling, ESP_ERR_INVALID_STATE ); + HID_RETURN_ON_FALSE_CRITICAL( STAILQ_EMPTY(&devices_tailq), ESP_ERR_INVALID_STATE ); + HID_RETURN_ON_FALSE_CRITICAL( STAILQ_EMPTY(&ifaces_tailq), ESP_ERR_INVALID_STATE ); + s_hid_driver->end_client_event_handling = true; + HID_EXIT_CRITICAL(); + + xSemaphoreTake(s_hid_driver->all_events_handled, portMAX_DELAY); + vSemaphoreDelete(s_hid_driver->all_events_handled); + free(s_hid_driver); + s_hid_driver = NULL; + return ESP_OK; +} + +void hid_host_event_handler_task(void *arg) +{ + event_handler_task(arg); +} + +esp_err_t hid_host_claim_interface(const hid_host_interface_config_t *iface_config, + hid_host_interface_handle_t *iface_handle) +{ + hid_iface_t *iface = NULL; + hid_report_protocol_t report_protocol; + + HID_RETURN_ON_FALSE((iface_config->proto > HID_PROTOCOL_NONE) + && (iface_config->proto < HID_PROTOCOL_MAX), + ESP_ERR_INVALID_ARG); + + // Search Interface in list + iface = get_interface_by_proto(iface_config->proto); + + // Interface not found in list + if (iface == NULL) { + return ESP_ERR_NOT_FOUND; + } + + // Interface alredy claimed + if (iface->report_cb != NULL) { + return ESP_ERR_INVALID_STATE; + } + + HID_RETURN_ON_ERROR( hid_host_interface_prepare_transfer(iface) ); + + HID_RETURN_ON_ERROR( hid_class_request_set_idle(iface, 0, 0) ); + + if (iface->proto == HID_PROTOCOL_MOUSE) { + HID_RETURN_ON_ERROR (hid_class_request_get_protocol(iface, &report_protocol) ); + if (report_protocol != HID_REPORT_PROTOCOL_BOOT) { + HID_RETURN_ON_ERROR( hid_class_request_set_protocol(iface, HID_REPORT_PROTOCOL_BOOT) ); + } + // verify that protocol has been successfully changed + report_protocol = HID_REPORT_PROTOCOL_MAX; + HID_RETURN_ON_ERROR (hid_class_request_get_protocol(iface, &report_protocol) ); + if (report_protocol != HID_REPORT_PROTOCOL_BOOT) { + ESP_LOGE(TAG, "Interface %d Set BOOT Protocol Failure, protocol=%d", + iface->num, + report_protocol); + } + } + + // Claim interface and save report callback + iface->report_cb = iface_config->callback; + + HID_RETURN_ON_ERROR( hid_host_iface_start_transfer(iface, 5000 /* timeout */) ); + + interface_user_callback(iface, HID_DEVICE_INTERFACE_CLAIM); + + *iface_handle = iface; + + return ESP_OK; +} + +esp_err_t hid_host_release_interface(hid_host_interface_handle_t iface_handle) +{ + hid_iface_t *iface = (hid_iface_t *)iface_handle; + + HID_RETURN_ON_FALSE(iface, ESP_ERR_NOT_FOUND); + + if (!is_interface_claimed(iface->proto)) { + return ESP_ERR_INVALID_STATE; + } + + if (iface->ready) { + HID_RETURN_ON_ERROR( hid_host_disable_interface(iface->dev, iface)); + HID_RETURN_ON_ERROR( usb_host_interface_release(s_hid_driver->client_handle, iface->dev->dev_hdl, iface->num) ); + } else { + usb_host_interface_release(s_hid_driver->client_handle, iface->dev->dev_hdl, iface->num); + } + + ESP_ERROR_CHECK( usb_host_transfer_free(iface->in_xfer) ); + + iface->report_cb = NULL; + + interface_user_callback(iface, HID_DEVICE_INTERFACE_RELEASE); + + return ESP_OK; +} + +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) +{ + esp_err_t ret; + hid_device_t *hid_device; + + HID_GOTO_ON_FALSE( hid_device = calloc(1, sizeof(hid_device_t)), ESP_ERR_NO_MEM ); + + HID_GOTO_ON_FALSE( hid_device->ctrl_xfer_done = xSemaphoreCreateBinary(), ESP_ERR_NO_MEM ); + HID_GOTO_ON_FALSE( hid_device->device_busy = xSemaphoreCreateMutex(), ESP_ERR_NO_MEM ); + + HID_GOTO_ON_ERROR( usb_host_device_open(s_hid_driver->client_handle, + hid_host_dev_config->dev_addr, + &hid_device->dev_hdl) ); + + + // dev_info.bMaxPacketSize0 + 1 because of the GET PROTOCOL specific class request + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(hid_device->dev_hdl, &dev_info)); + HID_GOTO_ON_ERROR(usb_host_transfer_alloc(dev_info.bMaxPacketSize0 + 1, 0, &hid_device->ctrl_xfer)); + + HID_GOTO_ON_ERROR( create_interface_list(hid_device, + hid_host_dev_config, + HID_SUBCLASS_BOOT_INTERFACE) ); + + HID_ENTER_CRITICAL(); + HID_GOTO_ON_FALSE_CRITICAL( s_hid_driver, ESP_ERR_INVALID_STATE ); + HID_GOTO_ON_FALSE_CRITICAL( s_hid_driver->client_handle, ESP_ERR_INVALID_STATE ); + STAILQ_INSERT_TAIL(&devices_tailq, hid_device, tailq_entry); + HID_EXIT_CRITICAL(); + + *hid_device_handle = hid_device; + + return ESP_OK; + +fail: + hid_host_uninstall_device(hid_device); + return ret; +} + +esp_err_t hid_host_uninstall_device(hid_host_device_handle_t hid_device_handle) +{ + hid_device_t *hid_device = (hid_device_t *)hid_device_handle; + + HID_RETURN_ON_INVALID_ARG(hid_device); + + for (int proto = HID_PROTOCOL_KEYBOARD; proto < HID_PROTOCOL_MAX; proto++) { + if (is_interface_claimed(proto)) { + return ESP_ERR_INVALID_STATE; + }; + } + + HID_RETURN_ON_ERROR( destroy_interface_list() ); + + HID_RETURN_ON_ERROR( usb_host_transfer_free(hid_device->ctrl_xfer) ); + HID_RETURN_ON_ERROR( usb_host_device_close(s_hid_driver->client_handle, hid_device->dev_hdl) ); + + if (hid_device->ctrl_xfer_done) { + vSemaphoreDelete(hid_device->ctrl_xfer_done); + } + + if (hid_device->device_busy) { + vSemaphoreDelete(hid_device->device_busy); + } + + HID_ENTER_CRITICAL(); + STAILQ_REMOVE(&devices_tailq, hid_device, hid_host_device, tailq_entry); + HID_EXIT_CRITICAL(); + + free(hid_device); + return ESP_OK; + +} + +esp_err_t hid_host_print_descriptors(hid_host_device_handle_t hid_device_handle) +{ + hid_device_t *hid_devive = (hid_device_t *)hid_device_handle; + const usb_device_desc_t *device_desc; + const usb_config_desc_t *config_desc; + HID_RETURN_ON_ERROR( usb_host_get_device_descriptor(hid_devive->dev_hdl, &device_desc) ); + HID_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(hid_devive->dev_hdl, &config_desc) ); + usb_print_device_descriptor(device_desc); + usb_print_config_descriptor(config_desc, NULL); + return ESP_OK; +} diff --git a/examples/peripherals/usb/host/hid/components/hid/include/hid.h b/examples/peripherals/usb/host/hid/components/hid/include/hid.h new file mode 100644 index 00000000000..7f6a99e6eb2 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/include/hid.h @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#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 diff --git a/examples/peripherals/usb/host/hid/components/hid/include/hid_host.h b/examples/peripherals/usb/host/hid/components/hid/include/hid_host.h new file mode 100644 index 00000000000..2352666bcfb --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/include/hid_host.h @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include + +#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 diff --git a/examples/peripherals/usb/host/hid/components/hid/include/hid_usage_keyboard.h b/examples/peripherals/usb/host/hid/components/hid/include/hid_usage_keyboard.h new file mode 100644 index 00000000000..7683733be53 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/include/hid_usage_keyboard.h @@ -0,0 +1,292 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#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 diff --git a/examples/peripherals/usb/host/hid/components/hid/include/hid_usage_mouse.h b/examples/peripherals/usb/host/hid/components/hid/include/hid_usage_mouse.h new file mode 100644 index 00000000000..7f1b21012a5 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/include/hid_usage_mouse.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#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 diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/CMakeLists.txt b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/CMakeLists.txt new file mode 100644 index 00000000000..9e5560df039 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/CMakeLists.txt @@ -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) diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/README.md b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/README.md new file mode 100644 index 00000000000..cfd217d9116 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/CMakeLists.txt b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/CMakeLists.txt new file mode 100644 index 00000000000..ab2a0531369 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/CMakeLists.txt @@ -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) diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_app_main.c b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_app_main.c new file mode 100644 index 00000000000..3617b52f692 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_app_main.c @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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(); +} diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_basic.c b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_basic.c new file mode 100644 index 00000000000..66c0657bc01 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_basic.c @@ -0,0 +1,379 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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); +} diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_basic.h b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_basic.h new file mode 100644 index 00000000000..ed7d84e2ede --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_basic.h @@ -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 diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_err_handling.c b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_err_handling.c new file mode 100644 index 00000000000..9d0fe8ef0c0 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_err_handling.c @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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])(); + } +} diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_usb_driver.c b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_usb_driver.c new file mode 100644 index 00000000000..466f376d494 --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/main/test_hid_usb_driver.c @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#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); +} diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/pytest_hid_basic.py b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/pytest_hid_basic.py new file mode 100644 index 00000000000..8d9c58c9d6c --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/pytest_hid_basic.py @@ -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() diff --git a/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/sdkconfig.ci.defaults b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/sdkconfig.ci.defaults new file mode 100644 index 00000000000..e0406a0174a --- /dev/null +++ b/examples/peripherals/usb/host/hid/components/hid/test_apps/hid_basic/sdkconfig.ci.defaults @@ -0,0 +1,3 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y diff --git a/examples/peripherals/usb/host/hid/main/CMakeLists.txt b/examples/peripherals/usb/host/hid/main/CMakeLists.txt new file mode 100644 index 00000000000..20aa32069e4 --- /dev/null +++ b/examples/peripherals/usb/host/hid/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "hid_host_example.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/usb/host/hid/main/hid_host_example.c b/examples/peripherals/usb/host/hid/main/hid_host_example.c new file mode 100644 index 00000000000..f64369dc0b6 --- /dev/null +++ b/examples/peripherals/usb/host/hid/main/hid_host_example.c @@ -0,0 +1,391 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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"); +}