Skip to content

Commit

Permalink
Merge branch 'feature/usb_composite_device_support' into 'master'
Browse files Browse the repository at this point in the history
usbd: add usb composite device example

Closes IDFGH-9035

See merge request espressif/esp-idf!23135
  • Loading branch information
esp-saurabhbansal committed Apr 25, 2023
2 parents d8f2c0e + 7cc5991 commit 2fb3681
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 3 deletions.
4 changes: 4 additions & 0 deletions docs/en/api-reference/peripherals/usb_device.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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()
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
set(srcs "tusb_composite_main.c")
set(requires fatfs wear_levelling)

idf_component_register(
SRCS "${srcs}"
INCLUDE_DIRS .
REQUIRES "${requires}"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb: "^1.2"
idf: "^5.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/

#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#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");
}
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion examples/peripherals/usb/host/msc/main/idf_component.yml
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 2 additions & 2 deletions examples/peripherals/usb/host/msc/main/msc_example_main.c
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -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) );
Expand Down

0 comments on commit 2fb3681

Please sign in to comment.