| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- /*
- * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Unlicense OR CC0-1.0
- */
- #include <stdio.h>
- #include <stdbool.h>
- #include <string.h>
- #include <unistd.h>
- #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"
- /* GPIO Pin number for quit from example logic */
- #define APP_QUIT_PIN GPIO_NUM_0
- static const char *TAG = "example";
- 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"
- };
- /**
- * @brief Key event
- */
- typedef struct {
- enum key_state {
- KEY_STATE_PRESSED = 0x00,
- KEY_STATE_RELEASED = 0x01
- } state;
- uint8_t modifier;
- uint8_t key_code;
- } key_event_t;
- /* Main char symbol for ENTER key */
- #define KEYBOARD_ENTER_MAIN_CHAR '\r'
- /* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */
- #define KEYBOARD_ENTER_LF_EXTEND 1
- /**
- * @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
- */
- 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);
- }
- }
- /**
- * @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)
- {
- unsigned char key_char;
- hid_print_new_device_report_header(HID_PROTOCOL_KEYBOARD);
- if (KEY_STATE_PRESSED == key_event->state) {
- if (hid_keyboard_get_char(key_event->modifier,
- key_event->key_code, &key_char)) {
- hid_keyboard_print_char(key_char);
- }
- }
- }
- /**
- * @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++) {
- 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;
- }
- 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);
- }
- /**
- * @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)
- {
- 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 (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));
- }
- }
- 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);
- // 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 BOOT button pressed callback
- *
- * Signal application to exit the HID Host task
- *
- * @param[in] arg Unused
- */
- static void gpio_isr_cb(void *arg)
- {
- BaseType_t xTaskWoken = pdFALSE;
- const app_event_queue_t evt_queue = {
- .event_group = APP_EVENT,
- };
- if (app_event_queue) {
- xQueueSendFromISR(app_event_queue, &evt_queue, &xTaskWoken);
- }
- if (xTaskWoken == pdTRUE) {
- portYIELD_FROM_ISR();
- }
- }
- /**
- * @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)
- {
- 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 app_main(void)
- {
- BaseType_t task_created;
- app_event_queue_t evt_queue;
- ESP_LOGI(TAG, "HID Host example");
- // Init BOOT button: Pressing the button simulates app request to exit
- // It will disconnect the USB device and uninstall the HID driver and USB Host Lib
- const gpio_config_t input_pin = {
- .pin_bit_mask = BIT64(APP_QUIT_PIN),
- .mode = GPIO_MODE_INPUT,
- .pull_up_en = GPIO_PULLUP_ENABLE,
- .intr_type = GPIO_INTR_NEGEDGE,
- };
- ESP_ERROR_CHECK(gpio_config(&input_pin));
- ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1));
- ESP_ERROR_CHECK(gpio_isr_handler_add(APP_QUIT_PIN, gpio_isr_cb, NULL));
- /*
- * 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));
- 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 == evt_queue.event_group) {
- // User pressed button
- usb_host_lib_info_t lib_info;
- ESP_ERROR_CHECK(usb_host_lib_info(&lib_info));
- if (lib_info.num_devices == 0) {
- // End while cycle
- break;
- } else {
- ESP_LOGW(TAG, "To shutdown example, remove all USB devices and press button again.");
- // Keep polling
- }
- }
- 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);
- }
- }
- }
- ESP_LOGI(TAG, "HID Driver uninstall");
- ESP_ERROR_CHECK(hid_host_uninstall());
- gpio_isr_handler_remove(APP_QUIT_PIN);
- xQueueReset(app_event_queue);
- vQueueDelete(app_event_queue);
- }
|