From b28af154484d6c9546c16a6f6513627e281cc584 Mon Sep 17 00:00:00 2001 From: Wunderbaeumchen99817 Date: Sat, 11 Nov 2023 23:21:34 +0100 Subject: [PATCH] add host midi example --- .../peripherals/usb/host/midi/CMakeLists.txt | 6 + examples/peripherals/usb/host/midi/README.md | 136 ++++++++ .../usb/host/midi/main/CMakeLists.txt | 5 + .../usb/host/midi/main/idf_component.yml | 6 + .../usb/host/midi/main/midi_class_driver.c | 313 ++++++++++++++++++ .../host/midi/main/midi_host_example_main.c | 74 +++++ 6 files changed, 540 insertions(+) create mode 100644 examples/peripherals/usb/host/midi/CMakeLists.txt create mode 100644 examples/peripherals/usb/host/midi/README.md create mode 100644 examples/peripherals/usb/host/midi/main/CMakeLists.txt create mode 100644 examples/peripherals/usb/host/midi/main/idf_component.yml create mode 100644 examples/peripherals/usb/host/midi/main/midi_class_driver.c create mode 100644 examples/peripherals/usb/host/midi/main/midi_host_example_main.c diff --git a/examples/peripherals/usb/host/midi/CMakeLists.txt b/examples/peripherals/usb/host/midi/CMakeLists.txt new file mode 100644 index 0000000000..bda697784a --- /dev/null +++ b/examples/peripherals/usb/host/midi/CMakeLists.txt @@ -0,0 +1,6 @@ +# 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) +project(app-template) diff --git a/examples/peripherals/usb/host/midi/README.md b/examples/peripherals/usb/host/midi/README.md new file mode 100644 index 0000000000..6f1539e5d4 --- /dev/null +++ b/examples/peripherals/usb/host/midi/README.md @@ -0,0 +1,136 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# USB MIDI Class Example + + +This example provides basic USB Host Midi functionality by implementing a midi class driver and a Host Library task. The example does the following: + +1. Install Host Library and register a client +2. Waits for a device connection +3. Prints the device's information (such as device/configuration/string descriptors) +4. Claims Interface with more that zero endpoints from device +5. Prints received bytes, if midi-device sent new data + +The example heavily follows the usb_host_lib example (https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/usb_host_lib). + +## How to use example + +### Hardware Required + +- Development board with USB capable ESP SoC (ESP32-S2/ESP32-S3) +- USB cable for Power supply and programming +- USB OTG Cable +- MIDI-device (e.g. KORG NANOKONTROL2) + +### 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 +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (314) app_start: Starting scheduler on CPU0 +I (319) app_start: Starting scheduler on CPU1 +I (319) main_task: Started on CPU0 +I (329) main_task: Calling app_main() +I (329) DAEMON: Installing USB Host Library +I (369) MIDI DRIVER: Registering Client +I (2109) MIDI DRIVER: Opening device at address 1 +I (2109) MIDI DRIVER: Getting device information +I (2109) MIDI DRIVER: Full speed +I (2109) MIDI DRIVER: bConfigurationValue 1 +I (2119) MIDI DRIVER: Getting device descriptor +*** Device descriptor *** +bLength 18 +bDescriptorType 1 +bcdUSB 1.10 +bDeviceClass 0x0 +bDeviceSubClass 0x0 +bDeviceProtocol 0x0 +bMaxPacketSize0 8 +idVendor 0xfc02 +idProduct 0x101 +bcdDevice 2.50 +iManufacturer 0 +iProduct 2 +iSerialNumber 0 +bNumConfigurations 1 +I (2139) MIDI DRIVER: Getting config descriptor +*** Configuration descriptor *** +bLength 9 +bDescriptorType 2 +wTotalLength 101 +bNumInterfaces 2 +bConfigurationValue 1 +iConfiguration 0 +bmAttributes 0x80 +bMaxPower 100mA + *** Interface descriptor *** + bLength 9 + bDescriptorType 4 + bInterfaceNumber 0 + bAlternateSetting 0 + bNumEndpoints 0 + bInterfaceClass 0x1 + bInterfaceSubClass 0x1 + bInterfaceProtocol 0x0 + iInterface 0 + *** Interface descriptor *** + bLength 9 + bDescriptorType 4 + bInterfaceNumber 1 + bAlternateSetting 0 + bNumEndpoints 2 + bInterfaceClass 0x1 + bInterfaceSubClass 0x3 + bInterfaceProtocol 0x0 + iInterface 0 + *** Endpoint descriptor *** + bLength 9 + bDescriptorType 5 + bEndpointAddress 0x81 EP 1 IN + bmAttributes 0x2 BULK + wMaxPacketSize 8 + bInterval 0 + *** Endpoint descriptor *** + bLength 9 + bDescriptorType 5 + bEndpointAddress 0x2 EP 2 OUT + bmAttributes 0x2 BULK + wMaxPacketSize 8 + bInterval 0 +I (2229) MIDI DRIVER: Getting interface config +Interface Number: 1, Alternate Setting: 0 +endpoint address: 129 , mps: 8 +I (2239) MIDI DRIVER: Getting Product string descriptor +USB MIDI Interface +I (2249) MIDI DRIVER: Claiming Interface +I (2249) MIDI DRIVER: Configuring usb-transfer object +set transfer parameters +I (4429) MIDI DRIVER: Received new data: + +15 248 0 0 +I (4429) MIDI DRIVER: Received new data: + +8 140 0 0 +I (6159) MIDI DRIVER: Received new data: + +11 176 14 60 +I (6199) MIDI DRIVER: Received new data: + +11 176 32 15 +I (6229) MIDI DRIVER: Received new data: + +11 176 2 32 +``` diff --git a/examples/peripherals/usb/host/midi/main/CMakeLists.txt b/examples/peripherals/usb/host/midi/main/CMakeLists.txt new file mode 100644 index 0000000000..2c882f478d --- /dev/null +++ b/examples/peripherals/usb/host/midi/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "midi_host_example_main.c" + INCLUDE_DIRS "." + PRIV_REQUIRES usb +) \ No newline at end of file diff --git a/examples/peripherals/usb/host/midi/main/idf_component.yml b/examples/peripherals/usb/host/midi/main/idf_component.yml new file mode 100644 index 0000000000..d0a3ca7b20 --- /dev/null +++ b/examples/peripherals/usb/host/midi/main/idf_component.yml @@ -0,0 +1,6 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/usb_host_hid: "==1.0.1" + ## Required IDF version + idf: + version: ">=4.1.0" \ No newline at end of file diff --git a/examples/peripherals/usb/host/midi/main/midi_class_driver.c b/examples/peripherals/usb/host/midi/main/midi_class_driver.c new file mode 100644 index 0000000000..10f295f53f --- /dev/null +++ b/examples/peripherals/usb/host/midi/main/midi_class_driver.c @@ -0,0 +1,313 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "usb/usb_host.h" + +#define ACTION_OPEN_DEV 0x01 +#define ACTION_GET_DEV_INFO 0x02 +#define ACTION_GET_DEV_DESC 0x04 +#define ACTION_GET_CONFIG_DESC 0x08 +#define ACTION_GET_STR_DESC 0x10 +#define ACTION_CLAIM_INTERFACE 0x20 +#define ACTION_START_READING_DATA 0x40 +#define ACTION_CLOSE_DEV 0x80 +#define ACTION_EXIT 0xA0 + +#define USB_CLIENT_NUM_EVENT_MSG 5 +#define MIDI_MESSAGE_LENGTH 4 + +typedef struct { + uint8_t interface_nmbr; + uint8_t alternate_setting; + uint8_t endpoint_address; + uint8_t max_packet_size; +} interface_config_t; + +typedef struct { + usb_host_client_handle_t client_hdl; + uint8_t dev_addr; + usb_device_handle_t dev_hdl; + uint32_t actions; + interface_config_t interface_conf; +} class_driver_t; + +static const char *DRIVER_TAG = "MIDI DRIVER"; + +static void midi_usb_host_callback(usb_transfer_t *transfer) { + int size = (int)transfer->actual_num_bytes; + //one message contains 4 bytes of data + int num_messages = size/MIDI_MESSAGE_LENGTH; + + int offset = 0; + //print messages + if(num_messages) { + + ESP_LOGI(DRIVER_TAG, "Received new data: \n"); + //print each message separately + for(int i = 0; i < num_messages; i++) { + for (int j = 0; j < MIDI_MESSAGE_LENGTH; j++) { + printf("%d ", transfer->data_buffer[j+offset]); + } + printf("\n"); + offset += MIDI_MESSAGE_LENGTH; + } + } + + //submit new usb-message to continuously receive data + ESP_ERROR_CHECK(usb_host_transfer_submit(transfer)); +} + +static void get_midi_interface_settings(const usb_config_desc_t *usb_conf, interface_config_t *interface_conf) { + assert(usb_conf != NULL); + assert(interface_conf != NULL); + + ESP_LOGI(DRIVER_TAG, "Getting interface config"); + + int offset = 0; + uint16_t wTotalLength = usb_conf->wTotalLength; + const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)usb_conf; + + do { + if(next_desc->bDescriptorType == USB_B_DESCRIPTOR_TYPE_INTERFACE) { + usb_intf_desc_t *interface_desc = (usb_intf_desc_t *)next_desc; + + //check if there are >0 endpoints + if(interface_desc->bNumEndpoints > 0) { + //use interface + interface_conf->interface_nmbr = interface_desc->bInterfaceNumber; + interface_conf->alternate_setting = interface_desc->bAlternateSetting; + + printf("Interface Number: %d, Alternate Setting: %d \n", interface_conf->interface_nmbr, interface_conf->alternate_setting); + + } + } + if(next_desc->bDescriptorType == USB_B_DESCRIPTOR_TYPE_ENDPOINT) { + usb_ep_desc_t *ep_desc = (usb_ep_desc_t *)next_desc; + if(USB_EP_DESC_GET_EP_DIR(ep_desc)) { + //endpoint is IN + interface_conf->endpoint_address = ep_desc->bEndpointAddress; + interface_conf->max_packet_size = ep_desc->wMaxPacketSize; + printf("endpoint address: %d , mps: %d\n", interface_conf->endpoint_address, interface_conf->max_packet_size); + } + } + + next_desc = usb_parse_next_descriptor(next_desc, wTotalLength, &offset); + + } while (next_desc != NULL); +} + + +static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + class_driver_t *driver_obj = (class_driver_t *)arg; + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + if (driver_obj->dev_addr == 0) { + driver_obj->dev_addr = event_msg->new_dev.address; + //Open the device next + driver_obj->actions |= ACTION_OPEN_DEV; + } + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: + if (driver_obj->dev_hdl != NULL) { + //Cancel any other actions and close the device next + driver_obj->actions = ACTION_CLOSE_DEV; + } + break; + default: + //Should never occur + abort(); + } +} + +static void action_open_dev(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_addr != 0); + ESP_LOGI(DRIVER_TAG, "Opening device at address %d", driver_obj->dev_addr); + ESP_ERROR_CHECK(usb_host_device_open(driver_obj->client_hdl, driver_obj->dev_addr, &driver_obj->dev_hdl)); + //Get the device's information next + driver_obj->actions &= ~ACTION_OPEN_DEV; + driver_obj->actions |= ACTION_GET_DEV_INFO; +} + +static void action_get_info(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(DRIVER_TAG, "Getting device information"); + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info)); + ESP_LOGI(DRIVER_TAG, "\t%s speed", (dev_info.speed == USB_SPEED_LOW) ? "Low" : "Full"); + ESP_LOGI(DRIVER_TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue); + //Todo: Print string descriptors + + //Get the device descriptor next + driver_obj->actions &= ~ACTION_GET_DEV_INFO; + driver_obj->actions |= ACTION_GET_DEV_DESC; +} + +static void action_get_dev_desc(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(DRIVER_TAG, "Getting device descriptor"); + const usb_device_desc_t *dev_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(driver_obj->dev_hdl, &dev_desc)); + usb_print_device_descriptor(dev_desc); + //Get the device's config descriptor next + driver_obj->actions &= ~ACTION_GET_DEV_DESC; + driver_obj->actions |= ACTION_GET_CONFIG_DESC; +} + +static void action_get_config_desc(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(DRIVER_TAG, "Getting config descriptor"); + const usb_config_desc_t *config_desc; + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(driver_obj->dev_hdl, &config_desc)); + usb_print_config_descriptor(config_desc, NULL); + + //save interface number & alternative setting for later use + interface_config_t interface_config = {0}; + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(driver_obj->dev_hdl, &config_desc)); + get_midi_interface_settings(config_desc, &interface_config); + + driver_obj->interface_conf = interface_config; + + //Get the device's string descriptors next + driver_obj->actions &= ~ACTION_GET_CONFIG_DESC; + driver_obj->actions |= ACTION_GET_STR_DESC; +} + +static void action_get_str_desc(class_driver_t *driver_obj) +{ + assert(driver_obj->dev_hdl != NULL); + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(driver_obj->dev_hdl, &dev_info)); + if (dev_info.str_desc_manufacturer) { + ESP_LOGI(DRIVER_TAG, "Getting Manufacturer string descriptor"); + usb_print_string_descriptor(dev_info.str_desc_manufacturer); + } + if (dev_info.str_desc_product) { + ESP_LOGI(DRIVER_TAG, "Getting Product string descriptor"); + usb_print_string_descriptor(dev_info.str_desc_product); + } + if (dev_info.str_desc_serial_num) { + ESP_LOGI(DRIVER_TAG, "Getting Serial Number string descriptor"); + usb_print_string_descriptor(dev_info.str_desc_serial_num); + } + //Claim interface next + driver_obj->actions &= ~ACTION_GET_STR_DESC; + driver_obj->actions |= ACTION_CLAIM_INTERFACE; +} + +static void action_claim_interface(class_driver_t *driver_obj) { + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(DRIVER_TAG, "Claiming Interface"); + ESP_ERROR_CHECK(usb_host_interface_claim( + driver_obj->client_hdl, + driver_obj->dev_hdl, + driver_obj->interface_conf.interface_nmbr, + driver_obj->interface_conf.alternate_setting)); + + driver_obj->actions &= ~ACTION_CLAIM_INTERFACE; + driver_obj->actions |= ACTION_START_READING_DATA; +} + +static void action_start_reading_data(class_driver_t *driver_obj) { + assert(driver_obj->dev_hdl != NULL); + ESP_LOGI(DRIVER_TAG, "Configuring usb-transfer object"); + + //configure usb-transfer object + usb_transfer_t *transfer_obj; + + usb_host_transfer_alloc(1024, 0, &transfer_obj); + + transfer_obj->num_bytes = driver_obj->interface_conf.max_packet_size; + transfer_obj->callback = midi_usb_host_callback; + transfer_obj->bEndpointAddress = driver_obj->interface_conf.endpoint_address; + transfer_obj->device_handle = driver_obj->dev_hdl; + printf("set transfer parameters\n"); + ESP_ERROR_CHECK(usb_host_transfer_submit(transfer_obj)); + + //Nothing to do until the device disconnects + driver_obj->actions &= ~ACTION_START_READING_DATA; + +} + +static void aciton_close_dev(class_driver_t *driver_obj) +{ + ESP_LOGI(DRIVER_TAG, "Releasing interface"); + ESP_ERROR_CHECK(usb_host_interface_release( + driver_obj->client_hdl, + driver_obj->dev_hdl, + driver_obj->interface_conf.interface_nmbr)); + ESP_LOGI(DRIVER_TAG, "Closing device"); + ESP_ERROR_CHECK(usb_host_device_close(driver_obj->client_hdl, driver_obj->dev_hdl)); + driver_obj->dev_hdl = NULL; + driver_obj->dev_addr = 0; + //We need to exit the event handler loop + driver_obj->actions &= ~ACTION_CLOSE_DEV; + driver_obj->actions |= ACTION_EXIT; +} + +void class_driver_task(void *arg) +{ + SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg; + class_driver_t driver_obj = {0}; + + //Wait until daemon task has installed USB Host Library + xSemaphoreTake(signaling_sem, portMAX_DELAY); + + ESP_LOGI(DRIVER_TAG, "Registering Client"); + usb_host_client_config_t client_config = { + .is_synchronous = false, //Synchronous clients currently not supported. Set this to false + .max_num_event_msg = USB_CLIENT_NUM_EVENT_MSG, + .async = { + .client_event_callback = client_event_cb, + .callback_arg = (void *) &driver_obj, + }, + }; + ESP_ERROR_CHECK(usb_host_client_register(&client_config, &driver_obj.client_hdl)); + + //classic state machine + while (1) { + if (driver_obj.actions == 0) { + usb_host_client_handle_events(driver_obj.client_hdl, portMAX_DELAY); + } else { + if (driver_obj.actions & ACTION_OPEN_DEV) { + action_open_dev(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_DEV_INFO) { + action_get_info(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_DEV_DESC) { + action_get_dev_desc(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_CONFIG_DESC) { + action_get_config_desc(&driver_obj); + } + if (driver_obj.actions & ACTION_GET_STR_DESC) { + action_get_str_desc(&driver_obj); + } + if(driver_obj.actions & ACTION_CLAIM_INTERFACE) { + action_claim_interface(&driver_obj); + } + if(driver_obj.actions & ACTION_START_READING_DATA) { + action_start_reading_data(&driver_obj); + } + if (driver_obj.actions & ACTION_CLOSE_DEV) { + aciton_close_dev(&driver_obj); + } + if (driver_obj.actions & ACTION_EXIT) { + driver_obj.actions = 0; + } + } + } +} + diff --git a/examples/peripherals/usb/host/midi/main/midi_host_example_main.c b/examples/peripherals/usb/host/midi/main/midi_host_example_main.c new file mode 100644 index 0000000000..448a2de60b --- /dev/null +++ b/examples/peripherals/usb/host/midi/main/midi_host_example_main.c @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_intr_alloc.h" + +#include "midi_class_driver.c" + +static const char *TAG = "DAEMON"; + +#define DAEMON_TASK_PRIORITY 2 +#define CLASS_TASK_PRIORITY 3 + +static void host_lib_daemon_task(void *arg) +{ + SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg; + + ESP_LOGI(TAG, "Installing USB Host Library"); + usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + + //Signal to the class driver task that the host library is installed + xSemaphoreGive(signaling_sem); + vTaskDelay(10); //Short delay to let client task spin up + + while (1) { + uint32_t event_flags; + ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags)); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + ESP_LOGI(TAG, "no clients available"); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGI(TAG, "no devices connected"); + } + } +} + +void app_main(void) +{ + SemaphoreHandle_t signaling_sem = xSemaphoreCreateBinary(); + + TaskHandle_t daemon_task_hdl; + TaskHandle_t class_driver_task_hdl; + //Create daemon task + xTaskCreatePinnedToCore(host_lib_daemon_task, + "daemon", + 4096, + (void *)signaling_sem, + DAEMON_TASK_PRIORITY, + &daemon_task_hdl, + 0); + //Create the class driver task + xTaskCreatePinnedToCore(class_driver_task, + "class", + 4096, + (void *)signaling_sem, + CLASS_TASK_PRIORITY, + &class_driver_task_hdl, + 0); + + vTaskDelay(10); //Add a short delay to let the tasks run +}