From 338f0d95c4986bd541dbe3b52df388c7d6a10424 Mon Sep 17 00:00:00 2001 From: zhaokeke Date: Fri, 3 Feb 2023 18:34:43 +0800 Subject: [PATCH] usb: Add NCM device example This example requires import esp_tinyusb library --- .../usb/device/tusb_ncm/CMakeLists.txt | 6 + .../peripherals/usb/device/tusb_ncm/README.md | 103 ++++++++++++++ .../usb/device/tusb_ncm/main/CMakeLists.txt | 2 + .../device/tusb_ncm/main/Kconfig.projbuild | 15 +++ .../device/tusb_ncm/main/idf_component.yml | 5 + .../usb/device/tusb_ncm/main/tusb_ncm_main.c | 127 ++++++++++++++++++ .../device/tusb_ncm/pytest_usb_device_ncm.py | 25 ++++ .../usb/device/tusb_ncm/sdkconfig.defaults | 1 + .../tusb_ncm/sdkconfig.defaults.esp32s3 | 3 + 9 files changed, 287 insertions(+) create mode 100644 examples/peripherals/usb/device/tusb_ncm/CMakeLists.txt create mode 100644 examples/peripherals/usb/device/tusb_ncm/README.md create mode 100644 examples/peripherals/usb/device/tusb_ncm/main/CMakeLists.txt create mode 100644 examples/peripherals/usb/device/tusb_ncm/main/Kconfig.projbuild create mode 100644 examples/peripherals/usb/device/tusb_ncm/main/idf_component.yml create mode 100644 examples/peripherals/usb/device/tusb_ncm/main/tusb_ncm_main.c create mode 100644 examples/peripherals/usb/device/tusb_ncm/pytest_usb_device_ncm.py create mode 100644 examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults create mode 100644 examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults.esp32s3 diff --git a/examples/peripherals/usb/device/tusb_ncm/CMakeLists.txt b/examples/peripherals/usb/device/tusb_ncm/CMakeLists.txt new file mode 100644 index 0000000000..dddc2f4f99 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/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.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(tusb_ncm) diff --git a/examples/peripherals/usb/device/tusb_ncm/README.md b/examples/peripherals/usb/device/tusb_ncm/README.md new file mode 100644 index 0000000000..66e6d6b2e8 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/README.md @@ -0,0 +1,103 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# TinyUSB Network Control Model Device Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +Network Control Model (NCM) is a sub-class of Communication Device Class (CDC) USB Device for Ethernet-over-USB applications. + +In this example, we implemented the ESP development board to transmit WiFi data to the Linux or Windows host via USB, so that the host could access the Internet. + +As a USB stack, a TinyUSB component is used. + +## How to use example + +### Hardware Required + +Any ESP board that have USB-OTG supported. + +#### 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). + +### Configure the project + +Open the project configuration menu (`idf.py menuconfig`). + +In the `Example Configuration` menu: + +* Set the Wi-Fi configuration. + * Set `WiFi SSID`. + * Set `WiFi Password`. + +### Build, Flash, and Run + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build 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 (399) main_task: Calling app_main() +I (429) USB_NCM: USB NCM device initialization +W (429) TinyUSB: The device's configuration descriptor is not provided by user, using default. +W (429) TinyUSB: The device's string descriptor is not provided by user, using default. +W (439) TinyUSB: The device's device descriptor is not provided by user, using default. +I (449) 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 (619) TinyUSB: TinyUSB Driver installed +I (619) USB_NCM: WiFi initialization +I (619) pp: pp rom version: e7ae62f +I (629) net80211: net80211 rom version: e7ae62f +I (689) wifi_init: rx ba win: 6 +I (699) wifi_init: tcpip mbox: 32 +I (699) wifi_init: udp mbox: 6 +I (699) wifi_init: tcp mbox: 6 +I (709) wifi_init: tcp tx win: 5744 +I (709) wifi_init: tcp rx win: 5744 +I (719) wifi_init: tcp mss: 1440 +I (719) wifi_init: WiFi IRAM OP enabled +I (719) wifi_init: WiFi RX IRAM OP enabled +I (729) phy_init: phy_version 600,8dd0147,Mar 31 2023,16:34:12 +I (779) USB_NCM: USB NCM and WiFi initialized and started +I (779) main_task: Returned from app_main() +I (849) USB_NCM: WiFi STA connected +``` diff --git a/examples/peripherals/usb/device/tusb_ncm/main/CMakeLists.txt b/examples/peripherals/usb/device/tusb_ncm/main/CMakeLists.txt new file mode 100644 index 0000000000..270ce7f388 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "tusb_ncm_main.c" + INCLUDE_DIRS "") diff --git a/examples/peripherals/usb/device/tusb_ncm/main/Kconfig.projbuild b/examples/peripherals/usb/device/tusb_ncm/main/Kconfig.projbuild new file mode 100644 index 0000000000..dd0b5e41b9 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "Example Configuration" + + config ESP_WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + + config ESP_WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + +endmenu diff --git a/examples/peripherals/usb/device/tusb_ncm/main/idf_component.yml b/examples/peripherals/usb/device/tusb_ncm/main/idf_component.yml new file mode 100644 index 0000000000..2576181c1c --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/main/idf_component.yml @@ -0,0 +1,5 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "^1.3.0" + idf: "^5.0" diff --git a/examples/peripherals/usb/device/tusb_ncm/main/tusb_ncm_main.c b/examples/peripherals/usb/device/tusb_ncm/main/tusb_ncm_main.c new file mode 100644 index 0000000000..be38d1b9e9 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/main/tusb_ncm_main.c @@ -0,0 +1,127 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +/* DESCRIPTION: + * This example demonstrates using ESP32-S2/S3 as a USB network device. It initializes WiFi in station mode, + * connects and bridges the WiFi and USB networks, so the USB device acts as a standard network interface that + * acquires an IP address from the AP/router which the WiFi station connects to. + */ + +#include + +#include "esp_log.h" +#include "esp_event.h" +#include "esp_check.h" +#include "nvs_flash.h" +#include "esp_mac.h" + +#include "esp_wifi.h" +#include "esp_private/wifi.h" + +#include "tinyusb.h" +#include "tinyusb_net.h" + + +static const char *TAG = "USB_NCM"; + +static esp_err_t usb_recv_callback(void *buffer, uint16_t len, void *ctx) +{ + bool *is_wifi_connected = ctx; + if (*is_wifi_connected) { + esp_wifi_internal_tx(ESP_IF_WIFI_STA, buffer, len); + } + return ESP_OK; +} + +static void wifi_pkt_free(void *eb, void *ctx) +{ + esp_wifi_internal_free_rx_buffer(eb); +} + +static esp_err_t pkt_wifi2usb(void *buffer, uint16_t len, void *eb) +{ + if (tinyusb_net_send_sync(buffer, len, eb, portMAX_DELAY) != ESP_OK) { + esp_wifi_internal_free_rx_buffer(eb); + } + return ESP_OK; +} + +static void wifi_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + bool *is_connected = arg; + + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGI(TAG, "WiFi STA disconnected"); + *is_connected = false; + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, NULL); + esp_wifi_connect(); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + ESP_LOGI(TAG, "WiFi STA connected"); + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, pkt_wifi2usb); + *is_connected = true; + } +} + +static esp_err_t start_wifi(bool *is_connected) +{ + ESP_RETURN_ON_ERROR(esp_event_loop_create_default(), TAG, "Cannot initialize event loop"); + + wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_RETURN_ON_ERROR(esp_wifi_init(&wifi_cfg), TAG, "Failed to initialize WiFi library"); + ESP_RETURN_ON_ERROR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, is_connected), + TAG, "Failed to register handler for wifi events"); + ESP_RETURN_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_STA), TAG, "Failed to set WiFi station mode"); + ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "Failed to start WiFi library"); + + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_ESP_WIFI_SSID, + .password = CONFIG_ESP_WIFI_PASSWORD, + }, + }; + + ESP_RETURN_ON_ERROR(esp_wifi_set_config(WIFI_IF_STA, &wifi_config), TAG, "Failed to set WiFi config"); + return esp_wifi_connect(); +} + +void app_main(void) +{ + static bool s_is_wifi_connected = false; // needs to be static as it's used after we exit app_main() + + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_LOGI(TAG, "USB NCM device initialization"); + const tinyusb_config_t tusb_cfg = { + .external_phy = false, + }; + ESP_GOTO_ON_ERROR(tinyusb_driver_install(&tusb_cfg), err, TAG, "Failed to install TinyUSB driver"); + + tinyusb_net_config_t net_config = { + .on_recv_callback = usb_recv_callback, + .free_tx_buffer = wifi_pkt_free, + .user_context = &s_is_wifi_connected + }; + esp_read_mac(net_config.mac_addr, ESP_MAC_WIFI_STA); + uint8_t *mac = net_config.mac_addr; + ESP_LOGI(TAG, "Network interface HW address: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + ESP_GOTO_ON_ERROR(tinyusb_net_init(TINYUSB_USBDEV_0, &net_config), err, TAG, "Failed to initialize TinyUSB NCM device class"); + + ESP_LOGI(TAG, "WiFi initialization"); + ESP_GOTO_ON_ERROR(start_wifi(&s_is_wifi_connected), err, TAG, "Failed to init and start WiFi"); + + ESP_LOGI(TAG, "USB NCM and WiFi initialized and started"); + return; + +err: + ESP_LOGE(TAG, "USB-WiFi bridge example failed!"); +} diff --git a/examples/peripherals/usb/device/tusb_ncm/pytest_usb_device_ncm.py b/examples/peripherals/usb/device/tusb_ncm/pytest_usb_device_ncm.py new file mode 100644 index 0000000000..96685127bc --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/pytest_usb_device_ncm.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import subprocess +import time + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32s2 +@pytest.mark.usb_device +def test_usb_device_ncm_example(dut: Dut) -> None: + netif_mac = dut.expect(r'Network interface HW address: ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})') + netif_mac = netif_mac.group(1).decode('utf-8') + dut.expect_exact('USB NCM and WiFi initialized and started') + dut.expect_exact('Returned from app_main()') + time.sleep(1) # Wait 1s for the network interface to appear + out_bytes = subprocess.check_output('ifconfig', shell=True, timeout=5) + out_str = out_bytes.decode('utf-8') + print('expected network interface HW address: ', netif_mac) + print('ifconfig command output:\n', out_str) + if netif_mac in out_str: + print("NCM device's MAC address {} was found in system network interfaces".format(netif_mac)) + else: + raise AssertionError('NCM device not found in network interface list') diff --git a/examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults b/examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults new file mode 100644 index 0000000000..65730e3edb --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_TINYUSB_NET_MODE_NCM=y diff --git a/examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults.esp32s3 b/examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults.esp32s3 new file mode 100644 index 0000000000..0776bebde0 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_ncm/sdkconfig.defaults.esp32s3 @@ -0,0 +1,3 @@ +CONFIG_TINYUSB_TASK_AFFINITY_CPU0=y +CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y +CONFIG_TINYUSB_NET_MODE_NCM=y