From 7cc59918bc802282ba1797020de03b75d8eeff10 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bansal Date: Mon, 10 Apr 2023 23:31:23 +0530 Subject: [PATCH] usbd: add usb composite device example --- .../api-reference/peripherals/usb_device.rst | 4 + .../CMakeLists.txt | 8 + .../tusb_composite_msc_serialdevice/README.md | 81 ++++++++++ .../main/CMakeLists.txt | 8 + .../main/idf_component.yml | 4 + .../main/tusb_composite_main.c | 149 ++++++++++++++++++ .../partitions.csv | 6 + .../pytest_usb_device_composite.py | 29 ++++ .../sdkconfig.defaults | 16 ++ .../usb/host/msc/main/idf_component.yml | 2 +- .../usb/host/msc/main/msc_example_main.c | 4 +- 11 files changed, 308 insertions(+), 3 deletions(-) create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/CMakeLists.txt create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/README.md create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/CMakeLists.txt create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/idf_component.yml create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/tusb_composite_main.c create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/partitions.csv create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/pytest_usb_device_composite.py create mode 100644 examples/peripherals/usb/device/tusb_composite_msc_serialdevice/sdkconfig.defaults diff --git a/docs/en/api-reference/peripherals/usb_device.rst b/docs/en/api-reference/peripherals/usb_device.rst index 72cab41248a..225dbb73176 100644 --- a/docs/en/api-reference/peripherals/usb_device.rst +++ b/docs/en/api-reference/peripherals/usb_device.rst @@ -24,6 +24,7 @@ Features - USB Serial Device (CDC-ACM) - Input and output streams through USB Serial Device - Other USB classes (MIDI, MSC, HID...) support directly via TinyUSB +- USB Composite Device (MSC + CDC) - VBUS monitoring for self-powered devices Hardware USB Connection @@ -53,6 +54,7 @@ On top of it the driver implements: - Customization of USB descriptors - Serial device support - Redirecting of standard streams through the Serial device +- Storage Media (SPI-Flash and SD-Card) for USB Device MSC Class. - Encapsulated driver's task servicing the TinyUSB Configuration @@ -221,3 +223,5 @@ The table below describes the code examples available in the directory :example: - How to set up {IDF_TARGET_NAME} chip to work as a USB Human Interface Device * - :example:`peripherals/usb/device/tusb_msc` - How to set up {IDF_TARGET_NAME} chip to work as a USB Mass Storage Device + * - :example:`peripherals/usb/device/tusb_composite_msc_serialdevice` + - How to set up {IDF_TARGET_NAME} chip to work as a Composite USB Device (MSC + CDC) diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/CMakeLists.txt b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/CMakeLists.txt new file mode 100644 index 00000000000..8add1893581 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five 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.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tusb_composite) diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/README.md b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/README.md new file mode 100644 index 00000000000..d7bff1f7cff --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/README.md @@ -0,0 +1,81 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# TinyUSB Composite Device (MSC + Serial) Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +A USB device can provide multiple functions that are active simultaneously. Such multi-function devices are also known as composite devices. This example shows how to set up ESP chip to work as a USB Serial Device as well as MSC device (Storage media as SPI-Flash). + +As a USB stack, a TinyUSB component is used. + +## How to use example + +### Hardware Required + +Any ESP board that supports the USB-OTG peripheral. + +### Pin Assignment + +_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). + +Next, for Self-Powered Devices with VBUS monitoring, user must set ``self_powered`` to ``true`` and ``vbus_monitor_io`` to GPIO number (``VBUS_MONITORING_GPIO_NUM``) that will be used for VBUS monitoring. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +```bash +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 + +After the flashing you should see the output at idf monitor: + +``` +I (344) main_task: Calling app_main() +I (344) example_main: USB Composite initialization +W (354) TinyUSB: The device's configuration descriptor is not provided by user, using default. +W (364) TinyUSB: The device's string descriptor is not provided by user, using default. +W (374) TinyUSB: The device's device descriptor is not provided by user, using default. +I (384) tusb_desc: +┌─────────────────────────────────┐ +│ USB Device Descriptor Summary │ +├───────────────────┬─────────────┤ +│bDeviceClass │ 239 │ +├───────────────────┼─────────────┤ +│bDeviceSubClass │ 2 │ +├───────────────────┼─────────────┤ +│bDeviceProtocol │ 1 │ +├───────────────────┼─────────────┤ +│bMaxPacketSize0 │ 64 │ +├───────────────────┼─────────────┤ +│idVendor │ 0x303a │ +├───────────────────┼─────────────┤ +│idProduct │ 0x4001 │ +├───────────────────┼─────────────┤ +│bcdDevice │ 0x100 │ +├───────────────────┼─────────────┤ +│iManufacturer │ 0x1 │ +├───────────────────┼─────────────┤ +│iProduct │ 0x2 │ +├───────────────────┼─────────────┤ +│iSerialNumber │ 0x3 │ +├───────────────────┼─────────────┤ +│bNumConfigurations │ 0x1 │ +└───────────────────┴─────────────┘ +I (544) TinyUSB: TinyUSB Driver installed +I (554) example_main: Initializing storage... +I (554) example_main: Initializing wear levelling +I (584) example_main: USB Composite initialization DONE +I (584) main_task: Returned from app_main() +``` diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/CMakeLists.txt b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/CMakeLists.txt new file mode 100644 index 00000000000..bed76116a72 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(srcs "tusb_composite_main.c") +set(requires fatfs wear_levelling) + +idf_component_register( + SRCS "${srcs}" + INCLUDE_DIRS . + REQUIRES "${requires}" +) diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/idf_component.yml b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/idf_component.yml new file mode 100644 index 00000000000..06b047d6a2d --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/idf_component.yml @@ -0,0 +1,4 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: "^1.2" + idf: "^5.0" diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/tusb_composite_main.c b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/tusb_composite_main.c new file mode 100644 index 00000000000..a7d4abbc5a4 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/main/tusb_composite_main.c @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_check.h" +#include "tinyusb.h" +#include "tusb_msc_storage.h" +#include "tusb_cdc_acm.h" + +#define BASE_PATH "/usb" // base path to mount the partition + +static const char *TAG = "example_main"; +static uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; + +void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + /* initialization */ + size_t rx_size = 0; + + /* read */ + esp_err_t ret = tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Data from channel %d:", itf); + ESP_LOG_BUFFER_HEXDUMP(TAG, buf, rx_size, ESP_LOG_INFO); + } else { + ESP_LOGE(TAG, "Read error"); + } + + /* write back */ + tinyusb_cdcacm_write_queue(itf, buf, rx_size); + tinyusb_cdcacm_write_flush(itf, 0); +} + +void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) +{ + int dtr = event->line_state_changed_data.dtr; + int rts = event->line_state_changed_data.rts; + ESP_LOGI(TAG, "Line state changed on channel %d: DTR:%d, RTS:%d", itf, dtr, rts); +} + +static bool file_exists(const char *file_path) +{ + struct stat buffer; + return stat(file_path, &buffer) == 0; +} + +static void file_operations(void) +{ + const char *directory = "/usb/esp"; + const char *file_path = "/usb/esp/test.txt"; + + struct stat s = {0}; + bool directory_exists = stat(directory, &s) == 0; + if (!directory_exists) { + if (mkdir(directory, 0775) != 0) { + ESP_LOGE(TAG, "mkdir failed with errno: %s\n", strerror(errno)); + } + } + + if (!file_exists(file_path)) { + ESP_LOGI(TAG, "Creating file"); + FILE *f = fopen(file_path, "w"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for writing"); + return; + } + fprintf(f, "Hello World!\n"); + fclose(f); + } + + FILE *f; + ESP_LOGI(TAG, "Reading file"); + f = fopen(file_path, "r"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for reading"); + return; + } + char line[64]; + fgets(line, sizeof(line), f); + fclose(f); + // strip newline + char *pos = strchr(line, '\n'); + if (pos) { + *pos = '\0'; + } + ESP_LOGI(TAG, "Read from file: '%s'", line); +} + +static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle) +{ + ESP_LOGI(TAG, "Initializing wear levelling"); + + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); + if (data_partition == NULL) { + ESP_LOGE(TAG, "Failed to find FATFS partition. Check the partition table."); + return ESP_ERR_NOT_FOUND; + } + + return wl_mount(data_partition, wl_handle); +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Initializing storage..."); + + static wl_handle_t wl_handle = WL_INVALID_HANDLE; + ESP_ERROR_CHECK(storage_init_spiflash(&wl_handle)); + + const tinyusb_msc_spiflash_config_t config_spi = { + .wl_handle = wl_handle + }; + ESP_ERROR_CHECK(tinyusb_msc_storage_init_spiflash(&config_spi)); + ESP_ERROR_CHECK(tinyusb_msc_storage_mount(BASE_PATH)); + file_operations(); + + ESP_LOGI(TAG, "USB Composite initialization"); + const tinyusb_config_t tusb_cfg = { + .device_descriptor = NULL, + .string_descriptor = NULL, + .string_descriptor_count = 0, + .external_phy = false, + .configuration_descriptor = NULL, + }; + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t acm_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = TINYUSB_CDC_ACM_0, + .rx_unread_buf_sz = 64, + .callback_rx = &tinyusb_cdc_rx_callback, // the first way to register a callback + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + + ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg)); + /* the second way to register a callback */ + ESP_ERROR_CHECK(tinyusb_cdcacm_register_callback( + TINYUSB_CDC_ACM_0, + CDC_EVENT_LINE_STATE_CHANGED, + &tinyusb_cdc_line_state_changed_callback)); + + ESP_LOGI(TAG, "USB Composite initialization DONE"); +} diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/partitions.csv b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/partitions.csv new file mode 100644 index 00000000000..1c79321a107 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/pytest_usb_device_composite.py b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/pytest_usb_device_composite.py new file mode 100644 index 00000000000..6adfa49573b --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/pytest_usb_device_composite.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +from time import sleep + +import pytest +from pytest_embedded import Dut +from serial import Serial +from serial.tools.list_ports import comports + + +@pytest.mark.esp32s2 +@pytest.mark.usb_device +def test_usb_composite_device_serial_example(dut: Dut) -> None: + dut.expect_exact('Hello World!') + dut.expect_exact('USB Composite initialization') + dut.expect_exact('USB Composite initialization DONE') + sleep(2) # Some time for the OS to enumerate our USB device + + # Find device with Espressif TinyUSB VID/PID + ports = comports() + for port, _, hwid in ports: + if '303A:4001' in hwid: + with Serial(port) as s: + s.write('text\r\n'.encode()) # Write dummy text to COM port + dut.expect_exact('Data from channel 0:') # Check ESP log + dut.expect_exact('|text..|') + res = s.readline() # Check COM echo + assert b'text\r\n' in res + return diff --git a/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/sdkconfig.defaults b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/sdkconfig.defaults new file mode 100644 index 00000000000..12e3b2f205a --- /dev/null +++ b/examples/peripherals/usb/device/tusb_composite_msc_serialdevice/sdkconfig.defaults @@ -0,0 +1,16 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_TINYUSB=y +CONFIG_TINYUSB_MSC_ENABLED=y + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_MODE_PERF=y + +CONFIG_TINYUSB_CDC_ENABLED=y +# CONFIG_TINYUSB_DESC_USE_DEFAULT_PID is not set +CONFIG_TINYUSB_DESC_CUSTOM_PID=0x4001 diff --git a/examples/peripherals/usb/host/msc/main/idf_component.yml b/examples/peripherals/usb/host/msc/main/idf_component.yml index 57f225551e3..fc50aa0adfc 100644 --- a/examples/peripherals/usb/host/msc/main/idf_component.yml +++ b/examples/peripherals/usb/host/msc/main/idf_component.yml @@ -1,4 +1,4 @@ ## IDF Component Manager Manifest File dependencies: idf: ">=4.4" - usb_host_msc: "^1.0.0" + usb_host_msc: "^1.0.4" diff --git a/examples/peripherals/usb/host/msc/main/msc_example_main.c b/examples/peripherals/usb/host/msc/main/msc_example_main.c index c37107a39de..eca2c415b7b 100644 --- a/examples/peripherals/usb/host/msc/main/msc_example_main.c +++ b/examples/peripherals/usb/host/msc/main/msc_example_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -177,7 +177,7 @@ void app_main(void) const msc_host_driver_config_t msc_config = { .create_backround_task = true, .task_priority = 5, - .stack_size = 2048, + .stack_size = 4096, .callback = msc_event_cb, }; ESP_ERROR_CHECK( msc_host_install(&msc_config) );