From bcb72b1d360045f34c627ada1bfb22c331737ba6 Mon Sep 17 00:00:00 2001 From: Patrick Moessler Date: Wed, 12 Jun 2024 22:11:21 +0200 Subject: [PATCH] initial --- .devcontainer/Dockerfile | 47 ++++++ .devcontainer/devcontainer.json | 45 ++++++ .gitignore | 5 + .vscode/c_cpp_properties.json | 27 ++++ .vscode/launch.json | 10 ++ .vscode/settings.json | 17 +++ .vscode/tasks.json | 259 ++++++++++++++++++++++++++++++++ CMakeLists.txt | 10 ++ README.md | 81 ++++++++++ dependencies.lock | 21 +++ main/CMakeLists.txt | 5 + main/idf_component.yml | 4 + main/tusb_composite_main.c | 150 ++++++++++++++++++ partitions.csv | 6 + pytest_usb_device_composite.py | 29 ++++ sdkconfig.defaults | 12 ++ 16 files changed, 728 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitignore create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 dependencies.lock create mode 100644 main/CMakeLists.txt create mode 100644 main/idf_component.yml create mode 100644 main/tusb_composite_main.c create mode 100644 partitions.csv create mode 100644 pytest_usb_device_composite.py create mode 100644 sdkconfig.defaults diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..1fe78dc --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,47 @@ +FROM espressif/idf + +ARG DEBIAN_FRONTEND=nointeractive +ARG CONTAINER_USER=esp +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +RUN apt-get update \ + && apt install -y -q \ + cmake \ + git \ + libglib2.0-0 \ + libnuma1 \ + libpixman-1-0 \ + && rm -rf /var/lib/apt/lists/* + +# QEMU +ENV QEMU_REL=esp_develop_8.2.0_20240122 +ENV QEMU_SHA256=e7c72ef5705ad1444d391711088c8717fc89f42e9bf6d1487f9c2a326b8cfa83 +ENV QEMU_DIST=qemu-xtensa-softmmu-${QEMU_REL}-x86_64-linux-gnu.tar.xz +ENV QEMU_URL=https://github.com/espressif/qemu/releases/download/esp-develop-8.2.0-20240122/${QEMU_DIST} + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN wget --no-verbose ${QEMU_URL} \ + && echo "${QEMU_SHA256} *${QEMU_DIST}" | sha256sum --check --strict - \ + && tar -xf $QEMU_DIST -C /opt \ + && rm ${QEMU_DIST} + +ENV PATH=/opt/qemu/bin:${PATH} + +RUN groupadd --gid $USER_GID $CONTAINER_USER \ + && adduser --uid $USER_UID --gid $USER_GID --disabled-password --gecos "" ${CONTAINER_USER} \ + && usermod -a -G root $CONTAINER_USER && usermod -a -G dialout $CONTAINER_USER + +RUN chmod -R 775 /opt/esp/python_env/ + +USER ${CONTAINER_USER} +ENV USER=${CONTAINER_USER} +WORKDIR /home/${CONTAINER_USER} + +RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc + +ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] + +CMD ["/bin/bash", "-c"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1d913ec --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,45 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.183.0/containers/ubuntu +{ + "name": "ESP-IDF QEMU", + "build": { + "dockerfile": "Dockerfile" + }, + // Add the IDs of extensions you want installed when the container is created + "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + /* the path of workspace folder to be opened after container is running + */ + "workspaceFolder": "${localWorkspaceFolder}", + "mounts": [ + "source=extensionCache,target=/root/.vscode-server/extensions,type=volume" + ], + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.customExtraPaths": "", + "idf.pythonBinPath": "/opt/esp/python_env/idf5.3_py3.10_env/bin/python", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension" + ], + }, + "codespaces": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.customExtraPaths": "", + "idf.pythonBinPath": "/opt/esp/python_env/idf5.3_py3.10_env/bin/python", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension" + ], + } + }, + "runArgs": ["--privileged"] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3789fb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.cache/ +build/ +sdkconfig.old +sdkconfig +managed_components/ \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..ee1cac1 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,27 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-13.2.0_20230928\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe", + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${config:idf.espAdfPath}/components/**", + "${config:idf.espAdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${config:idf.espAdfPath}/components/**", + "${config:idf.espAdfPathWin}/components/**", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": false + } + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6d2236f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..deead33 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.adapterTargetName": "esp32s3", + "idf.customExtraPaths": "c:\\toolchain\\esp32\\tools\\xtensa-esp-elf-gdb\\14.2_20240403\\xtensa-esp-elf-gdb\\bin;c:\\toolchain\\esp32\\tools\\riscv32-esp-elf-gdb\\14.2_20240403\\riscv32-esp-elf-gdb\\bin;c:\\toolchain\\esp32\\tools\\xtensa-esp-elf\\esp-13.2.0_20230928\\xtensa-esp-elf\\bin;c:\\toolchain\\esp32\\tools\\riscv32-esp-elf\\esp-13.2.0_20230928\\riscv32-esp-elf\\bin;c:\\toolchain\\esp32\\tools\\esp32ulp-elf\\2.35_20220830\\esp32ulp-elf\\bin;c:\\toolchain\\esp32\\tools\\cmake\\3.24.0\\bin;c:\\toolchain\\esp32\\tools\\openocd-esp32\\v0.12.0-esp32-20240318\\openocd-esp32\\bin;c:\\toolchain\\esp32\\tools\\ninja\\1.11.1;c:\\toolchain\\esp32\\tools\\idf-exe\\1.0.3;c:\\toolchain\\esp32\\tools\\ccache\\4.8\\ccache-4.8-windows-x86_64;c:\\toolchain\\esp32\\tools\\dfu-util\\0.11\\dfu-util-0.11-win64;c:\\toolchain\\esp32\\tools\\esp-rom-elfs\\20230320", + "idf.customExtraVars": { + "OPENOCD_SCRIPTS": "c:\\toolchain\\esp32\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts", + "IDF_CCACHE_ENABLE": "1", + "ESP_ROM_ELF_DIR": "c:\\toolchain\\esp32\\tools\\esp-rom-elfs\\20230320/" + }, + "idf.espIdfPathWin": "c:\\toolchain\\esp32\\v5.2.2\\esp-idf", + "idf.openOcdConfigs": [ + "board/esp32s3_builtin.cfg" + ], + "idf.portWin": "COM7", + "idf.pythonBinPathWin": "c:\\toolchain\\esp32\\python_env\\idf5.2_py3.11_env\\Scripts\\python.exe", + "idf.toolsPathWin": "c:\\toolchain\\esp32" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1dc7915 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,259 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build - Build project", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py build", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py build", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Set ESP-IDF Target", + "type": "shell", + "command": "${command:espIdf.setTarget}", + "problemMatcher": { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "Clean - Clean the project", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py fullclean", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py fullclean", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ] + }, + { + "label": "Flash - Flash the device", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py -p ${config:idf.port} -b ${config:idf.flashBaudRate} flash", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py flash -p ${config:idf.portWin} -b ${config:idf.flashBaudRate}", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ] + }, + { + "label": "Monitor: Start the monitor", + "type": "shell", + "command": "${config:idf.pythonBinPath} ${config:idf.espIdfPath}/tools/idf.py -p ${config:idf.port} monitor", + "windows": { + "command": "${config:idf.pythonBinPathWin} ${config:idf.espIdfPathWin}\\tools\\idf.py -p ${config:idf.portWin} monitor", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": [ + { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + ], + "dependsOn": "Flash - Flash the device" + }, + { + "label": "OpenOCD: Start openOCD", + "type": "shell", + "presentation": { + "echo": true, + "reveal": "never", + "focus": false, + "panel": "new" + }, + "command": "openocd -s ${command:espIdf.getOpenOcdScriptValue} ${command:espIdf.getOpenOcdConfigs}", + "windows": { + "command": "openocd.exe -s ${command:espIdf.getOpenOcdScriptValue} ${command:espIdf.getOpenOcdConfigs}", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}" + } + } + }, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}" + } + }, + "problemMatcher": { + "owner": "cpp", + "fileLocation": [ + "autoDetect", + "${workspaceFolder}" + ], + "pattern": { + "regexp": "^(.*?):(\\d+):(\\d*):?\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "adapter", + "type": "shell", + "command": "${config:idf.pythonBinPath}", + "isBackground": true, + "options": { + "env": { + "PATH": "${env:PATH}:${config:idf.customExtraPaths}", + "PYTHONPATH": "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter" + } + }, + "problemMatcher": { + "background": { + "beginsPattern": "\bDEBUG_ADAPTER_STARTED\b", + "endsPattern": "DEBUG_ADAPTER_READY2CONNECT", + "activeOnStart": true + }, + "pattern": { + "regexp": "(\\d+)-(\\d+)-(\\d+)\\s(\\d+):(\\d+):(\\d+),(\\d+)\\s-(.+)\\s(ERROR)", + "file": 8, + "line": 2, + "column": 3, + "severity": 4, + "message": 9 + } + }, + "args": [ + "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter_main.py", + "-e", + "${workspaceFolder}/build/${command:espIdf.getProjectName}.elf", + "-s", + "$OPENOCD_SCRIPTS", + "-dn", + "esp32", + "-om", + "connect_to_instance", + "-t", + "xtensa-esp32-elf-" + + ], + "windows": { + "command": "${config:idf.pythonBinPathWin}", + "options": { + "env": { + "PATH": "${env:PATH};${config:idf.customExtraPaths}", + "PYTHONPATH": "${command:espIdf.getExtensionPath}/esp_debug_adapter/debug_adapter" + } + } + } + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3e85c3b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +# 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) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(psram-msc) diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7bff1f --- /dev/null +++ b/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/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..1427fb8 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,21 @@ +dependencies: + espressif/esp_tinyusb: + component_hash: f151d680d6847bfcfd5d8eb6d1c3ff926c208e6b963b2e83643a141bc70baa15 + source: + service_url: https://api.components.espressif.com/ + type: service + version: 1.4.4 + espressif/tinyusb: + component_hash: 214989d502fc168241a4a4f83b097d8ac44a93cd6f1787b4ac10069a8b3bebd3 + source: + service_url: https://api.components.espressif.com/ + type: service + version: 0.15.0~10 + idf: + component_hash: null + source: + type: idf + version: 5.2.2 +manifest_hash: 67f54c7dd0b412a869c37cb399555d02190d19f73072d97a218f4fcc711a1255 +target: esp32s3 +version: 1.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..b8dc619 --- /dev/null +++ b/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS tusb_composite_main.c + INCLUDE_DIRS . + PRIV_REQUIRES fatfs wear_levelling esp_partition +) diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..06b047d --- /dev/null +++ b/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/main/tusb_composite_main.c b/main/tusb_composite_main.c new file mode 100644 index 0000000..332b508 --- /dev/null +++ b/main/tusb_composite_main.c @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_partition.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", 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/partitions.csv b/partitions.csv new file mode 100644 index 0000000..1c79321 --- /dev/null +++ b/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/pytest_usb_device_composite.py b/pytest_usb_device_composite.py new file mode 100644 index 0000000..6adfa49 --- /dev/null +++ b/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/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..ff7b5b1 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,12 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.2.2 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_MODE_PERF=y +CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n +CONFIG_TINYUSB_DESC_CUSTOM_PID=0x4001 +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_CDC_ENABLED=y