diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 45c6e7477..030382d23 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -39,7 +39,7 @@ - pip install idf_build_apps - python tools/build_apps.py ${EXAMPLE_DIR} --config ${EXAMPLE_CONFIG} -t all -vv -.build_idf_release_version: +.build_idf_active_release_version: parallel: matrix: - IMAGE: espressif/idf:release-v4.4 @@ -48,6 +48,14 @@ - IMAGE: espressif/idf:release-v5.2 - IMAGE: espressif/idf:release-v5.3 +.build_idf_version_larger_than_v5_0: + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:release-v5.2 + - IMAGE: espressif/idf:release-v5.3 + build_example_audio_wav_player: extends: - .build_examples_template @@ -565,6 +573,14 @@ build_example_ulp_lp_cpu_lp_environment_sensor: variables: EXAMPLE_DIR: examples/ulp/lp_cpu/lp_environment_sensor +build_example_usb_device_usb_dongle: + extends: + - .build_examples_template + - .rules:build:example_usb_device_usb_dongle + - .build_idf_version_larger_than_v5_0 + variables: + EXAMPLE_DIR: examples/usb/device/usb_dongle + build_example_usb_device_usb_uart_bridge: extends: - .build_examples_template @@ -580,7 +596,7 @@ build_example_usb_device_usb_uac: extends: - .build_examples_template - .rules:build:example_usb_device_usb_uac - - .build_idf_release_version + - .build_idf_active_release_version variables: EXAMPLE_DIR: examples/usb/device/usb_uac @@ -1057,7 +1073,7 @@ build_components_led_lightbulb_driver_test_apps: extends: - .build_examples_template - .rules:build:components_led_lightbulb_driver_test_apps - - .build_idf_release_version + - .build_idf_active_release_version variables: EXAMPLE_DIR: components/led/lightbulb_driver/test_apps diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index 6f9538974..1ab2136ba 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -417,6 +417,9 @@ .patterns-example_sensors_sensor_hub_monitor: &patterns-example_sensors_sensor_hub_monitor - "examples/sensors/sensor_hub_monitor/**/*" +.patterns-example_usb_device_usb_dongle: &patterns-example_usb_device_usb_dongle + - "examples/usb/device/usb_dongle/**/*" + .patterns-example_ulp_lp_cpu_lp_environment_sensor: &patterns-example_ulp_lp_cpu_lp_environment_sensor - "examples/ulp/lp_cpu/lp_environment_sensor/**/*" @@ -1048,6 +1051,15 @@ - <<: *if-dev-push changes: *patterns-example_usb_device_usb_hid_device +.rules:build:example_usb_device_usb_dongle: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-example_usb_device_usb_dongle + .rules:build:example_usb_device_usb_uart_bridge: rules: - <<: *if-protected diff --git a/examples/.build-rules.yml b/examples/.build-rules.yml index 52dfc25fd..91da2137c 100644 --- a/examples/.build-rules.yml +++ b/examples/.build-rules.yml @@ -213,6 +213,10 @@ examples/sensors/sensor_hub_monitor: enable: - if: IDF_TARGET in ["esp32","esp32s2"] and (IDF_VERSION_MAJOR == 4 and IDF_VERSION_MINOR == 4) +examples/usb/device/usb_dongle: + enable: + - if: SOC_USB_OTG_SUPPORTED == 1 + examples/ulp/lp_cpu/lp_environment_sensor: enable: - if: IDF_TARGET in ["esp32c6"] diff --git a/examples/usb/device/usb_dongle/CMakeLists.txt b/examples/usb/device/usb_dongle/CMakeLists.txt new file mode 100644 index 000000000..b5a04addd --- /dev/null +++ b/examples/usb/device/usb_dongle/CMakeLists.txt @@ -0,0 +1,7 @@ +# 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) + +add_compile_options(-fdiagnostics-color=always) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(usb_dongle) diff --git a/examples/usb/device/usb_dongle/Commands.md b/examples/usb/device/usb_dongle/Commands.md new file mode 100644 index 000000000..e48fc4f7d --- /dev/null +++ b/examples/usb/device/usb_dongle/Commands.md @@ -0,0 +1,269 @@ +### 命令介绍 + +#### 1.help + +**Function:** + +列出所有注册的命令 + +**Command:** + +``` +help +``` + +**Response:** + +``` +help: + Lists all the registered commands + +ap []: configure ssid and password +sta -s [-p ]: join specified soft-AP +sta -d: disconnect specified soft-AP +mode : station mode; ap mode +smartconfig [op]: op:1, start smartconfig; op:0, stop smartconfig +scan []: SSID of AP want to be scanned +ram: Get the current size of free heap memory and minimum size of free heap memory +restart: Software reset of the chip +version: Get version of chip and SDK +> +``` + +#### 2.ap + +**Function:** + +设置 AP 模式、查询 AP 设置 + +**Set Command:** + +``` +ap Soft_AP espressif +``` + +**Query Command:** + +``` +ap +``` + +**Response:** + +``` +AP mode:Soft_AP,espressif +> +``` + +Note: + +>password 为可选项,若不配置默认不加密 + +#### 3.sta + +**Function:** + +启动 Station 模式、查询所连接 AP 信息 + +**Set Command:** + +``` +sta -s AP_Test -p espressif +``` + +**Query Command:** + +``` +sta +``` + +**Response:** + +``` +,,, +> +``` + +| authmode_value | mode | +| :------------: | :------------------------ | +| 0 | WIFI_AUTH_OPEN | +| 1 | WIFI_AUTH_WEP | +| 2 | WIFI_AUTH_WPA_PSK | +| 3 | WIFI_AUTH_WPA2_PSK | +| 4 | WIFI_AUTH_WPA_WPA2_PSK | +| 5 | WIFI_AUTH_WPA2_ENTERPRISE | +| 6 | WIFI_AUTH_WPA3_PSK | +| 7 | WIFI_AUTH_WPA2_WPA3_PSK | +| 8 | WIFI_AUTH_WAPI_PSK | + +Note: + +>password 为可选项 + +**Function:** + +断开与 AP 的连接 + +**Set Command:** + +``` +sta -d +``` + +**Response:** + +``` +OK +> +``` + +#### 4.mode + +**Function:** + +设置 WiFi 模式 + +**Command:** + +* 设置 Station 模式 + + ``` + mode sta + ``` + +* 设置 AP 模式 + + ``` + mode ap + ``` + +#### 5.smartconfig + +**Function:** + +* 开启 SmartConfig 配网 + + **Command:** + + ``` + smartconfig 1 + ``` + + **Response:** + + ``` + >SSID:FAST_XLZ,PASSWORD:12345678 + OK + > + ``` + +* 关闭 SmartConfig 配网 + + **Command:** + + ``` + smartconfig 0 + ``` + + **Response:** + + ``` + OK + > + ``` + + Note: + + >使用 `smartconfig 1` 命令开启 SmartConfig 配网并成功连接后,不需要再使用 `smartconfig 0` 命令来关闭 SmartConfig 配网 + > + >`smartconfig 0` 命令只需要在 SmartConfig 配网失败时进行调用 + +配网步骤: + +>* 下载 ESPTOUCH APP :[Android source code](https://github.com/EspressifApp/EsptouchForAndroid) [iOS source code](https://github.com/EspressifApp/EsptouchForIOS) +>* 确保你的手机连接至目标 AP(2.4GHz) +>* 打开 ESPTOUCH APP 输入 password 并确认 +>* PC 端通过 USB 端口发送 `smartconfig 1` 命令 + +#### 6.scan + +**Function:** + +扫描 AP 并列出对应 SSID 以及 RSSI + +**Command:** + +* 扫描特定 AP + + ``` + scan + ``` + +* 扫描所有 AP + + ``` + scan + ``` + +**Response:** + +``` +> +[ssid][rssi=-22] +> +``` + +#### 7.ram + +**Function:** + +获取当前剩余内存大小以及系统运行期间最小时内存大小 + +**Command:** + +``` +ram +``` + +**Response:** + +``` +free heap size: 132612, min heap size: 116788 +> +``` + +#### 8.restart + +**Function:** + +重启系统 + +**Command:** + +``` +restart +``` + +#### 9.version + +**Function:** + +获取当前 IDF 版本以及芯片信息 + +**Command:** + +``` +version +``` + +**Response:** + +``` +IDF Version:v4.4-dev-2571-gb1c3ee71c5 +Chip info: + cores:1 + feature:/802.11bgn/External-Flash:2 MB + revision number:0 +> +``` + diff --git a/examples/usb/device/usb_dongle/Commands_EN.md b/examples/usb/device/usb_dongle/Commands_EN.md new file mode 100644 index 000000000..79282cfe7 --- /dev/null +++ b/examples/usb/device/usb_dongle/Commands_EN.md @@ -0,0 +1,269 @@ +### Command Set + +#### 1.help + +**Function:** + +List all registered commands. + +**Command:** + +``` +help +``` + +**Response:** + +``` +help: + Lists all the registered commands + +ap []: configure ssid and password +sta -s [-p ]: join specified soft-AP +sta -d: disconnect specified soft-AP +mode : station mode; ap mode +smartconfig [op]: op:1, start smartconfig; op:0, stop smartconfig +scan []: SSID of AP want to be scanned +ram: Get the current size of free heap memory and minimum size of free heap memory +restart: Software reset of the chip +version: Get version of chip and SDK +> +``` + +#### 2.ap + +**Function:** + +Set/get softAP configuration. + +**Set Command:** + +``` +ap Soft_AP espressif +``` + +**Query Command:** + +``` +ap +``` + +**Response:** + +``` +AP mode:Soft_AP,espressif +> +``` + +Note: + +>password is optional. If you didn't set any password, then the ESP softAP will be set into the open mode. + +#### 3.sta + +**Function:** + +Set station mode, query the information of the AP it connected to. + +**Set Command:** + +``` +sta -s AP_Test -p espressif +``` + +**Query Command:** + +``` +sta +``` + +**Response:** + +``` +,,, +> +``` + +| authmode_value | mode | +| :------------: | :------------------------ | +| 0 | WIFI_AUTH_OPEN | +| 1 | WIFI_AUTH_WEP | +| 2 | WIFI_AUTH_WPA_PSK | +| 3 | WIFI_AUTH_WPA2_PSK | +| 4 | WIFI_AUTH_WPA_WPA2_PSK | +| 5 | WIFI_AUTH_WPA2_ENTERPRISE | +| 6 | WIFI_AUTH_WPA3_PSK | +| 7 | WIFI_AUTH_WPA2_WPA3_PSK | +| 8 | WIFI_AUTH_WAPI_PSK | + +Note: + +>password is optional. + +**Function:** + +Disconnect from AP. + +**Set Command:** + +``` +sta -d +``` + +**Response:** + +``` +OK +> +``` + +#### 4.mode + +**Function:** + +Set Wi-Fi mode. + +**Command:** + +* Set Station mode + + ``` + mode sta + ``` + +* Set AP mode + + ``` + mode ap + ``` + +#### 5.smartconfig + +**Function:** + +* Start SmartConfig to connect to target AP. + + **Command:** + + ``` + smartconfig 1 + ``` + + **Response:** + + ``` + >SSID:FAST_XLZ,PASSWORD:12345678 + OK + > + ``` + +* Stop SmartConfig. + + **Command:** + + ``` + smartconfig 0 + ``` + + **Response:** + + ``` + OK + > + ``` + + Note: + + > After called `smartconfig 1` command, if the ESP device connects to the target AP successfully, then you don't need to call `smartconfig 0` to stop SmartConfig. + > + >`smartconfig 0` command only needs to be called, when the SmartConfig fails. + +SmartConfig steps: + +>* Download ESPTOUCH APP to your phone: [Android source code](https://github.com/EspressifApp/EsptouchForAndroid) [iOS source code](https://github.com/EspressifApp/EsptouchForIOS) +>* Connect your phone to the target AP (2.4GHz). +>* Open ESPTOUCH APP, input AP's password. +>* PC sends command `smartconfig 1` to USB port to start SmartConfig. + +#### 6.scan + +**Function:** + +Scan APs, list their SSID and RSSI. + +**Command:** + +* Scan a specific AP. + + ``` + scan + ``` + +* Scan all APs nearby. + + ``` + scan + ``` + +**Response:** + +``` +> +[ssid][rssi=-22] +> +``` + +#### 7.ram + +**Function:** + +Get the size of available heap, and get the minimum heap that has ever been available. + +**Command:** + +``` +ram +``` + +**Response:** + +``` +free heap size: 132612, min heap size: 116788 +> +``` + +#### 8.restart + +**Function:** + +Restart the system. + +**Command:** + +``` +restart +``` + +#### 9.version + +**Function:** + +Get the ESP-IDF and chip version. + +**Command:** + +``` +version +``` + +**Response:** + +``` +IDF Version:v4.4-dev-2571-gb1c3ee71c5 +Chip info: + cores:1 + feature:/802.11bgn/External-Flash:2 MB + revision number:0 +> +``` + diff --git a/examples/usb/device/usb_dongle/README.md b/examples/usb/device/usb_dongle/README.md new file mode 100644 index 000000000..e51ebee11 --- /dev/null +++ b/examples/usb/device/usb_dongle/README.md @@ -0,0 +1,286 @@ +# ESP32-S 系列 USB 无线适配器方案介绍 + +## 1.概述 + +本示例程序演示 ESP32-S 系列芯片如何实现 USB Dongle 设备功能,支持以下功能: + +* 支持 Host 主机通过 USB-ECM/RNDIS 无线上网 +* 支持 Host 主机通过 USB-BTH 进行 BLE 扫描、广播、配对、连接、绑定以及读写数据 +* 支持 Host 主机通过 USB-DFU 进行设备升级 +* 支持 Host 主机通过 USB-CDC、UART 对 ESP32-S 系列设备进行通信和控制 +* 支持多种 system、Wi-Fi 控制命令,使用 FreeRTOS-Plus-CLI 命令行接口,易拓展更多命令 +* 支持使用 USB webusb / 串口 / smartconfig 等多种配网方式 +* 支持热插拔 + +## 2. 如何使用示例 +### 2.1 硬件准备 + +支持 USB-OTG 的 ESP 开发板。 + +* ESP32-S2 + +* ESP32-S3 + +### 2.2 引脚分配 + +只有具有 USB-OTG 外设的 ESP 芯片才需要引脚分配。 如果您的电路板没有连接到 USB-OTG 专用 GPIO 的 USB 连接器,您可能需要自己动手制作电缆并将 **D+** 和 **D-** 连接到下面列出的引脚 + +``` +ESP BOARD USB CONNECTOR (type A) + -- + | || VCC +[USBPHY_DM_NUM] ------> | || D- +[USBPHY_DP_NUM] ------> | || D+ + | || GND + -- +``` + +| | USB_DP | USB_DM | +| ----------- | ------ | ------ | +| ESP32-S2/S3 | GPIO20 | GPIO19 | + +* ESP32-S2-Saola + +ESP32-S2 + +* ESP32-S3 DevKitC + +ESP32-S3 + +### 2.3 软件准备 + +1. 确认 ESP-IDF 环境成功搭建。 + ``` + >git checkout release/v5.0 + >git pull origin release/v5.0 + >git submodule update --init --recursive + > + ``` + +2. 添加 ESP-IDF 环境变量,Linux 方法如下,其它平台请查阅 [设置环境变量](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/get-started/index.html#get-started-set-up-env)。 + ``` + >cd esp-idf + >./install.sh + >. ./export.sh + > + ``` + +3. 确认已经完整下载 `ESP-IOT-SOLUTION` 仓库。 + + ``` + >git clone -b usb/add_usb_solutions --recursive https://github.com/espressif/esp-iot-solution + > + ``` + +4. 设置编译目标为 `esp32s2` 或 `esp32s3`。 + + ``` + >idf.py set-target esp32s2 + > + ``` + +### 2.4 软件工程配置 + +![tinyusb_config](./_static/tinyusb_config.png) + +![uart_config](./_static/uart_config.png) + +目前 USB-Dongle 支持如下最大组合选项: + +| USB-ECM/RNDIS | USB-BTH | USB-CDC | UART | USB-DFU | WEBUSB | +| :-----------: | :-----: | :-----: | :--: | :-----: | ------ | +| √ | | | √ | √ | √ | +| √ | | √ | | √ | | +| √ | √ | | √ | | | +| | √ | | √ | √ | | + +* 本软件默认使能 ECM、CDC。 +* UART 默认在 CDC 使能时禁用。 +* UART 可以用于命令通讯,与此同时,你也可以用于蓝牙通讯。 +* 可以通过 `component config -> TinyUSB Stack` 选择 USB 设备。 +* 同时使能 RNDIS/ECM 和 BTH 时,建议禁用 CDC,采用 UART 发送命令,可以通过 `Example Configuration` 进行串口配置。 + +>由于目前硬件限制,EndPoint 不能超过一定数量,故不支持 ECM/RNDIS、BTH、CDC 同时使能。 + +### 2.5 固件编译&烧录 + +您可以通过以下命令编译、下载固件,并查看输出。 + +``` +>idf.py -p (PORT) build flash monitor +> +``` + + * ``(PORT)`` 需要替换为实际的串口名称。 + * 使用命令 `Ctrl-]` 可以退出串口监控状态。 + + +### 2.6 使用说明 + +1. 完成上述[硬件连接](#connect)并成功烧录固件后,将 USB 连接至 PC 端 + +2. PC 端将会新增一个 USB 网卡以及一个蓝牙设备 + + * 显示网络设备 + + ``` + >ifconfig -a + > + ``` + + ifconfig + + * 显示蓝牙设备 + + ``` + >hciconfig + > + ``` + + ![hciconfig](./_static/hciconfig.png) + + * 显示 USB-CDC 设备 + + ``` + >ls /dev/ttyACM* + > + ``` + + ![ACM](./_static/ACM.png) + +3. 通过 USB-CDC 或者 UART 与 ESP 设备进行通信,使用 help 命令来查看目前所支持的所有指令 + + >与 ESP 设备进行通信时命令末尾需加上 LF(\n) + +4. 若使能 USB-ECM/RNDIS,则可通过指令来控制 ESP 设备进行配网操作 + +* [通过 sta 命令来连接至对应路由器](./Commands.md#3sta) +* [通过 startsmart 命令开启 smartconfig 配网](./Commands.md#5smartconfig) + +### 2.7 网络设备常见问题 + +- #### Windows + +Windows 平台只支持 RNDIS,USB ECM 无法识别 + +- #### MAC + +MAC 平台只支持 ECM,USB RNDIS 无法识别 + +- #### Linux + +Linux 平台同时支持 ECM 和 RNDIS,不过使用 RNDIS 时,如果切换不同的路由器,Linux 下的网络设备并不会主动重新获取 IP。 + +如果使用嵌入式 Linux, 没有显示网络设备,可能是在内核中没有使能上述两个模块,在 Linux 内核中使能如下两个配置项,分别用于支持 CDC-ECM 和 RNDIS。 + +``` +Device Drivers ---> + Network Device Support ---> + Usb Network Adapters ---> + Multi-purpose USB Networking Framework ---> + CDC Ethernet Support + Host For RDNIS and ActiveSync Devices +``` + +如果确定使能上述模块依然无法看到网络设备,请通过 `dmesg` 命令查看内核信息, 确定 Linux 内核中是否有探测到 ESP32-S USB 网络设备,以及是否有错误打印信息。 + +## 3. 配置连接 Wi-Fi 网络 + +本示例提供了两种连接 Wi-Fi 网络的方法。 + +### [方法 1. 通过 `sta` 命令连接至 Wi-Fi 路由器](./Commands.md#3sta) + +**命令示例** + +``` +sta -s -p [] +``` + +**说明** + +* `password` 为选填参数。 + +### [方法 2. 通过 smartconfig 连接至 Wi-Fi 路由器](./Commands.md#5smartconfig) + +(1) 硬件准备 + +从手机应用市场下载 ESPTOUCH APP:[Android source code](https://github.com/EspressifApp/EsptouchForAndroid) [iOS source code](https://github.com/EspressifApp/EsptouchForIOS)。 + +(2) 将手机连接到目标 Wi-Fi AP(2.4GHz)。 + +(3) 手机打开 ESPTOUCH app 输入 Wi-Fi 密码。 + +(4) Host 通过 USB ACM port 发送以下命令给 ESP 设备,开始配网。 + +**命令示例** + +``` +smartconfig 1 +``` + +## 4. 命令说明 + +[Command](./Commands.md) + +注意:Wi-Fi 相关命令只有在 USB Network Class 使能时才可以使用. + +## 5. 如何使用 USB-DFU 对设备升级 + +在使用 DFU 对设备升级之前,请确保已经在配置项中使能了 DFU 功能 + +``` +component config -> TinyUSB Stack -> Use TinyUSB Stack -> Firmware Upgrade Class (DFU) -> Enable TinyUSB DFU feature +``` + +#### Ubuntu + +在 Ubuntu 环境下首先需要安装 DFU 工具 + +``` + +``` + +使用如下命令进行升级操作 + +``` +sudo dfu-util -d -a 0 -D +``` + +其中: + +1. VendorID 为 USB vendor ID, 缺省为 0x303A +2. OTA_BIN_PATH 为需要升级的固件 + +使用如下命令进行上传操作,默认会把 OTA_0 分区读取出来 + +``` +sudo dfu-util -d -a 0 -U +``` + +其中: + +1. VendorID 为 USB vendor ID, 缺省为 0x303A +2. OTA_BIN_PATH 从设备读取固件保存的文件名 + +#### Windows + +1. 在 Windows 平台首先需要下载 [dfu-util](http://dfu-util.sourceforge.net/releases/dfu-util-0.9-win64.zip)。 + +2. dfu-util 使用 libusb 访问 USB 设备,这要求我们需要在 Windows 上安装 WinUSB 驱动,可以使用 [Zadig 工具](http://zadig.akeo.ie/) 安装。 + +3. 打开命令行窗口,将 dfu-util.exe 拖进去,然后执行如下命令进行升级操作 + + ``` + dfu-util.exe -d -a 0 -D + ``` + + 其中: + + 1. VendorID 为 USB vendor ID, 缺省为 0xcafe + 2. OTA_BIN_PATH 为需要升级的固件 + +#### 常见问题和错误 + +1. 各平台 dfu-util 工具安装错误的问题请参考[此链接](https://support.particle.io/hc/en-us/articles/360039251394-Installing-DFU-util) 。 +2. dfu-util 执行时打印 “No DFU capable USB device available”, 这说明 dfu-util 没有探测到 ESP32-S 芯片的 DFU 设备,请确保在配置项已经使能 DFU 功能。在 Ubuntu 中,请确保使用了管理员权限操作。 +sudo apt install dfu-util \ No newline at end of file diff --git a/examples/usb/device/usb_dongle/README_EN.md b/examples/usb/device/usb_dongle/README_EN.md new file mode 100644 index 000000000..3b7de4025 --- /dev/null +++ b/examples/usb/device/usb_dongle/README_EN.md @@ -0,0 +1,293 @@ +# ESP32-S USB Dongle Solution + +## 1.Overview + +This example shows how to set up ESP32-S chip to work as a USB Dongle Device. + +Supports the following functions: + +* Support Host to surf the Internet wirelessly via USB-ECM/RNDIS. +* Add BLE devices via USB-BTH, support scan, broadcast, connect and other functions. +* Support Host to communicate and control ESP32-S series devices via USB-CDC or UART. +* Support Host to upgrade device using USB-DFU. +* Support system commands and Wi-Fi control commands. It uses FreeRTOS-Plus-CLI interfaces, so it is easy to add more commands. +* Support hot swap. + +## 2.How to use example + +### 2.1 Hardware Preparation + +Any ESP boards that have USB-OTG supported. + +* ESP32-S2 + +* ESP32-S3 + +### 2.2 Hardware Connection + +Pin assignment is only needed for ESP chips that have an USB-OTG peripheral. If your board doesn't have a USB connector connected to the USB-OTG dedicated GPIOs, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +``` +ESP BOARD USB CONNECTOR (type A) + -- + | || VCC +[USBPHY_DM_NUM] ------> | || D- +[USBPHY_DP_NUM] ------> | || D+ + | || GND + -- +``` + +Refer to `soc/usb_pins.h` to find the real GPIO number of **USBPHY_DP_NUM** and **USBPHY_DM_NUM**. + +| | USB_DP | USB_DM | +| ----------- | ------ | ------ | +| ESP32-S2/S3 | GPIO20 | GPIO19 | + +* ESP32-S2-Saola + +ESP32-S2 + +* ESP32-S3 DevKitC + +ESP32-S3 + +### 2.3 Software Preparation + +* Confirm that the ESP-IDF environment is successfully set up. + + ``` + >git checkout release/v4.4 + >git pull origin release/v4.4 + >git submodule update --init --recursive + > + ``` + +* To add ESP-IDF environment variables. If you are using Linux OS, you can follow below steps. If you are using other OS, please refer to [Set up the environment variables](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#step-4-set-up-the-environment-variables). + + ``` + >cd esp-idf + >./install.sh + >. ./export.sh + > + ``` + +* Confirm that the `ESP-IOT-SOLUTION` repository has been completely downloaded, and switch to the `usb/add_usb_solutions` branch. + + ``` + >git clone -b usb/add_usb_solutions --recursive https://github.com/espressif/esp-iot-solution + >cd esp-iot-solution/example/usb/device/usb_dongle + > + ``` + +* Set the compilation target to `esp32s2` or `esp32s3` + + ``` + >idf.py set-target esp32s2 + > + ``` + +### 2.4 Project Configuration + +![tinyusb_config](./_static/tinyusb_config.png) + +![uart_config](./_static/uart_config.png) + +Currently USB-Dongle supports the following four combination options. + +| ECM/RNDIS | BTH | CDC | UART | DFU | +| :-------: | :--: | :--: | :--: | :--: | +| √ | | | √ | √ | +| √ | | √ | | √ | +| √ | √ | | √ | √ | +| | √ | | √ | √ | + +* UART is disabled by default when CDC is enabled. +* UART can be used for command communication, and you can also use Bluetooth for communication. +* The project enables ECM and CDC by default. +* You can select USB Device through `component config -> TinyUSB Stack`. +* When ECM/RNDIS and BTH are enabled at the same time, it is recommended to disable CDC, use UART to send commands, and configure the serial port through `Example Configuration`. + +>Due to current hardware limitations, the number of EndPoints cannot exceed a certain number, so ECM/RNDIS, BTH, and CDC should not be all enabled at the same time. + +### 2.5 build & flash & monitor + +You can use the following command to build and flash the firmware. + +``` +>idf.py -p (PORT) build flash monitor +> +``` + + * Replace ``(PORT)`` with the actual name of your serial port. + + * To exit the serial monitor, type `Ctrl-]`. + +### 2.6 User Guide + +1. After completing above preparation, you can connect PC and your ESP device with USB cable. + +2. You can see a USB network card and a bluetooth device on your PC. + + * Show network devices + + ``` + >ifconfig -a + > + ``` + + ifconfig + + * Show bluetooth device + + ``` + >hciconfig + > + ``` + + ![hciconfig](./_static/hciconfig.png) + + Show USB-CDC device + + ``` + >ls /dev/ttyACM* + > + ``` + + ![ACM](./_static/ACM.png) + +3. The PC can communicate with the ESP board through the USB ACM port or UART. And you can use ``help`` command to view all commands supported. + + > When communicating with ESP device, add **LF(\n)** at the end of the command + +4. If USB-RNDIS is enabled, you can connect ESP device to AP by commands. + * [Connect to target AP by sta command](./Commands_EN.md#3sta) + * [Connect to target AP by startsmart command (SmartConfig)](./Commands_EN.md#5smartconfig) + +### 2.7 Common network device Problems + +#### Windows + +Windows platform only supports RNDIS, USB ECM is not recognized. + +#### MAC + +MAC platform only supports ECM, USB RNDIS is not recognized. + +#### Linux + +Linux platform support both ECM and RNDIS. Howerver, if RNDIS is used, network devices in Linux do not proactively obtain IP addresses when switching between different routers. + +If embedded Linux is used and network devices are not displayed, the preceding two modules may not be enabled in the kernel. The following two configuration items are enabled in the Linux kernel to support CDC-ECM and RNDIS respectively. + +``` +Device Drivers ---> + Network Device Support ---> + Usb Network Adapters ---> + Multi-purpose USB Networking Framework ---> + CDC Ethernet Support + Host For RDNIS and ActiveSync Devices +``` + +If you are sure that the network device cannot be seen after the preceding modules are enabled, run the `dmesg` command to view the kernel information to check whether ESP32-S USB network devices are detected in the Linux kernel and whether error messages are displayed. + +## 3. Connect to a Wi-Fi AP + +The example provides two methods to connect ESP device to a Wi-Fi AP. + +### [Method 1. Connect to target AP by `sta` command](./Commands_EN.md#3sta) + +**Command Example** + +``` +sta -s -p [] +``` + +**Notes** + +* `password` is optional + + +### [Method 2. Connect to target AP by startsmart command (SmartConfig)](./Commands_EN.md#5smartconfig) + +(1) Hardware Required + +Download ESPTOUCH APP from app store: [Android source code](https://github.com/EspressifApp/EsptouchForAndroid) [iOS source code](https://github.com/EspressifApp/EsptouchForIOS) is available. + +(2) Make sure your phone connect to the target AP (2.4GHz). + +(3) Open ESPTOUCH app and input password. + +(4) Send commands via USB ACM port + +**Example** + +``` +smartconfig 1 +``` + +## 4.Command introduction + +[Commands](./Commands_EN.md) + +Note: Wi-Fi commands can only be used when USB Network Class is enabled + +## 5. How to use USB-DFU to upgrade device + +Before using DFU to upgrade ES[32-S device, ensure that the DFU feature has been enabled in the configuration item. + +``` +component config -> TinyUSB Stack -> Use TinyUSB Stack -> Firmware Upgrade Class (DFU) -> Enable TinyUSB DFU feature +``` + +#### Ubuntu + +You need to install the DFU tool first in Ubuntu. + +``` +sudo apt install dfu-util +``` + +Run the following command to upgrade. + +``` +sudo dfu-util -d -a 0 -D +``` + +**Notes** + +- VendorID is USB vendor ID, the default value 0x303A +- OTA_BIN_PATH is the upgrade firmware + +Run the following command to upload. + +``` +sudo dfu-util -d -a 0 -U +``` + +**Notes** + +- VendorID is USB vendor ID, the default value 0x303A +- Read firmware from device into OTA_BIN_PATH + +#### Windows + +1. On Windows you need to download [dfu-util](http://dfu-util.sourceforge.net/releases/dfu-util-0.9-win64.zip) first. + +2. `dfu-util` uses libusb to access the device. You have to register on Windows the device with the WinUSB driver. Installation using [Zadig](http://zadig.akeo.ie/) tool is recommended. + +3. Open the command window, run the following commands using `dfu-util.exe`. + + ``` + dfu-util.exe -d -a 0 -D + ``` + + **Notes** + + - VendorID is USB vendor ID, the default value 0x303A + - OTA_BIN_PATH is the upgrade firmware + +#### Common problems + +1. Please refer to [this link](https://support.particle.io/hc/en-us/articles/360039251394-Installing-DFU-util) for the installation error of `dfu-util` tool on each platform. +2. "No DFU capable USB device available" means `dfu-util` does not detect the DFU device of the ESP32-S chip. Ensure that the DFU feature is enabled in the configuration item. + In Linux platform, make sure you are using administrator rights. diff --git a/examples/usb/device/usb_dongle/_static/ACM.png b/examples/usb/device/usb_dongle/_static/ACM.png new file mode 100644 index 000000000..96259f04a Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/ACM.png differ diff --git a/examples/usb/device/usb_dongle/_static/ESP32-S2.png b/examples/usb/device/usb_dongle/_static/ESP32-S2.png new file mode 100644 index 000000000..39df3b9c3 Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/ESP32-S2.png differ diff --git a/examples/usb/device/usb_dongle/_static/ESP32-S3.png b/examples/usb/device/usb_dongle/_static/ESP32-S3.png new file mode 100644 index 000000000..0cb737615 Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/ESP32-S3.png differ diff --git a/examples/usb/device/usb_dongle/_static/error.png b/examples/usb/device/usb_dongle/_static/error.png new file mode 100644 index 000000000..9b4894503 Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/error.png differ diff --git a/examples/usb/device/usb_dongle/_static/hciconfig.png b/examples/usb/device/usb_dongle/_static/hciconfig.png new file mode 100644 index 000000000..170a005cf Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/hciconfig.png differ diff --git a/examples/usb/device/usb_dongle/_static/ifconfig.png b/examples/usb/device/usb_dongle/_static/ifconfig.png new file mode 100644 index 000000000..0e282fb77 Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/ifconfig.png differ diff --git a/examples/usb/device/usb_dongle/_static/tinyusb_config.png b/examples/usb/device/usb_dongle/_static/tinyusb_config.png new file mode 100644 index 000000000..bfdad8b89 Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/tinyusb_config.png differ diff --git a/examples/usb/device/usb_dongle/_static/uart_config.png b/examples/usb/device/usb_dongle/_static/uart_config.png new file mode 100644 index 000000000..1673713b2 Binary files /dev/null and b/examples/usb/device/usb_dongle/_static/uart_config.png differ diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/CMakeLists.txt b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/CMakeLists.txt new file mode 100644 index 000000000..4dae72e53 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "FreeRTOS_CLI.c" + INCLUDE_DIRS include + PRIV_REQUIRES ) diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/FreeRTOS_CLI.c b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/FreeRTOS_CLI.c new file mode 100644 index 000000000..02a26f990 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/FreeRTOS_CLI.c @@ -0,0 +1,322 @@ +/* + * FreeRTOS+CLI V1.0.4 + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +/* Standard includes. */ +#include +#include + +/* FreeRTOS includes. */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +/* Utils includes. */ +#include "FreeRTOS_CLI.h" + +/* If the application writer needs to place the buffer used by the CLI at a +fixed address then set configAPPLICATION_PROVIDES_cOutputBuffer to 1 in +FreeRTOSConfig.h, then declare an array with the following name and size in +one of the application files: + char cOutputBuffer[ configCOMMAND_INT_MAX_OUTPUT_SIZE ]; +*/ +#ifndef configAPPLICATION_PROVIDES_cOutputBuffer +#define configAPPLICATION_PROVIDES_cOutputBuffer 0 +#endif + +PRIVILEGED_DATA portMUX_TYPE xCLIMux = portMUX_INITIALIZER_UNLOCKED; + +typedef struct xCOMMAND_INPUT_LIST { + const CLI_Command_Definition_t *pxCommandLineDefinition; + struct xCOMMAND_INPUT_LIST *pxNext; +} CLI_Definition_List_Item_t; + +/* + * The callback function that is executed when "help" is entered. This is the + * only default command that is always present. + */ +static BaseType_t prvHelpCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +/* + * Return the number of parameters that follow the command name. + */ +static int8_t prvGetNumberOfParameters(const char *pcCommandString); + +/* The definition of the "help" command. This command is always at the front +of the list of registered commands. */ +static const CLI_Command_Definition_t xHelpCommand = { + "help", + "help:\r\n Lists all the registered commands\r\n\r\n", + prvHelpCommand, + 0 +}; + +/* The definition of the list of commands. Commands that are registered are +added to this list. */ +static CLI_Definition_List_Item_t xRegisteredCommands = { + &xHelpCommand, /* The first command in the list is always the help command, defined in this file. */ + NULL /* The next pointer is initialised to NULL, as there are no other registered commands yet. */ +}; + +/* A buffer into which command outputs can be written is declared here, rather +than in the command console implementation, to allow multiple command consoles +to share the same buffer. For example, an application may allow access to the +command interpreter by UART and by Ethernet. Sharing a buffer is done purely +to save RAM. Note, however, that the command console itself is not re-entrant, +so only one command interpreter interface can be used at any one time. For that +reason, no attempt at providing mutual exclusion to the cOutputBuffer array is +attempted. + +configAPPLICATION_PROVIDES_cOutputBuffer is provided to allow the application +writer to provide their own cOutputBuffer declaration in cases where the +buffer needs to be placed at a fixed address (rather than by the linker). */ +#if( configAPPLICATION_PROVIDES_cOutputBuffer == 0 ) +static char cOutputBuffer[ configCOMMAND_INT_MAX_OUTPUT_SIZE ]; +#else +extern char cOutputBuffer[ configCOMMAND_INT_MAX_OUTPUT_SIZE ]; +#endif + +/*-----------------------------------------------------------*/ + +void FreeRTOS_CLICreatMux(void) +{ + spinlock_initialize(&xCLIMux); +} +/*-----------------------------------------------------------*/ + +BaseType_t FreeRTOS_CLIRegisterCommand(const CLI_Command_Definition_t * const pxCommandToRegister) +{ + static CLI_Definition_List_Item_t *pxLastCommandInList = &xRegisteredCommands; + CLI_Definition_List_Item_t *pxNewListItem; + BaseType_t xReturn = pdFAIL; + + /* Check the parameter is not NULL. */ + configASSERT(pxCommandToRegister); + + /* Create a new list item that will reference the command being registered. */ + pxNewListItem = (CLI_Definition_List_Item_t *) pvPortMalloc(sizeof(CLI_Definition_List_Item_t)); + configASSERT(pxNewListItem); + + if (pxNewListItem != NULL) { + taskENTER_CRITICAL(&xCLIMux); + { + /* Reference the command being registered from the newly created + list item. */ + pxNewListItem->pxCommandLineDefinition = pxCommandToRegister; + + /* The new list item will get added to the end of the list, so + pxNext has nowhere to point. */ + pxNewListItem->pxNext = NULL; + + /* Add the newly created list item to the end of the already existing + list. */ + pxLastCommandInList->pxNext = pxNewListItem; + + /* Set the end of list marker to the new list item. */ + pxLastCommandInList = pxNewListItem; + } + taskEXIT_CRITICAL(&xCLIMux); + + xReturn = pdPASS; + } + + return xReturn; +} +/*-----------------------------------------------------------*/ + +BaseType_t FreeRTOS_CLIProcessCommand(const char * const pcCommandInput, char * pcWriteBuffer, size_t xWriteBufferLen) +{ + static const CLI_Definition_List_Item_t *pxCommand = NULL; + BaseType_t xReturn = pdTRUE; + const char *pcRegisteredCommandString; + size_t xCommandStringLength; + + /* Note: This function is not re-entrant. It must not be called from more + thank one task. */ + + if (pxCommand == NULL) { + /* Search for the command string in the list of registered commands. */ + for (pxCommand = &xRegisteredCommands; pxCommand != NULL; pxCommand = pxCommand->pxNext) { + pcRegisteredCommandString = pxCommand->pxCommandLineDefinition->pcCommand; + xCommandStringLength = strlen(pcRegisteredCommandString); + + /* To ensure the string lengths match exactly, so as not to pick up + a sub-string of a longer command, check the byte after the expected + end of the string is either the end of the string or a space before + a parameter. */ + if (strncmp(pcCommandInput, pcRegisteredCommandString, xCommandStringLength) == 0) { + if ((pcCommandInput[ xCommandStringLength ] == ' ') || (pcCommandInput[ xCommandStringLength ] == 0x00)) { + /* The command has been found. Check it has the expected + number of parameters. If cExpectedNumberOfParameters is -1, + then there could be a variable number of parameters and no + check is made. */ + if (pxCommand->pxCommandLineDefinition->cExpectedNumberOfParameters >= 0) { + if (prvGetNumberOfParameters(pcCommandInput) != pxCommand->pxCommandLineDefinition->cExpectedNumberOfParameters) { + xReturn = pdFALSE; + } + } + + break; + } + } + } + } + + if ((pxCommand != NULL) && (xReturn == pdFALSE)) { + /* The command was found, but the number of parameters with the command + was incorrect. */ + strncpy(pcWriteBuffer, "Incorrect command parameter(s). Enter \"help\" to view a list of available commands.\r\n", xWriteBufferLen); + pxCommand = NULL; + } else if (pxCommand != NULL) { + /* Call the callback function that is registered to this command. */ + xReturn = pxCommand->pxCommandLineDefinition->pxCommandInterpreter(pcWriteBuffer, xWriteBufferLen, pcCommandInput); + + /* If xReturn is pdFALSE, then no further strings will be returned + after this one, and pxCommand can be reset to NULL ready to search + for the next entered command. */ + if (xReturn == pdFALSE) { + pxCommand = NULL; + } + } else { + /* pxCommand was NULL, the command was not found. */ + strncpy(pcWriteBuffer, "Command not recognised. Enter 'help' to view a list of available commands.\r\n", xWriteBufferLen); + xReturn = pdFALSE; + } + + return xReturn; +} +/*-----------------------------------------------------------*/ + +char *FreeRTOS_CLIGetOutputBuffer(void) +{ + return cOutputBuffer; +} +/*-----------------------------------------------------------*/ + +const char *FreeRTOS_CLIGetParameter(const char *pcCommandString, UBaseType_t uxWantedParameter, BaseType_t *pxParameterStringLength) +{ + UBaseType_t uxParametersFound = 0; + const char *pcReturn = NULL; + + *pxParameterStringLength = 0; + + while (uxParametersFound < uxWantedParameter) { + /* Index the character pointer past the current word. If this is the start + of the command string then the first word is the command itself. */ + while (((*pcCommandString) != 0x00) && ((*pcCommandString) != ' ')) { + pcCommandString++; + } + + /* Find the start of the next string. */ + while (((*pcCommandString) != 0x00) && ((*pcCommandString) == ' ')) { + pcCommandString++; + } + + /* Was a string found? */ + if (*pcCommandString != 0x00) { + /* Is this the start of the required parameter? */ + uxParametersFound++; + + if (uxParametersFound == uxWantedParameter) { + /* How long is the parameter? */ + pcReturn = pcCommandString; + while (((*pcCommandString) != 0x00) && ((*pcCommandString) != ' ')) { + (*pxParameterStringLength)++; + pcCommandString++; + } + + if (*pxParameterStringLength == 0) { + pcReturn = NULL; + } + + break; + } + } else { + break; + } + } + + return pcReturn; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvHelpCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + static const CLI_Definition_List_Item_t * pxCommand = NULL; + BaseType_t xReturn; + + (void) pcCommandString; + + if (pxCommand == NULL) { + /* Reset the pxCommand pointer back to the start of the list. */ + pxCommand = &xRegisteredCommands; + } + + /* Return the next command help string, before moving the pointer on to + the next command in the list. */ + strncpy(pcWriteBuffer, pxCommand->pxCommandLineDefinition->pcHelpString, xWriteBufferLen); + pxCommand = pxCommand->pxNext; + + if (pxCommand == NULL) { + /* There are no more commands in the list, so there will be no more + strings to return after this one and pdFALSE should be returned. */ + xReturn = pdFALSE; + } else { + xReturn = pdTRUE; + } + + return xReturn; +} +/*-----------------------------------------------------------*/ + +static int8_t prvGetNumberOfParameters(const char *pcCommandString) +{ + int8_t cParameters = 0; + BaseType_t xLastCharacterWasSpace = pdFALSE; + + /* Count the number of space delimited words in pcCommandString. */ + while (*pcCommandString != 0x00) { + if ((*pcCommandString) == ' ') { + if (xLastCharacterWasSpace != pdTRUE) { + cParameters++; + xLastCharacterWasSpace = pdTRUE; + } + } else { + xLastCharacterWasSpace = pdFALSE; + } + + pcCommandString++; + } + + /* If the command string ended with spaces, then there will have been too + many parameters counted. */ + if (xLastCharacterWasSpace == pdTRUE) { + cParameters--; + } + + /* The value returned is one less than the number of space delimited words, + as the first word should be the command itself. */ + return cParameters; +} diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/History.txt b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/History.txt new file mode 100644 index 000000000..f3d2626ca --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/History.txt @@ -0,0 +1,31 @@ +Changes between V1.0.3 and V1.0.4 released + + + Update to use stdint and the FreeRTOS specific typedefs that were + introduced in FreeRTOS V8.0.0. + +Changes between V1.0.2 and V1.0.3 released + + + Previously, and in line with good software engineering practice, the + FreeRTOS coding standard did not permit the use of char types that were + not explicitly qualified as either signed or unsigned. As a result char + pointers used to reference strings required casts, as did the use of any + standard string handling functions. The casts ensured compiler warnings + were not generated by compilers that defaulted unqualified char types to + be signed or compilers that defaulted unqualified char types to be + unsigned. As it has in later MISRA standards, this rule has now been + relaxed, and unqualified char types are now permitted, but only when: + 1) The char is used to point to a human readable text string. + 2) The char is used to hold a single ASCII character. + +Changes between V1.0.1 and V1.0.2 released 14/10/2013 + + + Changed double quotes (") to single quotes (') in the help string to + allow the strings to be used with JSON in FreeRTOS+Nabto. + +Changes between V1.0.0 and V1.0.1 released 05/07/2012 + + + Change the name of the structure used to map a function that implements + a CLI command to the string used to call the command from + xCommandLineInput to CLI_Command_Definition_t, as it was always intended + to be. A #define was added to map the old name to the new name for + reasons of backward compatibility. diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/LICENSE_INFORMATION.txt b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/LICENSE_INFORMATION.txt new file mode 100644 index 000000000..a8b0a6d36 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/LICENSE_INFORMATION.txt @@ -0,0 +1,19 @@ +FreeRTOS+CLI is released under the following MIT license. + +Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/ReadMe.url b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/ReadMe.url new file mode 100644 index 000000000..afc826fd2 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/ReadMe.url @@ -0,0 +1,5 @@ +[InternetShortcut] +URL=http://www.freertos.org/cli +IDList= +[{000214A0-0000-0000-C000-000000000046}] +Prop3=19,2 diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/component.mk b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/component.mk new file mode 100644 index 000000000..1b25f971a --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/component.mk @@ -0,0 +1,7 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_ADD_INCLUDEDIRS := ./include +COMPONENT_SRCDIRS := . diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/include/FreeRTOS_CLI.h b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/include/FreeRTOS_CLI.h new file mode 100644 index 000000000..40049c409 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/include/FreeRTOS_CLI.h @@ -0,0 +1,101 @@ +/* + * FreeRTOS+CLI V1.0.4 + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +#ifndef COMMAND_INTERPRETER_H +#define COMMAND_INTERPRETER_H + +#include "freertos/FreeRTOS.h" + +/* The prototype to which callback functions used to process command line +commands must comply. pcWriteBuffer is a buffer into which the output from +executing the command can be written, xWriteBufferLen is the length, in bytes of +the pcWriteBuffer buffer, and pcCommandString is the entire string as input by +the user (from which parameters can be extracted).*/ +typedef BaseType_t (*pdCOMMAND_LINE_CALLBACK)(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +/* The structure that defines command line commands. A command line command +should be defined by declaring a const structure of this type. */ +typedef struct xCOMMAND_LINE_INPUT { + const char * const pcCommand; /* The command that causes pxCommandInterpreter to be executed. For example "help". Must be all lower case. */ + const char * const pcHelpString; /* String that describes how to use the command. Should start with the command itself, and end with "\r\n". For example "help: Returns a list of all the commands\r\n". */ + const pdCOMMAND_LINE_CALLBACK pxCommandInterpreter; /* A pointer to the callback function that will return the output generated by the command. */ + int8_t cExpectedNumberOfParameters; /* Commands expect a fixed number of parameters, which may be zero. */ +} CLI_Command_Definition_t; + +/* For backward compatibility. */ +#define xCommandLineInput CLI_Command_Definition_t +#define configCOMMAND_INT_MAX_OUTPUT_SIZE 500 + +/* + * Create a Mux. + * + * Note: Must be called before FreeRTOS_CLIProcessCommand. + */ +void FreeRTOS_CLICreatMux(void); + +/* + * Register the command passed in using the pxCommandToRegister parameter. + * Registering a command adds the command to the list of commands that are + * handled by the command interpreter. Once a command has been registered it + * can be executed from the command line. + */ +BaseType_t FreeRTOS_CLIRegisterCommand(const CLI_Command_Definition_t * const pxCommandToRegister); + +/* + * Runs the command interpreter for the command string "pcCommandInput". Any + * output generated by running the command will be placed into pcWriteBuffer. + * xWriteBufferLen must indicate the size, in bytes, of the buffer pointed to + * by pcWriteBuffer. + * + * FreeRTOS_CLIProcessCommand should be called repeatedly until it returns pdFALSE. + * + * pcCmdIntProcessCommand is not reentrant. It must not be called from more + * than one task - or at least - by more than one task at a time. + */ +BaseType_t FreeRTOS_CLIProcessCommand(const char * const pcCommandInput, char * pcWriteBuffer, size_t xWriteBufferLen); + +/*-----------------------------------------------------------*/ + +/* + * A buffer into which command outputs can be written is declared in the + * main command interpreter, rather than in the command console implementation, + * to allow application that provide access to the command console via multiple + * interfaces to share a buffer, and therefore save RAM. Note, however, that + * the command interpreter itself is not re-entrant, so only one command + * console interface can be used at any one time. For that reason, no attempt + * is made to provide any mutual exclusion mechanism on the output buffer. + * + * FreeRTOS_CLIGetOutputBuffer() returns the address of the output buffer. + */ +char *FreeRTOS_CLIGetOutputBuffer(void); + +/* + * Return a pointer to the xParameterNumber'th word in pcCommandString. + */ +const char *FreeRTOS_CLIGetParameter(const char *pcCommandString, UBaseType_t uxWantedParameter, BaseType_t *pxParameterStringLength); + +#endif /* COMMAND_INTERPRETER_H */ diff --git a/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/readme.txt b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/readme.txt new file mode 100644 index 000000000..5f3ed31b2 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/readme.txt @@ -0,0 +1,2 @@ +Contains source and header files that implement FreeRTOS+CLI. See +http://www.FreeRTOS.org/cli for documentation and license information. diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/CMakeLists.txt b/examples/usb/device/usb_dongle/components/tinyusb_dongle/CMakeLists.txt new file mode 100644 index 000000000..3f7affb97 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/CMakeLists.txt @@ -0,0 +1,65 @@ +set(srcs + "descriptors_control.c" + "tinyusb.c" + "usb_descriptors.c" + ) + +if(NOT CONFIG_TINYUSB_NO_DEFAULT_TASK) + list(APPEND srcs "tusb_tasks.c") +endif() # CONFIG_TINYUSB_NO_DEFAULT_TASK + +if(CONFIG_TINYUSB_CDC_ENABLED) + list(APPEND srcs + "cdc.c" + "tusb_cdc_acm.c" + ) + if(CONFIG_VFS_SUPPORT_IO) + list(APPEND srcs + "tusb_console.c" + "vfs_tinyusb.c" + ) + endif() # CONFIG_VFS_SUPPORT_IO +endif() # CONFIG_TINYUSB_CDC_ENABLED + +if(CONFIG_TINYUSB_BTH_ENABLED) + list(APPEND srcs + tusb_bth.c + ) +endif() # CONFIG_TINYUSB_BTH_ENABLED + +if(CONFIG_TINYUSB_DFU_ENABLED) + list (APPEND srcs + tusb_dfu.c + tusb_dfu_ota.c + ) +endif() # CONFIG_TINYUSB_DFU_ENABLED + +if(CONFIG_TINYUSB_NET_MODE_NCM + OR CONFIG_TINYUSB_NET_MODE_RNDIS + OR CONFIG_TINYUSB_NET_MODE_ECM) + list(APPEND srcs + tinyusb_net.c + ) +endif() # CONFIG_TINYUSB_NET_MODE_NCM || CONFIG_TINYUSB_NET_MODE_ECM || CONFIG_TINYUSB_NET_MODE_RNDIS + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "include_private" + PRIV_REQUIRES usb + REQUIRES fatfs vfs bt nvs_flash app_update + ) + +# Determine whether tinyusb is fetched from component registry or from local path +idf_build_get_property(build_components BUILD_COMPONENTS) +if(tinyusb IN_LIST build_components) + set(tinyusb_name tinyusb) # Local component +else() + set(tinyusb_name espressif__tinyusb) # Managed component +endif() + +# Pass tusb_config.h from this component to TinyUSB +idf_component_get_property(tusb_lib ${tinyusb_name} COMPONENT_LIB) +target_include_directories(${tusb_lib} PRIVATE "include") +if(CONFIG_TINYUSB_DFU_ENABLED) + target_sources(${tusb_lib} PUBLIC "tusb_dfu.c") +endif() # CONFIG_TINYUSB_DFU_ENABLED diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/Kconfig b/examples/usb/device/usb_dongle/components/tinyusb_dongle/Kconfig new file mode 100644 index 000000000..e356f377f --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/Kconfig @@ -0,0 +1,227 @@ +menu "TinyUSB Stack" + config TINYUSB_DEBUG_LEVEL + int "TinyUSB log level (0-3)" + default 1 + range 0 3 + help + Specify verbosity of TinyUSB log output. + + choice TINYUSB_RHPORT + depends on IDF_TARGET_ESP32P4 + prompt "TinyUSB PHY" + default TINYUSB_RHPORT_HS + help + Allows set the USB PHY Controller for TinyUSB: HS (USB OTG2.0 PHY for HighSpeed) + + config TINYUSB_RHPORT_HS + bool "HS" + endchoice + + menu "TinyUSB task configuration" + config TINYUSB_NO_DEFAULT_TASK + bool "Do not create a TinyUSB task" + default n + help + This option allows to not create the FreeRTOS task during the driver initialization. + User will have to handle TinyUSB events manually. + + config TINYUSB_TASK_PRIORITY + int "TinyUSB task priority" + default 5 + depends on !TINYUSB_NO_DEFAULT_TASK + help + Set the priority of the default TinyUSB main task. + + config TINYUSB_TASK_STACK_SIZE + int "TinyUSB task stack size (bytes)" + default 4096 + depends on !TINYUSB_NO_DEFAULT_TASK + help + Set the stack size of the default TinyUSB main task. + + choice TINYUSB_TASK_AFFINITY + prompt "TinyUSB task affinity" + default TINYUSB_TASK_AFFINITY_NO_AFFINITY + depends on !TINYUSB_NO_DEFAULT_TASK + help + Allows setting TinyUSB tasks affinity, i.e. whether the task is pinned to + CPU0, pinned to CPU1, or allowed to run on any CPU. + + config TINYUSB_TASK_AFFINITY_NO_AFFINITY + bool "No affinity" + config TINYUSB_TASK_AFFINITY_CPU0 + bool "CPU0" + config TINYUSB_TASK_AFFINITY_CPU1 + bool "CPU1" + depends on !FREERTOS_UNICORE + endchoice + + config TINYUSB_TASK_AFFINITY + hex + default FREERTOS_NO_AFFINITY if TINYUSB_TASK_AFFINITY_NO_AFFINITY + default 0x0 if TINYUSB_TASK_AFFINITY_CPU0 + default 0x1 if TINYUSB_TASK_AFFINITY_CPU1 + + config TINYUSB_INIT_IN_DEFAULT_TASK + bool "Initialize TinyUSB stack within the default TinyUSB task" + default n + depends on !TINYUSB_NO_DEFAULT_TASK + help + Run TinyUSB stack initialization just after starting the default TinyUSB task. + This is especially useful in multicore scenarios, when we need to pin the task + to a specific core and, at the same time initialize TinyUSB stack + (i.e. install interrupts) on the same core. + endmenu + + menu "Descriptor configuration" + comment "You can provide your custom descriptors via tinyusb_driver_install()" + config TINYUSB_DESC_USE_ESPRESSIF_VID + bool "VID: Use Espressif's vendor ID" + default y + help + Enable this option, USB device will use Espressif's vendor ID as its VID. + This is helpful at product develop stage. + + config TINYUSB_DESC_CUSTOM_VID + hex "VID: Custom vendor ID" + default 0xcafe + depends on !TINYUSB_DESC_USE_ESPRESSIF_VID + help + Custom Vendor ID. + + config TINYUSB_DESC_USE_DEFAULT_PID + bool "PID: Use a default PID assigned to TinyUSB" + default y + help + Default TinyUSB PID assigning uses values 0x4000...0x4007. + + config TINYUSB_DESC_CUSTOM_PID + hex "PID: Custom product ID" + default 0x5678 + depends on !TINYUSB_DESC_USE_DEFAULT_PID + help + Custom Product ID. + + config TINYUSB_DESC_BCD_DEVICE + hex "bcdDevice" + default 0x0100 + help + Version of the firmware of the USB device. + + config TINYUSB_DESC_MANUFACTURER_STRING + string "Manufacturer name" + default "Espressif Systems" + help + Name of the manufacturer of the USB device. + + config TINYUSB_DESC_PRODUCT_STRING + string "Product name" + default "Espressif Device" + help + Name of the USB device. + + config TINYUSB_DESC_SERIAL_STRING + string "Serial string" + default "123456" + help + Serial number of the USB device. + + config TINYUSB_DESC_CDC_STRING + depends on TINYUSB_CDC_ENABLED + string "CDC Device String" + default "Espressif CDC Device" + help + Name of the CDC device. + + config TINYUSB_DESC_BTH_STRING + depends on TINYUSB_BTH_ENABLED + string "BTH String" + default "Espressif BTH Device" + help + Name of the BTH device. + + endmenu # "Descriptor configuration" + + menu "Communication Device Class (CDC)" + config TINYUSB_CDC_ENABLED + bool "Enable TinyUSB CDC feature" + default n + help + Enable TinyUSB CDC feature. + + config TINYUSB_CDC_COUNT + int "CDC Channel Count" + default 1 + range 1 2 + depends on TINYUSB_CDC_ENABLED + help + Number of independent serial ports. + + config TINYUSB_CDC_RX_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC FIFO size of RX channel" + default 512 + range 64 10000 + help + CDC FIFO size of RX channel. + + config TINYUSB_CDC_TX_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC FIFO size of TX channel" + default 512 + help + CDC FIFO size of TX channel. + endmenu # "Communication Device Class" + + menu "Device Firmware Upgrade (DFU)" + config TINYUSB_DFU_ENABLED + bool "Enable TinyUSB DFU feature" + default n + help + Enable TinyUSB DFU feature. + + config TINYUSB_DFU_BUFSIZE + depends on TINYUSB_DFU_ENABLED + int "DFU XFER BUFFSIZE" + default 512 + help + DFU XFER BUFFSIZE. + endmenu # Device Firmware Upgrade (DFU) + + menu "Bluetooth Host Class (BTH)" + config TINYUSB_BTH_ENABLED + bool "Enable TinyUSB BTH feature" + default n + help + Enable TinyUSB BTH feature. + + config TINYUSB_BTH_ISO_ALT_COUNT + depends on TINYUSB_BTH_ENABLED + int "BTH ISO ALT COUNT" + default 0 + help + BTH ISO ALT COUNT. + endmenu # "Bluetooth Host Device Class" + + menu "Network driver (ECM/NCM/RNDIS)" + choice TINYUSB_NET_MODE + prompt "Network mode" + default TINYUSB_NET_MODE_NONE + help + Select network driver you want to use. + + config TINYUSB_NET_MODE_RNDIS + bool "RNDIS" + + config TINYUSB_NET_MODE_ECM + bool "ECM" + + config TINYUSB_NET_MODE_NCM + bool "NCM" + + config TINYUSB_NET_MODE_NONE + bool "None" + endchoice + endmenu # "Network driver (ECM/NCM/RNDIS)" + +endmenu # "TinyUSB Stack" diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/LICENSE b/examples/usb/device/usb_dongle/components/tinyusb_dongle/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/cdc.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/cdc.c new file mode 100644 index 000000000..0af2d5c1e --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/cdc.c @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "tusb.h" +#include "cdc.h" + +#define CDC_INTF_NUM CFG_TUD_CDC // number of cdc blocks +static esp_tusb_cdc_t *cdc_obj[CDC_INTF_NUM] = {}; +static const char *TAG = "tusb_cdc"; + +esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num) +{ + if (itf_num >= CDC_INTF_NUM || itf_num < 0) { + return NULL; + } + return cdc_obj[itf_num]; +} + +static esp_err_t cdc_obj_check(int itf, bool expected_inited, tusb_class_code_t expected_type) +{ + esp_tusb_cdc_t *this_itf = tinyusb_cdc_get_intf(itf); + + bool inited = (this_itf != NULL); + ESP_RETURN_ON_FALSE(expected_inited == inited, ESP_ERR_INVALID_STATE, TAG, "Wrong state of the interface. Expected state: %s", expected_inited ? "initialized" : "not initialized"); + ESP_RETURN_ON_FALSE(!(inited && (expected_type != -1) && !(this_itf->type == expected_type)), ESP_ERR_INVALID_STATE, TAG, "Wrong type of the interface. Should be : 0x%x (tusb_class_code_t)", expected_type); + return ESP_OK; +} + +static esp_err_t tusb_cdc_comm_init(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed"); + cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t)); + if (cdc_obj[itf] != NULL) { + cdc_obj[itf]->type = TUSB_CLASS_CDC; + ESP_LOGD(TAG, "CDC Comm class initialized"); + return ESP_OK; + } else { + ESP_LOGE(TAG, "CDC Comm initialization error"); + return ESP_FAIL; + } +} + +static esp_err_t tusb_cdc_deinit_comm(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC), TAG, "cdc_obj_check failed"); + free(cdc_obj[itf]); + cdc_obj[itf] = NULL; + return ESP_OK; +} + +static esp_err_t tusb_cdc_data_init(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed"); + cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t)); + if (cdc_obj[itf] != NULL) { + cdc_obj[itf]->type = TUSB_CLASS_CDC_DATA; + ESP_LOGD(TAG, "CDC Data class initialized"); + return ESP_OK; + } else { + ESP_LOGE(TAG, "CDC Data initialization error"); + return ESP_FAIL; + } +} + +static esp_err_t tusb_cdc_deinit_data(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed"); + free(cdc_obj[itf]); + cdc_obj[itf] = NULL; + return ESP_OK; +} + +esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed"); + + ESP_LOGD(TAG, "Init CDC %d", itf); + if (cfg->cdc_class == TUSB_CLASS_CDC) { + ESP_RETURN_ON_ERROR(tusb_cdc_comm_init(itf), TAG, "tusb_cdc_comm_init failed"); + cdc_obj[itf]->cdc_subclass.comm_subclass = cfg->cdc_subclass.comm_subclass; + } else { + ESP_RETURN_ON_ERROR(tusb_cdc_data_init(itf), TAG, "tusb_cdc_data_init failed"); + cdc_obj[itf]->cdc_subclass.data_subclass = cfg->cdc_subclass.data_subclass; + } + cdc_obj[itf]->usb_dev = cfg->usb_dev; + return ESP_OK; +} + +esp_err_t tinyusb_cdc_deinit(int itf) +{ + ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, -1), TAG, "cdc_obj_check failed"); + + ESP_LOGD(TAG, "Deinit CDC %d", itf); + if (cdc_obj[itf]->type == TUSB_CLASS_CDC) { + ESP_RETURN_ON_ERROR(tusb_cdc_deinit_comm(itf), TAG, "tusb_cdc_deinit_comm failed"); + } else if (cdc_obj[itf]->type == TUSB_CLASS_CDC_DATA) { + ESP_RETURN_ON_ERROR(tusb_cdc_deinit_data(itf), TAG, "tusb_cdc_deinit_data failed"); + } else { + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/descriptors_control.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/descriptors_control.c new file mode 100644 index 000000000..ea6e0efdc --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/descriptors_control.c @@ -0,0 +1,284 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_err.h" +#include "descriptors_control.h" +#include "usb_descriptors.h" + +#define MAX_DESC_BUF_SIZE 32 // Max length of string descriptor (can be extended, USB supports lengths up to 255 bytes) + +static const char *TAG = "tusb_desc"; + +// ============================================================================= +// STRUCTS +// ============================================================================= + +/** + * @brief Descriptor pointers for tinyusb descriptor requests callbacks + * + */ +typedef struct { + const tusb_desc_device_t *dev; /*!< Pointer to device descriptor */ + union { + const uint8_t *cfg; /*!< Pointer to FullSpeed configuration descriptor when device one-speed only */ + const uint8_t *fs_cfg; /*!< Pointer to FullSpeed configuration descriptor when device support HighSpeed */ + }; +#if (TUD_OPT_HIGH_SPEED) + const uint8_t *hs_cfg; /*!< Pointer to HighSpeed configuration descriptor */ + const tusb_desc_device_qualifier_t *qualifier; /*!< Pointer to Qualifier descriptor */ + uint8_t *other_speed; /*!< Pointer for other speed configuration descriptor */ +#endif // TUD_OPT_HIGH_SPEED + const char *str[USB_STRING_DESCRIPTOR_ARRAY_SIZE]; /*!< Pointer to array of UTF-8 strings */ + int str_count; /*!< Number of descriptors in str */ +} tinyusb_descriptor_config_t; + +static tinyusb_descriptor_config_t s_desc_cfg; + +// ============================================================================= +// CALLBACKS +// ============================================================================= + +/** + * @brief Invoked when received GET DEVICE DESCRIPTOR. + * Descriptor contents must exist long enough for transfer to complete + * + * @return Pointer to device descriptor + */ +uint8_t const *tud_descriptor_device_cb(void) +{ + assert(s_desc_cfg.dev); + return (uint8_t const *)s_desc_cfg.dev; +} + +/** + * @brief Invoked when received GET CONFIGURATION DESCRIPTOR. + * Descriptor contents must exist long enough for transfer to complete + * + * @param[in] index Index of required configuration + * @return Pointer to configuration descriptor + */ +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) +{ + (void)index; // Unused, this driver supports only 1 configuration + assert(s_desc_cfg.cfg); + +#if (TUD_OPT_HIGH_SPEED) + // HINT: cfg and fs_cfg are union, no need to assert(fs_cfg) + assert(s_desc_cfg.hs_cfg); + // Return configuration descriptor based on Host speed + return (TUSB_SPEED_HIGH == tud_speed_get()) + ? s_desc_cfg.hs_cfg + : s_desc_cfg.fs_cfg; +#else + return s_desc_cfg.cfg; +#endif // TUD_OPT_HIGH_SPEED +} + +#if (TUD_OPT_HIGH_SPEED) +/** + * @brief Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request + * Descriptor contents must exist long enough for transfer to complete + * If not highspeed capable stall this request + */ +uint8_t const *tud_descriptor_device_qualifier_cb(void) +{ + assert(s_desc_cfg.qualifier); + return (uint8_t const *)s_desc_cfg.qualifier; +} + +/** + * @brief Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request + * Descriptor contents must exist long enough for transfer to complete + * Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa + */ +uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + assert(s_desc_cfg.other_speed); + + const uint8_t *other_speed = (TUSB_SPEED_HIGH == tud_speed_get()) + ? s_desc_cfg.fs_cfg + : s_desc_cfg.hs_cfg; + + memcpy(s_desc_cfg.other_speed, + other_speed, + ((tusb_desc_configuration_t *)other_speed)->wTotalLength); + + ((tusb_desc_configuration_t *)s_desc_cfg.other_speed)->bDescriptorType = TUSB_DESC_OTHER_SPEED_CONFIG; + return s_desc_cfg.other_speed; +} +#endif // TUD_OPT_HIGH_SPEED + +/** + * @brief Invoked when received GET STRING DESCRIPTOR request + * + * @param[in] index Index of required descriptor + * @param[in] langid Language of the descriptor + * @return Pointer to UTF-16 string descriptor + */ +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; // Unused, this driver supports only one language in string descriptors + assert(s_desc_cfg.str); + uint8_t chr_count; + static uint16_t _desc_str[MAX_DESC_BUF_SIZE]; + + if (index == 0) { + memcpy(&_desc_str[1], s_desc_cfg.str[0], 2); + chr_count = 1; + } else { + if (index >= USB_STRING_DESCRIPTOR_ARRAY_SIZE) { + ESP_LOGW(TAG, "String index (%u) is out of bounds, check your string descriptor", index); + return NULL; + } + + if (s_desc_cfg.str[index] == NULL) { + ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index); + return NULL; + } + + const char *str = s_desc_cfg.str[index]; + chr_count = strnlen(str, MAX_DESC_BUF_SIZE - 1); // Buffer len - header + + // Convert ASCII string into UTF-16 + for (uint8_t i = 0; i < chr_count; i++) { + _desc_str[1 + i] = str[i]; + } + } + + // First byte is length in bytes (including header), second byte is descriptor type (TUSB_DESC_STRING) + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); + + return _desc_str; +} + +// ============================================================================= +// Driver functions +// ============================================================================= +esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config) +{ + esp_err_t ret = ESP_FAIL; + assert(config); + const char **pstr_desc; + // Flush descriptors control struct + memset(&s_desc_cfg, 0x00, sizeof(tinyusb_descriptor_config_t)); + // Parse configuration and save descriptors's pointer + // Select Device Descriptor + if (config->device_descriptor == NULL) { + ESP_LOGW(TAG, "No Device descriptor provided, using default."); + s_desc_cfg.dev = &descriptor_dev_default; + } else { + s_desc_cfg.dev = config->device_descriptor; + } + + // Select FullSpeed configuration descriptor + if (config->configuration_descriptor == NULL) { + ESP_LOGW(TAG, "No FullSpeed configuration descriptor provided, using default."); + s_desc_cfg.cfg = descriptor_fs_cfg_default; + } else { + s_desc_cfg.cfg = config->configuration_descriptor; + } + +#if (TUD_OPT_HIGH_SPEED) + // High Speed + if (config->hs_configuration_descriptor == NULL) { + ESP_LOGW(TAG, "No HighSpeed configuration descriptor provided, using default."); + s_desc_cfg.hs_cfg = descriptor_hs_cfg_default; + } else { + s_desc_cfg.hs_cfg = config->hs_configuration_descriptor; + } + + // HS and FS cfg desc should be equal length + ESP_GOTO_ON_FALSE(((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength == + ((tusb_desc_configuration_t *)s_desc_cfg.fs_cfg)->wTotalLength, + ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed and FullSpeed configuration descriptors must be same length"); + + // Qualifier Descriptor + if (config->qualifier_descriptor == NULL) { + ESP_GOTO_ON_FALSE((s_desc_cfg.dev == &descriptor_dev_default), ESP_ERR_INVALID_ARG, fail, TAG, "Qualifier descriptor must be present (Device Descriptor not default)."); + // Get default qualifier if device descriptor is default + ESP_LOGW(TAG, "No Qulifier descriptor provided, using default."); + s_desc_cfg.qualifier = &descriptor_qualifier_default; + } else { + s_desc_cfg.qualifier = config->qualifier_descriptor; + } + + // Other Speed buffer allocate + s_desc_cfg.other_speed = calloc(1, ((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength); + ESP_GOTO_ON_FALSE(s_desc_cfg.other_speed, ESP_ERR_NO_MEM, fail, TAG, "Other speed memory allocation error"); +#endif // TUD_OPT_HIGH_SPEED + + // Select String Descriptors and count them + if (config->string_descriptor == NULL) { + ESP_LOGW(TAG, "No String descriptors provided, using default."); + pstr_desc = descriptor_str_default; + while (descriptor_str_default[++s_desc_cfg.str_count] != NULL); + } else { + pstr_desc = config->string_descriptor; + s_desc_cfg.str_count = (config->string_descriptor_count != 0) + ? config->string_descriptor_count + : 8; // '8' is for backward compatibility with esp_tinyusb v1.0.0. Do NOT remove! + } + + ESP_GOTO_ON_FALSE(s_desc_cfg.str_count <= USB_STRING_DESCRIPTOR_ARRAY_SIZE, ESP_ERR_NOT_SUPPORTED, fail, TAG, "String descriptors exceed limit"); + memcpy(s_desc_cfg.str, pstr_desc, s_desc_cfg.str_count * sizeof(pstr_desc[0])); + + ESP_LOGI(TAG, "\n" + "┌─────────────────────────────────┐\n" + "│ USB Device Descriptor Summary │\n" + "├───────────────────┬─────────────┤\n" + "│bDeviceClass │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│bDeviceSubClass │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│bDeviceProtocol │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│bMaxPacketSize0 │ %-4u │\n" + "├───────────────────┼─────────────┤\n" + "│idVendor │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│idProduct │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│bcdDevice │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│iManufacturer │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│iProduct │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│iSerialNumber │ %-#10x │\n" + "├───────────────────┼─────────────┤\n" + "│bNumConfigurations │ %-#10x │\n" + "└───────────────────┴─────────────┘", + s_desc_cfg.dev->bDeviceClass, s_desc_cfg.dev->bDeviceSubClass, + s_desc_cfg.dev->bDeviceProtocol, s_desc_cfg.dev->bMaxPacketSize0, + s_desc_cfg.dev->idVendor, s_desc_cfg.dev->idProduct, s_desc_cfg.dev->bcdDevice, + s_desc_cfg.dev->iManufacturer, s_desc_cfg.dev->iProduct, s_desc_cfg.dev->iSerialNumber, + s_desc_cfg.dev->bNumConfigurations); + + return ESP_OK; + +fail: +#if (TUD_OPT_HIGH_SPEED) + free(s_desc_cfg.other_speed); +#endif // TUD_OPT_HIGH_SPEED + return ret; +} + +void tinyusb_set_str_descriptor(const char *str, int str_idx) +{ + assert(str_idx < USB_STRING_DESCRIPTOR_ARRAY_SIZE); + s_desc_cfg.str[str_idx] = str; +} + +void tinyusb_free_descriptors(void) +{ +#if (TUD_OPT_HIGH_SPEED) + assert(s_desc_cfg.other_speed); + free(s_desc_cfg.other_speed); +#endif // TUD_OPT_HIGH_SPEED +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/idf_component.yml b/examples/usb/device/usb_dongle/components/tinyusb_dongle/idf_component.yml new file mode 100644 index 000000000..93c3964b8 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/idf_component.yml @@ -0,0 +1,10 @@ +version: 0.1.0 +targets: + - esp32s2 + - esp32s3 +dependencies: + idf: + version: '>=5.0' + tinyusb: + public: true + version: '>=0.15.0' diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb.h new file mode 100644 index 000000000..484c86e6a --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb.h @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "tusb.h" +#include "tinyusb_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Configuration structure of the TinyUSB core + * + * USB specification mandates self-powered devices to monitor USB VBUS to detect connection/disconnection events. + * If you want to use this feature, connected VBUS to any free GPIO through a voltage divider or voltage comparator. + * The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port). + * The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge). + */ +typedef struct { + union { + const tusb_desc_device_t *device_descriptor; /*!< Pointer to a device descriptor. If set to NULL, the TinyUSB device will use a default device descriptor whose values are set in Kconfig */ + const tusb_desc_device_t *descriptor __attribute__((deprecated)); /*!< Alias to `device_descriptor` for backward compatibility */ + }; + const char **string_descriptor; /*!< Pointer to array of string descriptors. If set to NULL, TinyUSB device will use a default string descriptors whose values are set in Kconfig */ + int string_descriptor_count; /*!< Number of descriptors in above array */ + bool external_phy; /*!< Should USB use an external PHY */ + union { + struct { + const uint8_t *configuration_descriptor; /*!< Pointer to a configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */ + }; +#if (TUD_OPT_HIGH_SPEED) + struct { + const uint8_t *fs_configuration_descriptor; /*!< Pointer to a FullSpeed configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */ + }; + }; + const uint8_t *hs_configuration_descriptor; /*!< Pointer to a HighSpeed configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */ + const tusb_desc_device_qualifier_t *qualifier_descriptor; /*!< Pointer to a qualifier descriptor */ +#else + }; +#endif // TUD_OPT_HIGH_SPEED + bool self_powered; /*!< This is a self-powered USB device. USB VBUS must be monitored. */ + int vbus_monitor_io; /*!< GPIO for VBUS monitoring. Ignored if not self_powered. */ +} tinyusb_config_t; + +/** + * @brief This is an all-in-one helper function, including: + * 1. USB device driver initialization + * 2. Descriptors preparation + * 3. TinyUSB stack initialization + * 4. Creates and start a task to handle usb events + * + * @note Don't change Custom descriptor, but if it has to be done, + * Suggest to define as follows in order to match the Interface Association Descriptor (IAD): + * bDeviceClass = TUSB_CLASS_MISC, + * bDeviceSubClass = MISC_SUBCLASS_COMMON, + * + * @param config tinyusb stack specific configuration + * @retval ESP_ERR_INVALID_ARG Install driver and tinyusb stack failed because of invalid argument + * @retval ESP_FAIL Install driver and tinyusb stack failed because of internal error + * @retval ESP_OK Install driver and tinyusb stack successfully + */ +esp_err_t tinyusb_driver_install(const tinyusb_config_t *config); + +esp_err_t tinyusb_driver_uninstall(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_dfu_ota.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_dfu_ota.h new file mode 100644 index 000000000..199d0ca78 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_dfu_ota.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +/** + * @brief Starts the OTA (Over-The-Air) update process. + * + * This function is used to start the OTA update process by providing the OTA write data and the amount of data read. + * + * @param ota_write_data Pointer to the OTA write data. + * @param data_read The amount of data read. + * @return `esp_err_t` indicating the status of the OTA start process. + */ +esp_err_t ota_start(uint8_t const *ota_write_data, uint32_t data_read); + +/** + * @brief Completes the OTA (Over-The-Air) update process. + * + * This function is used to complete the OTA update process. + * + * @return `esp_err_t` indicating the status of the OTA completion process. + */ +esp_err_t ota_complete(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_net.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_net.h new file mode 100644 index 000000000..6c57fdf97 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_net.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "tinyusb_types.h" +#include "esp_err.h" +#include "sdkconfig.h" + +#if (CONFIG_TINYUSB_NET_MODE_NONE != 1) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief On receive callback type + */ +typedef esp_err_t (*tusb_net_rx_cb_t)(void *buffer, uint16_t len, void *ctx); + +/** + * @brief Free Tx buffer callback type + */ +typedef void (*tusb_net_free_tx_cb_t)(void *buffer, void *ctx); + +/** + * @brief On init callback type + */ +typedef void (*tusb_net_init_cb_t)(void *ctx); + +/** + * @brief ESP TinyUSB NCM driver configuration structure + */ +typedef struct { + uint8_t mac_addr[6]; /*!< MAC address. Must be 6 bytes long. */ + tusb_net_rx_cb_t on_recv_callback; /*!< TinyUSB receive data callbeck */ + tusb_net_free_tx_cb_t free_tx_buffer; /*!< User function for freeing the Tx buffer. + * - could be NULL, if user app is responsible for freeing the buffer + * - must be used in asynchronous send mode + * - is only called if the used tinyusb_net_send...() function returns ESP_OK + * - in sync mode means that the packet was accepted by TinyUSB + * - in async mode means that the packet was queued to be processed in TinyUSB task + */ + tusb_net_init_cb_t on_init_callback; /*!< TinyUSB init network callback */ + void *user_context; /*!< User context to be passed to any of the callback */ +} tinyusb_net_config_t; + +/** + * @brief Initialize TinyUSB NET driver + * + * @param[in] usb_dev USB device to use + * @param[in] cfg Configuration of the driver + * @return esp_err_t + */ +esp_err_t tinyusb_net_init(tinyusb_usbdev_t usb_dev, const tinyusb_net_config_t *cfg); + +/** + * @brief TinyUSB NET driver send data synchronously + * + * @note It is possible to use sync and async send interchangeably. + * This function needs some synchronization primitives, so using sync mode (even once) uses more heap + * + * @param[in] buffer USB send data + * @param[in] len Send data len + * @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback + * @param[in] timeout Send data len + * @return ESP_OK on success == packet has been consumed by tusb and would be eventually freed + * by free_tx_buffer() callback (if non null) + * ESP_ERR_TIMEOUT on timeout + * ESP_ERR_INVALID_STATE if tusb not initialized, ESP_ERR_NO_MEM on alloc failure + */ +esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout); + +/** + * @brief TinyUSB NET driver send data asynchronously + * + * @note If using asynchronous sends, you must free the buffer using free_tx_buffer() callback. + * @note It is possible to use sync and async send interchangeably. + * @note Async flavor of the send is useful when the USB stack runs faster than the caller, + * since we have no control over the transmitted packets, if they get accepted or discarded. + * + * @param[in] buffer USB send data + * @param[in] len Send data len + * @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback + * @return ESP_OK on success == packet has been consumed by tusb and will be freed + * by free_tx_buffer() callback (if non null) + * ESP_ERR_INVALID_STATE if tusb not initialized + */ +esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg); + +#endif // (CONFIG_TINYUSB_NET_MODE_NONE != 1) + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_types.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_types.h new file mode 100644 index 000000000..9263d108a --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tinyusb_types.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define USB_ESPRESSIF_VID 0x303A + +typedef enum { + TINYUSB_USBDEV_0, +} tinyusb_usbdev_t; + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_bth.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_bth.h new file mode 100644 index 000000000..39dc7d278 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_bth.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" +#include "tinyusb.h" + +static inline void hci_dump_buffer(const char *prefix, uint8_t *data, uint16_t len) +{ + uint16_t i; + + if (!data || !len) { + return; + } + + if (prefix) { + printf("%s: len %d\r\n", prefix, len); + } + + for (i = 0; i < len; i++) { + if (((i % 8) == 0) && (i != 0)) { + printf("\r\n"); + } + printf("%02x ", *(data + i)); + } + + printf("\r\n"); +} + +#ifdef HCI_DUMP_BUFFER +#define HCI_DUMP_BUFFER(_prefix, _data, _len) hci_dump_buffer(_prefix, _data, _len) +#else +#define HCI_DUMP_BUFFER(_prefix, _data, _len) +#endif + +/* HCI message type definitions (for H4 messages) */ +#define HCIT_TYPE_COMMAND 1 +#define HCIT_TYPE_ACL_DATA 2 +#define HCIT_TYPE_SCO_DATA 3 +#define HCIT_TYPE_EVENT 4 + +typedef struct acl_data { + bool is_new_pkt; + uint16_t pkt_total_len; + uint16_t pkt_cur_offset; + uint8_t * pkt_val; +} acl_data_t; + +/** + * @brief Initialize BTH Device. + */ +void tusb_bth_init(void); + +/** + * @brief Deinitialization BTH Device. + */ +void tusb_bth_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_cdc_acm.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_cdc_acm.h new file mode 100644 index 000000000..ac28db8f3 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_cdc_acm.h @@ -0,0 +1,205 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "sdkconfig.h" +#include "esp_err.h" + +#include "tinyusb_types.h" +#include "class/cdc/cdc.h" + +#if (CONFIG_TINYUSB_CDC_ENABLED != 1) +#error "TinyUSB CDC driver must be enabled in menuconfig" +#endif + +/** + * @brief CDC ports available to setup + */ +typedef enum { + TINYUSB_CDC_ACM_0 = 0x0, + TINYUSB_CDC_ACM_1, + TINYUSB_CDC_ACM_MAX +} tinyusb_cdcacm_itf_t; + +/* Callbacks and events + ********************************************************************* */ + +/** + * @brief Data provided to the input of the `callback_rx_wanted_char` callback + */ +typedef struct { + char wanted_char; /*!< Wanted character */ +} cdcacm_event_rx_wanted_char_data_t; + +/** + * @brief Data provided to the input of the `callback_line_state_changed` callback + */ +typedef struct { + bool dtr; /*!< Data Terminal Ready (DTR) line state */ + bool rts; /*!< Request To Send (RTS) line state */ +} cdcacm_event_line_state_changed_data_t; + +/** + * @brief Data provided to the input of the `line_coding_changed` callback + */ +typedef struct { + cdc_line_coding_t const *p_line_coding; /*!< New line coding value */ +} cdcacm_event_line_coding_changed_data_t; + +/** + * @brief Types of CDC ACM events + */ +typedef enum { + CDC_EVENT_RX, + CDC_EVENT_RX_WANTED_CHAR, + CDC_EVENT_LINE_STATE_CHANGED, + CDC_EVENT_LINE_CODING_CHANGED +} cdcacm_event_type_t; + +/** + * @brief Describes an event passing to the input of a callbacks + */ +typedef struct { + cdcacm_event_type_t type; /*!< Event type */ + union { + cdcacm_event_rx_wanted_char_data_t rx_wanted_char_data; /*!< Data input of the `callback_rx_wanted_char` callback */ + cdcacm_event_line_state_changed_data_t line_state_changed_data; /*!< Data input of the `callback_line_state_changed` callback */ + cdcacm_event_line_coding_changed_data_t line_coding_changed_data; /*!< Data input of the `line_coding_changed` callback */ + }; +} cdcacm_event_t; + +/** + * @brief CDC-ACM callback type + */ +typedef void(*tusb_cdcacm_callback_t)(int itf, cdcacm_event_t *event); + +/*********************************************************************** Callbacks and events*/ +/* Other structs + ********************************************************************* */ + +/** + * @brief Configuration structure for CDC-ACM + */ +typedef struct { + tinyusb_usbdev_t usb_dev; /*!< Usb device to set up */ + tinyusb_cdcacm_itf_t cdc_port; /*!< CDC port */ + size_t rx_unread_buf_sz __attribute__((deprecated("This parameter is not used any more. Configure RX buffer in menuconfig."))); + tusb_cdcacm_callback_t callback_rx; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ + tusb_cdcacm_callback_t callback_rx_wanted_char; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ + tusb_cdcacm_callback_t callback_line_state_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ + tusb_cdcacm_callback_t callback_line_coding_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */ +} tinyusb_config_cdcacm_t; + +/*********************************************************************** Other structs*/ +/* Public functions + ********************************************************************* */ +/** + * @brief Initialize CDC ACM. Initialization will be finished with + * the `tud_cdc_line_state_cb` callback + * + * @param[in] cfg Configuration structure + * @return esp_err_t + */ +esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg); + +/** + * @brief De-initialize CDC ACM. + * + * @param[in] itf Index of CDC interface + * @return esp_err_t + */ +esp_err_t tusb_cdc_acm_deinit(int itf); + +/** + * @brief Register a callback invoking on CDC event. If the callback had been + * already registered, it will be overwritten + * + * @param[in] itf Index of CDC interface + * @param[in] event_type Type of registered event for a callback + * @param[in] callback Callback function + * @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG + */ +esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf, + cdcacm_event_type_t event_type, + tusb_cdcacm_callback_t callback); + +/** + * @brief Unregister a callback invoking on CDC event + * + * @param[in] itf Index of CDC interface + * @param[in] event_type Type of registered event for a callback + * @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG + */ +esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type); + +/** + * @brief Sent one character to a write buffer + * + * @param[in] itf Index of CDC interface + * @param[in] ch Character to send + * @return size_t - amount of queued bytes + */ +size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch); + +/** + * @brief Write data to write buffer + * + * @param[in] itf Index of CDC interface + * @param[in] in_buf Data + * @param[in] in_size Data size in bytes + * @return size_t - amount of queued bytes + */ +size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size); + +/** + * @brief Flush data in write buffer of CDC interface + * + * Use `tinyusb_cdcacm_write_queue` to add data to the buffer + * + * WARNING! TinyUSB can block output Endpoint for several RX callbacks, after will do additional flush + * after the each transfer. That can leads to the situation when you requested a flush, but it will fail until + * one of the next callbacks ends. + * SO USING OF THE FLUSH WITH TIMEOUTS IN CALLBACKS IS NOT RECOMMENDED - YOU CAN GET A LOCK FOR THE TIMEOUT + * + * @param[in] itf Index of CDC interface + * @param[in] timeout_ticks Transfer timeout. Set to zero for non-blocking mode + * @return - ESP_OK All data flushed + * - ESP_ERR_TIMEOUT Time out occurred in blocking mode + * - ESP_NOT_FINISHED The transfer is still in progress in non-blocking mode + */ +esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks); + +/** + * @brief Receive data from CDC interface + * + * @param[in] itf Index of CDC interface + * @param[out] out_buf Data buffer + * @param[in] out_buf_sz Data buffer size in bytes + * @param[out] rx_data_size Number of bytes written to out_buf + * @return esp_err_t ESP_OK, ESP_FAIL or ESP_ERR_INVALID_STATE + */ +esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size); + +/** + * @brief Check if the CDC interface is initialized + * + * @param[in] itf Index of CDC interface + * @return - true Initialized + * - false Not Initialized + */ +bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf); + +/*********************************************************************** Public functions*/ + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_config.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_config.h new file mode 100644 index 000000000..7a0f11326 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_config.h @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org), + * SPDX-FileContributor: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2019 Ha Thach (tinyusb.org), + * Additions Copyright (c) 2020, Espressif Systems (Shanghai) PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#pragma once + +#include "tusb_option.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_TINYUSB_CDC_ENABLED +# define CONFIG_TINYUSB_CDC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_CDC_COUNT +# define CONFIG_TINYUSB_CDC_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_NET_MODE_ECM +# define CONFIG_TINYUSB_NET_MODE_ECM 0 +#endif + +#ifndef CONFIG_TINYUSB_NET_MODE_RNDIS +# define CONFIG_TINYUSB_NET_MODE_RNDIS 0 +#endif + +#ifndef CONFIG_TINYUSB_NET_MODE_NCM +# define CONFIG_TINYUSB_NET_MODE_NCM 0 +#endif + +#ifndef CONFIG_TINYUSB_DFU_ENABLED +# define CONFIG_TINYUSB_DFU_ENABLED 0 +# define CONFIG_TINYUSB_DFU_BUFSIZE 512 +#endif + +#ifndef CONFIG_TINYUSB_BTH_ENABLED +# define CONFIG_TINYUSB_BTH_ENABLED 0 +# define CONFIG_TINYUSB_BTH_ISO_ALT_COUNT 0 +#endif + +#ifndef CONFIG_TINYUSB_DEBUG_LEVEL +# define CONFIG_TINYUSB_DEBUG_LEVEL 0 +#endif + +#ifdef CONFIG_TINYUSB_RHPORT_HS +# define CFG_TUSB_RHPORT1_MODE OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED +#else +# define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED +#endif + +#define CFG_TUSB_OS OPT_OS_FREERTOS + +// Espressif IDF requires "freertos/" prefix in include path +#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3) +#define CFG_TUSB_OS_INC_PATH freertos/ +#endif + +#ifndef ESP_PLATFORM +#define ESP_PLATFORM 1 +#endif + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +# define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +// Debug Level +#define CFG_TUSB_DEBUG CONFIG_TINYUSB_DEBUG_LEVEL + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE + +// Vendor FIFO size of TX and RX +// If not configured vendor endpoints will not be buffered +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 + +// DFU macros +#define CFG_TUD_DFU_XFER_BUFSIZE CONFIG_TINYUSB_DFU_BUFSIZE + +// Number of BTH ISO alternatives +#define CFG_TUD_BTH_ISO_ALT_COUNT CONFIG_TINYUSB_BTH_ISO_ALT_COUNT + +// Enabled device class driver +#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_COUNT +#define CFG_TUD_CUSTOM_CLASS CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED +#define CFG_TUD_ECM_RNDIS (CONFIG_TINYUSB_NET_MODE_ECM || CONFIG_TINYUSB_NET_MODE_RNDIS) +#define CFG_TUD_NCM CONFIG_TINYUSB_NET_MODE_NCM +#define CFG_TUD_DFU CONFIG_TINYUSB_DFU_ENABLED +#define CFG_TUD_BTH CONFIG_TINYUSB_BTH_ENABLED + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_console.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_console.h new file mode 100644 index 000000000..eaf8fc0af --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_console.h @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +/** + * @brief Redirect output to the USB serial + * @param cdc_intf - interface number of TinyUSB's CDC + * + * @return esp_err_t - ESP_OK, ESP_FAIL or an error code + */ +esp_err_t esp_tusb_init_console(int cdc_intf); + +/** + * @brief Switch log to the default output + * @param cdc_intf - interface number of TinyUSB's CDC + * + * @return esp_err_t + */ +esp_err_t esp_tusb_deinit_console(int cdc_intf); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_tasks.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_tasks.h new file mode 100644 index 000000000..1ad8c20f4 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/tusb_tasks.h @@ -0,0 +1,39 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief This helper function creates and starts a task which wraps `tud_task()`. + * + * The wrapper function basically wraps tud_task and some log. + * Default parameters: stack size and priority as configured, argument = NULL, not pinned to any core. + * If you have more requirements for this task, you can create your own task which calls tud_task as the last step. + * + * @retval ESP_OK run tinyusb main task successfully + * @retval ESP_FAIL run tinyusb main task failed of internal error or initialization within the task failed when TINYUSB_INIT_IN_DEFAULT_TASK=y + * @retval ESP_FAIL initialization within the task failed if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK is enabled + * @retval ESP_ERR_INVALID_STATE tinyusb main task has been created before + */ +esp_err_t tusb_run_task(void); + +/** + * @brief This helper function stops and destroys the task created by `tusb_run_task()` + * + * @retval ESP_OK stop and destroy tinyusb main task successfully + * @retval ESP_ERR_INVALID_STATE tinyusb main task hasn't been created yet + */ +esp_err_t tusb_stop_task(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/vfs_tinyusb.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/vfs_tinyusb.h new file mode 100644 index 000000000..640b633a1 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include/vfs_tinyusb.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include "esp_vfs_common.h" // For esp_line_endings_t definitions + +#ifdef __cplusplus +extern "C" { +#endif + +#define VFS_TUSB_MAX_PATH 16 +#define VFS_TUSB_PATH_DEFAULT "/dev/tusb_cdc" + +/** + * @brief Register TinyUSB CDC at VFS with path + * + * Know limitation: + * In case there are multiple CDC interfaces in the system, only one of them can be registered to VFS. + * + * @param[in] cdc_intf Interface number of TinyUSB's CDC + * @param[in] path Path where the CDC will be registered, `/dev/tusb_cdc` will be used if left NULL. + * @return esp_err_t ESP_OK or ESP_FAIL + */ +esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path); + +/** + * @brief Unregister TinyUSB CDC from VFS + * + * @param[in] path Path where the CDC will be unregistered if NULL will be used `/dev/tusb_cdc` + * @return esp_err_t ESP_OK or ESP_FAIL + */ +esp_err_t esp_vfs_tusb_cdc_unregister(char const *path); + +/** + * @brief Set the line endings to sent + * + * This specifies the conversion between newlines ('\n', LF) on stdout and line + * endings sent: + * + * - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF + * - ESP_LINE_ENDINGS_CR: convert LF to CR + * - ESP_LINE_ENDINGS_LF: no modification + * + * @param[in] mode line endings to send + */ +void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode); + +/** + * @brief Set the line endings expected to be received + * + * This specifies the conversion between line endings received and + * newlines ('\n', LF) passed into stdin: + * + * - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF + * - ESP_LINE_ENDINGS_CR: convert CR to LF + * - ESP_LINE_ENDINGS_LF: no modification + * + * @param[in] mode line endings expected + */ +void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/cdc.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/cdc.h new file mode 100644 index 000000000..064f54d32 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/cdc.h @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/timers.h" +#include "tusb.h" +#include "tinyusb_types.h" + +/* CDC classification + ********************************************************************* */ +typedef enum { + TINYUSB_CDC_DATA = 0x00, +} cdc_data_sublcass_type_t; // CDC120 specification + +/* Note:other classification is represented in the file components\tinyusb\tinyusb\src\class\cdc\cdc.h */ + +/*********************************************************************** CDC classification*/ +/* Structs + ********************************************************************* */ +typedef struct { + tinyusb_usbdev_t usb_dev; /*!< USB device to set up */ + tusb_class_code_t cdc_class; /*!< CDC device class : Communications or Data device */ + union { + cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */ + cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/ + } cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */ +} tinyusb_config_cdc_t; /*!< Main configuration structure of a CDC device */ + +typedef struct { + tinyusb_usbdev_t usb_dev; /*!< USB device used for the instance */ + tusb_class_code_t type; + union { + cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */ + cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/ + } cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */ + void *subclass_obj; /*!< Dynamically allocated subclass specific object */ +} esp_tusb_cdc_t; +/*********************************************************************** Structs*/ +/* Functions + ********************************************************************* */ +/** + * @brief Initializing CDC basic object + * @param itf - number of a CDC object + * @param cfg - CDC configuration structure + * + * @return esp_err_t ESP_OK or ESP_FAIL + */ +esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg); + +/** + * @brief De-initializing CDC. Clean its objects + * @param itf - number of a CDC object + * @return esp_err_t ESP_OK, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE + * + */ +esp_err_t tinyusb_cdc_deinit(int itf); + +/** + * @brief Return interface of a CDC device + * + * @param itf_num + * @return esp_tusb_cdc_t* pointer to the interface or (NULL) on error + */ +esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num); +/*********************************************************************** Functions*/ + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/descriptors_control.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/descriptors_control.h new file mode 100644 index 000000000..fcf352226 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/descriptors_control.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "tinyusb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 8 // Max 8 string descriptors for a device. LANGID, Manufacturer, Product, Serial number + 4 user defined + +/** + * @brief Parse tinyusb configuration and prepare the device configuration pointer list to configure tinyusb driver + * + * @attention All descriptors passed to this function must exist for the duration of USB device lifetime + * + * @param[in] config tinyusb stack specific configuration + * @retval ESP_ERR_INVALID_ARG Default configuration descriptor is provided only for CDC, MSC and NCM classes + * @retval ESP_ERR_NO_MEM Memory allocation error + * @retval ESP_OK Descriptors configured without error + */ +esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config); + +/** + * @brief Set specific string descriptor + * + * @attention The descriptor passed to this function must exist for the duration of USB device lifetime + * + * @param[in] str UTF-8 string + * @param[in] str_idx String descriptor index + */ +void tinyusb_set_str_descriptor(const char *str, int str_idx); + +/** + * @brief Free memory allocated during tinyusb_set_descriptors + * + */ +void tinyusb_free_descriptors(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/usb_descriptors.h b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/usb_descriptors.h new file mode 100644 index 000000000..be1979ab7 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/include_private/usb_descriptors.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "tusb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Device descriptor generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own device descriptor via tinyusb_driver_install() call + */ +extern const tusb_desc_device_t descriptor_dev_default; + +#if (TUD_OPT_HIGH_SPEED) +/** + * @brief Qualifier Device descriptor generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own descriptor via tinyusb_driver_install() call + */ +extern const tusb_desc_device_qualifier_t descriptor_qualifier_default; +#endif // TUD_OPT_HIGH_SPEED + +/** + * @brief Array of string descriptors generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own descriptor via tinyusb_driver_install() call + */ +extern const char *descriptor_str_default[]; + +/** + * @brief FullSpeed configuration descriptor generated from Kconfig + * This descriptor is used by default. + * The user can provide their own FullSpeed configuration descriptor via tinyusb_driver_install() call + */ +extern const uint8_t descriptor_fs_cfg_default[]; + +#if (TUD_OPT_HIGH_SPEED) +/** + * @brief HighSpeed Configuration descriptor generated from Kconfig + * + * This descriptor is used by default. + * The user can provide their own HighSpeed configuration descriptor via tinyusb_driver_install() call + */ +extern const uint8_t descriptor_hs_cfg_default[]; +#endif // TUD_OPT_HIGH_SPEED + +uint8_t tusb_get_mac_string_id(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/sbom.yml b/examples/usb/device/usb_dongle/components/tinyusb_dongle/sbom.yml new file mode 100644 index 000000000..f1e805b9c --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/sbom.yml @@ -0,0 +1,2 @@ +supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' +originator: 'Organization: Espressif Systems (Shanghai) CO LTD' diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tinyusb.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tinyusb.c new file mode 100644 index 000000000..fbffe4b7c --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tinyusb.c @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_private/periph_ctrl.h" +#include "esp_private/usb_phy.h" +#include "soc/usb_pins.h" +#include "tinyusb.h" +#include "descriptors_control.h" +#include "tusb.h" +#include "tusb_tasks.h" + +const static char *TAG = "TinyUSB"; +static usb_phy_handle_t phy_hdl; + +esp_err_t tinyusb_driver_install(const tinyusb_config_t *config) +{ + ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL"); + + // Configure USB PHY + usb_phy_config_t phy_conf = { + .controller = USB_PHY_CTRL_OTG, + .otg_mode = USB_OTG_MODE_DEVICE, + }; + + // External PHY IOs config + usb_phy_ext_io_conf_t ext_io_conf = { + .vp_io_num = USBPHY_VP_NUM, + .vm_io_num = USBPHY_VM_NUM, + .rcv_io_num = USBPHY_RCV_NUM, + .oen_io_num = USBPHY_OEN_NUM, + .vpo_io_num = USBPHY_VPO_NUM, + .vmo_io_num = USBPHY_VMO_NUM, + }; + if (config->external_phy) { + phy_conf.target = USB_PHY_TARGET_EXT; + phy_conf.ext_io_conf = &ext_io_conf; + } else { + phy_conf.target = USB_PHY_TARGET_INT; + } + + // OTG IOs config + const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->vbus_monitor_io); + if (config->self_powered) { + phy_conf.otg_io_conf = &otg_io_conf; + } + ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed"); + + // Descriptors config + ESP_RETURN_ON_ERROR(tinyusb_set_descriptors(config), TAG, "Descriptors config failed"); + + // Init +#if !CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK + ESP_RETURN_ON_FALSE(tusb_init(), ESP_FAIL, TAG, "Init TinyUSB stack failed"); +#endif +#if !CONFIG_TINYUSB_NO_DEFAULT_TASK + ESP_RETURN_ON_ERROR(tusb_run_task(), TAG, "Run TinyUSB task failed"); +#endif + ESP_LOGI(TAG, "TinyUSB Driver installed"); + return ESP_OK; +} + +esp_err_t tinyusb_driver_uninstall() +{ + tinyusb_free_descriptors(); + return usb_del_phy(phy_hdl); +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tinyusb_net.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tinyusb_net.c new file mode 100644 index 000000000..2ccbf24b7 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tinyusb_net.c @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "tinyusb_net.h" +#include "descriptors_control.h" +#include "usb_descriptors.h" +#include "device/usbd_pvt.h" +#include "esp_check.h" + +#define MAC_ADDR_LEN 6 + +typedef struct packet { + void *buffer; + void *buff_free_arg; + uint16_t len; + esp_err_t result; +} packet_t; + +struct tinyusb_net_handle { + bool initialized; + SemaphoreHandle_t buffer_sema; + EventGroupHandle_t tx_flags; + tusb_net_rx_cb_t rx_cb; + tusb_net_free_tx_cb_t tx_buff_free_cb; + tusb_net_init_cb_t init_cb; + char mac_str[2 * MAC_ADDR_LEN + 1]; + void *ctx; + packet_t *packet_to_send; +}; + +const static int TX_FINISHED_BIT = BIT0; +static struct tinyusb_net_handle s_net_obj = { }; +static const char *TAG = "tusb_net"; + +static void do_send_sync(void *ctx) +{ + (void) ctx; + if (xSemaphoreTake(s_net_obj.buffer_sema, 0) != pdTRUE || s_net_obj.packet_to_send == NULL) { + return; + } + + packet_t *packet = s_net_obj.packet_to_send; + if (tud_network_can_xmit(packet->len)) { + tud_network_xmit(packet, packet->len); + packet->result = ESP_OK; + } else { + packet->result = ESP_FAIL; + } + xSemaphoreGive(s_net_obj.buffer_sema); + xEventGroupSetBits(s_net_obj.tx_flags, TX_FINISHED_BIT); +} + +static void do_send_async(void *ctx) +{ + packet_t *packet = ctx; + if (tud_network_can_xmit(packet->len)) { + tud_network_xmit(packet, packet->len); + } else if (s_net_obj.tx_buff_free_cb) { + ESP_LOGW(TAG, "Packet cannot be accepted on USB interface, dropping"); + s_net_obj.tx_buff_free_cb(packet->buff_free_arg, s_net_obj.ctx); + } + free(packet); +} + +esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg) +{ + if (!tud_ready()) { + return ESP_ERR_INVALID_STATE; + } + + packet_t *packet = calloc(1, sizeof(packet_t)); + packet->len = len; + packet->buffer = buffer; + packet->buff_free_arg = buff_free_arg; + ESP_RETURN_ON_FALSE(packet, ESP_ERR_NO_MEM, TAG, "Failed to allocate packet to send"); + usbd_defer_func(do_send_async, packet, false); + return ESP_OK; +} + +esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout) +{ + if (!tud_ready()) { + return ESP_ERR_INVALID_STATE; + } + + // Lazy init the flags and semaphores, as they might not be needed (if async approach is used) + if (!s_net_obj.tx_flags) { + s_net_obj.tx_flags = xEventGroupCreate(); + ESP_RETURN_ON_FALSE(s_net_obj.tx_flags, ESP_ERR_NO_MEM, TAG, "Failed to allocate event flags"); + } + if (!s_net_obj.buffer_sema) { + s_net_obj.buffer_sema = xSemaphoreCreateBinary(); + ESP_RETURN_ON_FALSE(s_net_obj.buffer_sema, ESP_ERR_NO_MEM, TAG, "Failed to allocate buffer semaphore"); + } + + packet_t packet = { + .buffer = buffer, + .len = len, + .buff_free_arg = buff_free_arg + }; + s_net_obj.packet_to_send = &packet; + xSemaphoreGive(s_net_obj.buffer_sema); // now the packet is ready, let's mark it available to tusb send + + // to execute the send function in tinyUSB task context + usbd_defer_func(do_send_sync, NULL, false); // arg=NULL -> sync send, we keep the packet inside the object + + // wait wor completion with defined timeout + EventBits_t bits = xEventGroupWaitBits(s_net_obj.tx_flags, TX_FINISHED_BIT, pdTRUE, pdTRUE, timeout); + xSemaphoreTake(s_net_obj.buffer_sema, portMAX_DELAY); // if tusb sending already started, we have wait before ditching the packet + s_net_obj.packet_to_send = NULL; // invalidate the argument + if (bits & TX_FINISHED_BIT) { // If transaction finished, return error code + return packet.result; + } + return ESP_ERR_TIMEOUT; +} + +esp_err_t tinyusb_net_init(tinyusb_usbdev_t usb_dev, const tinyusb_net_config_t *cfg) +{ + (void) usb_dev; + + ESP_RETURN_ON_FALSE(s_net_obj.initialized == false, ESP_ERR_INVALID_STATE, TAG, "TinyUSB Net class is already initialized"); + + // the semaphore and event flags are initialized only if needed + s_net_obj.rx_cb = cfg->on_recv_callback; + s_net_obj.init_cb = cfg->on_init_callback; + s_net_obj.tx_buff_free_cb = cfg->free_tx_buffer; + s_net_obj.ctx = cfg->user_context; + + const uint8_t *mac = &cfg->mac_addr[0]; + snprintf(s_net_obj.mac_str, sizeof(s_net_obj.mac_str), "%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + uint8_t mac_id = tusb_get_mac_string_id(); + // Pass it to Descriptor control module + tinyusb_set_str_descriptor(s_net_obj.mac_str, mac_id); + + s_net_obj.initialized = true; + + return ESP_OK; +} + +//--------------------------------------------------------------------+ +// tinyusb callbacks +//--------------------------------------------------------------------+ +bool tud_network_recv_cb(const uint8_t *src, uint16_t size) +{ + if (s_net_obj.rx_cb) { + s_net_obj.rx_cb((void *)src, size, s_net_obj.ctx); + } + tud_network_recv_renew(); + return true; +} + +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) +{ + packet_t *packet = ref; + uint16_t len = arg; + + memcpy(dst, packet->buffer, packet->len); + if (s_net_obj.tx_buff_free_cb) { + s_net_obj.tx_buff_free_cb(packet->buff_free_arg, s_net_obj.ctx); + } + return len; +} + +void tud_network_init_cb(void) +{ + if (s_net_obj.init_cb) { + s_net_obj.init_cb(s_net_obj.ctx); + } +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_bth.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_bth.c new file mode 100644 index 000000000..ec2236fc5 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_bth.c @@ -0,0 +1,266 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_bt.h" + +#include "tinyusb.h" +#include "tusb_bth.h" + +static const char *TAG = "tusb_bth"; + +static SemaphoreHandle_t evt_sem = NULL; +static SemaphoreHandle_t acl_sem = NULL; + +static acl_data_t acl_tx_data = { + .is_new_pkt = true, + .pkt_total_len = 0, + .pkt_cur_offset = 0, + .pkt_val = NULL +}; + +/* + * @brief: BT controller callback function, used to notify the upper layer that + * controller is ready to receive command + */ +static void controller_rcv_pkt_ready(void) +{ + // Do nothing, will check by esp_vhci_host_check_send_available(). +} + +/* + * @brief: BT controller callback function, to transfer data packet to upper + * controller is ready to receive command + */ +static int host_rcv_pkt(uint8_t *data, uint16_t len) +{ + uint16_t act_len = len - 1; + uint8_t type = data[0]; + uint8_t * acl_rx_buf = NULL; + uint8_t * evt_rx_buf = NULL; + + HCI_DUMP_BUFFER("Recv Pkt", data, len); + + switch (type) { + case HCIT_TYPE_COMMAND: + break; + case HCIT_TYPE_ACL_DATA: + acl_rx_buf = (uint8_t *)malloc(act_len * sizeof(uint8_t)); + assert(acl_rx_buf); + + memcpy(acl_rx_buf, data + 1, act_len); + tud_bt_acl_data_send(acl_rx_buf, act_len); + + xSemaphoreTake(acl_sem, portMAX_DELAY); + free(acl_rx_buf); + break; + case HCIT_TYPE_SCO_DATA: + break; + case HCIT_TYPE_EVENT: + evt_rx_buf = (uint8_t *)malloc(act_len * sizeof(uint8_t)); + assert(evt_rx_buf); + + memcpy(evt_rx_buf, data + 1, act_len); + tud_bt_event_send(evt_rx_buf, act_len); + + xSemaphoreTake(evt_sem, portMAX_DELAY); + free(evt_rx_buf); + break; + default: + ESP_LOGE(TAG, "Unknow type [0x%02x]", type); + break; + } + + return 0; +} + +//--------------------------------------------------------------------+ +// tinyusb callbacks +//--------------------------------------------------------------------+ + +// Invoked when HCI command was received over USB from Bluetooth host. +// Detailed format is described in Bluetooth core specification Vol 2, +// Part E, 5.4.1. +// Length of the command is from 3 bytes (2 bytes for OpCode, +// 1 byte for parameter total length) to 258. +void tud_bt_hci_cmd_cb(void * hci_cmd, size_t cmd_len) +{ + uint8_t * cmd_tx_buf = (uint8_t *)malloc((cmd_len + 1) * sizeof(uint8_t)); + + assert(cmd_tx_buf); + + cmd_tx_buf[0] = HCIT_TYPE_COMMAND; + memcpy(cmd_tx_buf + 1, hci_cmd, cmd_len); + + HCI_DUMP_BUFFER("Transmit Pkt", cmd_tx_buf, cmd_len + 1); + + while (!esp_vhci_host_check_send_available()) { + vTaskDelay(1); + } + esp_vhci_host_send_packet(cmd_tx_buf, cmd_len + 1); + + free(cmd_tx_buf); + cmd_tx_buf = NULL; +} + +// Invoked when ACL data was received over USB from Bluetooth host. +// Detailed format is described in Bluetooth core specification Vol 2, +// Part E, 5.4.2. +// Length is from 4 bytes, (12 bits for Handle, 4 bits for flags +// and 16 bits for data total length) to endpoint size. +void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len) +{ + if (acl_tx_data.is_new_pkt) { + acl_tx_data.pkt_total_len = *(((uint16_t *)acl_data) + 1) + 4; + acl_tx_data.pkt_cur_offset = 0; + + acl_tx_data.pkt_val = (uint8_t *)malloc((acl_tx_data.pkt_total_len + 1) * sizeof(uint8_t)); + assert(acl_tx_data.pkt_val); + memset(acl_tx_data.pkt_val, 0x0, (acl_tx_data.pkt_total_len + 1)); + + acl_tx_data.pkt_val[0] = HCIT_TYPE_ACL_DATA; + acl_tx_data.pkt_cur_offset++; + memcpy(acl_tx_data.pkt_val + acl_tx_data.pkt_cur_offset, acl_data, data_len); + acl_tx_data.pkt_cur_offset += data_len; + + if (data_len < acl_tx_data.pkt_total_len) { + acl_tx_data.is_new_pkt = false; + return; + } + + while (!esp_vhci_host_check_send_available()) { + vTaskDelay(1); + } + + HCI_DUMP_BUFFER("Transmit Pkt", acl_tx_data.pkt_val, data_len + 1); + esp_vhci_host_send_packet(acl_tx_data.pkt_val, data_len + 1); + goto reset_params; + } else { + memcpy(acl_tx_data.pkt_val + acl_tx_data.pkt_cur_offset, acl_data, data_len); + acl_tx_data.pkt_cur_offset += data_len; + + if ((acl_tx_data.pkt_cur_offset - 1) == acl_tx_data.pkt_total_len) { + while (!esp_vhci_host_check_send_available()) { + vTaskDelay(1); + } + + HCI_DUMP_BUFFER("Transmit Pkt", acl_tx_data.pkt_val, acl_tx_data.pkt_total_len + 1); + esp_vhci_host_send_packet(acl_tx_data.pkt_val, acl_tx_data.pkt_total_len + 1); + goto reset_params; + } + } + + return; + +reset_params: + + acl_tx_data.is_new_pkt = true; + acl_tx_data.pkt_total_len = 0; + acl_tx_data.pkt_cur_offset = 0; + + if (acl_tx_data.pkt_val) { + free(acl_tx_data.pkt_val); + acl_tx_data.pkt_val = NULL; + } + + return; +} + +// Called when event sent with tud_bt_event_send() was delivered to BT stack. +// Controller can release/reuse buffer with Event packet at this point. +void tud_bt_event_sent_cb(uint16_t sent_bytes) +{ + if (evt_sem) { + xSemaphoreGive(evt_sem); + } +} + +// Called when ACL data that was sent with tud_bt_acl_data_send() +// was delivered to BT stack. +// Controller can release/reuse buffer with ACL packet at this point. +void tud_bt_acl_data_sent_cb(uint16_t sent_bytes) +{ + if (acl_sem) { + xSemaphoreGive(acl_sem); + } +} + +static esp_vhci_host_callback_t vhci_host_cb = { + controller_rcv_pkt_ready, + host_rcv_pkt +}; + +/** + * @brief Initialize BTH Device. + */ +void tusb_bth_init(void) +{ + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + + if (esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + ESP_LOGE(TAG, "Bluetooth controller release classic bt memory failed."); + return; + } + + if (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + ESP_LOGE(TAG, "Bluetooth controller initialize failed."); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_BLE) != ESP_OK) { + ESP_LOGE(TAG, "Bluetooth controller enable failed."); + return; + } + + evt_sem = xSemaphoreCreateBinary(); + assert(evt_sem); + acl_sem = xSemaphoreCreateBinary(); + assert(acl_sem); + + acl_tx_data.is_new_pkt = true; + acl_tx_data.pkt_total_len = 0; + acl_tx_data.pkt_cur_offset = 0; + acl_tx_data.pkt_val = NULL; + + esp_vhci_host_register_callback(&vhci_host_cb); +} + +/** + * @brief Deinitialization BTH Device. + */ +void tusb_bth_deinit(void) +{ + if (esp_bt_controller_disable() != ESP_OK) { + ESP_LOGE(TAG, "Bluetooth controller disable failed."); + return; + } + + if (esp_bt_controller_deinit() != ESP_OK) { + ESP_LOGE(TAG, "Bluetooth controller deinit failed."); + return; + } + + if (evt_sem) { + vSemaphoreDelete(evt_sem); + evt_sem = NULL; + } + + if (acl_sem) { + vSemaphoreDelete(acl_sem); + acl_sem = NULL; + } + + acl_tx_data.is_new_pkt = true; + acl_tx_data.pkt_total_len = 0; + acl_tx_data.pkt_cur_offset = 0; + + if (acl_tx_data.pkt_val) { + free(acl_tx_data.pkt_val); + acl_tx_data.pkt_val = NULL; + } +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_cdc_acm.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_cdc_acm.c new file mode 100644 index 000000000..77211e11f --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_cdc_acm.c @@ -0,0 +1,345 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_check.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "tusb.h" +#include "tusb_cdc_acm.h" +#include "cdc.h" +#include "sdkconfig.h" + +#ifndef MIN +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif + +// CDC-ACM spinlock +static portMUX_TYPE cdc_acm_lock = portMUX_INITIALIZER_UNLOCKED; +#define CDC_ACM_ENTER_CRITICAL() portENTER_CRITICAL(&cdc_acm_lock) +#define CDC_ACM_EXIT_CRITICAL() portEXIT_CRITICAL(&cdc_acm_lock) + +typedef struct { + tusb_cdcacm_callback_t callback_rx; + tusb_cdcacm_callback_t callback_rx_wanted_char; + tusb_cdcacm_callback_t callback_line_state_changed; + tusb_cdcacm_callback_t callback_line_coding_changed; +} esp_tusb_cdcacm_t; /*!< CDC_ACM object */ + +static const char *TAG = "tusb_cdc_acm"; + +static inline esp_tusb_cdcacm_t *get_acm(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf); + if (cdc_inst == NULL) { + return (esp_tusb_cdcacm_t *)NULL; + } + return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj); +} + +/* TinyUSB callbacks + ********************************************************************* */ + +/* Invoked by cdc interface when line state changed e.g connected/disconnected */ +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (dtr && rts) { // connected + if (acm != NULL) { + ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf); + } else { + ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf); + return; + } + } else { // disconnected + if (acm != NULL) { + ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf); + } else { + return; + } + } + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_line_state_changed; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_LINE_STATE_CHANGED, + .line_state_changed_data = { + .dtr = dtr, + .rts = rts + } + }; + cb(itf, &event); + } + } +} + +/* Invoked when CDC interface received data from host */ +void tud_cdc_rx_cb(uint8_t itf) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_rx; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_RX + }; + cb(itf, &event); + } + } +} + +// Invoked when line coding is change via SET_LINE_CODING +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_line_coding_changed; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_LINE_CODING_CHANGED, + .line_coding_changed_data = { + .p_line_coding = p_line_coding, + } + }; + cb(itf, &event); + } + } +} + +// Invoked when received `wanted_char` +void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + CDC_ACM_ENTER_CRITICAL(); + tusb_cdcacm_callback_t cb = acm->callback_rx_wanted_char; + CDC_ACM_EXIT_CRITICAL(); + if (cb) { + cdcacm_event_t event = { + .type = CDC_EVENT_RX_WANTED_CHAR, + .rx_wanted_char_data = { + .wanted_char = wanted_char, + } + }; + cb(itf, &event); + } + } +} + +esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf, + cdcacm_event_type_t event_type, + tusb_cdcacm_callback_t callback) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + switch (event_type) { + case CDC_EVENT_RX: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_RX_WANTED_CHAR: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx_wanted_char = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_STATE_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_state_changed = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_CODING_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_coding_changed = callback; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + default: + ESP_LOGE(TAG, "Wrong event type"); + return ESP_ERR_INVALID_ARG; + } + } else { + ESP_LOGE(TAG, "CDC-ACM is not initialized"); + return ESP_ERR_INVALID_STATE; + } +} + +esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, + cdcacm_event_type_t event_type) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (!acm) { + ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization"); + return ESP_ERR_INVALID_STATE; + } + switch (event_type) { + case CDC_EVENT_RX: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_RX_WANTED_CHAR: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_rx_wanted_char = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_STATE_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_state_changed = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + case CDC_EVENT_LINE_CODING_CHANGED: + CDC_ACM_ENTER_CRITICAL(); + acm->callback_line_coding_changed = NULL; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; + default: + ESP_LOGE(TAG, "Wrong event type"); + return ESP_ERR_INVALID_ARG; + } +} + +/*********************************************************************** TinyUSB callbacks*/ +/* CDC-ACM + ********************************************************************* */ + +esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + ESP_RETURN_ON_FALSE(acm, ESP_ERR_INVALID_STATE, TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization"); + + if (tud_cdc_n_available(itf) == 0) { + *rx_data_size = 0; + } else { + *rx_data_size = tud_cdc_n_read(itf, out_buf, out_buf_sz); + } + return ESP_OK; +} + +size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch) +{ + if (!get_acm(itf)) { // non-initialized + return 0; + } + return tud_cdc_n_write_char(itf, ch); +} + +size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size) +{ + if (!get_acm(itf)) { // non-initialized + return 0; + } + const uint32_t size_available = tud_cdc_n_write_available(itf); + return tud_cdc_n_write(itf, in_buf, MIN(in_size, size_available)); +} + +static uint32_t tud_cdc_n_write_occupied(tinyusb_cdcacm_itf_t itf) +{ + return CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(itf); +} + +esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks) +{ + if (!get_acm(itf)) { // non-initialized + return ESP_FAIL; + } + + if (!timeout_ticks) { // if no timeout - nonblocking mode + // It might take some time until TinyUSB flushes the endpoint + // Since this call is non-blocking, we don't wait for flush finished, + // We only inform the user by returning ESP_ERR_NOT_FINISHED + tud_cdc_n_write_flush(itf); + if (tud_cdc_n_write_occupied(itf)) { + return ESP_ERR_NOT_FINISHED; + } + } else { // trying during the timeout + uint32_t ticks_start = xTaskGetTickCount(); + uint32_t ticks_now = ticks_start; + while (1) { // loop until success or until the time runs out + ticks_now = xTaskGetTickCount(); + tud_cdc_n_write_flush(itf); + if (tud_cdc_n_write_occupied(itf) == 0) { + break; // All data flushed + } + if ((ticks_now - ticks_start) > timeout_ticks) { // Time is up + ESP_LOGW(TAG, "Flush failed"); + return ESP_ERR_TIMEOUT; + } + vTaskDelay(1); + } + } + return ESP_OK; +} + +static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf); + if (cdc_inst == NULL) { + return ESP_FAIL; + } + cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t)); + if (cdc_inst->subclass_obj == NULL) { + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg) +{ + esp_err_t ret = ESP_OK; + int itf = (int)cfg->cdc_port; + /* Creating a CDC object */ + const tinyusb_config_cdc_t cdc_cfg = { + .usb_dev = cfg->usb_dev, + .cdc_class = TUSB_CLASS_CDC, + .cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL + }; + + ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg), TAG, "tinyusb_cdc_init failed"); + ESP_GOTO_ON_ERROR(alloc_obj(itf), fail, TAG, "alloc_obj failed"); + + /* Callbacks setting up*/ + if (cfg->callback_rx) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, cfg->callback_rx); + } + if (cfg->callback_rx_wanted_char) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX_WANTED_CHAR, cfg->callback_rx_wanted_char); + } + if (cfg->callback_line_state_changed) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_STATE_CHANGED, cfg->callback_line_state_changed); + } + if (cfg->callback_line_coding_changed) { + tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_CODING_CHANGED, cfg->callback_line_coding_changed); + } + + return ESP_OK; +fail: + tinyusb_cdc_deinit(itf); + return ret; +} + +esp_err_t tusb_cdc_acm_deinit(int itf) +{ + return tinyusb_cdc_deinit(itf); +} + +bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf) +{ + esp_tusb_cdcacm_t *acm = get_acm(itf); + if (acm) { + return true; + } else { + return false; + } +} +/*********************************************************************** CDC-ACM*/ diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_console.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_console.c new file mode 100644 index 000000000..60a233e23 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_console.c @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include "cdc.h" +#include "tusb_console.h" +#include "tinyusb.h" +#include "vfs_tinyusb.h" +#include "esp_check.h" + +#define STRINGIFY(s) STRINGIFY2(s) +#define STRINGIFY2(s) #s + +static const char *TAG = "tusb_console"; + +typedef struct { + FILE *in; + FILE *out; + FILE *err; +} console_handle_t; + +static console_handle_t con; + +/** + * @brief Reopen standard streams using a new path + * + * @param f_in - pointer to a pointer holding a file for in or NULL to don't change stdin + * @param f_out - pointer to a pointer holding a file for out or NULL to don't change stdout + * @param f_err - pointer to a pointer holding a file for err or NULL to don't change stderr + * @param path - mount point + * @return esp_err_t ESP_FAIL or ESP_OK + */ +static esp_err_t redirect_std_streams_to(FILE **f_in, FILE **f_out, FILE **f_err, const char *path) +{ + if (f_in) { + *f_in = freopen(path, "r", stdin); + if (*f_in == NULL) { + ESP_LOGE(TAG, "Failed to reopen in!"); + return ESP_FAIL; + } + } + if (f_out) { + *f_out = freopen(path, "w", stdout); + if (*f_out == NULL) { + ESP_LOGE(TAG, "Failed to reopen out!"); + return ESP_FAIL; + } + } + if (f_err) { + *f_err = freopen(path, "w", stderr); + if (*f_err == NULL) { + ESP_LOGE(TAG, "Failed to reopen err!"); + return ESP_FAIL; + } + } + + return ESP_OK; +} + +/** + * @brief Restore output to default + * + * @param f_in - pointer to a pointer of an in file updated with `redirect_std_streams_to` or NULL to don't change stdin + * @param f_out - pointer to a pointer of an out file updated with `redirect_std_streams_to` or NULL to don't change stdout + * @param f_err - pointer to a pointer of an err file updated with `redirect_std_streams_to` or NULL to don't change stderr + * @return esp_err_t ESP_FAIL or ESP_OK + */ +static esp_err_t restore_std_streams(FILE **f_in, FILE **f_out, FILE **f_err) +{ + const char *default_uart_dev = "/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM); + if (f_in) { + stdin = freopen(default_uart_dev, "r", *f_in); + if (stdin == NULL) { + ESP_LOGE(TAG, "Failed to reopen stdin!"); + return ESP_FAIL; + } + } + if (f_out) { + stdout = freopen(default_uart_dev, "w", *f_out); + if (stdout == NULL) { + ESP_LOGE(TAG, "Failed to reopen stdout!"); + return ESP_FAIL; + } + } + if (f_err) { + stderr = freopen(default_uart_dev, "w", *f_err); + if (stderr == NULL) { + ESP_LOGE(TAG, "Failed to reopen stderr!"); + return ESP_FAIL; + } + } + return ESP_OK; +} + +esp_err_t esp_tusb_init_console(int cdc_intf) +{ + /* Registering TUSB at VFS */ + ESP_RETURN_ON_ERROR(esp_vfs_tusb_cdc_register(cdc_intf, NULL), TAG, ""); + ESP_RETURN_ON_ERROR(redirect_std_streams_to(&con.in, &con.out, &con.err, VFS_TUSB_PATH_DEFAULT), TAG, "Failed to redirect STD streams"); + return ESP_OK; +} + +esp_err_t esp_tusb_deinit_console(int cdc_intf) +{ + ESP_RETURN_ON_ERROR(restore_std_streams(&con.in, &con.out, &con.err), TAG, "Failed to restore STD streams"); + esp_vfs_tusb_cdc_unregister(NULL); + return ESP_OK; +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_dfu.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_dfu.c new file mode 100644 index 000000000..99cd1d98f --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_dfu.c @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * After device is enumerated in dfu mode run the following commands + * + * To transfer firmware from host to device (best to test with text file) + * + * $ dfu-util -d cafe -a 0 -D [filename] + * $ dfu-util -d cafe -a 1 -D [filename] + * + * To transfer firmware from device to host: + * + * $ dfu-util -d cafe -a 0 -U [filename] + * $ dfu-util -d cafe -a 1 -U [filename] + * + */ + +#include +#include +#include + +#include "esp_log.h" +#include "tusb.h" +#include "tinyusb.h" +#include "tinyusb_dfu_ota.h" + +static char* TAG = "esp_dfu"; + +//--------------------------------------------------------------------+ +// DFU callbacks +// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. +//--------------------------------------------------------------------+ + +// Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST) +// Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation. +// During this period, USB host won't try to communicate with us. +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state) +{ + if (state == DFU_DNBUSY) { + // For this example + // - Atl0 Flash is fast : 1 ms + // - Alt1 EEPROM is slow: 100 ms + return (alt == 0) ? 1 : 100; + } else if (state == DFU_MANIFEST) { + // since we don't buffer entire image and do any flashing in manifest stage + return 0; + } + + return 0; +} + +// Invoked when received DFU_DNLOAD (wLength>0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests +// This callback could be returned before flashing op is complete (async). +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_download_cb(uint8_t alt, uint16_t block_num, uint8_t const* data, uint16_t length) +{ + (void) alt; + (void) block_num; + + ESP_LOGI(TAG, "Received Alt %u BlockNum %u of length %u", alt, block_num, length); + + esp_err_t ret = ota_start(data, length); + if (ret == ESP_OK) { + tud_dfu_finish_flashing(DFU_STATUS_OK); + } else { + tud_dfu_finish_flashing(DFU_STATUS_ERR_WRITE); + } +} + +// Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest) +// Application can do checksum, or actual flashing if buffered entire image previously. +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_manifest_cb(uint8_t alt) +{ + (void) alt; + ESP_LOGI(TAG, "Download completed, enter manifestation\r\n"); + + esp_err_t ret = ota_complete(); + // flashing op for manifest is complete without error + // Application can perform checksum, should it fail, use appropriate status such as errVERIFY. + + if (ret == ESP_OK) { + tud_dfu_finish_flashing(DFU_STATUS_OK); + } else { + tud_dfu_finish_flashing(DFU_STATUS_ERR_VERIFY); + } +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_dfu_ota.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_dfu_ota.c new file mode 100644 index 000000000..bffaa9c25 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_dfu_ota.c @@ -0,0 +1,207 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * After device is enumerated in dfu mode run the following commands + * + * To transfer firmware from host to device (best to test with text file) + * + * $ dfu-util -d cafe -a 0 -D [filename] + * $ dfu-util -d cafe -a 1 -D [filename] + * + * To transfer firmware from device to host: + * + * $ dfu-util -d cafe -a 0 -U [filename] + * $ dfu-util -d cafe -a 1 -U [filename] + * + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "tusb.h" +#include "tinyusb.h" +#include "nvs_flash.h" + +#include "esp_app_format.h" +#include "esp_system.h" +#include "esp_log.h" +#include "esp_ota_ops.h" +#include "esp_flash_partitions.h" +#include "esp_partition.h" + +static char* TAG = "esp_dfu_ota"; + +#define BUFFSIZE CONFIG_TINYUSB_DFU_BUFSIZE + +static esp_ota_handle_t update_handle = 0; +static esp_partition_t const *update_partition = NULL; +static int binary_file_length = 0; +/*deal with all receive packet*/ +static bool image_header_was_checked = false; + +static const esp_partition_t *ota_partition; +static uint32_t current_index = 0; + +esp_err_t ota_start(uint8_t const *ota_write_data, uint32_t data_read) +{ + esp_err_t err; + /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */ + if (image_header_was_checked == false) { + esp_app_desc_t new_app_info; + + ESP_LOGI(TAG, "Starting OTA example"); + + const esp_partition_t *running = esp_ota_get_running_partition(); + + ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%"PRIx32")", + running->type, running->subtype, running->address); + + update_partition = esp_ota_get_next_update_partition(NULL); + assert(update_partition != NULL); + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32"", + update_partition->subtype, update_partition->address); + if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) { + // check current version with downloading + memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); + ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); + + esp_app_desc_t running_app_info; + if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version); + } + + const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition(); + esp_app_desc_t invalid_app_info; + if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); + } + + // check current version with last invalid partition + if (last_invalid_app != NULL) { + if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) { + ESP_LOGW(TAG, "New version is the same as invalid version."); + ESP_LOGW(TAG, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version); + ESP_LOGW(TAG, "The firmware has been rolled back to the previous version."); + } + } + + image_header_was_checked = true; + + err = esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err)); + esp_ota_abort(update_handle); + return err; + } + ESP_LOGI(TAG, "esp_ota_begin succeeded"); + } else { + ESP_LOGE(TAG, "received package is not fit len"); + esp_ota_abort(update_handle); + return err; + } + } + err = esp_ota_write(update_handle, (const void *)ota_write_data, data_read); + if (err != ESP_OK) { + esp_ota_abort(update_handle); + return err; + } + binary_file_length += data_read; + ESP_LOGD(TAG, "Written image length %d", binary_file_length); + return err; +} + +esp_err_t ota_complete(void) +{ + esp_err_t err = ESP_OK; + ESP_LOGI(TAG, "Total Write binary data length: %d", binary_file_length); + + err = esp_ota_end(update_handle); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + ESP_LOGE(TAG, "Image validation failed, image is corrupted"); + } else { + ESP_LOGE(TAG, "esp_ota_end failed (%s)!", esp_err_to_name(err)); + } + return err; + } + + err = esp_ota_set_boot_partition(update_partition); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err)); + return err; + } + ESP_LOGI(TAG, "OTA done, please restart system!"); + return ESP_OK; +} + +static uint16_t upload_bin(uint8_t* data, uint16_t length) +{ + uint16_t read_size = length; + if (ota_partition == NULL) { + ota_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_MIN, NULL); + if (ota_partition == NULL) { + ESP_LOGE(TAG, "not found ota_0"); + return 0; + } + ESP_LOGI(TAG, "Bin size: %"PRIu32", addr: %p", ota_partition->size, (void *)ota_partition->address); + } + if (current_index > ota_partition->size) { + ESP_LOGI(TAG, "read error %"PRIu32",%"PRIu32"\n", current_index, ota_partition->size); + return 0; + } + + if (current_index == ota_partition->size) { // upload done + ESP_LOGI(TAG, "Upload done"); + current_index = 0; + ota_partition = NULL; + return 0; + } else if (current_index + read_size > ota_partition->size) { // the last data + read_size = ota_partition->size - current_index; + } + ESP_ERROR_CHECK(esp_partition_read(ota_partition, current_index, data, read_size)); + + current_index += read_size; + + return read_size; +} + +//--------------------------------------------------------------------+ +// DFU callbacks +// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. +//--------------------------------------------------------------------+ + +// Invoked when received DFU_UPLOAD request +// Application must populate data with up to length bytes and +// Return the number of written bytes +uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length) +{ + (void) block_num; + (void) length; + ESP_LOGI(TAG, "upload data,block_num: %d,length: %d\n", block_num, length); + + uint16_t xfer_len = upload_bin(data, length); + + return xfer_len; +} + +// Invoked when the Host has terminated a download or upload transfer +void tud_dfu_abort_cb(uint8_t alt) +{ + (void) alt; + ESP_LOGI(TAG, "Host aborted transfer\r\n"); +} + +// Invoked when a DFU_DETACH request is received +void tud_dfu_detach_cb(void) +{ + ESP_LOGI(TAG, "Host detach, upload/download done\r\n"); + //esp_restart(); +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_tasks.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_tasks.c new file mode 100644 index 000000000..051dd677d --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/tusb_tasks.c @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_log.h" +#include "esp_check.h" +#include "tinyusb.h" +#include "tusb_tasks.h" + +const static char *TAG = "tusb_tsk"; +static TaskHandle_t s_tusb_tskh; + +#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK +const static int INIT_OK = BIT0; +const static int INIT_FAILED = BIT1; +#endif + +/** + * @brief This top level thread processes all usb events and invokes callbacks + */ +static void tusb_device_task(void *arg) +{ + ESP_LOGD(TAG, "tinyusb task started"); +#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK + EventGroupHandle_t *init_flags = arg; + if (!tusb_init()) { + ESP_LOGI(TAG, "Init TinyUSB stack failed"); + xEventGroupSetBits(*init_flags, INIT_FAILED); + vTaskDelete(NULL); + } + ESP_LOGD(TAG, "tinyusb task has been initialized"); + xEventGroupSetBits(*init_flags, INIT_OK); +#endif // CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK + while (1) { // RTOS forever loop + tud_task(); + } +} + +esp_err_t tusb_run_task(void) +{ + // This function is not guaranteed to be thread safe, if invoked multiple times without calling `tusb_stop_task`, will cause memory leak + // doing a sanity check anyway + ESP_RETURN_ON_FALSE(!s_tusb_tskh, ESP_ERR_INVALID_STATE, TAG, "TinyUSB main task already started"); + + void *task_arg = NULL; +#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK + // need to synchronize to potentially report issue if init failed + EventGroupHandle_t init_flags = xEventGroupCreate(); + ESP_RETURN_ON_FALSE(init_flags, ESP_ERR_NO_MEM, TAG, "Failed to allocate task sync flags"); + task_arg = &init_flags; +#endif + // Create a task for tinyusb device stack: + xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", CONFIG_TINYUSB_TASK_STACK_SIZE, task_arg, CONFIG_TINYUSB_TASK_PRIORITY, &s_tusb_tskh, CONFIG_TINYUSB_TASK_AFFINITY); + ESP_RETURN_ON_FALSE(s_tusb_tskh, ESP_FAIL, TAG, "create TinyUSB main task failed"); +#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK + // wait until tusb initialization has completed + EventBits_t bits = xEventGroupWaitBits(init_flags, INIT_OK | INIT_FAILED, pdFALSE, pdFALSE, portMAX_DELAY); + vEventGroupDelete(init_flags); + ESP_RETURN_ON_FALSE(bits & INIT_OK, ESP_FAIL, TAG, "Init TinyUSB stack failed"); +#endif + + return ESP_OK; +} + +esp_err_t tusb_stop_task(void) +{ + ESP_RETURN_ON_FALSE(s_tusb_tskh, ESP_ERR_INVALID_STATE, TAG, "TinyUSB main task not started yet"); + vTaskDelete(s_tusb_tskh); + s_tusb_tskh = NULL; + return ESP_OK; +} diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/usb_descriptors.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/usb_descriptors.c new file mode 100644 index 000000000..67546d387 --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/usb_descriptors.c @@ -0,0 +1,307 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb_descriptors.h" +#include "sdkconfig.h" +#include "tinyusb_types.h" + +/* + * A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) +#define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) ) //| _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) ) + +/**** Kconfig driven Descriptor ****/ + +//------------- Device Descriptor -------------// +const tusb_desc_device_t descriptor_dev_default = { + .bLength = sizeof(descriptor_dev_default), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + +#if CFG_TUD_CDC + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, +#else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, +#endif + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + +#if CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID + .idVendor = USB_ESPRESSIF_VID, +#else + .idVendor = CONFIG_TINYUSB_DESC_CUSTOM_VID, +#endif + +#if CONFIG_TINYUSB_DESC_USE_DEFAULT_PID + .idProduct = USB_TUSB_PID, +#else + .idProduct = CONFIG_TINYUSB_DESC_CUSTOM_PID, +#endif + + .bcdDevice = CONFIG_TINYUSB_DESC_BCD_DEVICE, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +#if (TUD_OPT_HIGH_SPEED) +const tusb_desc_device_qualifier_t descriptor_qualifier_default = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + +#if CFG_TUD_CDC + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, +#else + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, +#endif + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; +#endif // TUD_OPT_HIGH_SPEED + +//------------- Array of String Descriptors -------------// +const char *descriptor_str_default[] = { + // array of pointer to string descriptors + (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) + CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer + CONFIG_TINYUSB_DESC_PRODUCT_STRING, // 2: Product + CONFIG_TINYUSB_DESC_SERIAL_STRING, // 3: Serials, should use chip ID + +#if CONFIG_TINYUSB_CDC_ENABLED + CONFIG_TINYUSB_DESC_CDC_STRING, // 4: CDC Interface +#endif + +#if CONFIG_TINYUSB_BTH_ENABLED + CONFIG_TINYUSB_DESC_BTH_STRING, // 6: BTH Interface +#endif + +#if CONFIG_TINYUSB_DFU_ENABLED + "FLASH", // 4: DFU Partition 1 + "EEPROM", // 5: DFU Partition 2 +#endif + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + "USB net", // 8. NET Interface + "", // 9. MAC +#endif + + NULL // NULL: Must be last. Indicates end of array +}; + +//------------- Interfaces enumeration -------------// +enum { + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + ITF_NUM_NET = 0, + ITF_NUM_NET_DATA, +#endif + +#if CFG_TUD_CDC + ITF_NUM_CDC, + ITF_NUM_CDC_DATA, +#endif + +#if CFG_TUD_CDC > 1 + ITF_NUM_CDC1, + ITF_NUM_CDC1_DATA, +#endif + +#if CFG_TUD_BTH + ITF_NUM_BTH, +#endif + +#if CFG_TUD_DFU + ITF_NUM_DFU, +#endif + + ITF_NUM_TOTAL +}; + +#define DFU_ALT_COUNT 2 +#define FUNC_ATTRS (DFU_ATTR_CAN_UPLOAD | DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_MANIFESTATION_TOLERANT) + +enum { + TUSB_DESC_TOTAL_LEN = TUD_CONFIG_DESC_LEN + + CFG_TUD_CDC * TUD_CDC_DESC_LEN + + CFG_TUD_NCM * TUD_CDC_NCM_DESC_LEN + + CFG_TUD_BTH * TUD_BTH_DESC_LEN + + CFG_TUD_DFU * TUD_DFU_DESC_LEN(DFU_ALT_COUNT) + + CONFIG_TINYUSB_NET_MODE_ECM * TUD_CDC_ECM_DESC_LEN + + CONFIG_TINYUSB_NET_MODE_RNDIS * TUD_RNDIS_DESC_LEN +}; + +//------------- USB Endpoint numbers -------------// +enum { + // Available USB Endpoints: 5 IN/OUT EPs and 1 IN EP + EP_EMPTY = 0, + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + EPNUM_NET_NOTIF, + EPNUM_NET_DATA, +#endif + +#if CFG_TUD_CDC + EPNUM_0_CDC_NOTIF, + EPNUM_0_CDC, +#endif + +#if CFG_TUD_CDC > 1 + EPNUM_1_CDC_NOTIF, + EPNUM_1_CDC, +#endif + +#if CFG_TUD_BTH + EPNUM_BT_EVT, + EPNUM_BT_BULK_OUT, +#endif + +}; + +//------------- STRID -------------// +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + +#if CFG_TUD_CDC + STRID_CDC_INTERFACE, +#endif + +#if CFG_TUD_BTH + STRID_BTH_INTERFACE, +#endif + +#if CFG_TUD_DFU + STRID_DFU_INTERFACE, +#endif + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + STRID_NET_INTERFACE, + STRID_MAC, +#endif + +}; + +//------------- Configuration Descriptor -------------// +uint8_t const descriptor_fs_cfg_default[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + +#if CFG_TUD_NCM + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 64, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 64, CFG_TUD_NET_MTU), +#endif + +#if CFG_TUD_ECM_RNDIS +#if CONFIG_TINYUSB_NET_MODE_RNDIS + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_RNDIS_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, (0x80 | EPNUM_NET_NOTIF), 8, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 64), +#elif CONFIG_TINYUSB_NET_MODE_ECM + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_ECM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 64, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 64, CFG_TUD_NET_MTU), +#endif +#endif + +#if CFG_TUD_CDC + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, STRID_CDC_INTERFACE, 0x80 | EPNUM_0_CDC_NOTIF, 8, EPNUM_0_CDC, 0x80 | EPNUM_0_CDC, 64), +#endif + +#if CFG_TUD_CDC > 1 + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC1, STRID_CDC_INTERFACE, 0x80 | EPNUM_1_CDC_NOTIF, 8, EPNUM_1_CDC, 0x80 | EPNUM_1_CDC, 64), +#endif + +#if CFG_TUD_BTH + // BT Primary controller descriptor + // Interface number, string index, attributes, event endpoint, event endpoint size, interval, data in, data out, data endpoint size, iso endpoint sizes + TUD_BTH_DESCRIPTOR(ITF_NUM_BTH, STRID_BTH_INTERFACE, (0x80 | EPNUM_BT_EVT), 16, 1, (0x80 | EPNUM_BT_BULK_OUT), EPNUM_BT_BULK_OUT, 64, 64, 64), +#endif + +#if CFG_TUD_DFU + // Interface number, Alternate count, starting string index, attributes, detach timeout, transfer size + TUD_DFU_DESCRIPTOR(ITF_NUM_DFU, DFU_ALT_COUNT, STRID_DFU_INTERFACE, FUNC_ATTRS, 1000, CFG_TUD_DFU_XFER_BUFSIZE), +#endif + +}; + +#if (TUD_OPT_HIGH_SPEED) +uint8_t const descriptor_hs_cfg_default[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + +#if CFG_TUD_NCM + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 512, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 512, CFG_TUD_NET_MTU), +#endif + +#if CFG_TUD_ECM_RNDIS +#if CONFIG_TINYUSB_NET_MODE_RNDIS + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_RNDIS_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, (0x80 | EPNUM_NET_NOTIF), 8, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 512), +#elif CONFIG_TINYUSB_NET_MODE_ECM + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_ECM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 512, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 512, CFG_TUD_NET_MTU), +#endif +#endif + +#if CFG_TUD_CDC + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, STRID_CDC_INTERFACE, 0x80 | EPNUM_0_CDC_NOTIF, 8, EPNUM_0_CDC, 0x80 | EPNUM_0_CDC, 512), +#endif + +#if CFG_TUD_CDC > 1 + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC1, STRID_CDC_INTERFACE, 0x80 | EPNUM_1_CDC_NOTIF, 8, EPNUM_1_CDC, 0x80 | EPNUM_1_CDC, 512), +#endif + +#if CFG_TUD_BTH + // BT Primary controller descriptor + // Interface number, string index, attributes, event endpoint, event endpoint size, interval, data in, data out, data endpoint size, iso endpoint sizes + TUD_BTH_DESCRIPTOR(ITF_NUM_BTH, STRID_BTH_INTERFACE, (0x80 | EPNUM_BT_EVT), 16, 1, (0x80 | EPNUM_BT_BULK_OUT), EPNUM_BT_BULK_OUT, 512, 1024, 1024), +#endif + +#if CFG_TUD_DFU + // Interface number, Alternate count, starting string index, attributes, detach timeout, transfer size + TUD_DFU_DESCRIPTOR(ITF_NUM_DFU, DFU_ALT_COUNT, STRID_DFU_INTERFACE, FUNC_ATTRS, 1000, CFG_TUD_DFU_XFER_BUFSIZE), +#endif + +}; +#endif // TUD_OPT_HIGH_SPEED + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS +uint8_t tusb_get_mac_string_id(void) +{ + return STRID_MAC; +} +#endif + +/* End of Kconfig driven Descriptor */ diff --git a/examples/usb/device/usb_dongle/components/tinyusb_dongle/vfs_tinyusb.c b/examples/usb/device/usb_dongle/components/tinyusb_dongle/vfs_tinyusb.c new file mode 100644 index 000000000..b3d431a2b --- /dev/null +++ b/examples/usb/device/usb_dongle/components/tinyusb_dongle/vfs_tinyusb.c @@ -0,0 +1,298 @@ +/* + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_attr.h" +#include "esp_log.h" +#include "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "tinyusb.h" +#include "tusb_cdc_acm.h" +#include "vfs_tinyusb.h" +#include "sdkconfig.h" + +const static char *TAG = "tusb_vfs"; + +// Token signifying that no character is available +#define NONE -1 + +#define FD_CHECK(fd, ret_val) do { \ + if ((fd) != 0) { \ + errno = EBADF; \ + return (ret_val); \ + } \ + } while (0) + +#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF +# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CRLF +#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR +# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CR +#else +# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_LF +#endif + +#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF +# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CRLF +#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR +# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CR +#else +# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_LF +#endif + +typedef struct { + _lock_t write_lock; + _lock_t read_lock; + esp_line_endings_t tx_mode; // Newline conversion mode when transmitting + esp_line_endings_t rx_mode; // Newline conversion mode when receiving + uint32_t flags; + char vfs_path[VFS_TUSB_MAX_PATH]; + int cdc_intf; +} vfs_tinyusb_t; + +static vfs_tinyusb_t s_vfstusb; + +static esp_err_t apply_path(char const *path) +{ + if (path == NULL) { + path = VFS_TUSB_PATH_DEFAULT; + } + + size_t path_len = strlen(path) + 1; + if (path_len > VFS_TUSB_MAX_PATH) { + ESP_LOGE(TAG, "The path is too long; maximum is %d characters", VFS_TUSB_MAX_PATH); + return ESP_ERR_INVALID_ARG; + } + strncpy(s_vfstusb.vfs_path, path, (VFS_TUSB_MAX_PATH - 1)); + ESP_LOGV(TAG, "Path is set to `%s`", path); + return ESP_OK; +} + +/** + * @brief Fill s_vfstusb + * + * @param cdc_intf - interface of tusb for registration + * @param path - a path where the CDC will be registered + * @return esp_err_t ESP_OK or ESP_ERR_INVALID_ARG + */ +static esp_err_t vfstusb_init(int cdc_intf, char const *path) +{ + s_vfstusb.cdc_intf = cdc_intf; + s_vfstusb.tx_mode = DEFAULT_TX_MODE; + s_vfstusb.rx_mode = DEFAULT_RX_MODE; + + return apply_path(path); +} + +/** + * @brief Clear s_vfstusb to default values + */ +static void vfstusb_deinit(void) +{ + memset(&s_vfstusb, 0, sizeof(s_vfstusb)); +} + +static int tusb_open(const char *path, int flags, int mode) +{ + (void) mode; + (void) path; + s_vfstusb.flags = flags | O_NONBLOCK; // for now only non-blocking mode is implemented + return 0; +} + +static ssize_t tusb_write(int fd, const void *data, size_t size) +{ + FD_CHECK(fd, -1); + size_t written_sz = 0; + const char *data_c = (const char *)data; + _lock_acquire(&(s_vfstusb.write_lock)); + for (size_t i = 0; i < size; i++) { + int c = data_c[i]; + if (c != '\n') { + if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, c)) { + break; // can't write anymore + } + } else { + if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CRLF || s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CR) { + char cr = '\r'; + if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, cr)) { + break; // can't write anymore + } + } + if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CRLF || s_vfstusb.tx_mode == ESP_LINE_ENDINGS_LF) { + char lf = '\n'; + if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, lf)) { + break; // can't write anymore + } + } + } + written_sz++; + } + tud_cdc_n_write_flush(s_vfstusb.cdc_intf); + _lock_release(&(s_vfstusb.write_lock)); + return written_sz; +} + +static int tusb_close(int fd) +{ + FD_CHECK(fd, -1); + return 0; +} + +static ssize_t tusb_read(int fd, void *data, size_t size) +{ + FD_CHECK(fd, -1); + char *data_c = (char *) data; + size_t received = 0; + _lock_acquire(&(s_vfstusb.read_lock)); + + if (tud_cdc_n_available(s_vfstusb.cdc_intf) == 0) { + goto finish; + } + while (received < size) { + int c = tud_cdc_n_read_char(s_vfstusb.cdc_intf); + if (c == NONE) { // if data ends + break; + } + + // Handle line endings. From configured mode -> LF mode + if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CR) { + // Change CRs to newlines + if (c == '\r') { + c = '\n'; + } + } else if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CRLF) { + if (c == '\r') { + uint8_t next_char = NONE; + // Check if next char is newline. If yes, we got CRLF sequence + tud_cdc_n_peek(s_vfstusb.cdc_intf, &next_char); + if (next_char == '\n') { + c = tud_cdc_n_read_char(s_vfstusb.cdc_intf); // Remove '\n' from the fifo + } + } + } + + data_c[received] = (char) c; + ++received; + if (c == '\n') { + break; + } + } +finish: + _lock_release(&(s_vfstusb.read_lock)); + if (received > 0) { + return received; + } + errno = EWOULDBLOCK; + return -1; +} + +static int tusb_fstat(int fd, struct stat *st) +{ + FD_CHECK(fd, -1); + memset(st, 0, sizeof(*st)); + st->st_mode = S_IFCHR; + return 0; +} + +static int tusb_fcntl(int fd, int cmd, int arg) +{ + FD_CHECK(fd, -1); + int result = 0; + switch (cmd) { + case F_GETFL: + result = s_vfstusb.flags; + break; + case F_SETFL: + s_vfstusb.flags = arg; + break; + default: + result = -1; + errno = ENOSYS; + break; + } + return result; +} + +esp_err_t esp_vfs_tusb_cdc_unregister(char const *path) +{ + ESP_LOGD(TAG, "Unregistering CDC-VFS driver"); + int res; + + if (path == NULL) { // NULL means using the default path for unregistering: VFS_TUSB_PATH_DEFAULT + path = VFS_TUSB_PATH_DEFAULT; + } + res = strcmp(s_vfstusb.vfs_path, path); + + if (res) { + res = ESP_ERR_INVALID_ARG; + ESP_LOGE(TAG, "There is no CDC-VFS driver registered to path '%s' (err: 0x%x)", path, res); + return res; + } + + res = esp_vfs_unregister(s_vfstusb.vfs_path); + if (res != ESP_OK) { + ESP_LOGE(TAG, "Can't unregister CDC-VFS driver from '%s' (err: 0x%x)", s_vfstusb.vfs_path, res); + } else { + ESP_LOGD(TAG, "Unregistered CDC-VFS driver"); + vfstusb_deinit(); + } + return res; +} + +esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path) +{ + ESP_LOGD(TAG, "Registering CDC-VFS driver"); + int res; + if (!tusb_cdc_acm_initialized(cdc_intf)) { + ESP_LOGE(TAG, "TinyUSB CDC#%d is not initialized", cdc_intf); + return ESP_ERR_INVALID_STATE; + } + + res = vfstusb_init(cdc_intf, path); + if (res != ESP_OK) { + return res; + } + + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .close = &tusb_close, + .fcntl = &tusb_fcntl, + .fstat = &tusb_fstat, + .open = &tusb_open, + .read = &tusb_read, + .write = &tusb_write, + }; + + res = esp_vfs_register(s_vfstusb.vfs_path, &vfs, NULL); + if (res != ESP_OK) { + ESP_LOGE(TAG, "Can't register CDC-VFS driver (err: %x)", res); + } else { + ESP_LOGD(TAG, "CDC-VFS registered (%s)", s_vfstusb.vfs_path); + } + return res; +} + +void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode) +{ + _lock_acquire(&(s_vfstusb.read_lock)); + s_vfstusb.rx_mode = mode; + _lock_release(&(s_vfstusb.read_lock)); +} + +void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode) +{ + _lock_acquire(&(s_vfstusb.write_lock)); + s_vfstusb.tx_mode = mode; + _lock_release(&(s_vfstusb.write_lock)); +} diff --git a/examples/usb/device/usb_dongle/main/CLI_Commands.c b/examples/usb/device/usb_dongle/main/CLI_Commands.c new file mode 100644 index 000000000..dc61c3aeb --- /dev/null +++ b/examples/usb/device/usb_dongle/main/CLI_Commands.c @@ -0,0 +1,579 @@ +/* + * FreeRTOS V202107.00 + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_chip_info.h" +// #include "esp_spi_flash.h" +#include "esp_flash.h" +#include "FreeRTOS_CLI.h" + +#include "tinyusb.h" +#include "cmd_wifi.h" + +#define cliNEW_LINE "\r\n" + +char* null_password = ""; + +#if CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS +static BaseType_t prvTaskStatusCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); +#endif + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS +static BaseType_t prvStationCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +static BaseType_t prvScanCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +static BaseType_t prvAPCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +static BaseType_t prvSetWiFiModeCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +static BaseType_t prvSmartConfigCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); +#endif /* CFG_TUD_NCM || CFG_TUD_ECM_RNDIS */ + +static BaseType_t prvRamCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +static BaseType_t prvRestartCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +static BaseType_t prvGetVersionCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); + +#if CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS +/* Structure that defines the "task-stats" command line command. */ +static const CLI_Command_Definition_t xTaskStatus = { + "task-status", /* The command string to type. */ + "task-status: Displays the state of each task\r\n", + prvTaskStatusCommand, /* The function to run. */ + 0 /* No parameters are expected. */ +}; +#endif + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS +/* Structure that defines the "sta" command line command. */ +static const CLI_Command_Definition_t xStationCommand = { + "sta", /* The command string to type. */ + "sta -s [-p ]: join specified soft-AP\r\nsta -d: disconnect specified soft-AP\r\n", + prvStationCommand, /* The function to run. */ + -1 /* The user can enter any number of commands. */ +}; + +/* Structure that defines the "scan" command line command. */ +static const CLI_Command_Definition_t xScanCommand = { + "scan", /* The command string to type. */ + "scan []: SSID of AP want to be scanned\r\n", + prvScanCommand, /* The function to run. */ + -1 /* The user can enter any number of commands. */ +}; + +/* Structure that defines the "ap" command line command. */ +static const CLI_Command_Definition_t xAPCommand = { + "ap", /* The command string to type. */ + "ap []: configure ssid and password\r\n", + prvAPCommand, /* The function to run. */ + -1 /* The user can enter any number of commands. */ +}; + +/* Structure that defines the "mode" command line command. */ +static const CLI_Command_Definition_t xSetWiFiModeCommand = { + "mode", /* The command string to type. */ + "mode : station mode; ap mode\r\n", + prvSetWiFiModeCommand, /* The function to run. */ + 1 /* one parameters are expected. */ +}; + +/* Structure that defines the "smartconfig" command line command. */ +static const CLI_Command_Definition_t xSmartConfigCommand = { + "smartconfig", /* The command string to type. */ + "smartconfig [op]: op:1, start smartconfig; op:0, stop smartconfig\r\n", + prvSmartConfigCommand, /* The function to run. */ + 1 /* No parameters are expected. */ +}; +#endif /* CFG_TUD_NCM || CFG_TUD_ECM_RNDIS */ + +/* Structure that defines the "ram" command line command. */ +static const CLI_Command_Definition_t xRamCommand = { + "ram", /* The command string to type. */ + "ram: Get the current size of free heap memory and minimum size of free heap memory\r\n", + prvRamCommand, /* The function to run. */ + 0 /* No parameters are expected. */ +}; + +/* Structure that defines the "restart" command line command. */ +static const CLI_Command_Definition_t xRestartCommand = { + "restart", /* The command string to type. */ + "restart: Software reset of the chip\r\n", + prvRestartCommand, /* The function to run. */ + 0 /* No parameters are expected. */ +}; + +/* Structure that defines the "version" command line command. */ +static const CLI_Command_Definition_t xGetVersionCommand = { + "version", /* The command string to type. */ + "version: Get version of chip and SDK\r\n", + prvGetVersionCommand, /* The function to run. */ + 0 /* No parameters are expected. */ +}; + +/*-----------------------------------------------------------*/ + +void vRegisterCLICommands(void) +{ + FreeRTOS_CLICreatMux(); + + /* Register all the command line commands defined immediately above. */ +#if CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS + FreeRTOS_CLIRegisterCommand(&xTaskStatus); +#endif +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + FreeRTOS_CLIRegisterCommand(&xAPCommand); + FreeRTOS_CLIRegisterCommand(&xStationCommand); + FreeRTOS_CLIRegisterCommand(&xSetWiFiModeCommand); + FreeRTOS_CLIRegisterCommand(&xSmartConfigCommand); + FreeRTOS_CLIRegisterCommand(&xScanCommand); +#endif /* CFG_TUD_NCM || CFG_TUD_ECM_RNDIS */ + FreeRTOS_CLIRegisterCommand(&xRamCommand); + FreeRTOS_CLIRegisterCommand(&xRestartCommand); + FreeRTOS_CLIRegisterCommand(&xGetVersionCommand); +} +/*-----------------------------------------------------------*/ + +#if CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS +static BaseType_t prvTaskStatusCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + const char *const pcHeader = "Task State Priority Stack #\r\n************************************************\r\n"; + + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + char *data = (char *)malloc(512); + /* Generate a table of task stats. */ + strcpy(data, pcHeader); + vTaskList(data + strlen(pcHeader)); + printf("%s", data); + free(data); + + /* There is no more data to return after this single string, so return pdFALSE. */ + return pdFALSE; +} +#endif +/*-----------------------------------------------------------*/ + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS +static BaseType_t prvStationCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + char *pc1, *pc2, *pc3, *pc4, *pc5, *pc6; + BaseType_t xLength1, xLength2, xLength3, xLength4, xLength5, xLength6; + + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + /* Obtain the sixth parameter */ + pc6 = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 6, /* Return the Sixth parameter. */ + &xLength6 /* Store the parameter string length. */ + ); + + /* Obtain the fifth parameter */ + pc5 = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 5, /* Return the fifth parameter. */ + &xLength5 /* Store the parameter string length. */ + ); + + /* Obtain the fourth parameter */ + pc4 = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 4, /* Return the fourth parameter. */ + &xLength4 /* Store the parameter string length. */ + ); + + /* Obtain the third parameter. */ + pc3 = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 3, /* Return the third parameter. */ + &xLength3 /* Store the parameter string length. */ + ); + + /* Obtain the second parameter */ + pc2 = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 2, /* Return the second parameter. */ + &xLength2 /* Store the parameter string length. */ + ); + + /* Obtain the first parameter. */ + pc1 = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 1, /* Return the first parameter. */ + &xLength1 /* Store the parameter string length. */ + ); + + if (pc1 == NULL) { + wifi_cmd_query(); + return pdFALSE; + } else { + /* Sanity check something was returned. */ + configASSERT(pc1); + /* Terminate the string. */ + pc1[ xLength1 ] = 0x00; + + if (strncmp(pc1, "-d", strlen("-d")) == 0) { + if (wif_cmd_disconnect_wifi() == ESP_OK) { + sprintf(pcWriteBuffer, "OK\r\n"); + } else { + sprintf(pcWriteBuffer, "FAIL\r\n"); + } + return pdFALSE; + } else if (strncmp(pc1, "-s", strlen("-s")) != 0) { + sprintf(pcWriteBuffer, "Invalid parameter\r\n"); + return pdFALSE; + } + } + + if (pc2 == NULL) { + sprintf(pcWriteBuffer, "Invalid parameter\r\n"); + return pdFALSE; + } else { + if (pc3 == NULL) { + wifi_cmd_sta_join(pc2, null_password); + printf("the ssid is %s.\r\n", pc2); + printf("the ssid len is %d.\r\n", xLength2); + return pdFALSE; + } + } + + if (pc4 == NULL) { + wifi_cmd_sta_join(pc2, null_password); + printf("the ssid is %s.\r\n", pc2); + printf("the ssid len is %d.\r\n", (xLength2 + xLength3 + 1)); + return pdFALSE; + } + + if (pc5 == NULL) { + /* Terminate the string. */ + pc2[ xLength2 ] = 0x00; + + if (strncmp(pc3, "-p", strlen("-p")) != 0) { + sprintf(pcWriteBuffer, "Invalid parameter\r\n"); + return pdFALSE; + } + /* Terminate the string. */ + pc3[ xLength3 ] = 0x00; + wifi_cmd_sta_join(pc2, pc4); + printf("the ssid is %s, the password is %s.\r\n", pc2, pc4); + printf("the ssid len is %d, the password len is %d.\r\n", xLength2, xLength4); + return pdFALSE; + } + + if (pc6 == NULL) { + if (strncmp(pc3, "-p", strlen("-p")) == 0) { + if (strncmp(pc4, "-p", strlen("-p")) != 0) { + /* Terminate the string. */ + pc2[ xLength2 ] = 0x00; + pc3[ xLength3 ] = 0x00; + wifi_cmd_sta_join(pc2, pc4); + printf("the ssid is %s, the password is %s.\r\n", pc2, pc4); + printf("the ssid len is %d, the password len is %d.\r\n", xLength2, (xLength4 + xLength5 + 1)); + return pdFALSE; + } + } + /* Terminate the string. */ + pc3[ xLength3 ] = 0x00; + pc4[ xLength4 ] = 0x00; + wifi_cmd_sta_join(pc2, pc5); + printf("the ssid is %s, the password is %s.\r\n", pc2, pc5); + printf("the ssid len is %d, the password len is %d.\r\n", (xLength2 + xLength3 + 1), xLength5); + } else { + /* Terminate the string. */ + pc3[ xLength3 ] = 0x00; + pc4[ xLength4 ] = 0x00; + wifi_cmd_sta_join(pc2, pc5); + printf("the ssid is %s, the password is %s.\r\n", pc2, pc5); + printf("the ssid len is %d, the password len is %d.\r\n", (xLength2 + xLength3 + 1), (xLength5 + xLength6 + 1)); + } + + return pdFALSE; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvScanCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + const char *pcSSID; + BaseType_t SSIDLength; + + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + /* Obtain the SSID of AP . */ + pcSSID = FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 1, /* Return the first parameter. */ + &SSIDLength /* Store the parameter string length. */ + ); + + if (pcSSID == NULL) { + /* TODO */ + wifi_cmd_sta_scan(NULL); + } else { + /* TODO */ + wifi_cmd_sta_scan(pcSSID); + } + + /* There is no more data to return after this single string, so return + pdFALSE. */ + return pdFALSE; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvAPCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + char *pcSSID, *pcPassWord; + BaseType_t xSSIDLength, xPassWordLength; + + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + /* Obtain the ssid of AP. */ + pcPassWord = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 2, /* Return the second parameter. */ + &xPassWordLength /* Store the parameter string length. */ + ); + + /* Obtain the password of AP */ + pcSSID = (char *) FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 1, /* Return the first parameter. */ + &xSSIDLength /* Store the parameter string length. */ + ); + + if (pcSSID == NULL) { + wifi_cmd_query(); + return pdFALSE; + } + + /* Sanity check something was returned. */ + configASSERT(pcSSID); + + /* Terminate the string. */ + pcSSID[ xSSIDLength ] = 0x00; + + if (pcPassWord == NULL) { + /* TODO */ + wifi_cmd_ap_set(pcSSID, null_password); + printf("the ssid is %s.\r\n", pcSSID); + printf("the ssid len is %d.\r\n", xSSIDLength); + } else { + /* TODO */ + wifi_cmd_ap_set(pcSSID, pcPassWord); + printf("the ssid is %s, the password is %s.\r\n", pcSSID, pcPassWord); + printf("the ssid len is %d, the password len is %d.\r\n", xSSIDLength, xPassWordLength); + } + + return pdFALSE; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvSetWiFiModeCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + const char *pcWiFiMode; + BaseType_t WiFiModeLength; + + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + /* Obtain the parameter string. */ + pcWiFiMode = FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 1, /* Return the first parameter. */ + &WiFiModeLength /* Store the parameter string length. */ + ); + + /* Sanity check something was returned. */ + configASSERT(pcWiFiMode); + + if (wifi_cmd_set_mode((char *)pcWiFiMode) == ESP_FAIL) { + sprintf(pcWriteBuffer, "Invalid parameter\r\n"); + } + + /* There is no more data to return after this single string, so return + pdFALSE. */ + return pdFALSE; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvSmartConfigCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + const char *pcSmartConfig; + BaseType_t Length; + + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + /* Obtain the parameter string. */ + pcSmartConfig = FreeRTOS_CLIGetParameter + ( + pcCommandString, /* The command string itself. */ + 1, /* Return the first parameter. */ + &Length /* Store the parameter string length. */ + ); + + /* Sanity check something was returned. */ + configASSERT(pcSmartConfig); + + /* There are only two valid parameter values. */ + if (strncmp(pcSmartConfig, "0", strlen("0")) == 0) { + if (wifi_cmd_stop_smart_config() == ESP_OK) { + sprintf(pcWriteBuffer, "OK\r\n"); + } else { + sprintf(pcWriteBuffer, "FAIL\r\n"); + } + // sprintf(pcWriteBuffer, "Stop SmartConfig\r\n"); + } else if (strncmp(pcSmartConfig, "1", strlen("1")) == 0) { + if (wifi_cmd_start_smart_config() == ESP_FAIL) { + sprintf(pcWriteBuffer, "SmartConfig Task has been created, Don't create repeatedly\r\n"); + } + } else { + sprintf(pcWriteBuffer, "Valid parameters are '0' and '1'\r\n"); + } + + return pdFALSE; +} +/*-----------------------------------------------------------*/ +#endif /* CFG_TUD_NCM || CFG_TUD_ECM_RNDIS */ + +static BaseType_t prvRamCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + uint32_t size = esp_get_free_heap_size(); + + sprintf(pcWriteBuffer, "free heap size: %"PRIu32", min heap size: %"PRIu32"\r\n", size, heap_size); + + return pdFALSE; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvRestartCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + esp_restart(); + + return pdFALSE; +} +/*-----------------------------------------------------------*/ + +static BaseType_t prvGetVersionCommand(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) +{ + /* Remove compile time warnings about unused parameters, and check the + write buffer is not NULL. NOTE - for simplicity, this example assumes the + write buffer length is adequate, so does not check for buffer overflows. */ + (void) pcCommandString; + (void) xWriteBufferLen; + configASSERT(pcWriteBuffer); + memset(pcWriteBuffer, 0x00, xWriteBufferLen); + + esp_chip_info_t info; + esp_chip_info(&info); + uint32_t flash_size = 0; + esp_flash_get_physical_size(NULL, &flash_size); + sprintf(pcWriteBuffer, "IDF Version:%s\r\nChip info:\r\n\tcores:%d\r\n\tfeature:%s%s%s%s%"PRIu32"%s\r\n\trevision number:%d\r\n", + esp_get_idf_version(), + info.cores, + info.features & CHIP_FEATURE_WIFI_BGN ? "/802.11bgn" : "", + info.features & CHIP_FEATURE_BLE ? "/BLE" : "", + info.features & CHIP_FEATURE_BT ? "/BT" : "", + info.features & CHIP_FEATURE_EMB_FLASH ? "/Embedded-Flash:" : "/External-Flash:", + flash_size / (1024 * 1024), " MB", + info.revision); + + return pdFALSE; +} +/*-----------------------------------------------------------*/ diff --git a/examples/usb/device/usb_dongle/main/CMakeLists.txt b/examples/usb/device/usb_dongle/main/CMakeLists.txt new file mode 100644 index 000000000..6e41355cf --- /dev/null +++ b/examples/usb/device/usb_dongle/main/CMakeLists.txt @@ -0,0 +1,17 @@ +set(srcs) + +list(APPEND srcs + "usb_dongle_main.c" + "CLI_Commands.c" + "Command_Parse.c" + "cmd_wifi.c" + "data_back.c" + ) + +if(CONFIG_UART_ENABLE) + list(APPEND srcs + "uart.c") +endif() # CONFIG_UART_ENABLE + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS .) diff --git a/examples/usb/device/usb_dongle/main/Command_Parse.c b/examples/usb/device/usb_dongle/main/Command_Parse.c new file mode 100644 index 000000000..2f0fa34c4 --- /dev/null +++ b/examples/usb/device/usb_dongle/main/Command_Parse.c @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include "esp_err.h" + +#include "FreeRTOS_CLI.h" + +#include "data_back.h" + +/* Dimensions the buffer into which input characters are placed. */ +#define cmdMAX_INPUT_SIZE 80 + +/* Dimensions the buffer into which string outputs can be placed. */ +#define cmdMAX_OUTPUT_SIZE 1024 + +/* Dimensions the buffer passed to the recvfrom() call. */ +#define cmdSOCKET_INPUT_BUFFER_SIZE 80 + +void Command_Parse(char* Cmd) +{ + size_t lBytes, lByte; + signed char cInChar, cInputIndex = 0; + static char cInputString[ cmdMAX_INPUT_SIZE ], cOutputString[ cmdMAX_OUTPUT_SIZE ], cLocalBuffer[ cmdSOCKET_INPUT_BUFFER_SIZE ]; + BaseType_t xMoreDataToFollow; + uint8_t *data = (uint8_t *)heap_caps_malloc(256, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + char *format_buf = (char *)malloc(64); + + lBytes = strlen(Cmd); + memcpy(cLocalBuffer, Cmd, lBytes); + + /* Process each received byte in turn. */ + lByte = 0; + while (lByte < lBytes) { + /* The next character in the input buffer. */ + cInChar = cLocalBuffer[ lByte ]; + lByte++; + + /* Newline characters are taken as the end of the command string. */ + if (cInChar == '\n') { + size_t lenth = sprintf(format_buf, "\r\n"); + esp_data_back(format_buf, lenth, ENABLE_FLUSH); + /* Process the input string received prior to the newline. */ + do { + memset(data, 0x00, 256); + /* Pass the string to FreeRTOS+CLI. */ + xMoreDataToFollow = FreeRTOS_CLIProcessCommand(cInputString, cOutputString, cmdMAX_OUTPUT_SIZE); + printf("%s%d\r\n", cOutputString, strlen(cOutputString)); + /* Send the output generated by the command's implementation. */ + memcpy(data, (uint8_t*)cOutputString, strlen(cOutputString)); + esp_data_back(data, strlen(cOutputString), DISABLE_FLUSH); + } while (xMoreDataToFollow != pdFALSE); /* Until the command does not generate any more output. */ + + /* All the strings generated by the command processing have been sent. + Clear the input string ready to receive the next command. */ + lenth = sprintf(format_buf, ">"); + esp_data_back(format_buf, lenth, ENABLE_FLUSH); + cInputIndex = 0; + memset(cInputString, 0x00, cmdMAX_INPUT_SIZE); + free(data); + } else { + if (cInChar == '\r') { + /* Ignore the character. Newlines are used to detect the end of the input string. */ + } else if (cInChar == '\b') { + /* Backspace was pressed. Erase the last character in the string - if any. */ + if (cInputIndex > 0) { + cInputIndex--; + cInputString[ cInputIndex ] = '\0'; + } + } else { + /* A character was entered. Add it to the string entered so far. + When a \n is entered the complete string will be passed to the command interpreter.*/ + if (cInputIndex < cmdMAX_INPUT_SIZE) { + cInputString[ cInputIndex ] = cInChar; + cInputIndex++; + } + } + } + } +} diff --git a/examples/usb/device/usb_dongle/main/Kconfig.projbuild b/examples/usb/device/usb_dongle/main/Kconfig.projbuild new file mode 100644 index 000000000..7bfa284b4 --- /dev/null +++ b/examples/usb/device/usb_dongle/main/Kconfig.projbuild @@ -0,0 +1,59 @@ +menu "Example Configuration" + + config UART_ENABLE + depends on !TINYUSB_CDCACM_ENABLED + bool "Use Uart to Communication" + default y + help + Enable Uart. + + config UART_PORT_NUM + depends on UART_ENABLE + int "UART port number" + range 0 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 + range 0 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 + default 1 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 + help + UART communication port number for the example. + See UART documentation for available port numbers. + + config UART_BAUD_RATE + depends on UART_ENABLE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config UART_RXD + depends on UART_ENABLE + int "UART RXD pin number" + range 0 34 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 5 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config UART_TXD + depends on UART_ENABLE + int "UART TXD pin number" + range 0 34 if IDF_TARGET_ESP32 + range 0 46 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + range 0 19 if IDF_TARGET_ESP32C3 + default 4 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + config TASK_STACK_SIZE + depends on UART_ENABLE + int "UART echo example task stack size" + range 1024 16384 + default 4096 + help + Defines stack size for UART echo example. Insufficient stack size can cause crash. + +endmenu # "Example Configuration" diff --git a/examples/usb/device/usb_dongle/main/cmd_wifi.c b/examples/usb/device/usb_dongle/main/cmd_wifi.c new file mode 100644 index 000000000..eeb2036ad --- /dev/null +++ b/examples/usb/device/usb_dongle/main/cmd_wifi.c @@ -0,0 +1,457 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "tinyusb.h" + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "lwip/ip_addr.h" +#include "lwip/netif.h" + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_wifi.h" +#include "esp_eap_client.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_system.h" +#include "esp_smartconfig.h" +#include "esp_private/wifi.h" + +#include "tinyusb_net.h" +#include "tinyusb.h" +#include "data_back.h" + +static const char *TAG = "esp_network"; + +static TaskHandle_t Smart_Config_Handle = NULL; + +static bool reconnect = true; +static bool wifi_start = false; +static bool smart_config = false; + +static EventGroupHandle_t wifi_event_group; +const int CONNECTED_BIT = BIT0; +const int DISCONNECTED_BIT = BIT1; +const int ESPTOUCH_DONE_BIT = BIT2; +esp_netif_t *ap_netif; +esp_netif_t *sta_netif; + +DRAM_ATTR uint8_t tud_network_mac_address[6] = {0x02, 0x02, 0x84, 0x6A, 0x96, 0x00}; +bool s_wifi_is_connected = false; + +static esp_err_t pkt_wifi2usb(void *buffer, uint16_t len, void *eb); + +static void scan_done_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + uint16_t sta_number = 0; + uint8_t i; + wifi_ap_record_t *ap_list_buffer; + char *scan_buf = (char *)malloc(64); + size_t lenth = 0; + + esp_wifi_scan_get_ap_num(&sta_number); + if (!sta_number) { + ESP_LOGE(TAG, "No AP found"); + lenth = sprintf(scan_buf, "\r\nNo AP found\r\n>"); + esp_data_back(scan_buf, lenth, ENABLE_FLUSH); + free(scan_buf); + return; + } + + ap_list_buffer = malloc(sta_number * sizeof(wifi_ap_record_t)); + if (ap_list_buffer == NULL) { + ESP_LOGE(TAG, "Failed to malloc buffer to print scan results"); + lenth = sprintf(scan_buf, "\r\nFailed to malloc\r\n>"); + esp_data_back(scan_buf, lenth, ENABLE_FLUSH); + free(scan_buf); + return; + } + + if (esp_wifi_scan_get_ap_records(&sta_number, (wifi_ap_record_t *)ap_list_buffer) == ESP_OK) { + for (i = 0; i < sta_number; i++) { + ESP_LOGI(TAG, "[%s][rssi=%d]", ap_list_buffer[i].ssid, ap_list_buffer[i].rssi); + lenth = sprintf(scan_buf, "\r\n[%s][rssi=%d]", ap_list_buffer[i].ssid, ap_list_buffer[i].rssi); + esp_data_back(scan_buf, lenth, DISABLE_FLUSH); + } + } + lenth = sprintf(scan_buf, "\r\n>"); + esp_data_back(scan_buf, lenth, ENABLE_FLUSH); + free(scan_buf); + free(ap_list_buffer); + ESP_LOGI(TAG, "sta scan done"); +} + +static void wifi_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + char *wifi_event_buf = (char *)malloc(128); + size_t lenth = 0; + + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { + uint8_t *tud_network_mac_address_dummy = tud_network_mac_address; + esp_wifi_get_mac(ESP_IF_WIFI_STA, tud_network_mac_address_dummy); + // esp_wifi_connect(); + wifi_start = true; + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + ESP_LOGI(TAG, "Wi-Fi STA disconnected"); + s_wifi_is_connected = false; + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, NULL); + + if (reconnect && tud_ready()) { + ESP_LOGI(TAG, "sta disconnect, reconnect..."); + esp_wifi_connect(); + } else { + ESP_LOGI(TAG, "sta disconnect"); + } + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, DISCONNECTED_BIT); + ESP_LOGI(TAG, "DISCONNECTED_BIT\r\n"); + } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { + if (smart_config == false) { + ESP_LOGI(TAG, "Wi-Fi STA connected"); + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, pkt_wifi2usb); + s_wifi_is_connected = true; + xEventGroupClearBits(wifi_event_group, DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + ESP_LOGI(TAG, "CONNECTED_BIT\r\n"); + } + } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) { + ESP_LOGI(TAG, "Scan done"); + } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) { + ESP_LOGI(TAG, "Found channel"); + } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { + ESP_LOGI(TAG, "Got SSID and password"); + + smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data; + wifi_config_t wifi_config; + uint8_t ssid[33] = { 0 }; + uint8_t password[65] = { 0 }; + uint8_t rvd_data[33] = { 0 }; + + bzero(&wifi_config, sizeof(wifi_config_t)); + memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid)); + memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password)); + wifi_config.sta.bssid_set = evt->bssid_set; + if (wifi_config.sta.bssid_set == true) { + memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid)); + } + + memcpy(ssid, evt->ssid, sizeof(evt->ssid)); + memcpy(password, evt->password, sizeof(evt->password)); + ESP_LOGI(TAG, "SSID:%s PASSWORD:%s", ssid, password); + lenth = sprintf(wifi_event_buf, "SSID:%s,PASSWORD:%s\r\n", ssid, password); + esp_data_back(wifi_event_buf, lenth, ENABLE_FLUSH); + + if (evt->type == SC_TYPE_ESPTOUCH_V2) { + ESP_ERROR_CHECK(esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data))); + ESP_LOGI(TAG, "RVD_DATA:"); + for (int i = 0; i < 33; i++) { + printf("%02x ", rvd_data[i]); + } + printf("\n"); + } + + ESP_ERROR_CHECK(esp_wifi_disconnect()); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + esp_wifi_connect(); + } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) { + ESP_LOGI(TAG, "Send ACK done"); + xEventGroupSetBits(wifi_event_group, ESPTOUCH_DONE_BIT); + esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_STA, pkt_wifi2usb); + s_wifi_is_connected = true; + xEventGroupClearBits(wifi_event_group, DISCONNECTED_BIT); + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + } + free(wifi_event_buf); +} + +uint32_t wifi_get_local_ip(void) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + esp_netif_t * netif = ap_netif; + esp_netif_ip_info_t ip_info; + wifi_mode_t mode; + + esp_wifi_get_mode(&mode); + if (WIFI_MODE_STA == mode) { + bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + netif = sta_netif; + } else { + ESP_LOGE(TAG, "sta has no IP"); + return 0; + } + } + + esp_netif_get_ip_info(netif, &ip_info); + return ip_info.ip.addr; +} + +esp_err_t wifi_cmd_set_mode(char* mode) +{ + esp_err_t ret = ESP_FAIL; + if (!strncmp(mode, "sta", strlen("sta"))) { + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ret = ESP_OK; + } else if (!strncmp(mode, "ap", strlen("ap"))) { + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ret = ESP_OK; + } + + return ret; +} + +esp_err_t wifi_cmd_sta_join(const char* ssid, const char* pass) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + + wifi_config_t wifi_config = { 0 }; + wifi_config.sta.pmf_cfg.capable = true; + + strlcpy((char*) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid)); + if (pass) { + strlcpy((char*) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password)); + } + + if (bits & CONNECTED_BIT) { + reconnect = false; + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + ESP_ERROR_CHECK(esp_wifi_disconnect()); + + xEventGroupWaitBits(wifi_event_group, DISCONNECTED_BIT, 0, 1, 1000 / portTICK_PERIOD_MS); + } + + reconnect = true; + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + esp_wifi_connect(); + + EventBits_t status = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 5000 / portTICK_PERIOD_MS); + + if (status & CONNECTED_BIT) { + ESP_LOGI(TAG, "connect success\n"); + + return ESP_OK; + } + + ESP_LOGE(TAG, "Connect fail\n"); + reconnect = false; + return ESP_FAIL; +} + +esp_err_t wif_cmd_disconnect_wifi(void) +{ + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + reconnect = false; + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + ESP_ERROR_CHECK(esp_wifi_disconnect()); + + xEventGroupWaitBits(wifi_event_group, DISCONNECTED_BIT, 0, 1, portTICK_PERIOD_MS); + return ESP_OK; + } + return ESP_FAIL; +} + +esp_err_t wifi_cmd_sta_scan(const char* ssid) +{ + wifi_scan_config_t scan_config = { 0 }; + scan_config.ssid = (uint8_t *) ssid; + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + esp_wifi_scan_start(&scan_config, false); + + return ESP_OK; +} + +esp_err_t wifi_cmd_ap_set(const char* ssid, const char* pass) +{ + wifi_config_t wifi_config = { + .ap = { + .ssid = "", + .ssid_len = 0, + .max_connection = 4, + .password = "", + .authmode = WIFI_AUTH_WPA_WPA2_PSK + }, + }; + + reconnect = false; + strlcpy((char*) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid)); + if (pass) { + if (strlen(pass) != 0 && strlen(pass) < 8) { + reconnect = true; + ESP_LOGE(TAG, "password less than 8"); + return ESP_FAIL; + } + strlcpy((char*) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password)); + } + + if (strlen(pass) == 0) { + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + } + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); + return ESP_OK; +} + +esp_err_t wifi_cmd_query(void) +{ + wifi_config_t cfg = {0}; + wifi_mode_t mode; + char *query_buf = (char *)malloc(128); + + memset(&cfg, 0, sizeof(cfg)); + + esp_wifi_get_mode(&mode); + if (WIFI_MODE_AP == mode) { + esp_wifi_get_config(WIFI_IF_AP, &cfg); + ESP_LOGI(TAG, "AP mode, %s %s", cfg.ap.ssid, cfg.ap.password); + size_t lenth = sprintf(query_buf, "AP mode:%s,%s\r\n", cfg.ap.ssid, cfg.ap.password); + esp_data_back(query_buf, lenth, ENABLE_FLUSH); + } else if (WIFI_MODE_STA == mode) { + int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, 0, 1, 0); + if (bits & CONNECTED_BIT) { + esp_wifi_get_config(WIFI_IF_STA, &cfg); + ESP_LOGI(TAG, "STA mode: %s,%d,%d,%d", cfg.sta.ssid, cfg.sta.channel, cfg.sta.listen_interval, cfg.sta.threshold.authmode); + size_t lenth = sprintf(query_buf, "STA mode:%s,%d,%d,%d\r\n", cfg.sta.ssid, cfg.sta.channel, cfg.sta.listen_interval, cfg.sta.threshold.authmode); + esp_data_back(query_buf, lenth, ENABLE_FLUSH); + } else { + ESP_LOGI(TAG, "sta mode, disconnected"); + } + } else { + ESP_LOGI(TAG, "NULL mode"); + return ESP_FAIL; + } + free(query_buf); + return ESP_OK; +} + +static void smartconfig_task(void * parm) +{ + EventBits_t uxBits; + char *sm_buf = (char *)malloc(52); + ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH)); + smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); + wif_cmd_disconnect_wifi(); + + ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); + while (1) { + uxBits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); + if (uxBits & CONNECTED_BIT) { + ESP_LOGI(TAG, "WiFi Connected to ap"); + } + + if (uxBits & ESPTOUCH_DONE_BIT) { + ESP_LOGI(TAG, "smartconfig over"); + size_t lenth = sprintf(sm_buf, "OK\r\n>"); + esp_data_back(sm_buf, lenth, ENABLE_FLUSH); + esp_smartconfig_stop(); + smart_config = false; + ESP_LOGI(TAG, "free the buffer taken by smartconfig"); + free(sm_buf); + vTaskDelete(NULL); + } + } +} + +esp_err_t wifi_cmd_start_smart_config(void) +{ + if (wifi_start) { + if (smart_config) { + ESP_LOGE(TAG, "SmartConfig Task is Created\r\n"); + return ESP_FAIL; + } + xTaskCreate(smartconfig_task, "smartconfig_task", 4096, NULL, 3, &Smart_Config_Handle); + ESP_LOGI(TAG, "Smart Config Task Create Success\r\n"); + smart_config = true; + return ESP_OK; + } + return ESP_FAIL; +} + +esp_err_t wifi_cmd_stop_smart_config(void) +{ + if (smart_config) { + ESP_LOGI(TAG, "stop smartconfig"); + esp_smartconfig_stop(); + smart_config = false; + ESP_LOGI(TAG, "free the buffer taken by smartconfig"); + vTaskDelete(Smart_Config_Handle); + ESP_LOGI(TAG, "delete OK\r\n"); + return ESP_OK; + } + return ESP_FAIL; +} + +void wifi_buffer_free(void *buffer, void *ctx) +{ + esp_wifi_internal_free_rx_buffer(buffer); +} + +esp_err_t wifi_recv_callback(void *buffer, uint16_t len, void *ctx) +{ + if (s_wifi_is_connected) { + esp_wifi_internal_tx(ESP_IF_WIFI_STA, buffer, len); + } + return ESP_OK; +} + +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; +} + +/* Initialize Wi-Fi as sta and set scan method */ +void initialise_wifi(void) +{ + esp_log_level_set("wifi", ESP_LOG_WARN); + static bool initialized = false; + + if (initialized) { + return; + } + + ESP_ERROR_CHECK(esp_netif_init()); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ap_netif = esp_netif_create_default_wifi_ap(); + assert(ap_netif); + sta_netif = esp_netif_create_default_wifi_sta(); + assert(sta_netif); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, + WIFI_EVENT_SCAN_DONE, + &scan_done_handler, + NULL, + NULL)); + + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + initialized = true; +} + +#endif /* CFG_TUD_NCM || CFG_TUD_ECM_RNDIS */ diff --git a/examples/usb/device/usb_dongle/main/cmd_wifi.h b/examples/usb/device/usb_dongle/main/cmd_wifi.h new file mode 100644 index 000000000..eac6b657a --- /dev/null +++ b/examples/usb/device/usb_dongle/main/cmd_wifi.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __CMD_WIFI_H +#define __CMD_WIFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +void initialise_wifi(void); + +uint32_t wifi_get_local_ip(void); + +esp_err_t wifi_cmd_sta_join(const char* ssid, const char* pass); + +esp_err_t wifi_cmd_ap_set(const char* ssid, const char* pass); + +esp_err_t wifi_cmd_sta_scan(const char* ssid); + +esp_err_t wifi_cmd_query(void); + +esp_err_t wifi_cmd_set_mode(char* mode); + +esp_err_t wifi_cmd_start_smart_config(void); + +esp_err_t wifi_cmd_stop_smart_config(void); + +esp_err_t wif_cmd_disconnect_wifi(void); + +void wifi_buffer_free(void *buffer, void *ctx); + +esp_err_t wifi_recv_callback(void *buffer, uint16_t len, void *ctx); + +#ifdef __cplusplus +} +#endif + +#endif // __CMD_WIFI_H diff --git a/examples/usb/device/usb_dongle/main/data_back.c b/examples/usb/device/usb_dongle/main/data_back.c new file mode 100644 index 000000000..15e66b206 --- /dev/null +++ b/examples/usb/device/usb_dongle/main/data_back.c @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "data_back.h" +#include "sdkconfig.h" +#include "tinyusb.h" + +#ifndef CONFIG_UART_ENABLE +#define CONFIG_UART_ENABLE 0 +#endif + +#if CONFIG_UART_ENABLE +#include "driver/uart.h" +#define UART_NUM (CONFIG_UART_PORT_NUM) +#elif CFG_TUD_CDCACM +#include "tusb_cdc_acm.h" +#define ITF_NUM_CDC 0 +#endif + +void esp_data_back(void* data_buf, size_t lenth, bool flush) +{ +#if CONFIG_UART_ENABLE + uart_write_bytes(UART_NUM, (char*)data_buf, lenth); +#elif CFG_TUD_CDCACM + tinyusb_cdcacm_write_queue(ITF_NUM_CDC, (uint8_t*)data_buf, lenth); + if (flush) { + tinyusb_cdcacm_write_flush(ITF_NUM_CDC, 0); + } +#endif /* CONFIG_UART_ENABLE */ +} diff --git a/examples/usb/device/usb_dongle/main/data_back.h b/examples/usb/device/usb_dongle/main/data_back.h new file mode 100644 index 000000000..fc9b9ef6b --- /dev/null +++ b/examples/usb/device/usb_dongle/main/data_back.h @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define ENABLE_FLUSH 1 +#define DISABLE_FLUSH 0 + +void esp_data_back(void* data_buf, size_t lenth, bool flush); diff --git a/examples/usb/device/usb_dongle/main/idf_component.yml b/examples/usb/device/usb_dongle/main/idf_component.yml new file mode 100644 index 000000000..e6a86d849 --- /dev/null +++ b/examples/usb/device/usb_dongle/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + idf: ">=5.0" diff --git a/examples/usb/device/usb_dongle/main/uart.c b/examples/usb/device/usb_dongle/main/uart.c new file mode 100644 index 000000000..3073f1e6d --- /dev/null +++ b/examples/usb/device/usb_dongle/main/uart.c @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/uart.h" +#include "esp_log.h" + +static const char *TAG = "UART"; + +/** + * - Port: UART0 + * - Receive (Rx) buffer: on + * - Transmit (Tx) buffer: off + * - Flow control: off + * - Event queue: on + * - Pin assignment: TxD (default), RxD (default) + */ + +#define UART_TXD (CONFIG_UART_TXD) +#define UART_RXD (CONFIG_UART_RXD) +#define UART_RTS (UART_PIN_NO_CHANGE) +#define UART_CTS (UART_PIN_NO_CHANGE) + +#define UART_NUM (CONFIG_UART_PORT_NUM) +#define UART_BAUD_RATE (CONFIG_UART_BAUD_RATE) +#define UART_TASK_STACK_SIZE (CONFIG_TASK_STACK_SIZE) + +#define PATTERN_CHR_NUM (3) /*!< Set the number of consecutive and identical characters received by receiver which defines a UART pattern*/ + +#define BUF_SIZE (1024) +#define RD_BUF_SIZE (BUF_SIZE) + +void Command_Parse(char* Cmd); + +static QueueHandle_t uart_queue; + +static void uart_receive_task(void *pvParameters) +{ + uart_event_t event; + uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE); + for (;;) { + //Waiting for UART event. + if (xQueueReceive(uart_queue, (void *)&event, portMAX_DELAY)) { + bzero(dtmp, RD_BUF_SIZE); + ESP_LOGI(TAG, "uart[%d] event:", UART_NUM); + switch (event.type) { + //Event of UART receving data + /*We'd better handler data event fast, there would be much more data events than + other types of events. If we take too much time on data event, the queue might + be full.*/ + case UART_DATA: + ESP_LOGI(TAG, "[UART DATA]: %d", event.size); + uart_read_bytes(UART_NUM, dtmp, event.size, portMAX_DELAY); + ESP_LOGI(TAG, "[DATA EVT]:"); + Command_Parse((char*)dtmp); + break; + //Event of HW FIFO overflow detected + case UART_FIFO_OVF: + ESP_LOGI(TAG, "hw fifo overflow"); + // If fifo overflow happened, you should consider adding flow control for your application. + // The ISR has already reset the rx FIFO, + // As an example, we directly flush the rx buffer here in order to read more data. + uart_flush_input(UART_NUM); + xQueueReset(uart_queue); + break; + //Event of UART ring buffer full + case UART_BUFFER_FULL: + ESP_LOGI(TAG, "ring buffer full"); + // If buffer full happened, you should consider encreasing your buffer size + // As an example, we directly flush the rx buffer here in order to read more data. + uart_flush_input(UART_NUM); + xQueueReset(uart_queue); + break; + //Event of UART RX break detected + case UART_BREAK: + ESP_LOGI(TAG, "uart rx break"); + break; + //Event of UART parity check error + case UART_PARITY_ERR: + ESP_LOGI(TAG, "uart parity error"); + break; + //Event of UART frame error + case UART_FRAME_ERR: + ESP_LOGI(TAG, "uart frame error"); + break; + //Others + default: + ESP_LOGI(TAG, "uart event type: %d", event.type); + break; + } + } + } + free(dtmp); + dtmp = NULL; + vTaskDelete(NULL); +} + +void initialise_uart(void) +{ + /* Configure parameters of an UART driver, + * communication pins and install the driver */ + uart_config_t uart_config = { + .baud_rate = UART_BAUD_RATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_APB, + }; + //Install UART driver, and get the queue. + uart_driver_install(UART_NUM, BUF_SIZE, BUF_SIZE * 4, 20, &uart_queue, 0); + uart_param_config(UART_NUM, &uart_config); + + //Set UART log level + esp_log_level_set(TAG, ESP_LOG_INFO); + //Set UART pins (using UART0 default pins ie no changes.) + ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TXD, UART_RXD, UART_RTS, UART_CTS)); + + //Create a task to handler UART event from ISR + xTaskCreate(uart_receive_task, "uart_receive_task", UART_TASK_STACK_SIZE, NULL, 12, NULL); +} diff --git a/examples/usb/device/usb_dongle/main/usb_dongle_main.c b/examples/usb/device/usb_dongle/main/usb_dongle_main.c new file mode 100644 index 000000000..d7a2f9714 --- /dev/null +++ b/examples/usb/device/usb_dongle/main/usb_dongle_main.c @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_mac.h" +#include "sdkconfig.h" + +#include "cmd_wifi.h" + +#include "tinyusb.h" +#include "tinyusb_net.h" +#include "tusb_console.h" +#include "tusb_bth.h" + +#if CFG_TUD_CDC +#include "tusb_cdc_acm.h" +static uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; +void Command_Parse(char* Cmd); +tinyusb_config_cdcacm_t amc_cfg; +#elif CONFIG_UART_ENABLE +void initialise_uart(void); +#endif + +#ifdef CONFIG_HEAP_TRACING +#include "esp_heap_trace.h" +#define NUM_RECORDS 100 +static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM +#endif /* CONFIG_HEAP_TRACING */ + +static const char *TAG = "USB_Dongle"; + +/* + * Register commands that can be used with FreeRTOS+CLI through the UDP socket. + * The commands are defined in CLI-commands.c. + */ +void vRegisterCLICommands(void); + +#if CFG_TUD_CDC +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(0, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + if (ret == ESP_OK) { + buf[rx_size] = '\0'; + Command_Parse((char*)buf); + } else { + ESP_LOGE(TAG, "itf %d: itf Read error", itf); + } +} + +void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) +{ + int dtr = event->line_state_changed_data.dtr; + int rst = event->line_state_changed_data.rts; + ESP_LOGI(TAG, "Line state changed! itf:%d dtr:%d, rst:%d", itf, dtr, rst); +} +#endif /* CFG_TUD_CDC */ + +void app_main(void) +{ + // Initialize NVS + 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 initialization"); + + tinyusb_config_t tusb_cfg = { + .external_phy = false // In the most cases you need to use a `false` value + }; + + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + +#if CFG_TUD_NCM || CFG_TUD_ECM_RNDIS + tinyusb_net_config_t tusb_net_cfg = { + .on_recv_callback = wifi_recv_callback, + .free_tx_buffer = wifi_buffer_free, + }; + + esp_read_mac(tusb_net_cfg.mac_addr, ESP_MAC_WIFI_STA); + + tinyusb_net_init(TINYUSB_USBDEV_0, &tusb_net_cfg); + initialise_wifi(); +#endif /* CFG_TUD_NCM || CFG_TUD_ECM_RNDIS */ + +#if CFG_TUD_BTH + // init ble controller + tusb_bth_init(); +#endif /* CFG_TUD_BTH */ + +#if CFG_TUD_CDC + tinyusb_config_cdcacm_t amc_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = TINYUSB_CDC_ACM_0, + .rx_unread_buf_sz = 128, + .callback_rx = &tinyusb_cdc_rx_callback, // the first way to register a callback + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback, + .callback_line_coding_changed = NULL + }; + + ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); + esp_tusb_init_console(TINYUSB_CDC_ACM_0); +#elif CONFIG_UART_ENABLE + initialise_uart(); +#endif /* CFG_TUD_CDC */ + + ESP_LOGI(TAG, "USB initialization DONE"); + +#ifdef CONFIG_HEAP_TRACING + heap_trace_init_standalone(trace_record, NUM_RECORDS); + heap_trace_start(HEAP_TRACE_LEAKS); +#endif + + /* Register commands with the FreeRTOS+CLI command interpreter. */ + vRegisterCLICommands(); +} diff --git a/examples/usb/device/usb_dongle/sdkconfig.ci.bth b/examples/usb/device/usb_dongle/sdkconfig.ci.bth new file mode 100644 index 000000000..509ae896f --- /dev/null +++ b/examples/usb/device/usb_dongle/sdkconfig.ci.bth @@ -0,0 +1,4 @@ +# Only build for esp32s3 +CONFIG_IDF_TARGET="esp32s3" +CONFIG_TINYUSB_NET_MODE_NONE=y +CONFIG_TINYUSB_BTH_ENABLED=y diff --git a/examples/usb/device/usb_dongle/sdkconfig.ci.dfu b/examples/usb/device/usb_dongle/sdkconfig.ci.dfu new file mode 100644 index 000000000..fe0096eae --- /dev/null +++ b/examples/usb/device/usb_dongle/sdkconfig.ci.dfu @@ -0,0 +1 @@ +CONFIG_TINYUSB_DFU_ENABLED=y diff --git a/examples/usb/device/usb_dongle/sdkconfig.ci.ecm b/examples/usb/device/usb_dongle/sdkconfig.ci.ecm new file mode 100644 index 000000000..e55fb3a28 --- /dev/null +++ b/examples/usb/device/usb_dongle/sdkconfig.ci.ecm @@ -0,0 +1 @@ +CONFIG_TINYUSB_NET_MODE_ECM=y diff --git a/examples/usb/device/usb_dongle/sdkconfig.ci.ncm b/examples/usb/device/usb_dongle/sdkconfig.ci.ncm new file mode 100644 index 000000000..65730e3ed --- /dev/null +++ b/examples/usb/device/usb_dongle/sdkconfig.ci.ncm @@ -0,0 +1 @@ +CONFIG_TINYUSB_NET_MODE_NCM=y diff --git a/examples/usb/device/usb_dongle/sdkconfig.ci.rndis b/examples/usb/device/usb_dongle/sdkconfig.ci.rndis new file mode 100644 index 000000000..b897c133a --- /dev/null +++ b/examples/usb/device/usb_dongle/sdkconfig.ci.rndis @@ -0,0 +1 @@ +CONFIG_TINYUSB_NET_MODE_RNDIS=y diff --git a/examples/usb/device/usb_dongle/sdkconfig.defaults b/examples/usb/device/usb_dongle/sdkconfig.defaults new file mode 100644 index 000000000..807571c9a --- /dev/null +++ b/examples/usb/device/usb_dongle/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) 5.2.1 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_TWO_OTA=y +CONFIG_BT_ENABLED=y +CONFIG_BT_CONTROLLER_ONLY=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_FREERTOS_UNICORE=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_TINYUSB_DEBUG_LEVEL=0 +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_RX_BUFSIZE=128 +CONFIG_TINYUSB_CDC_TX_BUFSIZE=3072 +CONFIG_TINYUSB_NET_MODE_RNDIS=y diff --git a/tools/ci/check_copyright_config.yaml b/tools/ci/check_copyright_config.yaml index 9a388da2e..b3f6f2e15 100644 --- a/tools/ci/check_copyright_config.yaml +++ b/tools/ci/check_copyright_config.yaml @@ -77,3 +77,5 @@ ignore: # You can also select ignoring files here - 'examples/usb/host/usb_cdc_4g_module/components/json_parse/**/*' - 'tools/ci/check_copyright_ignore.txt' - 'tools/cmake_utilities/test_apps/pytest_cmake_utilities.py' + - 'examples/usb/device/usb_dongle/main/CLI_Commands.c' + - 'examples/usb/device/usb_dongle/components/FreeRTOS-Plus-CLI/**/*'