From cede028f6c920654709465731ba7850263f09bf9 Mon Sep 17 00:00:00 2001 From: nopnop2002 Date: Wed, 21 Aug 2024 15:49:59 +0900 Subject: [PATCH] added KeyboardDemo --- KeyboardDemo/CMakeLists.txt | 8 + KeyboardDemo/README.md | 18 + KeyboardDemo/main/CMakeLists.txt | 1 + KeyboardDemo/main/component.mk | 8 + KeyboardDemo/main/idf_component.yml | 4 + KeyboardDemo/main/main.c | 110 +++++ KeyboardDemo/main/usb_hid.c | 599 ++++++++++++++++++++++++++++ KeyboardDemo/main/usb_hid.h | 28 ++ KeyboardDemo/sdkconfig.defaults | 18 + 9 files changed, 794 insertions(+) create mode 100644 KeyboardDemo/CMakeLists.txt create mode 100644 KeyboardDemo/README.md create mode 100644 KeyboardDemo/main/CMakeLists.txt create mode 100644 KeyboardDemo/main/component.mk create mode 100644 KeyboardDemo/main/idf_component.yml create mode 100644 KeyboardDemo/main/main.c create mode 100644 KeyboardDemo/main/usb_hid.c create mode 100644 KeyboardDemo/main/usb_hid.h create mode 100644 KeyboardDemo/sdkconfig.defaults diff --git a/KeyboardDemo/CMakeLists.txt b/KeyboardDemo/CMakeLists.txt new file mode 100644 index 0000000..2aa16fe --- /dev/null +++ b/KeyboardDemo/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) + +set(EXTRA_COMPONENT_DIRS ../components/ssd1306) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ssd1306) diff --git a/KeyboardDemo/README.md b/KeyboardDemo/README.md new file mode 100644 index 0000000..3228191 --- /dev/null +++ b/KeyboardDemo/README.md @@ -0,0 +1,18 @@ +# KeyboardDemo for SSD1306 +Demo that displays input from the keyboard. +I used [this](https://components.espressif.com/components/espressif/usb_host_hid/) component. + +# Hardware requirements + +- ESP32S2/S3 + These have USB-HOST functionality. + +- USB Connector + I used this: + ![usb-conector](https://github.com/user-attachments/assets/a8fb5313-54f6-422a-98de-5f4aff8c94b7) + +- USB keyboard + +![usb-keyboard-1](https://github.com/user-attachments/assets/92ecc30b-6ed1-47ce-9eb4-a06596c06ce7) + + diff --git a/KeyboardDemo/main/CMakeLists.txt b/KeyboardDemo/main/CMakeLists.txt new file mode 100644 index 0000000..660507f --- /dev/null +++ b/KeyboardDemo/main/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "main.c" "usb_hid.c" INCLUDE_DIRS "." REQUIRED_IDF_TARGETS esp32s2 esp32s3) diff --git a/KeyboardDemo/main/component.mk b/KeyboardDemo/main/component.mk new file mode 100644 index 0000000..61f8990 --- /dev/null +++ b/KeyboardDemo/main/component.mk @@ -0,0 +1,8 @@ +# +# Main component makefile. +# +# This Makefile can be left empty. By default, it will take the sources in the +# src/ directory, compile them and link them into lib(subdirectory_name).a +# in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# diff --git a/KeyboardDemo/main/idf_component.yml b/KeyboardDemo/main/idf_component.yml new file mode 100644 index 0000000..0c3620e --- /dev/null +++ b/KeyboardDemo/main/idf_component.yml @@ -0,0 +1,4 @@ +## IDF Component Manager Manifest File +dependencies: + idf: ">=4.4" + usb_host_hid: "^1.0.1" diff --git a/KeyboardDemo/main/main.c b/KeyboardDemo/main/main.c new file mode 100644 index 0000000..9c70a9f --- /dev/null +++ b/KeyboardDemo/main/main.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +//#include "esp_random.h" +#include "esp_log.h" + +#include "ssd1306.h" + +#include "usb_hid.h" + +QueueHandle_t app_event_hid = NULL; + +void usb_hid_task(void *pvParameters); + +/* + You have to set this config value with menuconfig + CONFIG_INTERFACE + + for i2c + CONFIG_MODEL + CONFIG_SDA_GPIO + CONFIG_SCL_GPIO + CONFIG_RESET_GPIO + + for SPI + CONFIG_CS_GPIO + CONFIG_DC_GPIO + CONFIG_RESET_GPIO +*/ + +#define tag "SSD1306" + +void app_main(void) +{ + SSD1306_t dev; + +#if CONFIG_I2C_INTERFACE + ESP_LOGI(tag, "INTERFACE is i2c"); + ESP_LOGI(tag, "CONFIG_SDA_GPIO=%d",CONFIG_SDA_GPIO); + ESP_LOGI(tag, "CONFIG_SCL_GPIO=%d",CONFIG_SCL_GPIO); + ESP_LOGI(tag, "CONFIG_RESET_GPIO=%d",CONFIG_RESET_GPIO); + i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO); +#endif // CONFIG_I2C_INTERFACE + +#if CONFIG_SPI_INTERFACE + ESP_LOGI(tag, "INTERFACE is SPI"); + ESP_LOGI(tag, "CONFIG_MOSI_GPIO=%d",CONFIG_MOSI_GPIO); + ESP_LOGI(tag, "CONFIG_SCLK_GPIO=%d",CONFIG_SCLK_GPIO); + ESP_LOGI(tag, "CONFIG_CS_GPIO=%d",CONFIG_CS_GPIO); + ESP_LOGI(tag, "CONFIG_DC_GPIO=%d",CONFIG_DC_GPIO); + ESP_LOGI(tag, "CONFIG_RESET_GPIO=%d",CONFIG_RESET_GPIO); + spi_master_init(&dev, CONFIG_MOSI_GPIO, CONFIG_SCLK_GPIO, CONFIG_CS_GPIO, CONFIG_DC_GPIO, CONFIG_RESET_GPIO); +#endif // CONFIG_SPI_INTERFACE + +#if CONFIG_SSD1306_128x64 + ESP_LOGI(tag, "Panel is 128x64"); + ssd1306_init(&dev, 128, 64); + int maxPage = 8; +#endif // CONFIG_SSD1306_128x64 +#if CONFIG_SSD1306_128x32 + ESP_LOGI(tag, "Panel is 128x32"); + ssd1306_init(&dev, 128, 32); + int maxPage = 4; +#endif // CONFIG_SSD1306_128x32 + + // Create queue + app_event_hid = xQueueCreate(10, sizeof(HID_EVENT_t)); + configASSERT( app_event_hid ); + + // Start tasks + xTaskCreate(&usb_hid_task, "usb_hid_task", 1024*2, NULL, 9, NULL); + + ssd1306_clear_screen(&dev, false); + ssd1306_contrast(&dev, 0xff); + + // Wait event + HID_EVENT_t hidEvent; + char text[16]; + memset(text, 0x20, sizeof(text)); + int currentIndex = 0; + int currentPage = 0; + while (1) { + BaseType_t received = xQueueReceive(app_event_hid, &hidEvent, portMAX_DELAY); + ESP_LOGI(tag, "xQueueReceive received=%d hidEvent.hid_event_type=%d", received, hidEvent.hid_event_type); + if (hidEvent.hid_event_type == APP_EVENT_KEYBOARD) { + if (hidEvent.key_event.state == KEY_STATE_PRESSED) { + ESP_LOGI(tag, "key_event.modifier=%d key_event.key_code=%d key_event.key_char=[%c]", + hidEvent.key_event.modifier, hidEvent.key_event.key_code, hidEvent.key_event.key_char); + if (hidEvent.key_event.key_code == 40) { // Enter + if (currentPage+1 == maxPage) continue; + memset(text, 0x20, sizeof(text)); + currentIndex = 0; + currentPage++; + } else if (hidEvent.key_event.key_code == 42) { // Back Space + if (currentIndex == 0) continue; + text[--currentIndex] = 0x20; + ssd1306_display_text(&dev, currentPage, text, 16, false); + } else { + if (currentIndex == 16) continue; + text[currentIndex++] = hidEvent.key_event.key_char; + ssd1306_display_text(&dev, currentPage, text, 16, false); + } + } + } + } +} diff --git a/KeyboardDemo/main/usb_hid.c b/KeyboardDemo/main/usb_hid.c new file mode 100644 index 0000000..d25f0f1 --- /dev/null +++ b/KeyboardDemo/main/usb_hid.c @@ -0,0 +1,599 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "freertos/queue.h" +#include "esp_err.h" +#include "esp_log.h" +#include "usb/usb_host.h" +//#include "errno.h" +#include "driver/gpio.h" + +#include "usb/hid_host.h" +#include "usb/hid_usage_keyboard.h" +#include "usb/hid_usage_mouse.h" + +#include "usb_hid.h" + +static const char *TAG = "usb_hid"; + +extern QueueHandle_t app_event_hid; +QueueHandle_t app_event_queue = NULL; + +/** + * @brief APP event group + * + * Application logic can be different. There is a one among other ways to distingiush the + * event by application event group. + * In this example we have two event groups: + * APP_EVENT - General event, which is APP_QUIT_PIN press event (Generally, it is IO0). + * APP_EVENT_HID_HOST - HID Host Driver event, such as device connection/disconnection or input report. + */ +typedef enum { + APP_EVENT = 0, + APP_EVENT_HID_HOST +} app_event_group_t; + +/** + * @brief APP event queue + * + * This event is used for delivering the HID Host event from callback to a task. + */ +typedef struct { + app_event_group_t event_group; + /* HID Host - Device related info */ + struct { + hid_host_device_handle_t handle; + hid_host_driver_event_t event; + void *arg; + } hid_host_device; +} app_event_queue_t; + +/** + * @brief HID Protocol string names + */ +static const char *hid_proto_name_str[] = { + "NONE", + "KEYBOARD", + "MOUSE" +}; + +/* Main char symbol for ENTER key */ +#define KEYBOARD_ENTER_MAIN_CHAR '\r' +#if 0 +/* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */ +#define KEYBOARD_ENTER_LF_EXTEND 1 +#endif + +/** + * @brief Scancode to ascii table + */ +const uint8_t keycode2ascii [57][2] = { + {0, 0}, /* HID_KEY_NO_PRESS */ + {0, 0}, /* HID_KEY_ROLLOVER */ + {0, 0}, /* HID_KEY_POST_FAIL */ + {0, 0}, /* HID_KEY_ERROR_UNDEFINED */ + {'a', 'A'}, /* HID_KEY_A */ + {'b', 'B'}, /* HID_KEY_B */ + {'c', 'C'}, /* HID_KEY_C */ + {'d', 'D'}, /* HID_KEY_D */ + {'e', 'E'}, /* HID_KEY_E */ + {'f', 'F'}, /* HID_KEY_F */ + {'g', 'G'}, /* HID_KEY_G */ + {'h', 'H'}, /* HID_KEY_H */ + {'i', 'I'}, /* HID_KEY_I */ + {'j', 'J'}, /* HID_KEY_J */ + {'k', 'K'}, /* HID_KEY_K */ + {'l', 'L'}, /* HID_KEY_L */ + {'m', 'M'}, /* HID_KEY_M */ + {'n', 'N'}, /* HID_KEY_N */ + {'o', 'O'}, /* HID_KEY_O */ + {'p', 'P'}, /* HID_KEY_P */ + {'q', 'Q'}, /* HID_KEY_Q */ + {'r', 'R'}, /* HID_KEY_R */ + {'s', 'S'}, /* HID_KEY_S */ + {'t', 'T'}, /* HID_KEY_T */ + {'u', 'U'}, /* HID_KEY_U */ + {'v', 'V'}, /* HID_KEY_V */ + {'w', 'W'}, /* HID_KEY_W */ + {'x', 'X'}, /* HID_KEY_X */ + {'y', 'Y'}, /* HID_KEY_Y */ + {'z', 'Z'}, /* HID_KEY_Z */ + {'1', '!'}, /* HID_KEY_1 */ + {'2', '@'}, /* HID_KEY_2 */ + {'3', '#'}, /* HID_KEY_3 */ + {'4', '$'}, /* HID_KEY_4 */ + {'5', '%'}, /* HID_KEY_5 */ + {'6', '^'}, /* HID_KEY_6 */ + {'7', '&'}, /* HID_KEY_7 */ + {'8', '*'}, /* HID_KEY_8 */ + {'9', '('}, /* HID_KEY_9 */ + {'0', ')'}, /* HID_KEY_0 */ + {KEYBOARD_ENTER_MAIN_CHAR, KEYBOARD_ENTER_MAIN_CHAR}, /* HID_KEY_ENTER */ + {0, 0}, /* HID_KEY_ESC */ + {'\b', 0}, /* HID_KEY_DEL */ + {0, 0}, /* HID_KEY_TAB */ + {' ', ' '}, /* HID_KEY_SPACE */ + {'-', '_'}, /* HID_KEY_MINUS */ + {'=', '+'}, /* HID_KEY_EQUAL */ + {'[', '{'}, /* HID_KEY_OPEN_BRACKET */ + {']', '}'}, /* HID_KEY_CLOSE_BRACKET */ + {'\\', '|'}, /* HID_KEY_BACK_SLASH */ + {'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH + {';', ':'}, /* HID_KEY_COLON */ + {'\'', '"'}, /* HID_KEY_QUOTE */ + {'`', '~'}, /* HID_KEY_TILDE */ + {',', '<'}, /* HID_KEY_LESS */ + {'.', '>'}, /* HID_KEY_GREATER */ + {'/', '?'} /* HID_KEY_SLASH */ +}; + +/** + * @brief Makes new line depending on report output protocol type + * + * @param[in] proto Current protocol to output + */ +static void hid_print_new_device_report_header(hid_protocol_t proto) +{ + static hid_protocol_t prev_proto_output = -1; + + if (prev_proto_output != proto) { + prev_proto_output = proto; + printf("\r\n"); + if (proto == HID_PROTOCOL_MOUSE) { + printf("Mouse\r\n"); + } else if (proto == HID_PROTOCOL_KEYBOARD) { + printf("Keyboard\r\n"); + } else { + printf("Generic\r\n"); + } + fflush(stdout); + } +} + +/** + * @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_shift(uint8_t modifier) +{ + if (((modifier & HID_LEFT_SHIFT) == HID_LEFT_SHIFT) || + ((modifier & HID_RIGHT_SHIFT) == 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 + * @param[in] key_char Pointer to key char data + * + * @return true Key scancode converted successfully + * @return false Key scancode unknown + */ +static inline bool hid_keyboard_get_char(uint8_t modifier, + uint8_t key_code, + unsigned char *key_char) +{ + uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0; + + if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_SLASH)) { + *key_char = keycode2ascii[key_code][mod]; + } else { + // All other key pressed + return false; + } + + return true; +} + +/** + * @brief HID Keyboard print char symbol + * + * @param[in] key_char Keyboard char to stdout + */ +#if 0 +static inline void hid_keyboard_print_char(unsigned int key_char) +{ + if (!!key_char) { + putchar(key_char); +#if (KEYBOARD_ENTER_LF_EXTEND) + if (KEYBOARD_ENTER_MAIN_CHAR == key_char) { + putchar('\n'); + } +#endif // KEYBOARD_ENTER_LF_EXTEND + fflush(stdout); + } +} +#endif + +/** + * @brief Key Event. Key event with the key code, state and modifier. + * + * @param[in] key_event Pointer to Key Event structure + * + */ +static void key_event_callback(key_event_t *key_event) +{ + ESP_LOGI(TAG, "key_event_callback key_event->state=%d", key_event->state); + unsigned char key_char; + + hid_print_new_device_report_header(HID_PROTOCOL_KEYBOARD); + HID_EVENT_t hidEvent; + hidEvent.hid_event_type = APP_EVENT_KEYBOARD; + + if (KEY_STATE_PRESSED == key_event->state) { + ESP_LOGI(TAG, "key_event_callback key_event->modifier=%d key_event->key_code=%d", key_event->modifier, key_event->key_code); + if (hid_keyboard_get_char(key_event->modifier, + key_event->key_code, &key_char)) { + + hidEvent.key_event.state = KEY_STATE_PRESSED; + hidEvent.key_event.modifier = key_event->modifier; + hidEvent.key_event.key_code = key_event->key_code; + hidEvent.key_event.key_char = key_char; + xQueueSendFromISR(app_event_hid, &hidEvent, NULL); +#if 0 + hid_keyboard_print_char(key_char); +#endif + + } + } + if (KEY_STATE_RELEASED == key_event->state) { + hidEvent.key_event.state = KEY_STATE_RELEASED; + xQueueSendFromISR(app_event_hid, &hidEvent, NULL); + } +} + +/** + * @brief Key buffer scan code search. + * + * @param[in] src Pointer to source buffer where to search + * @param[in] key Key scancode to search + * @param[in] length Size of the source buffer + */ +static inline bool key_found(const uint8_t *const src, + uint8_t key, + unsigned int length) +{ + for (unsigned int i = 0; i < length; i++) { + ESP_LOGD(TAG, "key_found src[%d]=%d key=%d", i, src[i], key); + if (src[i] == key) { + return true; + } + } + return false; +} + +/** + * @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) +{ + hid_keyboard_input_report_boot_t *kb_report = (hid_keyboard_input_report_boot_t *)data; + + if (length < sizeof(hid_keyboard_input_report_boot_t)) { + return; + } + + static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = { 0 }; + key_event_t key_event; + + for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { + + // key has been released verification + if (prev_keys[i] > HID_KEY_ERROR_UNDEFINED && + !key_found(kb_report->key, prev_keys[i], HID_KEYBOARD_KEY_MAX)) { + key_event.key_code = prev_keys[i]; + key_event.modifier = 0; + key_event.state = KEY_STATE_RELEASED; + key_event_callback(&key_event); + } + + // key has been pressed verification + if (kb_report->key[i] > HID_KEY_ERROR_UNDEFINED && + !key_found(prev_keys, kb_report->key[i], HID_KEYBOARD_KEY_MAX)) { + key_event.key_code = kb_report->key[i]; + key_event.modifier = kb_report->modifier.val; + key_event.state = KEY_STATE_PRESSED; + key_event_callback(&key_event); + } + } + + memcpy(prev_keys, &kb_report->key, HID_KEYBOARD_KEY_MAX); +} + +/** + * @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; + + if (length < sizeof(hid_mouse_input_report_boot_t)) { + return; + } + + ESP_LOGD(TAG, "hid_host_mouse_report_callback mouse_report->x_displacement=%d mouse_report->y_displacement=%d", + mouse_report->x_displacement, mouse_report->y_displacement); + ESP_LOGD(TAG, "hid_host_mouse_report_callback mouse_report->buttons.button1=%d mouse_report->buttons.button2=%d", + mouse_report->buttons.button1, mouse_report->buttons.button2); + HID_EVENT_t hidEvent; + hidEvent.hid_event_type = APP_EVENT_MOUSE; + hidEvent.mouse_event.x_displacement = mouse_report->x_displacement; + hidEvent.mouse_event.y_displacement = mouse_report->y_displacement; + hidEvent.mouse_event.button1 = mouse_report->buttons.button1; + hidEvent.mouse_event.button2 = mouse_report->buttons.button2; + hidEvent.mouse_event.button3 = mouse_report->buttons.button3; + xQueueSendFromISR(app_event_hid, &hidEvent, NULL); + +#if 0 + 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_print_new_device_report_header(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); +#endif + +} + +/** + * @brief USB HID Host Generic Interface report callback handler + * + * 'generic' means anything else than mouse or keyboard + * + * @param[in] data Pointer to input report data buffer + * @param[in] length Length of input report data buffer + */ +static void hid_host_generic_report_callback(const uint8_t *const data, const int length) +{ + hid_print_new_device_report_header(HID_PROTOCOL_NONE); + for (int i = 0; i < length; i++) { + printf("%02X", data[i]); + } + putchar('\r'); +} + +/** + * @brief USB HID Host interface callback + * + * @param[in] hid_device_handle HID Device handle + * @param[in] event HID Host interface event + * @param[in] arg Pointer to arguments, does not used + */ +void hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, + void *arg) +{ + ESP_LOGD(TAG, "hid_host_interface_callback event=%d", event); + uint8_t data[64] = { 0 }; + size_t data_length = 0; + hid_host_dev_params_t dev_params; + ESP_ERROR_CHECK(hid_host_device_get_params(hid_device_handle, &dev_params)); + + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + ESP_ERROR_CHECK(hid_host_device_get_raw_input_report_data(hid_device_handle, + data, + 64, + &data_length)); + + if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class) { + if (HID_PROTOCOL_KEYBOARD == dev_params.proto) { + hid_host_keyboard_report_callback(data, data_length); + } else if (HID_PROTOCOL_MOUSE == dev_params.proto) { + hid_host_mouse_report_callback(data, data_length); + } + } else { + hid_host_generic_report_callback(data, data_length); + } + + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "HID Device, protocol '%s' DISCONNECTED", + hid_proto_name_str[dev_params.proto]); + ESP_ERROR_CHECK(hid_host_device_close(hid_device_handle)); + break; + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + ESP_LOGI(TAG, "HID Device, protocol '%s' TRANSFER_ERROR", + hid_proto_name_str[dev_params.proto]); + break; + default: + ESP_LOGE(TAG, "HID Device, protocol '%s' Unhandled event", + hid_proto_name_str[dev_params.proto]); + break; + } +} + +/** + * @brief USB HID Host Device event + * + * @param[in] hid_device_handle HID Device handle + * @param[in] event HID Host Device event + * @param[in] arg Pointer to arguments, does not used + */ +void hid_host_device_event(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + hid_host_dev_params_t dev_params; + ESP_ERROR_CHECK(hid_host_device_get_params(hid_device_handle, &dev_params)); + + switch (event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + ESP_LOGI(TAG, "HID Device, protocol '%s' CONNECTED", + hid_proto_name_str[dev_params.proto]); + + const hid_host_device_config_t dev_config = { + .callback = hid_host_interface_callback, + .callback_arg = NULL + }; + + ESP_ERROR_CHECK(hid_host_device_open(hid_device_handle, &dev_config)); +#if 1 + if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class) { + ESP_ERROR_CHECK(hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT)); + if (HID_PROTOCOL_KEYBOARD == dev_params.proto) { + ESP_ERROR_CHECK(hid_class_request_set_idle(hid_device_handle, 0, 0)); + } + } +#endif + ESP_ERROR_CHECK(hid_host_device_start(hid_device_handle)); + break; + default: + break; + } +} + +/** + * @brief Start USB Host install and handle common USB host library events while app pin not low + * + * @param[in] arg Not used + */ +static void usb_lib_task(void *arg) +{ + 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)); + xTaskNotifyGive(arg); + + while (true) { + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + ESP_LOGI(pcTaskGetName(NULL), "event_flags=0x%"PRIx32, event_flags); + // In this example, there is only one client registered + // So, once we deregister the client, this call must succeed with ESP_OK + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + ESP_ERROR_CHECK(usb_host_device_free_all()); + break; + } + } + + ESP_LOGI(TAG, "USB shutdown"); + // Clean up USB Host + vTaskDelay(10); // Short delay to allow clients clean-up + ESP_ERROR_CHECK(usb_host_uninstall()); + vTaskDelete(NULL); +} + +/** + * @brief HID Host Device callback + * + * Puts new HID Device event to the queue + * + * @param[in] hid_device_handle HID Device handle + * @param[in] event HID Device event + * @param[in] arg Not used + */ +void hid_host_device_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + ESP_LOGI(TAG, "hid_host_device_callback"); + const app_event_queue_t evt_queue = { + .event_group = APP_EVENT_HID_HOST, + // HID Host Device related info + .hid_host_device.handle = hid_device_handle, + .hid_host_device.event = event, + .hid_host_device.arg = arg + }; + + if (app_event_queue) { + xQueueSend(app_event_queue, &evt_queue, 0); + } +} + +void usb_hid_task(void *pvParameters) +{ + BaseType_t task_created; + app_event_queue_t evt_queue; + ESP_LOGI(TAG, "HID Host example"); + + /* + * Create usb_lib_task to: + * - initialize USB Host library + * - Handle USB Host events while APP pin in in HIGH state + */ + task_created = xTaskCreatePinnedToCore(usb_lib_task, + "usb_events", + 4096, + xTaskGetCurrentTaskHandle(), + 2, NULL, 0); + assert(task_created == pdTRUE); + + // Wait for notification from usb_lib_task to proceed + ulTaskNotifyTake(false, 1000); + + /* + * HID host driver configuration + * - create background task for handling low level event inside the HID driver + * - provide the device callback to get new HID Device connection event + */ + const hid_host_driver_config_t hid_host_driver_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = hid_host_device_callback, + .callback_arg = NULL + }; + + ESP_ERROR_CHECK(hid_host_install(&hid_host_driver_config)); + + // Create queue + app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t)); + configASSERT( app_event_queue ); + + ESP_LOGI(TAG, "Waiting for HID Device to be connected"); + + while (1) { + // Wait queue + if (xQueueReceive(app_event_queue, &evt_queue, portMAX_DELAY)) { + if (APP_EVENT_HID_HOST == evt_queue.event_group) { + hid_host_device_event(evt_queue.hid_host_device.handle, + evt_queue.hid_host_device.event, + evt_queue.hid_host_device.arg); + } + } + } + + // Never reach here + ESP_LOGI(TAG, "HID Driver uninstall"); + ESP_ERROR_CHECK(hid_host_uninstall()); + xQueueReset(app_event_queue); + vQueueDelete(app_event_queue); +} diff --git a/KeyboardDemo/main/usb_hid.h b/KeyboardDemo/main/usb_hid.h new file mode 100644 index 0000000..dead819 --- /dev/null +++ b/KeyboardDemo/main/usb_hid.h @@ -0,0 +1,28 @@ +typedef enum { + APP_EVENT_KEYBOARD = 0, + APP_EVENT_MOUSE +} hid_event_type_t; + +typedef struct { + enum key_state { + KEY_STATE_PRESSED = 0x00, + KEY_STATE_RELEASED = 0x01 + } state; + uint8_t modifier; + uint8_t key_code; + unsigned char key_char; +} key_event_t; + +typedef struct { + int x_displacement; + int y_displacement; + int button1; + int button2; + int button3; +} mouse_event_t; + +typedef struct { + hid_event_type_t hid_event_type; + key_event_t key_event; + mouse_event_t mouse_event; +} HID_EVENT_t; diff --git a/KeyboardDemo/sdkconfig.defaults b/KeyboardDemo/sdkconfig.defaults new file mode 100644 index 0000000..27d6f75 --- /dev/null +++ b/KeyboardDemo/sdkconfig.defaults @@ -0,0 +1,18 @@ +# +# ESP32-specific +# +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 + +# +# ESP32S2-specific +# +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ=240 + +# +# ESP32S3-specific +# +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240 +