diff --git a/.github/ci/prepare-libs.sh b/.github/ci/prepare-libs.sh index b6c7b5fe..6c2e44d5 100644 --- a/.github/ci/prepare-libs.sh +++ b/.github/ci/prepare-libs.sh @@ -14,5 +14,6 @@ cp -r ./libraries/TFT_eSPI-2.5.43 $HOME/arduino_ide/libraries/ cp -r ./libraries/lvgl-8.2.0 $HOME/arduino_ide/libraries/ cp -r ./libraries/EspLuaEngine-1.0.2 $HOME/arduino_ide/libraries/ cp -r ./libraries/BMx280MI-1.2.0 $HOME/arduino_ide/libraries/ +cp -r ./libraries/esp32-usb-serial-1.0.1 $HOME/arduino_ide/libraries/ #TODO add SDFat libraries according version and target diff --git a/libraries/esp32-usb-serial-1.0.1/.gitignore b/libraries/esp32-usb-serial-1.0.1/.gitignore new file mode 100644 index 00000000..259148fa --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/libraries/esp32-usb-serial-1.0.1/LICENSE b/libraries/esp32-usb-serial-1.0.1/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/LICENSE @@ -0,0 +1,201 @@ + 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/libraries/esp32-usb-serial-1.0.1/README.md b/libraries/esp32-usb-serial-1.0.1/README.md new file mode 100644 index 00000000..39b9a870 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/README.md @@ -0,0 +1,30 @@ +# esp32-usb-serial +Arduino Library to use USB as OTG on ESP32 capable devices based on espressif IDF components + +The code use the following versions of the components: +``` +## IDF Component Manager Manifest File +dependencies: + usb_host_ch34x_vcp: "^2" + usb_host_cp210x_vcp: "^2" + usb_host_ftdi_vcp: "^2" + usb_host_vcp: "^1" + idf: ">=5.1.0" +``` + +It support the following USB to Serial chips: +- CH34X +- CP210x +- FTDI FT23x + +The library is based on the IDF USB Host VCP component and it is a wrapper to use it on Arduino IDE. + +Tested on ESP32S3, but should work on S2 based devices. + +The sample code is a simple USB<->Serial bridge, it reads from the USB and writes to the Serial and viceversa. + +I need IDF 5.1 to use the USB Host VCP component, so I had to use the latest version of the arduino-esp32 core, which is 3.0.0-alpha3 at the time of writing this. + +> [!IMPORTANT] +> +>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/libraries/esp32-usb-serial-1.0.1/example/usb-serial/usb-serial.ino b/libraries/esp32-usb-serial-1.0.1/example/usb-serial/usb-serial.ino new file mode 100644 index 00000000..51b38aba --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/example/usb-serial/usb-serial.ino @@ -0,0 +1,199 @@ +/* + Sample code to use the esp32-usb-serial library +3 Copyright (c) 2023 Luc Lebosse. All rights reserved. + + This code is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#include "esp32_usb_serial.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +#define ESP_USB_SERIAL_BAUDRATE 115200 +#define ESP_USB_SERIAL_DATA_BITS (8) +#define ESP_USB_SERIAL_PARITY \ + (0) // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits +#define ESP_USB_SERIAL_STOP_BITS \ + (0) // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space + +#define ESP_USB_SERIAL_RX_BUFFER_SIZE 512 +#define ESP_USB_SERIAL_TX_BUFFER_SIZE 128 +#define ESP_USB_SERIAL_TASK_SIZE 4096 +#define ESP_USB_SERIAL_TASK_CORE 1 +#define ESP_USB_SERIAL_TASK_PRIORITY 10 + +SemaphoreHandle_t device_disconnected_sem; +std::unique_ptr vcp; +bool isConnected = false; +bool usbReady = false; +TaskHandle_t xHandle; + +/** + * @brief Data received callback + */ +bool rx_callback(const uint8_t *data, size_t data_len, void *arg) { + Serial.write(data, data_len); + return true; +} + +/** + * @brief Device event callback + * + * Apart from handling device disconnection it doesn't do anything useful + */ +void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) { + switch (event->type) { + case CDC_ACM_HOST_ERROR: + Serial.printf("CDC-ACM error has occurred, err_no = %d\n", + event->data.error); + break; + case CDC_ACM_HOST_DEVICE_DISCONNECTED: + Serial.println("Device suddenly disconnected"); + xSemaphoreGive(device_disconnected_sem); + isConnected = false; + break; + case CDC_ACM_HOST_SERIAL_STATE: + Serial.printf("Serial state notif 0x%04X\n", + event->data.serial_state.val); + break; + case CDC_ACM_HOST_NETWORK_CONNECTION: + Serial.println("Network connection established"); + break; + default: + Serial.println("Unknown event"); + break; + } +} + +void connectDevice() { + if (!usbReady || isConnected) { + return; + } + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 5000, // 5 seconds, enough time to plug the + // device in or experiment with timeout + .out_buffer_size = ESP_USB_SERIAL_TX_BUFFER_SIZE, + .in_buffer_size = ESP_USB_SERIAL_RX_BUFFER_SIZE, + .event_cb = handle_event, + .data_cb = rx_callback, + .user_arg = NULL, + }; + cdc_acm_line_coding_t line_coding = { + .dwDTERate = ESP_USB_SERIAL_BAUDRATE, + .bCharFormat = ESP_USB_SERIAL_STOP_BITS, + .bParityType = ESP_USB_SERIAL_PARITY, + .bDataBits = ESP_USB_SERIAL_DATA_BITS, + }; + // You don't need to know the device's VID and PID. Just plug in any device + // and the VCP service will pick correct (already registered) driver for the + // device + Serial.println("Opening any VCP device..."); + vcp = std::unique_ptr(esp_usb::VCP::open(&dev_config)); + + if (vcp == nullptr) { + Serial.println("Failed to open VCP device, retrying..."); + return; + } + + vTaskDelay(10); + + Serial.println("USB detected"); + + if (vcp->line_coding_set(&line_coding) == ESP_OK) { + Serial.println("USB Connected"); + isConnected = true; + uint16_t vid = esp_usb::getVID(); + uint16_t pid = esp_usb::getPID(); + Serial.printf("USB device with VID: 0x%04X (%s), PID: 0x%04X (%s) found\n", + vid, esp_usb::getVIDString(), pid, esp_usb::getPIDString()); + xSemaphoreTake(device_disconnected_sem, portMAX_DELAY); + vTaskDelay(10); + + vcp = nullptr; + } else { + Serial.println("USB device not identified"); + } +} + +// this task only handle connection +static void esp_usb_serial_connection_task(void *pvParameter) { + (void)pvParameter; + while (1) { + /* Delay */ + vTaskDelay(pdMS_TO_TICKS(10)); + if (!usbReady) { + break; + } + connectDevice(); + } + /* A task should NEVER return */ + vTaskDelete(NULL); +} + +void handle() { + if (!usbReady) return; + if (Serial.available()) { + size_t size = Serial.available(); + uint8_t *data = (uint8_t *)malloc(size); + if (data) { + size = Serial.readBytes(data, size); + if (vcp && vcp->tx_blocking(data, size) == ESP_OK) { + if (!(vcp && vcp->set_control_line_state(true, true) == ESP_OK)) { + Serial.println("Failed set line"); + } + } else { + Serial.println("Failed to send message"); + } + free(data); + } + } +} + +void setup() { + Serial.begin(115200); + if (ESP_OK != usb_serial_init()) { + Serial.println("Initialisation failed"); + } else { + if (ESP_OK != usb_serial_create_task()) { + Serial.println("Task Creation failed"); + } else { + Serial.println("Success"); + } + device_disconnected_sem = xSemaphoreCreateBinary(); + if (device_disconnected_sem == NULL) { + Serial.println("Semaphore creation failed"); + return; + } + BaseType_t res = xTaskCreatePinnedToCore( + esp_usb_serial_connection_task, "esp_usb_serial_task", + ESP_USB_SERIAL_TASK_SIZE, NULL, ESP_USB_SERIAL_TASK_PRIORITY, &xHandle, + ESP_USB_SERIAL_TASK_CORE); + if (res != pdPASS || !xHandle) { + Serial.println("Task creation failed"); + return; + } + Serial.println("USB Serial Connection Task created successfully"); + } + usbReady = true; +} + +void loop() { + if (usbReady) { + handle(); + } +} diff --git a/libraries/esp32-usb-serial-1.0.1/library.properties b/libraries/esp32-usb-serial-1.0.1/library.properties new file mode 100644 index 00000000..405c7a29 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/library.properties @@ -0,0 +1,9 @@ +name=esp32-usb-serial +version=1.0.1 +author=Luc Lebosse +maintainer=Luc Lebosse, +sentence=An usb-serial otg library for ESP32 boards with esp32-arduino core. +paragraph=This library implements a wrapper to use cdc feature from espressif IDF 5.1.X +category=Communication +url=https://github.com/luc-github/esp32-usb-serial +architectures= esp32s2, esp32s3 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/.component_hash b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/.component_hash new file mode 100644 index 00000000..14db8989 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/.component_hash @@ -0,0 +1 @@ +b08bc47056e1d4f8b30fbaec77636cedad0435446f256fa0a75650752d1deca6 \ No newline at end of file diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/CHANGELOG.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/CHANGELOG.md new file mode 100644 index 00000000..7d557e43 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/CHANGELOG.md @@ -0,0 +1,21 @@ +## 2.0.2 + +- Return an error if the selected interface does not have IN and OUT bulk endpoints + +## 2.0.1 + +- Add support for USB "triple null" devices, which use USB interface association descriptors, but have Device Class, Device Subclass, and Device Protocol all set to 0x00, instead of 0xEF, 0x02, and 0x01 respectively. USB Standard reference: https://www.usb.org/defined-class-codes, Base Class 00h (Device) section. + +## 2.0.0 + +- New function `cdc_acm_host_register_new_dev_callback`. This allows you to get New Device notifications even if you use the default driver. +- Receive buffer has configurable size. This is useful if you expect data transfers larger then Maximum Packet Size. +- Receive buffer has 'append' function. In the Data Received callback you can signal that you wait for more data and the current data were not yet processed. In this case, the CDC driver appends new data to the already received data. This is especially useful if the upper layer messages consist of multiple USB transfers and you don't want to waste more RAM and CPU copying the data around. + +## 1.0.4 + +- C++ methods are now virtual to allow derived classes to override them. + +## 1.0.0 + +- Initial version diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/CMakeLists.txt b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/CMakeLists.txt new file mode 100644 index 00000000..f2ff73f8 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/CMakeLists.txt @@ -0,0 +1,15 @@ +set(srcs) +set(include) +# As CONFIG_USB_OTG_SUPPORTED comes from Kconfig, it is not evaluated yet +# when components are being registered. +set(require usb) + +if(CONFIG_USB_OTG_SUPPORTED) + list(APPEND srcs "cdc_acm_host.c") + list(APPEND include "include") +endif() + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${include} + REQUIRES ${require} + ) diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/LICENSE b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/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/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/README.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/README.md new file mode 100644 index 00000000..b4f2952a --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/README.md @@ -0,0 +1,50 @@ +# USB Host CDC-ACM Class Driver + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_cdc_acm/badge.svg)](https://components.espressif.com/components/espressif/usb_host_cdc_acm) + +This component contains an implementation of a USB CDC-ACM Host Class Driver that is implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html). + +## Supported Devices + +The CDC-ACM Host driver supports the following types of CDC devices: + +1. CDC-ACM devices +2. CDC-like vendor specific devices (usually found on USB to UART bridge devices or cellular modems) + +### CDC-ACM Devices + +The CDC-ACM Class driver supports CDC-ACM devices that meet the following requirements: +- The device class code must be set to the CDC class `0x02` or implement Interface Association Descriptor (IAD) +- The CDC-ACM must contain the following interfaces: + - A Communication Class Interface containing a management element (EP0) and may also contain a notification element (an interrupt endpoint). The driver will check this interface for CDC Functional Descriptors. + - A Data Class Interface with two BULK endpoints (IN and OUT). Other transfer types are not supported by the driver + +### CDC-Like Vendor Specific Devices + +The CDC-ACM Class driver supports CDC-like devices that meet the following requirements: +- The device class code must be set to the vendor specific class code `0xFF` +- The device needs to provide and interface containing the following endpoints: + - (Mandatory) Two Bulk endpoints (IN and OUT) for data + - (Optional) An interrupt endpoint (IN) for the notification element + +For CDC-like devices, users are responsible for ensuring that they only call APIs (e.g., `cdc_acm_host_send_break()`) that are supported by the target device. + + +## Usage + +The following steps outline the typical API call pattern of the CDC-ACM Class Driver + +1. Install the USB Host Library via `usb_host_install()` +2. Install the CDC-ACM driver via `cdc_acm_host_install()` +3. Call `cdc_acm_host_open()`/`cdc_acm_host_open_vendor_specific()` to open a target CDC-ACM/CDC-like device. These functions will block until the target device is connected or time-out +4. To transmit data, call `cdc_acm_host_data_tx_blocking()` +5. When data is received, the driver will automatically run the receive data callback +6. An opened device can be closed via `cdc_acm_host_close()` +7. The CDC-ACM driver can be uninstalled via `cdc_acm_host_uninstall()` + +## Examples + +- For an example with a CDC-ACM device, refer to [cdc_acm_host](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/cdc/cdc_acm_host) +- For an example with a CDC-like device, refer to [cdc_acm_host_bg96](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/cdc/cdc_acm_bg96) +- For an example with Virtual COM devices, refer to [cdc_acm_vcp](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/cdc/cdc_acm_vcp) +- For examples with [esp_modem](https://components.espressif.com/components/espressif/esp_modem), refer to [esp_modem examples](https://github.com/espressif/esp-protocols/tree/master/components/esp_modem/examples) diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/cdc_acm_host.c b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/cdc_acm_host.c new file mode 100644 index 00000000..02fc5f6d --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/cdc_acm_host.c @@ -0,0 +1,1331 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "inttypes.h" +#include +#include +#include +#include "usb/usb_host.h" +#include "usb/cdc_acm_host.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +#include "esp_check.h" +#include "esp_system.h" + +static const char *TAG = "cdc_acm"; + +// CDC devices often implement Interface Association Descriptor (IAD). Parse IAD only when +// bDeviceClass = 0xEF (Miscellaneous Device Class), bDeviceSubClass = 0x02 (Common Class), bDeviceProtocol = 0x01 (Interface Association Descriptor), +// or when bDeviceClass, bDeviceSubClass, and bDeviceProtocol are 0x00 (Null class code triple), as per https://www.usb.org/defined-class-codes, "Base Class 00h (Device)" section +// @see USB Interface Association Descriptor: Device Class Code and Use Model rev 1.0, Table 1-1 +#define USB_SUBCLASS_NULL 0x00 +#define USB_SUBCLASS_COMMON 0x02 +#define USB_PROTOCOL_NULL 0x00 +#define USB_DEVICE_PROTOCOL_IAD 0x01 + +// 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) + +// CDC-ACM events +#define CDC_ACM_TEARDOWN BIT0 +#define CDC_ACM_TEARDOWN_COMPLETE BIT1 + +// CDC-ACM check macros +#define CDC_ACM_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) + +#define CDC_ACM_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + CDC_ACM_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) + +// CDC-ACM driver object +typedef struct { + usb_host_client_handle_t cdc_acm_client_hdl; /*!< USB Host handle reused for all CDC-ACM devices in the system */ + SemaphoreHandle_t open_close_mutex; + EventGroupHandle_t event_group; + cdc_acm_new_dev_callback_t new_dev_cb; + SLIST_HEAD(list_dev, cdc_dev_s) cdc_devices_list; /*!< List of open pseudo devices */ +} cdc_acm_obj_t; + +static cdc_acm_obj_t *p_cdc_acm_obj = NULL; + +/** + * @brief Default CDC-ACM driver configuration + * + * This configuration is used when user passes NULL to config pointer during device open. + */ +static const cdc_acm_host_driver_config_t cdc_acm_driver_config_default = { + .driver_task_stack_size = 4096, + .driver_task_priority = 10, + .xCoreID = 0, + .new_dev_cb = NULL, +}; + +/** + * @brief USB CDC PSTN Call Descriptor + * + * @see Table 3, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t call_management: 1; // Device handles call management itself + uint8_t call_over_data_if: 1; // Device sends/receives call management information over Data Class interface + uint8_t reserved: 6; + }; + uint8_t val; + } bmCapabilities; + uint8_t bDataInterface; // Interface number of Data Class interface optionally used for call management +} __attribute__((packed)) cdc_acm_call_desc_t; + +/** + * @brief USB CDC PSTN Abstract Control Model Descriptor + * + * @see Table 4, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t feature: 1; // Device supports Set/Clear/Get_Comm_Feature requests + uint8_t serial: 1; // Device supports Set/Get_Line_Coding, Set_Control_Line_State and Serial_State request and notifications + uint8_t send_break: 1; // Device supports Send_Break request + uint8_t network: 1; // Device supports Network_Connection notification + uint8_t reserved: 4; + }; + uint8_t val; + } bmCapabilities; +} __attribute__((packed)) cdc_acm_acm_desc_t; + +typedef struct cdc_dev_s cdc_dev_t; +struct cdc_dev_s { + usb_device_handle_t dev_hdl; // USB device handle + void *cb_arg; // Common argument for user's callbacks (data IN and Notification) + struct { + usb_transfer_t *out_xfer; // OUT data transfer + usb_transfer_t *in_xfer; // IN data transfer + cdc_acm_data_callback_t in_cb; // User's callback for async (non-blocking) data IN + uint16_t in_mps; // IN endpoint Maximum Packet Size + uint8_t *in_data_buffer_base; // Pointer to IN data buffer in usb_transfer_t + const usb_intf_desc_t *intf_desc; // Pointer to data interface descriptor + SemaphoreHandle_t out_mux; // OUT mutex + } data; + + struct { + usb_transfer_t *xfer; // IN notification transfer + const usb_intf_desc_t *intf_desc; // Pointer to notification interface descriptor, can be NULL if there is no notification channel in the device + cdc_acm_host_dev_callback_t cb; // User's callback for device events + } notif; // Structure with Notif pipe data + + usb_transfer_t *ctrl_transfer; // CTRL (endpoint 0) transfer + SemaphoreHandle_t ctrl_mux; // CTRL mutex + cdc_acm_uart_state_t serial_state; // Serial State + cdc_comm_protocol_t comm_protocol; + cdc_data_protocol_t data_protocol; + int num_cdc_intf_desc; // Number of CDC Interface descriptors in following array + const usb_standard_desc_t **cdc_intf_desc; // CDC Interface descriptors + SLIST_ENTRY(cdc_dev_s) list_entry; +}; + +/** + * @brief Notification received callback + * + * Notification (interrupt) IN transfer is submitted at the end of this function to ensure periodic poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void notif_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data received callback + * + * Data (bulk) IN transfer is submitted at the end of this function to ensure continuous poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void in_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data send callback + * + * Reused for bulk OUT and CTRL transfers + * + * @param[in] transfer Transfer that triggered the callback + */ +static void out_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief USB Host Client event callback + * + * Handling of USB device connection/disconnection to/from root HUB. + * + * @param[in] event_msg Event message type + * @param[in] arg Caller's argument (not used in this driver) + */ +static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg); + +/** + * @brief Send CDC specific request + * + * Helper function that will send CDC specific request to default endpoint. + * Both IN and OUT requests are sent through this API, depending on the in_transfer parameter. + * + * @see Chapter 6.2, USB CDC specification rev. 1.2 + * @note CDC specific requests are only supported by devices that have dedicated management element. + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] in_transfer Direction of data phase. true: IN, false: OUT + * @param[in] request CDC request code + * @param[inout] data Pointer to data buffer. Input for OUT transfers, output for IN transfers. + * @param[in] data_len Length of data buffer + * @param[in] value Value to be set in bValue of Setup packet + * @return esp_err_t + */ +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value); + +/** + * @brief Reset IN transfer + * + * In in_xfer_cb() we can modify IN transfer parameters, this function resets the transfer to its defaults + * + * @param[in] cdc_dev Pointer to CDC device + */ +static void cdc_acm_reset_in_transfer(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev->data.in_xfer); + usb_transfer_t *transfer = cdc_dev->data.in_xfer; + uint8_t **ptr = (uint8_t **)(&(transfer->data_buffer)); + *ptr = cdc_dev->data.in_data_buffer_base; + transfer->num_bytes = transfer->data_buffer_size; +} + +/** + * @brief CDC-ACM driver handling task + * + * USB host client registration and deregistration is handled here. + * + * @param[in] arg User's argument. Handle of a task that started this task. + */ +static void cdc_acm_client_task(void *arg) +{ + vTaskSuspend(NULL); // Task will be resumed from cdc_acm_host_install() + cdc_acm_obj_t *cdc_acm_obj = p_cdc_acm_obj; // Make local copy of the driver's handle + assert(cdc_acm_obj->cdc_acm_client_hdl); + + // Start handling client's events + while (1) { + usb_host_client_handle_events(cdc_acm_obj->cdc_acm_client_hdl, portMAX_DELAY); + EventBits_t events = xEventGroupGetBits(cdc_acm_obj->event_group); + if (events & CDC_ACM_TEARDOWN) { + break; + } + } + + ESP_LOGD(TAG, "Deregistering client"); + ESP_ERROR_CHECK(usb_host_client_deregister(cdc_acm_obj->cdc_acm_client_hdl)); + xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE); + vTaskDelete(NULL); +} + +/** + * @brief Cancel transfer and reset endpoint + * + * This function will cancel ongoing transfer a reset its endpoint to ready state. + * + * @param[in] dev_hdl USB device handle + * @param[in] transfer Transfer to be cancelled + * @return esp_err_t + */ +static esp_err_t cdc_acm_reset_transfer_endpoint(usb_device_handle_t dev_hdl, usb_transfer_t *transfer) +{ + assert(dev_hdl); + assert(transfer); + + ESP_RETURN_ON_ERROR(usb_host_endpoint_halt(dev_hdl, transfer->bEndpointAddress), TAG,); + ESP_RETURN_ON_ERROR(usb_host_endpoint_flush(dev_hdl, transfer->bEndpointAddress), TAG,); + usb_host_endpoint_clear(dev_hdl, transfer->bEndpointAddress); + return ESP_OK; +} + +/** + * @brief Start CDC device + * + * After this call, USB host peripheral will continuously poll IN endpoints. + * + * @param cdc_dev + * @param[in] event_cb Device event callback + * @param[in] in_cb Data received callback + * @param[in] user_arg Optional user's argument, that will be passed to the callbacks + * @return esp_err_t + */ +static esp_err_t cdc_acm_start(cdc_dev_t *cdc_dev, cdc_acm_host_dev_callback_t event_cb, cdc_acm_data_callback_t in_cb, void *user_arg) +{ + esp_err_t ret = ESP_OK; + assert(cdc_dev); + + CDC_ACM_ENTER_CRITICAL(); + cdc_dev->notif.cb = event_cb; + cdc_dev->data.in_cb = in_cb; + cdc_dev->cb_arg = user_arg; + CDC_ACM_EXIT_CRITICAL(); + + // Claim data interface and start polling its IN endpoint + ESP_GOTO_ON_ERROR(usb_host_interface_claim(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber, 0), err, TAG,); + if (cdc_dev->data.in_xfer) { + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->data.in_xfer)); + } + + // If notification are supported, claim its interface and start polling its IN endpoint + if (cdc_dev->notif.xfer) { + if (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc) { + ESP_GOTO_ON_ERROR(usb_host_interface_claim(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, + cdc_dev->notif.intf_desc->bInterfaceNumber, 0), err, TAG,); + } + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->notif.xfer)); + } + + // Everything OK, add the device into list and return + CDC_ACM_ENTER_CRITICAL(); + SLIST_INSERT_HEAD(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, list_entry); + CDC_ACM_EXIT_CRITICAL(); + return ret; + +err: + usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber); + if (cdc_dev->notif.xfer && (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber); + } + return ret; +} + +static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev); +/** + * @brief Helper function that releases resources claimed by CDC device + * + * Close underlying USB device, free device driver memory + * + * @note All interfaces claimed by this device must be release before calling this function + * @param cdc_dev CDC device handle to be removed + */ +static void cdc_acm_device_remove(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev); + cdc_acm_transfers_free(cdc_dev); + free(cdc_dev->cdc_intf_desc); + // We don't check the error code of usb_host_device_close, as the close might fail, if someone else is still using the device (not all interfaces are released) + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl); // Gracefully continue on error + free(cdc_dev); +} + +/** + * @brief Open USB device with requested VID/PID + * + * This function has two regular return paths: + * 1. USB device with matching VID/PID is already opened by this driver: allocate new CDC device on top of the already opened USB device. + * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll USB connected devices until it is found. + * + * @note This function will block for timeout_ms, if the device is not enumerated at the moment of calling this function. + * @param[in] vid Vendor ID + * @param[in] pid Product ID + * @param[in] timeout_ms Connection timeout [ms] + * @param[out] dev CDC-ACM device + * @return esp_err_t + */ +static esp_err_t cdc_acm_find_and_open_usb_device(uint16_t vid, uint16_t pid, int timeout_ms, cdc_dev_t **dev) +{ + assert(p_cdc_acm_obj); + assert(dev); + + *dev = calloc(1, sizeof(cdc_dev_t)); + if (*dev == NULL) { + return ESP_ERR_NO_MEM; + } + + // First, check list of already opened CDC devices + ESP_LOGD(TAG, "Checking list of opened USB devices"); + cdc_dev_t *cdc_dev; + SLIST_FOREACH(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry) { + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // Return path 1: + (*dev)->dev_hdl = cdc_dev->dev_hdl; + return ESP_OK; + } + } + + // Second, poll connected devices until new device is connected or timeout + TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + TimeOut_t connection_timeout; + vTaskSetTimeOutState(&connection_timeout); + + do { + ESP_LOGD(TAG, "Checking list of connected USB devices"); + uint8_t dev_addr_list[10]; + int num_of_devices; + ESP_ERROR_CHECK(usb_host_device_addr_list_fill(sizeof(dev_addr_list), dev_addr_list, &num_of_devices)); + + // Go through device address list and find the one we are looking for + for (int i = 0; i < num_of_devices; i++) { + usb_device_handle_t current_device; + // Open USB device + if (usb_host_device_open(p_cdc_acm_obj->cdc_acm_client_hdl, dev_addr_list[i], ¤t_device) != ESP_OK) { + continue; // In case we failed to open this device, continue with next one in the list + } + assert(current_device); + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); + if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // Return path 2: + (*dev)->dev_hdl = current_device; + return ESP_OK; + } + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, current_device); + } + vTaskDelay(pdMS_TO_TICKS(50)); + } while (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) == pdFALSE); + + // Timeout was reached, clean-up + free(*dev); + *dev = NULL; + return ESP_ERR_NOT_FOUND; +} + +esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config) +{ + CDC_ACM_CHECK(!p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + + // Check driver configuration, use default if NULL is passed + if (driver_config == NULL) { + driver_config = &cdc_acm_driver_config_default; + } + + // Allocate all we need for this driver + esp_err_t ret; + cdc_acm_obj_t *cdc_acm_obj = heap_caps_calloc(1, sizeof(cdc_acm_obj_t), MALLOC_CAP_DEFAULT); + EventGroupHandle_t event_group = xEventGroupCreate(); + SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); + TaskHandle_t driver_task_h = NULL; + xTaskCreatePinnedToCore( + cdc_acm_client_task, "USB-CDC", driver_config->driver_task_stack_size, NULL, + driver_config->driver_task_priority, &driver_task_h, driver_config->xCoreID); + + if (cdc_acm_obj == NULL || driver_task_h == NULL || event_group == NULL || mutex == NULL) { + ret = ESP_ERR_NO_MEM; + goto err; + } + + // Register USB Host client + usb_host_client_handle_t usb_client = NULL; + const usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = 3, + .async.client_event_callback = usb_event_cb, + .async.callback_arg = NULL + }; + ESP_GOTO_ON_ERROR(usb_host_client_register(&client_config, &usb_client), err, TAG, "Failed to register USB host client"); + + // Initialize CDC-ACM driver structure + SLIST_INIT(&(cdc_acm_obj->cdc_devices_list)); + cdc_acm_obj->event_group = event_group; + cdc_acm_obj->open_close_mutex = mutex; + cdc_acm_obj->cdc_acm_client_hdl = usb_client; + cdc_acm_obj->new_dev_cb = driver_config->new_dev_cb; + + // Between 1st call of this function and following section, another task might try to install this driver: + // Make sure that there is only one instance of this driver in the system + CDC_ACM_ENTER_CRITICAL(); + if (p_cdc_acm_obj) { + // Already created + ret = ESP_ERR_INVALID_STATE; + CDC_ACM_EXIT_CRITICAL(); + goto client_err; + } else { + p_cdc_acm_obj = cdc_acm_obj; + } + CDC_ACM_EXIT_CRITICAL(); + + // Everything OK: Start CDC-Driver task and return + vTaskResume(driver_task_h); + return ESP_OK; + +client_err: + usb_host_client_deregister(usb_client); +err: // Clean-up + free(cdc_acm_obj); + if (event_group) { + vEventGroupDelete(event_group); + } + if (driver_task_h) { + vTaskDelete(driver_task_h); + } + if (mutex) { + vSemaphoreDelete(mutex); + } + return ret; +} + +esp_err_t cdc_acm_host_uninstall() +{ + esp_err_t ret; + + CDC_ACM_ENTER_CRITICAL(); + CDC_ACM_CHECK_FROM_CRIT(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + cdc_acm_obj_t *cdc_acm_obj = p_cdc_acm_obj; // Save Driver's handle to temporary handle + CDC_ACM_EXIT_CRITICAL(); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); // Wait for all open/close calls to finish + + CDC_ACM_ENTER_CRITICAL(); + if (SLIST_EMPTY(&p_cdc_acm_obj->cdc_devices_list)) { // Check that device list is empty (all devices closed) + p_cdc_acm_obj = NULL; // NULL static driver pointer: No open/close calls form this point + } else { + ret = ESP_ERR_INVALID_STATE; + CDC_ACM_EXIT_CRITICAL(); + goto unblock; + } + CDC_ACM_EXIT_CRITICAL(); + + // Signal to CDC task to stop, unblock it and wait for its deletion + xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN); + usb_host_client_unblock(cdc_acm_obj->cdc_acm_client_hdl); + ESP_GOTO_ON_FALSE( + xEventGroupWaitBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE, pdFALSE, pdFALSE, pdMS_TO_TICKS(100)), + ESP_ERR_NOT_FINISHED, unblock, TAG,); + + // Free remaining resources and return + vEventGroupDelete(cdc_acm_obj->event_group); + xSemaphoreGive(cdc_acm_obj->open_close_mutex); + vSemaphoreDelete(cdc_acm_obj->open_close_mutex); + free(cdc_acm_obj); + return ESP_OK; + +unblock: + xSemaphoreGive(cdc_acm_obj->open_close_mutex); + return ret; +} + +esp_err_t cdc_acm_host_register_new_dev_callback(cdc_acm_new_dev_callback_t new_dev_cb) +{ + CDC_ACM_ENTER_CRITICAL(); + p_cdc_acm_obj->new_dev_cb = new_dev_cb; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; +} + +/** + * @brief Free USB transfers used by this device + * + * @note There can be no transfers in flight, at the moment of calling this function. + * @param[in] cdc_dev Pointer to CDC device + */ +static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev) +{ + assert(cdc_dev); + if (cdc_dev->notif.xfer != NULL) { + usb_host_transfer_free(cdc_dev->notif.xfer); + } + if (cdc_dev->data.in_xfer != NULL) { + cdc_acm_reset_in_transfer(cdc_dev); + usb_host_transfer_free(cdc_dev->data.in_xfer); + } + if (cdc_dev->data.out_xfer != NULL) { + if (cdc_dev->data.out_xfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->data.out_xfer->context); + } + if (cdc_dev->data.out_mux != NULL) { + vSemaphoreDelete(cdc_dev->data.out_mux); + } + usb_host_transfer_free(cdc_dev->data.out_xfer); + } + if (cdc_dev->ctrl_transfer != NULL) { + if (cdc_dev->ctrl_transfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context); + } + if (cdc_dev->ctrl_mux != NULL) { + vSemaphoreDelete(cdc_dev->ctrl_mux); + } + usb_host_transfer_free(cdc_dev->ctrl_transfer); + } +} + +/** + * @brief Allocate CDC transfers + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] notif_ep_desc Pointer to notification EP descriptor + * @param[in] in_ep_desc- Pointer to data IN EP descriptor + * @param[in] in_buf_len Length of data IN buffer + * @param[in] out_ep_desc Pointer to data OUT EP descriptor + * @param[in] out_buf_len Length of data OUT buffer + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Not enough memory for transfers and semaphores allocation + * - ESP_ERR_NOT_FOUND: IN or OUT endpoints were not found in the selected interface + */ +static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_desc_t *notif_ep_desc, const usb_ep_desc_t *in_ep_desc, size_t in_buf_len, const usb_ep_desc_t *out_ep_desc, size_t out_buf_len) +{ + esp_err_t ret; + + // 0. Check IN and OUT endpoints + // If your open functions fail here, it signals that you either selected wrong interface number + // or that you are trying to open IAD CDC device with cdc_acm_host_open_vendor_specific() instead of cdc_acm_host_open() + // Refer to README.md for more information + ESP_RETURN_ON_FALSE(in_ep_desc && out_ep_desc, ESP_ERR_NOT_FOUND, TAG, "IN or OUT endpoint not found in this data interface"); + + // 1. Setup notification transfer if it is supported + if (notif_ep_desc) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(notif_ep_desc), 0, &cdc_dev->notif.xfer), + err, TAG,); + cdc_dev->notif.xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->notif.xfer->bEndpointAddress = notif_ep_desc->bEndpointAddress; + cdc_dev->notif.xfer->callback = notif_xfer_cb; + cdc_dev->notif.xfer->context = cdc_dev; + cdc_dev->notif.xfer->num_bytes = USB_EP_DESC_GET_MPS(notif_ep_desc); + } + + // 2. Setup control transfer + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, &cdc_dev->ctrl_transfer), + err, TAG,); + cdc_dev->ctrl_transfer->timeout_ms = 1000; + cdc_dev->ctrl_transfer->bEndpointAddress = 0; + cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->ctrl_transfer->context = cdc_dev; + cdc_dev->ctrl_transfer->callback = out_xfer_cb; + cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, TAG,); + cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG,); + + // 3. Setup IN data transfer (if it is required (in_buf_len > 0)) + if (in_buf_len != 0) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(in_buf_len, 0, &cdc_dev->data.in_xfer), + err, TAG, + ); + assert(cdc_dev->data.in_xfer); + cdc_dev->data.in_xfer->callback = in_xfer_cb; + cdc_dev->data.in_xfer->num_bytes = in_buf_len; + cdc_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress; + cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.in_xfer->context = cdc_dev; + cdc_dev->data.in_mps = USB_EP_DESC_GET_MPS(in_ep_desc); + cdc_dev->data.in_data_buffer_base = cdc_dev->data.in_xfer->data_buffer; + } + + // 4. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + if (out_buf_len != 0) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer), + err, TAG, + ); + assert(cdc_dev->data.out_xfer); + cdc_dev->data.out_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.out_xfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->context, ESP_ERR_NO_MEM, err, TAG,); + cdc_dev->data.out_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_mux, ESP_ERR_NO_MEM, err, TAG,); + cdc_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress; + cdc_dev->data.out_xfer->callback = out_xfer_cb; + } + return ESP_OK; + +err: + cdc_acm_transfers_free(cdc_dev); + return ret; +} + +/** + * @brief Find CDC interface descriptor and its endpoint descriptors + * + * @note This function is called in open procedure of CDC compliant devices only. + * @param[in] cdc_dev Pointer to CDC device + * @param[in] intf_idx Index of CDC interface that should be used for this device + * @param[out] notif_ep Pointer to notification EP descriptor + * @param[out] in_ep Pointer to data IN EP descriptor + * @param[out] out_ep Pointer to data OUT EP descriptor + * @return + * - ESP_OK: Success + * - ESP_ERR_NOT_FOUND: Interfaces and endpoints NOT found + */ +static esp_err_t cdc_acm_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, uint8_t intf_idx, const usb_ep_desc_t **notif_ep, const usb_ep_desc_t **in_ep, const usb_ep_desc_t **out_ep) +{ + bool interface_found = false; + const usb_config_desc_t *config_desc; + const usb_device_desc_t *device_desc; + int data_intf_idx, notif_intf_idx; + int desc_offset = 0; + + // Get required descriptors + ESP_ERROR_CHECK(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + + if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) && + (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) || + ((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) && + (device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) { + // This is a composite device, that uses Interface Association Descriptor + const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc; + do { + this_desc = usb_parse_next_descriptor_of_type( + this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset); + + if (this_desc == NULL) { + break; // Reached end of configuration descriptor + } + + const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc; + if (iad_desc->bFirstInterface == intf_idx) { + // IAD with correct interface number was found: Check Class/Subclass codes, save Interface indexes + assert(iad_desc->bInterfaceCount == 2); + assert(iad_desc->bFunctionClass == USB_CLASS_COMM); + assert(iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM); + notif_intf_idx = iad_desc->bFirstInterface; + data_intf_idx = iad_desc->bFirstInterface + 1; + interface_found = true; + } + } while (!interface_found); + } else if ((device_desc->bDeviceClass == USB_CLASS_COMM) && (intf_idx == 0)) { + // This is a Communication Device Class + notif_intf_idx = 0; + data_intf_idx = 1; + interface_found = true; + } + + // Save found interfaces descriptors: + if (interface_found) { + // Notification IF and EP + cdc_dev->notif.intf_desc = usb_parse_interface_descriptor(config_desc, notif_intf_idx, 0, &desc_offset); + assert(cdc_dev->notif.intf_desc); + + // CDC specific descriptors should be right after CDC-Communication interface descriptor + // Note: That's why we use usb_parse_next_descriptor instead of usb_parse_next_descriptor_of_type. + // The latter could return CDC specific descriptors that don't belong to this interface + const usb_standard_desc_t *cdc_desc = (usb_standard_desc_t *)cdc_dev->notif.intf_desc; + do { + cdc_desc = usb_parse_next_descriptor(cdc_desc, config_desc->wTotalLength, &desc_offset); + if ((cdc_desc == NULL) || (cdc_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE ))) { + break; // We found all CDC specific descriptors + } + cdc_dev->num_cdc_intf_desc++; + cdc_dev->cdc_intf_desc = + realloc(cdc_dev->cdc_intf_desc, cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *))); + assert(cdc_dev->cdc_intf_desc); + cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc; + } while (1); + *notif_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset); + assert(notif_ep); + + // Data IF and EP + cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, data_intf_idx, 0, &desc_offset); + assert(cdc_dev->data.intf_desc); + int temp_offset = desc_offset; + for (int i = 0; i < 2; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + *in_ep = this_ep; + } else { + *out_ep = this_ep; + } + desc_offset = temp_offset; + } + return ESP_OK; + } + return ESP_ERR_NOT_FOUND; +} + +esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) +{ + esp_err_t ret; + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + // Find underlying USB device + cdc_dev_t *cdc_dev; + ESP_GOTO_ON_ERROR( + cdc_acm_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev), + exit, TAG, "USB device with VID: 0x%04X, PID: 0x%04X not found", vid, pid); + + // Find and save relevant interface and endpoint descriptors + const usb_ep_desc_t *notif_ep = NULL; + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + ESP_GOTO_ON_ERROR( + cdc_acm_find_intf_and_ep_desc(cdc_dev, interface_idx, ¬if_ep, &in_ep, &out_ep), + err, TAG, "Could not find required interface"); + + // Check whether found Interfaces are really CDC-ACM + assert(cdc_dev->notif.intf_desc->bInterfaceClass == USB_CLASS_COMM); + assert(cdc_dev->notif.intf_desc->bInterfaceSubClass == USB_CDC_SUBCLASS_ACM); + assert(cdc_dev->notif.intf_desc->bNumEndpoints == 1); + assert(cdc_dev->data.intf_desc->bInterfaceClass == USB_CLASS_CDC_DATA); + assert(cdc_dev->data.intf_desc->bNumEndpoints == 2); + + // Save Communication and Data protocols + cdc_dev->comm_protocol = (cdc_comm_protocol_t)cdc_dev->notif.intf_desc->bInterfaceProtocol; + cdc_dev->data_protocol = (cdc_data_protocol_t)cdc_dev->data.intf_desc->bInterfaceProtocol; + + // The following line is here for backward compatibility with v1.0.* + // where fixed size of IN buffer (equal to IN Maximum Packe Size) was used + const size_t in_buf_size = (dev_config->data_cb && (dev_config->in_buffer_size == 0)) ? USB_EP_DESC_GET_MPS(in_ep) : dev_config->in_buffer_size; + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + ESP_GOTO_ON_ERROR(cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, in_buf_size, out_ep, dev_config->out_buffer_size), err, TAG,); + ESP_GOTO_ON_ERROR(cdc_acm_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG,); + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; + +err: + cdc_acm_device_remove(cdc_dev); +exit: + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + *cdc_hdl_ret = NULL; + return ret; +} + +esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) +{ + esp_err_t ret; + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + // Find underlying USB device + cdc_dev_t *cdc_dev; + ret = cdc_acm_find_and_open_usb_device(vid, pid, dev_config->connection_timeout_ms, &cdc_dev); + if (ESP_OK != ret) { + goto exit; + } + + // Open procedure for CDC-ACM non-compliant devices: + const usb_config_desc_t *config_desc; + int desc_offset; + ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + cdc_dev->data.intf_desc = usb_parse_interface_descriptor(config_desc, interface_num, 0, &desc_offset); + ESP_GOTO_ON_FALSE( + cdc_dev->data.intf_desc, + ESP_ERR_NOT_FOUND, err, TAG, "Required interface no %d was not found.", interface_num); + const int temp_offset = desc_offset; // Save this offset for later + + // The interface can have 2-3 endpoints. 2 for data and 1 optional for notifications + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + const usb_ep_desc_t *notif_ep = NULL; + + // Go through all interface's endpoints and parse Interrupt and Bulk endpoints + for (int i = 0; i < cdc_dev->data.intf_desc->bNumEndpoints; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + + if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_INTR) { + // Notification channel does not have its dedicated interface (data and notif interface is the same) + cdc_dev->notif.intf_desc = cdc_dev->data.intf_desc; + notif_ep = this_ep; + } else if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_BULK) { + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + in_ep = this_ep; + } else { + out_ep = this_ep; + } + } + desc_offset = temp_offset; + } + + // The following line is here for backward compatibility with v1.0.* + // where fixed size of IN buffer (equal to IN Maximum Packet Size) was used + const size_t in_buf_size = (dev_config->data_cb && (dev_config->in_buffer_size == 0)) ? USB_EP_DESC_GET_MPS(in_ep) : dev_config->in_buffer_size; + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + ESP_GOTO_ON_ERROR(cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, in_buf_size, out_ep, dev_config->out_buffer_size), err, TAG, ); + ESP_GOTO_ON_ERROR(cdc_acm_start(cdc_dev, dev_config->event_cb, dev_config->data_cb, dev_config->user_arg), err, TAG,); + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +err: + cdc_acm_device_remove(cdc_dev); +exit: + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ret; +} + +esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl) +{ + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + // Cancel polling of BULK IN and INTERRUPT IN + CDC_ACM_ENTER_CRITICAL(); + cdc_dev->notif.cb = NULL; + cdc_dev->data.in_cb = NULL; + CDC_ACM_EXIT_CRITICAL(); + if (cdc_dev->data.in_xfer) { + ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.in_xfer)); + } + if (cdc_dev->notif.xfer != NULL) { + ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->notif.xfer)); + } + + // Release all interfaces + ESP_ERROR_CHECK(usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->data.intf_desc->bInterfaceNumber)); + if ((cdc_dev->notif.intf_desc != NULL) && (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + ESP_ERROR_CHECK(usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, cdc_dev->notif.intf_desc->bInterfaceNumber)); + } + + CDC_ACM_ENTER_CRITICAL(); + SLIST_REMOVE(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, cdc_dev_s, list_entry); + CDC_ACM_EXIT_CRITICAL(); + + cdc_acm_device_remove(cdc_dev); + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +} + +/** + * @brief Print CDC specific descriptor in human readable form + * + * This is a callback function that is called from USB Host library, + * when it wants to print full configuration descriptor to stdout. + * + * @param[in] _desc CDC specific descriptor + */ +static void cdc_acm_print_desc(const usb_standard_desc_t *_desc) +{ + if (_desc->bDescriptorType != ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE )) { + // Quietly return in case that this descriptor is not CDC interface descriptor + return; + } + + switch (((cdc_header_desc_t *)_desc)->bDescriptorSubtype) { + case USB_CDC_DESC_SUBTYPE_HEADER: { + cdc_header_desc_t *desc = (cdc_header_desc_t *)_desc; + printf("\t*** CDC Header Descriptor ***\n"); + printf("\tbcdCDC: %d.%d0\n", ((desc->bcdCDC >> 8) & 0xF), ((desc->bcdCDC >> 4) & 0xF)); + break; + } + case USB_CDC_DESC_SUBTYPE_CALL: { + cdc_acm_call_desc_t *desc = (cdc_acm_call_desc_t *)_desc; + printf("\t*** CDC Call Descriptor ***\n"); + printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + printf("\tbDataInterface: %d\n", desc->bDataInterface); + break; + } + case USB_CDC_DESC_SUBTYPE_ACM: { + cdc_acm_acm_desc_t *desc = (cdc_acm_acm_desc_t *)_desc; + printf("\t*** CDC ACM Descriptor ***\n"); + printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + break; + } + case USB_CDC_DESC_SUBTYPE_UNION: { + cdc_union_desc_t *desc = (cdc_union_desc_t *)_desc; + printf("\t*** CDC Union Descriptor ***\n"); + printf("\tbControlInterface: %d\n", desc->bControlInterface); + printf("\tbSubordinateInterface[0]: %d\n", desc->bSubordinateInterface[0]); + break; + } + default: + ESP_LOGW(TAG, "Unsupported CDC specific descriptor"); + break; + } +} + +void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl) +{ + assert(cdc_hdl); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + const usb_device_desc_t *device_desc; + const usb_config_desc_t *config_desc; + ESP_ERROR_CHECK_WITHOUT_ABORT(usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + ESP_ERROR_CHECK_WITHOUT_ABORT(usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + usb_print_device_descriptor(device_desc); + usb_print_config_descriptor(config_desc, cdc_acm_print_desc); +} + +/** + * @brief Check finished transfer status + * + * Return to on transfer completed OK. + * Cancel the transfer and issue user's callback in case of an error. + * + * @param[in] transfer Transfer to be checked + * @return true Transfer completed + * @return false Transfer NOT completed + */ +static bool cdc_acm_is_transfer_completed(usb_transfer_t *transfer) +{ + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + bool completed = false; + + switch (transfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + completed = true; + break; + case USB_TRANSFER_STATUS_NO_DEVICE: // User is notified about device disconnection from usb_event_cb + case USB_TRANSFER_STATUS_CANCELED: + break; + case USB_TRANSFER_STATUS_ERROR: + case USB_TRANSFER_STATUS_TIMED_OUT: + case USB_TRANSFER_STATUS_STALL: + case USB_TRANSFER_STATUS_OVERFLOW: + case USB_TRANSFER_STATUS_SKIPPED: + default: + // Transfer was not completed or cancelled by user. Inform user about this + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t error_event = { + .type = CDC_ACM_HOST_ERROR, + .data.error = (int) transfer->status + }; + cdc_dev->notif.cb(&error_event, cdc_dev->cb_arg); + } + } + return completed; +} + +static void in_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "in xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (!cdc_acm_is_transfer_completed(transfer)) { + return; + } + + if (cdc_dev->data.in_cb) { + const bool data_processed = cdc_dev->data.in_cb(transfer->data_buffer, transfer->actual_num_bytes, cdc_dev->cb_arg); + + // Information for developers: + // In order to save RAM and CPU time, the application can indicate that the received data was not processed and that the application expects more data. + // In this case, the next received data must be appended to the existing buffer. + // Since the data_buffer in usb_transfer_t is a constant pointer, we must cast away to const qualifier. + if (!data_processed) { + // In case the received data was not processed, the next RX data must be appended to current buffer + uint8_t **ptr = (uint8_t **)(&(transfer->data_buffer)); + *ptr += transfer->actual_num_bytes; + + // Calculate remaining space in the buffer. Attention: pointer arithmetics! + size_t space_left = transfer->data_buffer_size - (transfer->data_buffer - cdc_dev->data.in_data_buffer_base); + uint16_t mps = cdc_dev->data.in_mps; + transfer->num_bytes = (space_left / mps) * mps; // Round down to MPS for next transfer + + if (transfer->num_bytes == 0) { + // The IN buffer cannot accept more data, inform the user and reset the buffer + ESP_LOGW(TAG, "IN buffer overflow"); + cdc_dev->serial_state.bOverRun = true; + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t serial_state_event = { + .type = CDC_ACM_HOST_SERIAL_STATE, + .data.serial_state = cdc_dev->serial_state + }; + cdc_dev->notif.cb(&serial_state_event, cdc_dev->cb_arg); + } + + cdc_acm_reset_in_transfer(cdc_dev); + cdc_dev->serial_state.bOverRun = false; + } + } else { + cdc_acm_reset_in_transfer(cdc_dev); + } + } + + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + usb_host_transfer_submit(cdc_dev->data.in_xfer); +} + +static void notif_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "notif xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (cdc_acm_is_transfer_completed(transfer)) { + cdc_notification_t *notif = (cdc_notification_t *)transfer->data_buffer; + switch (notif->bNotificationCode) { + case USB_CDC_NOTIF_NETWORK_CONNECTION: { + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t net_conn_event = { + .type = CDC_ACM_HOST_NETWORK_CONNECTION, + .data.network_connected = (bool) notif->wValue + }; + cdc_dev->notif.cb(&net_conn_event, cdc_dev->cb_arg); + } + break; + } + case USB_CDC_NOTIF_SERIAL_STATE: { + cdc_dev->serial_state.val = *((uint16_t *)notif->Data); + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t serial_state_event = { + .type = CDC_ACM_HOST_SERIAL_STATE, + .data.serial_state = cdc_dev->serial_state + }; + cdc_dev->notif.cb(&serial_state_event, cdc_dev->cb_arg); + } + break; + } + case USB_CDC_NOTIF_RESPONSE_AVAILABLE: // Encapsulated commands not implemented - fallthrough + default: + ESP_LOGW("CDC_ACM", "Unsupported notification type 0x%02X", notif->bNotificationCode); + ESP_LOG_BUFFER_HEX("CDC_ACM", transfer->data_buffer, transfer->actual_num_bytes); + break; + } + + // Start polling for new data again + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + usb_host_transfer_submit(cdc_dev->notif.xfer); + } +} + +static void out_xfer_cb(usb_transfer_t *transfer) +{ + ESP_LOGD("CDC_ACM", "out/ctrl xfer cb"); + assert(transfer->context); + xSemaphoreGive((SemaphoreHandle_t)transfer->context); +} + +static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: + // Guard p_cdc_acm_obj->new_dev_cb from concurrent access + ESP_LOGD(TAG, "New device connected"); + CDC_ACM_ENTER_CRITICAL(); + cdc_acm_new_dev_callback_t _new_dev_cb = p_cdc_acm_obj->new_dev_cb; + CDC_ACM_EXIT_CRITICAL(); + + if (_new_dev_cb) { + usb_device_handle_t new_dev; + if (usb_host_device_open(p_cdc_acm_obj->cdc_acm_client_hdl, event_msg->new_dev.address, &new_dev) != ESP_OK) { + break; + } + assert(new_dev); + _new_dev_cb(new_dev); + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, new_dev); + } + + break; + case USB_HOST_CLIENT_EVENT_DEV_GONE: { + ESP_LOGD(TAG, "Device suddenly disconnected"); + // Find CDC pseudo-devices associated with this USB device and close them + cdc_dev_t *cdc_dev; + cdc_dev_t *tcdc_dev; + // We are using 'SAFE' version of 'SLIST_FOREACH' which enables user to close the disconnected device in the callback + SLIST_FOREACH_SAFE(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry, tcdc_dev) { + if (cdc_dev->dev_hdl == event_msg->dev_gone.dev_hdl && cdc_dev->notif.cb) { + // The suddenly disconnected device was opened by this driver: inform user about this + const cdc_acm_host_dev_event_data_t disconn_event = { + .type = CDC_ACM_HOST_DEVICE_DISCONNECTED, + .data.cdc_hdl = (cdc_acm_dev_hdl_t) cdc_dev, + }; + cdc_dev->notif.cb(&disconn_event, cdc_dev->cb_arg); + } + } + break; + } + default: + assert(false); + break; + } +} + +esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms) +{ + esp_err_t ret; + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + CDC_ACM_CHECK(data && (data_len > 0), ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_dev->data.out_xfer, ESP_ERR_NOT_SUPPORTED); // Device was opened as read-only. + CDC_ACM_CHECK(data_len <= cdc_dev->data.out_xfer->data_buffer_size, ESP_ERR_INVALID_SIZE); + + // Take OUT mutex and fill the OUT transfer + BaseType_t taken = xSemaphoreTake(cdc_dev->data.out_mux, pdMS_TO_TICKS(timeout_ms)); + if (taken != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + ESP_LOGD("CDC_ACM", "Submitting BULK OUT transfer"); + memcpy(cdc_dev->data.out_xfer->data_buffer, data, data_len); + cdc_dev->data.out_xfer->num_bytes = data_len; + cdc_dev->data.out_xfer->timeout_ms = timeout_ms; + ESP_GOTO_ON_ERROR(usb_host_transfer_submit(cdc_dev->data.out_xfer), unblock, TAG,); + + // Wait for OUT transfer completion + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->data.out_xfer->context, pdMS_TO_TICKS(timeout_ms)); + if (!taken) { + // Reset the endpoint + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.out_xfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Bulk OUT transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->actual_num_bytes == data_len, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->data.out_mux); + return ret; +} + +esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding) +{ + CDC_ACM_CHECK(line_coding, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, true, USB_CDC_REQ_GET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0), + TAG,); + ESP_LOGD(TAG, "Line Get: Rate: %"PRIu32", Stop bits: %d, Parity: %d, Databits: %d", line_coding->dwDTERate, + line_coding->bCharFormat, line_coding->bParityType, line_coding->bDataBits); + return ESP_OK; +} + +esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding) +{ + CDC_ACM_CHECK(line_coding, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SET_LINE_CODING, (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), 0), + TAG,); + ESP_LOGD(TAG, "Line Set: Rate: %"PRIu32", Stop bits: %d, Parity: %d, Databits: %d", line_coding->dwDTERate, + line_coding->bCharFormat, line_coding->bParityType, line_coding->bDataBits); + return ESP_OK; +} + +esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts) +{ + const uint16_t ctrl_bitmap = (uint16_t)dtr | ((uint16_t)rts << 1); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SET_CONTROL_LINE_STATE, NULL, 0, ctrl_bitmap), + TAG,); + ESP_LOGD(TAG, "Control Line Set: DTR: %d, RTS: %d", dtr, rts); + return ESP_OK; +} + +esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms) +{ + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SEND_BREAK, NULL, 0, duration_ms), + TAG,); + + // Block until break is deasserted + vTaskDelay(pdMS_TO_TICKS(duration_ms + 1)); + return ESP_OK; +} + +esp_err_t cdc_acm_host_send_custom_request(cdc_acm_dev_hdl_t cdc_hdl, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data) +{ + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + if (wLength > 0) { + CDC_ACM_CHECK(data, ESP_ERR_INVALID_ARG); + } + CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= wLength, ESP_ERR_INVALID_SIZE); + + esp_err_t ret; + + // Take Mutex and fill the CTRL request + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(5000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + usb_setup_packet_t *req = (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); + uint8_t *start_of_data = (uint8_t *)req + sizeof(usb_setup_packet_t); + req->bmRequestType = bmRequestType; + req->bRequest = bRequest; + req->wValue = wValue; + req->wIndex = wIndex; + req->wLength = wLength; + + // For IN transfers we must transfer data ownership to CDC driver + const bool in_transfer = bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; + if (!in_transfer) { + memcpy(start_of_data, data, wLength); + } + + cdc_dev->ctrl_transfer->num_bytes = wLength + sizeof(usb_setup_packet_t); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->ctrl_transfer), + unblock, TAG, "CTRL transfer failed"); + + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, pdMS_TO_TICKS(5000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 5 seconds + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == cdc_dev->ctrl_transfer->num_bytes, ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Incorrect number of bytes transferred"); + + // For OUT transfers, we must transfer data ownership to user + if (in_transfer) { + memcpy(data, start_of_data, wLength); + } + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->ctrl_mux); + return ret; +} + +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_request_code_t request, uint8_t *data, uint16_t data_len, uint16_t value) +{ + CDC_ACM_CHECK(cdc_dev, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_dev->notif.intf_desc, ESP_ERR_NOT_SUPPORTED); + + uint8_t req_type = USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + if (in_transfer) { + req_type |= USB_BM_REQUEST_TYPE_DIR_IN; + } else { + req_type |= USB_BM_REQUEST_TYPE_DIR_OUT; + } + return cdc_acm_host_send_custom_request((cdc_acm_dev_hdl_t) cdc_dev, req_type, request, value, cdc_dev->notif.intf_desc->bInterfaceNumber, data_len, data); +} + +esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data) +{ + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + if (comm != NULL) { + *comm = cdc_dev->comm_protocol; + } + if (data != NULL) { + *data = cdc_dev->data_protocol; + } + return ESP_OK; +} diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/idf_component.yml b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/idf_component.yml new file mode 100644 index 00000000..f5ec5a8e --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/idf_component.yml @@ -0,0 +1,10 @@ +dependencies: + idf: + version: '>=4.4' +description: USB Host CDC-ACM driver +targets: +- esp32s2 +- esp32s3 +- esp32p4 +url: https://github.com/espressif/idf-extra-components/tree/master/usb/usb_host_cdc_acm +version: 2.0.2~1 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/include/usb/cdc_acm_host.h b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/include/usb/cdc_acm_host.h new file mode 100644 index 00000000..7443d903 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/include/usb/cdc_acm_host.h @@ -0,0 +1,360 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "usb/usb_host.h" +#include "usb_types_cdc.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct cdc_dev_s *cdc_acm_dev_hdl_t; + +/** + * @brief Line Coding structure + * @see Table 17, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint32_t dwDTERate; // in bits per second + uint8_t bCharFormat; // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits + uint8_t bParityType; // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space + uint8_t bDataBits; // 5, 6, 7, 8 or 16 +} __attribute__((packed)) cdc_acm_line_coding_t; + +/** + * @brief UART State Bitmap + * @see Table 31, USB CDC-PSTN specification rev. 1.2 + */ +typedef union { + struct { + uint16_t bRxCarrier : 1; // State of receiver carrier detection mechanism of device. This signal corresponds to V.24 signal 109 and RS-232 signal DCD. + uint16_t bTxCarrier : 1; // State of transmission carrier. This signal corresponds to V.24 signal 106 and RS-232 signal DSR. + uint16_t bBreak : 1; // State of break detection mechanism of the device. + uint16_t bRingSignal : 1; // State of ring signal detection of the device. + uint16_t bFraming : 1; // A framing error has occurred. + uint16_t bParity : 1; // A parity error has occurred. + uint16_t bOverRun : 1; // Received data has been discarded due to overrun in the device. + uint16_t reserved : 9; + }; + uint16_t val; +} cdc_acm_uart_state_t; + +/** + * @brief CDC-ACM Device Event types to upper layer + * + */ +typedef enum { + CDC_ACM_HOST_ERROR, + CDC_ACM_HOST_SERIAL_STATE, + CDC_ACM_HOST_NETWORK_CONNECTION, + CDC_ACM_HOST_DEVICE_DISCONNECTED +} cdc_acm_host_dev_event_t; + +/** + * @brief CDC-ACM Device Event data structure + * + */ +typedef struct { + cdc_acm_host_dev_event_t type; + union { + int error; //!< Error code from USB Host + cdc_acm_uart_state_t serial_state; //!< Serial (UART) state + bool network_connected; //!< Network connection event + cdc_acm_dev_hdl_t cdc_hdl; //!< Disconnection event + } data; +} cdc_acm_host_dev_event_data_t; + +/** + * @brief New USB device callback + * + * Provides already opened usb_dev, that will be closed after this callback returns. + * This is useful for peeking device's descriptors, e.g. peeking VID/PID and loading proper driver. + * + * @attention This callback is called from USB Host context, so the CDC device can't be opened here. + */ +typedef void (*cdc_acm_new_dev_callback_t)(usb_device_handle_t usb_dev); + +/** + * @brief Data receive callback type + * + * @param[in] data Pointer to received data + * @param[in] data_len Lenght of received data in bytes + * @param[in] user_arg User's argument passed to open function + * @return true Received data was processed -> Flush RX buffer + * @return false Received data was NOT processed -> Append new data to the buffer + */ +typedef bool (*cdc_acm_data_callback_t)(const uint8_t *data, size_t data_len, void *user_arg); + +/** + * @brief Device event callback type + * + * @param[in] event Event strucutre + * @param[in] user_arg User's argument passed to open function + */ +typedef void (*cdc_acm_host_dev_callback_t)(const cdc_acm_host_dev_event_data_t *event, void *user_ctx); + +/** + * @brief Configuration structure of USB Host CDC-ACM driver + * + */ +typedef struct { + size_t driver_task_stack_size; /**< Stack size of the driver's task */ + unsigned driver_task_priority; /**< Priority of the driver's task */ + int xCoreID; /**< Core affinity of the driver's task */ + cdc_acm_new_dev_callback_t new_dev_cb; /**< New USB device connected callback. Can be NULL. */ +} cdc_acm_host_driver_config_t; + +/** + * @brief Configuration structure of CDC-ACM device + * + */ +typedef struct { + uint32_t connection_timeout_ms; /**< Timeout for USB device connection in [ms] */ + size_t out_buffer_size; /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */ + size_t in_buffer_size; /**< Maximum size of USB bulk in transfer */ + cdc_acm_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */ + cdc_acm_data_callback_t data_cb; /**< Device's data RX callback function. Can be NULL for write-only devices */ + void *user_arg; /**< User's argument that will be passed to the callbacks */ +} cdc_acm_host_device_config_t; + +/** + * @brief Install CDC-ACM driver + * + * - USB Host Library must already be installed before calling this function (via usb_host_install()) + * - This function should be called before calling any other CDC driver functions + * + * @param[in] driver_config Driver configuration structure. If set to NULL, a default configuration will be used. + * @return esp_err_t + */ +esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config); + +/** + * @brief Uninstall CDC-ACM driver + * + * - Users must ensure that all CDC devices must be closed via cdc_acm_host_close() before calling this function + * + * @return esp_err_t + */ +esp_err_t cdc_acm_host_uninstall(void); + +/** + * @brief Register new USB device callback + * + * The callback will be called for every new USB device, not just CDC-ACM class. + * + * @param[in] new_dev_cb New device callback function + * @return esp_err_t + */ +esp_err_t cdc_acm_host_register_new_dev_callback(cdc_acm_new_dev_callback_t new_dev_cb); + +/** + * @brief Open CDC-ACM compliant device + * + * CDC-ACM compliant device must contain either an Interface Association Descriptor or CDC-Union descriptor, + * which are used for the driver's configuration. + * + * @param[in] vid Device's Vendor ID + * @param[in] pid Device's Product ID + * @param[in] interface_idx Index of device's interface used for CDC-ACM communication + * @param[in] dev_config Configuration structure of the device + * @param[out] cdc_hdl_ret CDC device handle + * @return esp_err_t + */ +esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); + +/** + * @brief Open CDC-ACM non-compliant device + * + * CDC-ACM non-compliant device acts as CDC-ACM device but doesn't support all its features. + * User must provide the interface index that will be used (zero for non-composite devices). + * + * @param[in] vid Device's Vendor ID + * @param[in] pid Device's Product ID + * @param[in] interface_idx Index of device's interface used for CDC-ACM like communication + * @param[in] dev_config Configuration structure of the device + * @param[out] cdc_hdl_ret CDC device handle + * @return esp_err_t + */ +esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); + +/** + * @brief Close CDC device and release its resources + * + * @note All in-flight transfers will be prematurely canceled. + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @return esp_err_t + */ +esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl); + +/** + * @brief Transmit data - blocking mode + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] data Data to be sent + * @param[in] data_len Data length + * @param[in] timeout_ms Timeout in [ms] + * @return esp_err_t + */ +esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms); + +/** + * @brief SetLineCoding function + * + * @see Chapter 6.3.10, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ +esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding); + +/** + * @brief GetLineCoding function + * + * @see Chapter 6.3.11, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[out] line_coding Line Coding structure to be filled + * @return esp_err_t + */ +esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding); + +/** + * @brief SetControlLineState function + * + * @see Chapter 6.3.12, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ +esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts); + +/** + * @brief SendBreak function + * + * This function will block until the duration_ms has passed. + * + * @see Chapter 6.3.13, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] duration_ms Duration of the Break signal in [ms] + * @return esp_err_t + */ +esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms); + +/** + * @brief Print device's descriptors + * + * Device and full Configuration descriptors are printed in human readable format to stdout. + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + */ +void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl); + +/** + * @brief Get protocols defined in USB-CDC interface descriptors + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[out] comm Communication protocol + * @param[out] data Data protocol + * @return esp_err_t + */ +esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data); + +/** + * @brief Send command to CTRL endpoint + * + * Sends Control transfer as described in USB specification chapter 9. + * This function can be used by device drivers that use custom/vendor specific commands. + * These commands can either extend or replace commands defined in USB CDC-PSTN specification rev. 1.2. + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] bmRequestType Field of USB control request + * @param[in] bRequest Field of USB control request + * @param[in] wValue Field of USB control request + * @param[in] wIndex Field of USB control request + * @param[in] wLength Field of USB control request + * @param[inout] data Field of USB control request + * @return esp_err_t + */ +esp_err_t cdc_acm_host_send_custom_request(cdc_acm_dev_hdl_t cdc_hdl, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data); + +#ifdef __cplusplus +} +class CdcAcmDevice { +public: + // Operators + CdcAcmDevice() : cdc_hdl(NULL) {}; + virtual ~CdcAcmDevice() + { + // Close CDC-ACM device, if it wasn't explicitly closed + if (this->cdc_hdl != NULL) { + this->close(); + } + } + + inline esp_err_t tx_blocking(uint8_t *data, size_t len, uint32_t timeout_ms = 100) + { + return cdc_acm_host_data_tx_blocking(this->cdc_hdl, data, len, timeout_ms); + } + + inline esp_err_t open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config) + { + return cdc_acm_host_open(vid, pid, interface_idx, dev_config, &this->cdc_hdl); + } + + inline esp_err_t open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config) + { + return cdc_acm_host_open_vendor_specific(vid, pid, interface_idx, dev_config, &this->cdc_hdl); + } + + inline esp_err_t close() + { + const esp_err_t err = cdc_acm_host_close(this->cdc_hdl); + if (err == ESP_OK) { + this->cdc_hdl = NULL; + } + return err; + } + + virtual inline esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding) const + { + return cdc_acm_host_line_coding_get(this->cdc_hdl, line_coding); + } + + virtual inline esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding) + { + return cdc_acm_host_line_coding_set(this->cdc_hdl, line_coding); + } + + virtual inline esp_err_t set_control_line_state(bool dtr, bool rts) + { + return cdc_acm_host_set_control_line_state(this->cdc_hdl, dtr, rts); + } + + virtual inline esp_err_t send_break(uint16_t duration_ms) + { + return cdc_acm_host_send_break(this->cdc_hdl, duration_ms); + } + + inline esp_err_t send_custom_request(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data) + { + return cdc_acm_host_send_custom_request(this->cdc_hdl, bmRequestType, bRequest, wValue, wIndex, wLength, data); + } + +private: + CdcAcmDevice &operator= (const CdcAcmDevice &Copy); + bool operator== (const CdcAcmDevice ¶m) const; + bool operator!= (const CdcAcmDevice ¶m) const; + cdc_acm_dev_hdl_t cdc_hdl; +}; +#endif diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/include/usb/usb_types_cdc.h b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/include/usb/usb_types_cdc.h new file mode 100644 index 00000000..bd8b1d07 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/include/usb/usb_types_cdc.h @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include + +/** + * @brief USB CDC Descriptor Subtypes + * + * @see Table 13, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_DESC_SUBTYPE_HEADER = 0x00, // Header Functional Descriptor + USB_CDC_DESC_SUBTYPE_CALL = 0x01, // Call Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_ACM = 0x02, // Abstract Control Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_DLM = 0x03, // Direct Line Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_RINGER = 0x04, // Telephone Ringer Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_CLSR = 0x05, // Telephone Call and Line State Reporting Capabilities Functional Descriptor + USB_CDC_DESC_SUBTYPE_UNION = 0x06, // Union Functional Descriptor + USB_CDC_DESC_SUBTYPE_COUNTRY = 0x07, // Country Selection Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_MODE = 0x08, // Telephone Operational Modes Functional Descriptor + USB_CDC_DESC_SUBTYPE_TERMINAL = 0x09, // USB Terminal + USB_CDC_DESC_SUBTYPE_NCHT = 0x0A, // Network Channel Terminal + USB_CDC_DESC_SUBTYPE_PROTOCOL = 0x08, // Protocol Unit + USB_CDC_DESC_SUBTYPE_EXTENSION = 0x0C, // Extension Unit + USB_CDC_DESC_SUBTYPE_MULTI_CHAN = 0x0D, // Multi-Channel Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_CAPI = 0x0E, // CAPI Control + USB_CDC_DESC_SUBTYPE_ETH = 0x0F, // Ethernet Networking + USB_CDC_DESC_SUBTYPE_ATM = 0x10, // ATM Networking + USB_CDC_DESC_SUBTYPE_WHANDSET = 0x11, // Wireless Handset Control Model Functional Descriptor + USB_CDC_DESC_SUBTYPE_MDLM = 0x12, // Mobile Direct Line Model + USB_CDC_DESC_SUBTYPE_MDLM_DETAIL = 0x13, // MDLM Detail + USB_CDC_DESC_SUBTYPE_DMM = 0x14, // Device Management Model + USB_CDC_DESC_SUBTYPE_OBEX = 0x15, // OBEX Functional + USB_CDC_DESC_SUBTYPE_COMMAND_SET = 0x16, // Command Set + USB_CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_CM = 0x18, // Telephone Control Model Functional Descriptor + USB_CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19, // OBEX Service Identifier Functional Descriptor + USB_CDC_DESC_SUBTYPE_NCM = 0x1A // NCM Functional Descriptor +} __attribute__((packed)) cdc_desc_subtype_t; + +/** + * @brief USB CDC Subclass codes + * + * @see Table 4, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_SUBCLASS_DLCM = 0x01, // Direct Line Control Model + USB_CDC_SUBCLASS_ACM = 0x02, // Abstract Control Model + USB_CDC_SUBCLASS_TCM = 0x03, // Telephone Control Model + USB_CDC_SUBCLASS_MCHCM = 0x04, // Multi-Channel Control Model + USB_CDC_SUBCLASS_CAPI = 0x05, // CAPI Control Model + USB_CDC_SUBCLASS_ECM = 0x06, // Ethernet Networking Control Model + USB_CDC_SUBCLASS_ATM = 0x07, // ATM Networking Model + USB_CDC_SUBCLASS_HANDSET = 0x08, // Wireless Handset Control Model + USB_CDC_SUBCLASS_DEV_MAN = 0x09, // Device Management + USB_CDC_SUBCLASS_MOBILE = 0x0A, // Mobile Direct Line Model + USB_CDC_SUBCLASS_OBEX = 0x0B, // OBEX + USB_CDC_SUBCLASS_EEM = 0x0C, // Ethernet Emulation Model + USB_CDC_SUBCLASS_NCM = 0x0D // Network Control Model +} __attribute__((packed)) cdc_subclass_t; + +/** + * @brief USB CDC Communications Protocol Codes + * + * @see Table 5, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_COMM_PROTOCOL_NONE = 0x00, // No class specific protocol required + USB_CDC_COMM_PROTOCOL_V250 = 0x01, // AT Commands: V.250 etc + USB_CDC_COMM_PROTOCOL_PCAA = 0x02, // AT Commands defined by PCCA-101 + USB_CDC_COMM_PROTOCOL_PCAA_A = 0x03, // AT Commands defined by PCAA-101 & Annex O + USB_CDC_COMM_PROTOCOL_GSM = 0x04, // AT Commands defined by GSM 07.07 + USB_CDC_COMM_PROTOCOL_3GPP = 0x05, // AT Commands defined by 3GPP 27.007 + USB_CDC_COMM_PROTOCOL_TIA = 0x06, // AT Commands defined by TIA for CDMA + USB_CDC_COMM_PROTOCOL_EEM = 0x07, // Ethernet Emulation Model + USB_CDC_COMM_PROTOCOL_EXT = 0xFE, // External Protocol: Commands defined by Command Set Functional Descriptor + USB_CDC_COMM_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_comm_protocol_t; + +/** + * @brief USB CDC Data Protocol Codes + * + * @see Table 7, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_DATA_PROTOCOL_NONE = 0x00, // No class specific protocol required + USB_CDC_DATA_PROTOCOL_NCM = 0x01, // Network Transfer Block + USB_CDC_DATA_PROTOCOL_I430 = 0x30, // Physical interface protocol for ISDN BRI + USB_CDC_DATA_PROTOCOL_HDLC = 0x31, // HDLC + USB_CDC_DATA_PROTOCOL_Q921M = 0x50, // Management protocol for Q.921 data link protocol + USB_CDC_DATA_PROTOCOL_Q921 = 0x51, // Data link protocol for Q.931 + USB_CDC_DATA_PROTOCOL_Q921TM = 0x52, // TEI-multiplexor for Q.921 data link protocol + USB_CDC_DATA_PROTOCOL_V42BIS = 0x90, // Data compression procedures + USB_CDC_DATA_PROTOCOL_Q931 = 0x91, // Euro-ISDN protocol control + USB_CDC_DATA_PROTOCOL_V120 = 0x92, // V.24 rate adaptation to ISDN + USB_CDC_DATA_PROTOCOL_CAPI = 0x93, // CAPI Commands + USB_CDC_DATA_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_data_protocol_t; + +/** + * @brief USB CDC Request Codes + * + * @see Table 19, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00, + USB_CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01, + USB_CDC_REQ_SET_COMM_FEATURE = 0x02, + USB_CDC_REQ_GET_COMM_FEATURE = 0x03, + USB_CDC_REQ_CLEAR_COMM_FEATURE = 0x04, + USB_CDC_REQ_SET_AUX_LINE_STATE = 0x10, + USB_CDC_REQ_SET_HOOK_STATE = 0x11, + USB_CDC_REQ_PULSE_SETUP = 0x12, + USB_CDC_REQ_SEND_PULSE = 0x13, + USB_CDC_REQ_SET_PULSE_TIME = 0x14, + USB_CDC_REQ_RING_AUX_JACK = 0x15, + USB_CDC_REQ_SET_LINE_CODING = 0x20, + USB_CDC_REQ_GET_LINE_CODING = 0x21, + USB_CDC_REQ_SET_CONTROL_LINE_STATE = 0x22, + USB_CDC_REQ_SEND_BREAK = 0x23, + USB_CDC_REQ_SET_RINGER_PARMS = 0x30, + USB_CDC_REQ_GET_RINGER_PARMS = 0x31, + USB_CDC_REQ_SET_OPERATION_PARMS = 0x32, + USB_CDC_REQ_GET_OPERATION_PARMS = 0x33, + USB_CDC_REQ_SET_LINE_PARMS = 0x34, + USB_CDC_REQ_GET_LINE_PARMS = 0x35, + USB_CDC_REQ_DIAL_DIGITS = 0x36, + USB_CDC_REQ_SET_UNIT_PARAMETER = 0x37, + USB_CDC_REQ_GET_UNIT_PARAMETER = 0x38, + USB_CDC_REQ_CLEAR_UNIT_PARAMETER = 0x39, + USB_CDC_REQ_GET_PROFILE = 0x3A, + USB_CDC_REQ_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + USB_CDC_REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + USB_CDC_REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + USB_CDC_REQ_SET_ETHERNET_PACKET_FILTER = 0x43, + USB_CDC_REQ_GET_ETHERNET_STATISTIC = 0x44, + USB_CDC_REQ_SET_ATM_DATA_FORMAT = 0x50, + USB_CDC_REQ_GET_ATM_DEVICE_STATISTICS = 0x51, + USB_CDC_REQ_SET_ATM_DEFAULT_VC = 0x52, + USB_CDC_REQ_GET_ATM_VC_STATISTICS = 0x53, + USB_CDC_REQ_GET_NTB_PARAMETERS = 0x80, + USB_CDC_REQ_GET_NET_ADDRESS = 0x81, + USB_CDC_REQ_SET_NET_ADDRESS = 0x82, + USB_CDC_REQ_GET_NTB_FORMAT = 0x83, + USB_CDC_REQ_SET_NTB_FORMAT = 0x84, + USB_CDC_REQ_GET_NTB_INPUT_SIZE = 0x85, + USB_CDC_REQ_SET_NTB_INPUT_SIZE = 0x86, + USB_CDC_REQ_GET_MAX_DATAGRAM_SIZE = 0x87, + USB_CDC_REQ_SET_MAX_DATAGRAM_SIZE = 0x88, + USB_CDC_REQ_GET_CRC_MODE = 0x89, + USB_CDC_REQ_SET_CRC_MODE = 0x8A +} __attribute__((packed)) cdc_request_code_t; + +/** + * @brief USB CDC Notification Codes + * + * @see Table 20, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_NOTIF_NETWORK_CONNECTION = 0x00, + USB_CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, + USB_CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, + USB_CDC_NOTIF_RING_DETECT = 0x09, + USB_CDC_NOTIF_SERIAL_STATE = 0x20, + USB_CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + USB_CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + USB_CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A +} __attribute__((packed)) cdc_notification_code_t; + +typedef struct { + uint8_t bmRequestType; + cdc_notification_code_t bNotificationCode; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint8_t Data[]; +} __attribute__((packed)) cdc_notification_t; + +/** + * @brief USB CDC Header Functional Descriptor + * + * @see Table 15, USB CDC specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + uint16_t bcdCDC; // CDC version as binary-coded decimal. This driver is written for version 1.2 +} __attribute__((packed)) cdc_header_desc_t; + +/** + * @brief USB CDC Union Functional Descriptor + * + * @see Table 16, USB CDC specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + const uint8_t bControlInterface; // Master/controlling interface + uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces +} __attribute__((packed)) cdc_union_desc_t; diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/CMakeLists.txt b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/CMakeLists.txt new file mode 100644 index 00000000..97c08f86 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/CMakeLists.txt @@ -0,0 +1,10 @@ +include($ENV{IDF_PATH}/tools/cmake/version.cmake) +set (TINYUSB_LIB) +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") + set(TINYUSB_LIB "esp_tinyusb") +else() + set(TINYUSB_LIB "tinyusb") +endif() +idf_component_register(SRCS "test_cdc_acm_host.c" "usb_device.c" + INCLUDE_DIRS "." + REQUIRES usb_host_cdc_acm unity ${TINYUSB_LIB}) diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/test_cdc_acm_host.c b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/test_cdc_acm_host.c new file mode 100644 index 00000000..c234c63d --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/test_cdc_acm_host.c @@ -0,0 +1,533 @@ +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "soc/soc_caps.h" +#if SOC_USB_OTG_SUPPORTED + +#include +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_err.h" + +#include "esp_private/usb_phy.h" +#include "usb/usb_host.h" +#include "usb/cdc_acm_host.h" +#include + +#include "esp_intr_alloc.h" + +#include "unity.h" +#include "soc/usb_wrap_struct.h" + +static uint8_t tx_buf[] = "HELLO"; +static uint8_t tx_buf2[] = "WORLD"; +static int nb_of_responses; +static int nb_of_responses2; +static bool new_dev_cb_called = false; +static bool rx_overflow = false; +static usb_phy_handle_t phy_hdl = NULL; + +static void force_conn_state(bool connected, TickType_t delay_ticks) +{ + TEST_ASSERT_NOT_EQUAL(NULL, phy_hdl); + if (delay_ticks > 0) { + //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. + vTaskDelay(delay_ticks); + } + ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); +} + +void usb_lib_task(void *arg) +{ + // Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl)); + // Install USB Host driver. Should only be called once in entire application + const usb_host_config_t host_config = { + .skip_phy_setup = true, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config)); + printf("USB Host installed\n"); + xTaskNotifyGive(arg); + + bool all_clients_gone = false; + bool all_dev_free = false; + while (!all_clients_gone || !all_dev_free) { + // Start handling system events + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + printf("No more clients\n"); + usb_host_device_free_all(); + all_clients_gone = true; + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + printf("All devices freed\n"); + all_dev_free = true; + } + } + + // Clean up USB Host + vTaskDelay(10); // Short delay to allow clients clean-up + TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall()); + TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); //Tear down USB PHY + phy_hdl = NULL; + vTaskDelete(NULL); +} + +void test_install_cdc_driver(void) +{ + // Create a task that will handle USB library events + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(usb_lib_task, "usb_lib", 4 * 4096, xTaskGetCurrentTaskHandle(), 10, NULL, 0)); + ulTaskNotifyTake(false, 1000); + + printf("Installing CDC-ACM driver\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_install(NULL)); +} + +/* ------------------------------- Callbacks -------------------------------- */ +static bool handle_rx(const uint8_t *data, size_t data_len, void *arg) +{ + printf("Data received\n"); + nb_of_responses++; + TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len); + return true; +} + +static bool handle_rx2(const uint8_t *data, size_t data_len, void *arg) +{ + printf("Data received 2\n"); + nb_of_responses2++; + TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len); + return true; +} + +static bool handle_rx_advanced(const uint8_t *data, size_t data_len, void *arg) +{ + bool *process_data = (bool *)arg; + return *process_data; +} + +static void notif_cb(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +{ + switch (event->type) { + case CDC_ACM_HOST_ERROR: + printf("Error event %d\n", event->data.error); + break; + case CDC_ACM_HOST_SERIAL_STATE: + if (event->data.serial_state.bOverRun) { + rx_overflow = true; + } + break; + case CDC_ACM_HOST_NETWORK_CONNECTION: + break; + case CDC_ACM_HOST_DEVICE_DISCONNECTED: + printf("Disconnection event\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(event->data.cdc_hdl)); + xTaskNotifyGive(user_ctx); + break; + default: + assert(false); + } +} + +static void new_dev_cb(usb_device_handle_t usb_dev) +{ + new_dev_cb_called = true; + const usb_config_desc_t *config_desc; + const usb_device_desc_t *device_desc; + + // Get descriptors + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_device_descriptor(usb_dev, &device_desc)); + TEST_ASSERT_EQUAL(ESP_OK, usb_host_get_active_config_descriptor(usb_dev, &config_desc)); + + printf("New device connected. VID = 0x%04X PID = %04X\n", device_desc->idVendor, device_desc->idProduct); +} + +/* Basic test to check CDC communication: + * open/read/write/close device + * CDC-ACM specific commands: set/get_line_coding, set_control_line_state */ +TEST_CASE("read_write", "[cdc_acm]") +{ + nb_of_responses = 0; + cdc_acm_dev_hdl_t cdc_dev = NULL; + + test_install_cdc_driver(); + + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx, + .user_arg = tx_buf, + }; + + printf("Opening CDC-ACM device\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + TEST_ASSERT_NOT_NULL(cdc_dev); + cdc_acm_host_desc_print(cdc_dev); + vTaskDelay(100); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + vTaskDelay(100); // Wait until responses are processed + + // We sent two messages, should get two responses + TEST_ASSERT_EQUAL(2, nb_of_responses); + + cdc_acm_line_coding_t line_coding_get; + const cdc_acm_line_coding_t line_coding_set = { + .dwDTERate = 9600, + .bDataBits = 7, + .bParityType = 1, + .bCharFormat = 1, + }; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_set(cdc_dev, &line_coding_set)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get)); + TEST_ASSERT_EQUAL_MEMORY(&line_coding_set, &line_coding_get, sizeof(cdc_acm_line_coding_t)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false)); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + + vTaskDelay(20); //Short delay to allow task to be cleaned up +} + +/* Test communication with multiple CDC-ACM devices from one thread */ +TEST_CASE("multiple_devices", "[cdc_acm]") +{ + nb_of_responses = 0; + nb_of_responses2 = 0; + + test_install_cdc_driver(); + + printf("Opening 2 CDC-ACM devices\n"); + cdc_acm_dev_hdl_t cdc_dev1, cdc_dev2; + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 1000, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx, + .user_arg = tx_buf, + }; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev1)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + dev_config.data_cb = handle_rx2; + dev_config.user_arg = tx_buf2; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 2, &dev_config, &cdc_dev2)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + TEST_ASSERT_NOT_NULL(cdc_dev1); + TEST_ASSERT_NOT_NULL(cdc_dev2); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev1, tx_buf, sizeof(tx_buf), 1000)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev2, tx_buf2, sizeof(tx_buf2), 1000)); + + vTaskDelay(100); // Wait for RX callbacks + + // We sent two messages, should get two responses + TEST_ASSERT_EQUAL(1, nb_of_responses); + TEST_ASSERT_EQUAL(1, nb_of_responses2); + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev1)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev2)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + + //Short delay to allow task to be cleaned up + vTaskDelay(20); +} + +#define MULTIPLE_THREADS_TRANSFERS_NUM 5 +#define MULTIPLE_THREADS_TASKS_NUM 4 +void tx_task(void *arg) +{ + cdc_acm_dev_hdl_t cdc_dev = (cdc_acm_dev_hdl_t) arg; + // Send multiple transfers to make sure that some of them will run at the same time + for (int i = 0; i < MULTIPLE_THREADS_TRANSFERS_NUM; i++) { + // BULK endpoints + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + + // CTRL endpoints + cdc_acm_line_coding_t line_coding_get; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_line_coding_get(cdc_dev, &line_coding_get)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_set_control_line_state(cdc_dev, true, false)); + } + vTaskDelete(NULL); +} + +/** + * @brief Multiple threads test + * + * In this test, one CDC device is accessed from multiple threads. + * It has to be opened/closed just once, though. + */ +TEST_CASE("multiple_threads", "[cdc_acm]") +{ + nb_of_responses = 0; + cdc_acm_dev_hdl_t cdc_dev; + test_install_cdc_driver(); + + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 5000, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx, + .user_arg = tx_buf, + }; + + printf("Opening CDC-ACM device\n"); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); // 0x303A:0x4002 (TinyUSB Dual CDC device) + TEST_ASSERT_NOT_NULL(cdc_dev); + + // Create two tasks that will try to access cdc_dev + for (int i = 0; i < MULTIPLE_THREADS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(tx_task, "CDC TX", 4096, cdc_dev, i + 3, NULL)); + } + + // Wait until all tasks finish + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ASSERT_EQUAL(MULTIPLE_THREADS_TASKS_NUM * MULTIPLE_THREADS_TRANSFERS_NUM, nb_of_responses); + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +/* Test CDC driver reaction to USB device sudden disconnection */ +TEST_CASE("sudden_disconnection", "[cdc_acm]") +{ + test_install_cdc_driver(); + + cdc_acm_dev_hdl_t cdc_dev; + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 1000, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx + }; + dev_config.user_arg = xTaskGetCurrentTaskHandle(); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + + force_conn_state(false, pdMS_TO_TICKS(10)); // Simulate device disconnection + TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(false, pdMS_TO_TICKS(100))); // Notify will succeed only if CDC_ACM_HOST_DEVICE_DISCONNECTED notification was generated + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); //Short delay to allow task to be cleaned up +} + +/** + * @brief CDC-ACM error handling test + * + * There are multiple erroneous scenarios checked in this test: + * + * -# Install CDC-ACM driver without USB Host + * -# Open device without installed driver + * -# Uninstall driver before installing it + * -# Open non-existent device + * -# Open the same device twice + * -# Uninstall driver with open devices + * -# Send data that is too large + * -# Send unsupported CDC request + * -# Write to read-only device + */ +TEST_CASE("error_handling", "[cdc_acm]") +{ + cdc_acm_dev_hdl_t cdc_dev; + cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 64, + .event_cb = notif_cb, + .data_cb = handle_rx + }; + + // Install CDC-ACM driver without USB Host + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_install(NULL)); + + // Open device without installed driver + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + + // Uninstall driver before installing it + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall()); + + // Properly install USB and CDC drivers + test_install_cdc_driver(); + + // Open non-existent device + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, cdc_acm_host_open(0x303A, 0x1234, 0, &dev_config, &cdc_dev)); // 0x303A:0x1234 this device is not connected to USB Host + TEST_ASSERT_NULL(cdc_dev); + + // Open regular device + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + + // Open one CDC-ACM device twice + cdc_acm_dev_hdl_t cdc_dev_test; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev_test)); + TEST_ASSERT_NULL(cdc_dev_test); + + // Uninstall driver with open devices + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, cdc_acm_host_uninstall()); + + // Send data that is too large and NULL data + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, 1024, 1000)); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, cdc_acm_host_data_tx_blocking(cdc_dev, NULL, 10, 1000)); + + // Change mode to read-only and try to write to it + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + dev_config.out_buffer_size = 0; // Read-only device + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, cdc_acm_host_data_tx_blocking(cdc_dev, tx_buf, sizeof(tx_buf), 1000)); + + // Send unsupported CDC request (TinyUSB accepts SendBreak command, eventhough it doesn't support it) + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_send_break(cdc_dev, 100)); + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +TEST_CASE("custom_command", "[cdc_acm]") +{ + test_install_cdc_driver(); + + // Open device with only CTRL endpoint (endpoint no 0) + cdc_acm_dev_hdl_t cdc_dev; + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 0, + .event_cb = notif_cb, + .data_cb = NULL + }; + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + + // Corresponds to command: Set Control Line State, DTR on, RTS off + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_send_custom_request(cdc_dev, 0x21, 34, 1, 0, 0, NULL)); + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +TEST_CASE("new_device_connection_1", "[cdc_acm]") +{ + // Create a task that will handle USB library events + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(usb_lib_task, "usb_lib", 4 * 4096, xTaskGetCurrentTaskHandle(), 10, NULL, 0)); + ulTaskNotifyTake(false, 1000); + + // Option 1: Register callback during driver install + printf("Installing CDC-ACM driver\n"); + cdc_acm_host_driver_config_t driver_config = { + .driver_task_priority = 11, + .driver_task_stack_size = 2048, + .xCoreID = 0, + .new_dev_cb = new_dev_cb, + }; + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_install(&driver_config)); + + vTaskDelay(50); + TEST_ASSERT_TRUE_MESSAGE(new_dev_cb_called, "New device callback was not called\n"); + new_dev_cb_called = false; + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +TEST_CASE("new_device_connection_2", "[cdc_acm]") +{ + test_install_cdc_driver(); + + // Option 2: Register callback after driver install + force_conn_state(false, 0); + vTaskDelay(50); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_register_new_dev_callback(new_dev_cb)); + force_conn_state(true, 0); + vTaskDelay(50); + TEST_ASSERT_TRUE_MESSAGE(new_dev_cb_called, "New device callback was not called\n"); + new_dev_cb_called = false; + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +TEST_CASE("rx_buffer", "[cdc_acm]") +{ + test_install_cdc_driver(); + bool process_data = true; // This variable will determine return value of data_cb + + cdc_acm_dev_hdl_t cdc_dev; + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 500, + .out_buffer_size = 64, + .in_buffer_size = 512, + .event_cb = notif_cb, + .data_cb = handle_rx_advanced, + .user_arg = &process_data, + }; + + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_open(0x303A, 0x4002, 0, &dev_config, &cdc_dev)); + TEST_ASSERT_NOT_NULL(cdc_dev); + + // 1. Send > in_buffer_size bytes of data in normal operation: Expect no error + uint8_t tx_data[64] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + for (int i = 0; i < 10; i++) { + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_data, sizeof(tx_data), 1000)); + vTaskDelay(5); + } + TEST_ASSERT_FALSE_MESSAGE(rx_overflow, "RX overflowed"); + rx_overflow = false; + + // 2. Send < (in_buffer_size - IN_MPS) bytes of data in 'not processed' mode: Expect no error + process_data = false; + for (int i = 0; i < 7; i++) { + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_data, sizeof(tx_data), 1000)); + vTaskDelay(5); + } + TEST_ASSERT_FALSE_MESSAGE(rx_overflow, "RX overflowed"); + rx_overflow = false; + + // 3. Send >= (in_buffer_size - IN_MPS) bytes of data in 'not processed' mode: Expect error + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_data, sizeof(tx_data), 1000)); + vTaskDelay(5); + TEST_ASSERT_TRUE_MESSAGE(rx_overflow, "RX did not overflow"); + rx_overflow = false; + + // 4. Send more data to the EP: Expect no error + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_data_tx_blocking(cdc_dev, tx_data, sizeof(tx_data), 1000)); + vTaskDelay(5); + TEST_ASSERT_FALSE_MESSAGE(rx_overflow, "RX overflowed"); + rx_overflow = false; + + // Clean-up + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_dev)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_uninstall()); + vTaskDelay(20); +} + +/* Following test case implements dual CDC-ACM USB device that can be used as mock device for CDC-ACM Host tests */ +void run_usb_dual_cdc_device(void); +TEST_CASE("mock_device_app", "[cdc_acm_device][ignore]") +{ + run_usb_dual_cdc_device(); + while (1) { + vTaskDelay(10); + } +} + +#endif diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/usb_device.c b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/usb_device.c new file mode 100644 index 00000000..74793ed4 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cdc_acm/test/usb_device.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include "sdkconfig.h" +#include "tinyusb.h" +#include "tusb_cdc_acm.h" +#include "esp_idf_version.h" + +static uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; +static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + size_t rx_size = 0; + /* read and write back */ + ESP_ERROR_CHECK(tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size)); + tinyusb_cdcacm_write_queue(itf, buf, rx_size); + tinyusb_cdcacm_write_flush(itf, 0); +} + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +static const tusb_desc_device_t cdc_device_descriptor = { + .bLength = sizeof(cdc_device_descriptor), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = USB_ESPRESSIF_VID, + .idProduct = 0x4002, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN; +static const uint8_t cdc_desc_configuration[] = { + TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 64), + TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 64), +}; +#endif + +void run_usb_dual_cdc_device(void) +{ + const tinyusb_config_t tusb_cfg = { + .external_phy = false, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + .device_descriptor = &cdc_device_descriptor, + .configuration_descriptor = cdc_desc_configuration +#endif + }; + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t amc_cfg = { + .usb_dev = TINYUSB_USBDEV_0, + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = &tinyusb_cdc_rx_callback, + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + + ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); +#if (CONFIG_TINYUSB_CDC_COUNT > 1) + amc_cfg.cdc_port = TINYUSB_CDC_ACM_1; + ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); +#endif + + printf("USB initialization DONE\n"); +} diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/.component_hash b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/.component_hash new file mode 100644 index 00000000..02b1e44d --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/.component_hash @@ -0,0 +1 @@ +c7305154c452531950d8e4ee9838c474b5e4b84fdd12c40b2cae3f6f938752cb \ No newline at end of file diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/CHANGELOG.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/CHANGELOG.md new file mode 100644 index 00000000..5c6e1d4b --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/CHANGELOG.md @@ -0,0 +1,8 @@ +## 1.0.0 +- Initial version + +## 1.0.1 +- Fix GCC-12 compile errors + +## 2.0.0 +- Update to [CDC-ACM driver](https://components.espressif.com/components/espressif/usb_host_cdc_acm) to v2 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/CMakeLists.txt b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/CMakeLists.txt new file mode 100644 index 00000000..f7c14aea --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "usb_host_ch34x_vcp.cpp" + INCLUDE_DIRS "include") diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/LICENSE b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/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/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/README.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/README.md new file mode 100644 index 00000000..c430c32a --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/README.md @@ -0,0 +1,8 @@ +# CH34x USB-UART converter driver + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_ch34x_vcp/badge.svg)](https://components.espressif.com/components/espressif/usb_host_ch34x_vcp) + +Limited implementation only. The vendor does not provide full specification. + +* CH340 and CH341 supported +* [Datasheet](http://www.wch-ic.com/downloads/CH341DS1_PDF.html) diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/idf_component.yml b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/idf_component.yml new file mode 100644 index 00000000..3ec06273 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + espressif/usb_host_cdc_acm: + public: true + version: '>=1.0.4,<3.0.0' + idf: + version: '>=4.4' +description: USB Host driver for CH34x series of chips +url: https://github.com/espressif/idf-extra-components/tree/master/usb/usb_host_ch34x_vcp +version: 2.0.0 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/include/usb/vcp_ch34x.hpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/include/usb/vcp_ch34x.hpp new file mode 100644 index 00000000..7a819071 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/include/usb/vcp_ch34x.hpp @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021 WCH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "usb/cdc_acm_host.h" + +#define NANJING_QINHENG_MICROE_VID (0x1A86) +#define CH340_PID (0x7522) +#define CH340_PID_1 (0x7523) +#define CH341_PID (0x5523) + +namespace esp_usb { +class CH34x : public CdcAcmDevice { +public: + /** + * @brief Constructor for this CH34x driver + * + * @note USB Host library and CDC-ACM driver must be already installed + * + * @param[in] pid PID eg. CH340_PID + * @param[in] dev_config CDC device configuration + * @param[in] interface_idx Interface number + * @return CdcAcmDevice Pointer to created and opened CH34x device + */ + CH34x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief Set Line Coding method + * + * @note Overrides default implementation in CDC-ACM driver + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Control Line State method + * + * @note Overrides default implementation in CDC-ACM driver + * @note Both signals are active low + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ + esp_err_t set_control_line_state(bool dtr, bool rts); + + // List of supported VIDs and PIDs + static constexpr uint16_t vid = NANJING_QINHENG_MICROE_VID; + static constexpr std::array pids = {CH340_PID, CH340_PID_1, CH341_PID}; + +private: + const uint8_t intf; + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; + using CdcAcmDevice::send_break; // Break is not supported by CH34x + using CdcAcmDevice::line_coding_get; // Manufacturer doesn't provide enough information to implement this + + // This function comes from official Linux driver + static int calculate_baud_divisor(unsigned int baud_rate, unsigned char *factor, unsigned char *divisor); +}; +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/usb_host_ch34x_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/usb_host_ch34x_vcp.cpp new file mode 100644 index 00000000..f0d9b4f5 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ch34x_vcp/usb_host_ch34x_vcp.cpp @@ -0,0 +1,210 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021 WCH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb/vcp_ch34x.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS +#error This component requires C++ exceptions +#endif + +#define CH34X_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_DEVICE | USB_BM_REQUEST_TYPE_DIR_IN) +#define CH34X_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_DEVICE | USB_BM_REQUEST_TYPE_DIR_OUT) + +#define CH34X_CMD_READ_TYPE 0xC0 +#define CH34X_CMD_READ 0x95 +#define CH34X_CMD_WRITE 0x9A +#define CH34X_CMD_SERIAL_INIT 0xA1 +#define CH34X_CMD_MODEM_OUT 0xA4 +#define CH34X_CMD_VERSION 0x5F + +// For CMD 0xA4 +#define CH34X_UART_CTS 0x01 +#define CH34X_UART_DSR 0x02 +#define CH34X_UART_RING 0x04 +#define CH34X_UART_DCD 0x08 +#define CH34X_CONTROL_OUT 0x10 +#define CH34X_CONTROL_DTR 0x20 +#define CH34X_CONTROL_RTS 0x40 + +// Uart state +#define CH34X_UART_STATE 0x00 +#define CH34X_UART_OVERRUN_ERROR 0x01 +#define CH34X_UART_BREAK_ERROR // no define +#define CH34X_UART_PARITY_ERROR 0x02 +#define CH34X_UART_FRAME_ERROR 0x06 +#define CH34X_UART_RECV_ERROR 0x02 +#define CH34X_UART_STATE_TRANSIENT_MASK 0x07 + +//CH34x Baud Rate +#define CH34x_BAUDRATE_FACTOR 1532620800 +#define CH34x_BAUDRATE_DIVMAX 3 + +// Line Coding Register (LCR) +#define CH34x_REG_LCR 0x18 +#define CH34x_LCR_ENABLE_RX 0x80 +#define CH34x_LCR_ENABLE_TX 0x40 +#define CH34x_LCR_MARK_SPACE 0x20 +#define CH34x_LCR_PAR_EVEN 0x10 +#define CH34x_LCR_ENABLE_PAR 0x08 +#define CH34x_LCR_STOP_BITS_2 0x04 +#define CH34x_LCR_CS8 0x03 +#define CH34x_LCR_CS7 0x02 +#define CH34x_LCR_CS6 0x01 +#define CH34x_LCR_CS5 0x00 + +static const char *TAG = "CH34X"; + +namespace esp_usb { +CH34x::CH34x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) + : intf(interface_idx) +{ + const esp_err_t err = this->open_vendor_specific(vid, pid, this->intf, dev_config); + if (err != ESP_OK) { + throw (err); + } +}; + +esp_err_t CH34x::line_coding_set(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + // Baudrate + if (line_coding->dwDTERate != 0) { + uint8_t factor, divisor; + if (calculate_baud_divisor(line_coding->dwDTERate, &factor, &divisor) != 0) { + return ESP_ERR_INVALID_ARG; + } + uint16_t baud_reg_val = (factor << 8) | divisor; + baud_reg_val |= BIT7; + ESP_RETURN_ON_ERROR(this->send_custom_request(CH34X_WRITE_REQ, CH34X_CMD_WRITE, 0x1312, baud_reg_val, 0, NULL), TAG, "Set baudrate failed"); + } + + // Line coding + if (line_coding->bDataBits != 0) { + uint8_t lcr = CH34x_LCR_ENABLE_RX | CH34x_LCR_ENABLE_TX; + + switch (line_coding->bDataBits) { + case 5: + lcr |= CH34x_LCR_CS5; + break; + case 6: + lcr |= CH34x_LCR_CS6; + break; + case 7: + lcr |= CH34x_LCR_CS7; + break; + case 8: + lcr |= CH34x_LCR_CS8; + break; + default: + return ESP_ERR_INVALID_ARG; + } + + switch (line_coding->bParityType) { + case 0: + break; + case 1: + lcr |= CH34x_LCR_ENABLE_PAR; + break; + case 2: + lcr |= CH34x_LCR_ENABLE_PAR | CH34x_LCR_PAR_EVEN; + break; + case 3: // Mark + case 4: + lcr |= CH34x_LCR_ENABLE_PAR | CH34x_LCR_MARK_SPACE; + break; + default: + return ESP_ERR_INVALID_ARG; + } + + switch (line_coding->bCharFormat) { + case 0: + break; // 1 Stop bit + case 2: + lcr |= CH34x_LCR_STOP_BITS_2; + break; + default: + return ESP_ERR_INVALID_ARG; // 1.5 stop bits not supported + } + + ESP_RETURN_ON_ERROR(this->send_custom_request(CH34X_WRITE_REQ, CH34X_CMD_WRITE, 0x2518, lcr, 0, NULL), TAG, + "Set line coding failed"); + } + + return ESP_OK; +} + +esp_err_t CH34x::set_control_line_state(bool dtr, bool rts) +{ + uint16_t wValue = 0; + if (dtr) { + wValue |= CH34X_CONTROL_DTR; + } + if (rts) { + wValue |= CH34X_CONTROL_RTS; + } + return this->send_custom_request(CH34X_WRITE_REQ, CH34X_CMD_MODEM_OUT, wValue, this->intf, 0, NULL); +} + +int CH34x::calculate_baud_divisor(unsigned int baud_rate, unsigned char *factor, unsigned char *divisor) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + assert(factor); + assert(divisor); + + switch (baud_rate) { + case 921600: + a = 0xf3; + b = 7; + break; + case 307200: + a = 0xd9; + b = 7; + break; + default: + if (baud_rate > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baud_rate > 750000 / 255) { + b = 2; + c = 750000; + } else if (baud_rate > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + + a = (unsigned char)(c / baud_rate); + if (a == 0 || a == 0xFF) { + return -1; // Can't set required baud rate + } + // Deal with integer division + const int delta_0 = c / a - baud_rate; + const int delta_1 = baud_rate - c / (a + 1); + if (delta_0 > delta_1) { + a++; + } + a = 256 - a; + break; + } + + *factor = a; + *divisor = b; + return 0; +} +} diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/.component_hash b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/.component_hash new file mode 100644 index 00000000..91fd9e4d --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/.component_hash @@ -0,0 +1 @@ +6a36fd6179b2264a7d8854aa643d872f0d6408dd8f67a23d96e8cb8e49dfd087 \ No newline at end of file diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/CHANGELOG.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/CHANGELOG.md new file mode 100644 index 00000000..5c6e1d4b --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/CHANGELOG.md @@ -0,0 +1,8 @@ +## 1.0.0 +- Initial version + +## 1.0.1 +- Fix GCC-12 compile errors + +## 2.0.0 +- Update to [CDC-ACM driver](https://components.espressif.com/components/espressif/usb_host_cdc_acm) to v2 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/CMakeLists.txt b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/CMakeLists.txt new file mode 100644 index 00000000..c7103731 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "usb_host_cp210x_vcp.cpp" + INCLUDE_DIRS "include") diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/LICENSE b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/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/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/README.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/README.md new file mode 100644 index 00000000..1e7cde06 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/README.md @@ -0,0 +1,6 @@ +# Silicon Labs CP210x USB-UART converter driver + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_cp210x_vcp/badge.svg)](https://components.espressif.com/components/espressif/usb_host_cp210x_vcp) + +* [Datasheet](https://www.silabs.com/documents/public/data-sheets/CP2102-9.pdf) +* [Application note](https://www.silabs.com/documents/public/application-notes/an197.pdf) diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/idf_component.yml b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/idf_component.yml new file mode 100644 index 00000000..cabef1b8 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + espressif/usb_host_cdc_acm: + public: true + version: '>=1.0.4,<3.0.0' + idf: + version: '>=4.4' +description: USB Host driver for CP210x series of chips +url: https://github.com/espressif/idf-extra-components/tree/master/usb/usb_host_cp210x_vcp +version: 2.0.0 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/include/usb/vcp_cp210x.hpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/include/usb/vcp_cp210x.hpp new file mode 100644 index 00000000..6a5f54d6 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/include/usb/vcp_cp210x.hpp @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "usb/cdc_acm_host.h" + +#define SILICON_LABS_VID (0x10C4) +#define CP210X_PID (0xEA60) // Single i.e. CP2101 - CP2104 +#define CP2105_PID (0xEA70) // Dual +#define CP2108_PID (0xEA71) // Quad + +// @see AN571: CP210x Virtual COM Port Interface, chapter 5 +#define CP210X_CMD_IFC_ENABLE (0x00) // Enable or disable the interface +#define CP210X_CMD_SET_BAUDDIV (0x01) // Set the baud rate divisor +#define CP210X_CMD_GET_BAUDDIV (0x02) // Get the baud rate divisor +#define CP210X_CMD_SET_LINE_CTL (0x03) // Set the line control +#define CP210X_CMD_GET_LINE_CTL (0x04) // Get the line control +#define CP210X_CMD_SET_BREAK (0x05) // Set a BREAK +#define CP210X_CMD_IMM_CHAR (0x06) // Send character out of order +#define CP210X_CMD_SET_MHS (0x07) // Set modem handshaking +#define CP210X_CMD_GET_MDMSTS (0x08) // Get modem status +#define CP210X_CMD_SET_XON (0x09) // Emulate XON +#define CP210X_CMD_SET_XOFF (0x0A) // Emulate XOFF +#define CP210X_CMD_SET_EVENTMASK (0x0B) // Set the event mask +#define CP210X_CMD_GET_EVENTMASK (0x0C) // Get the event mask +#define CP210X_CMD_GET_EVENTSTATE (0x16) // Get the event state +#define CP210X_CMD_SET_RECEIVE (0x17) // Set receiver max timeout +#define CP210X_CMD_GET_RECEIVE (0x18) // Get receiver max timeout +#define CP210X_CMD_SET_CHAR (0x0D) // Set special character individually +#define CP210X_CMD_GET_CHARS (0x0E) // Get special characters +#define CP210X_CMD_GET_PROPS (0x0F) // Get properties +#define CP210X_CMD_GET_COMM_STATUS (0x10) // Get the serial status +#define CP210X_CMD_RESET (0x11) // Reset +#define CP210X_CMD_PURGE (0x12) // Purge +#define CP210X_CMD_SET_FLOW (0x13) // Set flow control +#define CP210X_CMD_GET_FLOW (0x14) // Get flow control +#define CP210X_CMD_EMBED_EVENTS (0x15) // Control embedding of events in the data stream +#define CP210X_CMD_GET_BAUDRATE (0x1D) // Get the baud rate +#define CP210X_CMD_SET_BAUDRATE (0x1E) // Set the baud rate +#define CP210X_CMD_SET_CHARS (0x19) // Set special characters +#define CP210X_CMD_VENDOR_SPECIFIC (0xFF) // Read/write latch values + +namespace esp_usb { +class CP210x : public CdcAcmDevice { +public: + /** + * @brief Constructor for this CP210x driver + * + * @note USB Host library and CDC-ACM driver must be already installed + * + * @param[in] pid PID eg. CP210X_PID + * @param[in] dev_config CDC device configuration + * @param[in] interface_idx Interface number + * @return CdcAcmDevice Pointer to created and opened CP210x device + */ + CP210x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief Get Line Coding method + * + * @see AN571: CP210x Virtual COM Port Interface chapters 5.6 and 5.8 + * @note Overrides default implementation in CDC-ACM driver + * @param[out] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Line Coding method + * + * @see AN571: CP210x Virtual COM Port Interface chapters 5.5 and 5.7 + * @note Overrides default implementation in CDC-ACM driver + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Control Line State method + * + * @see AN571: CP210x Virtual COM Port Interface chapter 5.9 + * @note Overrides default implementation in CDC-ACM driver + * @note Both signals are active low + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ + esp_err_t set_control_line_state(bool dtr, bool rts); + + /** + * @brief Send Break method + * + * @see AN571: CP210x Virtual COM Port Interface chapter 5.20 + * @note Overrides default implementation in CDC-ACM driver + * @param[in] duration_ms Duration of the break condition in [ms] + * @return esp_err_t + */ + esp_err_t send_break(uint16_t duration_ms); + + // List of supported VIDs and PIDs + static constexpr uint16_t vid = SILICON_LABS_VID; + static constexpr std::array pids = {CP210X_PID, CP2105_PID, CP2108_PID}; + +private: + const uint8_t intf; + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; +}; +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/usb_host_cp210x_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/usb_host_cp210x_vcp.cpp new file mode 100644 index 00000000..4e466f2f --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_cp210x_vcp/usb_host_cp210x_vcp.cpp @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb/vcp_cp210x.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS +#error This component requires C++ exceptions +#endif + +#define CP210X_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_INTERFACE | USB_BM_REQUEST_TYPE_DIR_IN) +#define CP210X_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_INTERFACE | USB_BM_REQUEST_TYPE_DIR_OUT) + +namespace esp_usb { +CP210x::CP210x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) + : intf(interface_idx) +{ + esp_err_t err; + err = this->open_vendor_specific(vid, pid, this->intf, dev_config); + if (err != ESP_OK) { + throw (err); + } + + // CP210X interfaces must be explicitly enabled + err = this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_IFC_ENABLE, 1, this->intf, 0, NULL); + if (err != ESP_OK) { + throw (err); + } +}; + +esp_err_t CP210x::line_coding_get(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_READ_REQ, CP210X_CMD_GET_BAUDRATE, 0, this->intf, sizeof(line_coding->dwDTERate), (uint8_t *)&line_coding->dwDTERate), "CP210X",); + + uint8_t temp_data[2]; + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_READ_REQ, CP210X_CMD_GET_LINE_CTL, 0, this->intf, 2, temp_data), "CP210X",); + line_coding->bCharFormat = temp_data[0] & 0x0F; + line_coding->bParityType = (temp_data[0] & 0xF0) >> 4; + line_coding->bDataBits = temp_data[1]; + + return ESP_OK; +} + +esp_err_t CP210x::line_coding_set(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + if (line_coding->dwDTERate != 0) { + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_BAUDRATE, 0, this->intf, sizeof(line_coding->dwDTERate), (uint8_t *)&line_coding->dwDTERate), "CP210X",); + } + + if (line_coding->bDataBits != 0) { + const uint16_t wValue = line_coding->bCharFormat | (line_coding->bParityType << 4) | (line_coding->bDataBits << 8); + return this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_LINE_CTL, wValue, this->intf, 0, NULL); + } + return ESP_OK; +} + +esp_err_t CP210x::set_control_line_state(bool dtr, bool rts) +{ + const uint16_t wValue = (uint16_t)dtr | ((uint16_t)rts << 1) | 0x0300; + return this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_MHS, wValue, this->intf, 0, NULL); +} + +esp_err_t CP210x::send_break(uint16_t duration_ms) +{ + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_BREAK, 1, this->intf, 0, NULL), "CP210x",); + vTaskDelay(pdMS_TO_TICKS(duration_ms)); + return this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_BREAK, 0, this->intf, 0, NULL); +} +} diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/.component_hash b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/.component_hash new file mode 100644 index 00000000..44d3cc1f --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/.component_hash @@ -0,0 +1 @@ +a24180724b8d7fdf01b920b6baebd01057d52d1a4ae5bf7ce009963cc24ebb42 \ No newline at end of file diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/CHANGELOG.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/CHANGELOG.md new file mode 100644 index 00000000..5c6e1d4b --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/CHANGELOG.md @@ -0,0 +1,8 @@ +## 1.0.0 +- Initial version + +## 1.0.1 +- Fix GCC-12 compile errors + +## 2.0.0 +- Update to [CDC-ACM driver](https://components.espressif.com/components/espressif/usb_host_cdc_acm) to v2 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/CMakeLists.txt b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/CMakeLists.txt new file mode 100644 index 00000000..02f11697 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "usb_host_ftdi_vcp.cpp" + INCLUDE_DIRS "include") diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/LICENSE b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/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/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/README.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/README.md new file mode 100644 index 00000000..eeb5d978 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/README.md @@ -0,0 +1,7 @@ +# FTDI UART-USB converters driver + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_ftdi_vcp/badge.svg)](https://components.espressif.com/components/espressif/usb_host_ftdi_vcp) + +Supported devices: +* FT231 +* FT232 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/idf_component.yml b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/idf_component.yml new file mode 100644 index 00000000..d744597e --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + espressif/usb_host_cdc_acm: + public: true + version: ^2.0.0 + idf: + version: '>=4.4' +description: USB Host driver for FTDI USB<->UART converters series of chips +url: https://github.com/espressif/idf-extra-components/tree/master/usb/usb_host_ftdi_vcp +version: 2.0.0 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/include/usb/vcp_ftdi.hpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/include/usb/vcp_ftdi.hpp new file mode 100644 index 00000000..cfea297e --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/include/usb/vcp_ftdi.hpp @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "usb/cdc_acm_host.h" + +#define FTDI_VID (0x0403) +#define FT232_PID (0x6001) +#define FT231_PID (0x6015) + +#define FTDI_CMD_RESET (0x00) +#define FTDI_CMD_SET_FLOW (0x01) +#define FTDI_CMD_SET_MHS (0x02) // Modem handshaking +#define FTDI_CMD_SET_BAUDRATE (0x03) +#define FTDI_CMD_SET_LINE_CTL (0x04) +#define FTDI_CMD_GET_MDMSTS (0x05) // Modem status + +namespace esp_usb { +class FT23x : public CdcAcmDevice { +public: + /** + * @brief Constructor for this FTDI driver + * + * @note USB Host library and CDC-ACM driver must be already installed + * + * @param[in] pid PID eg. FTDI_FT232_PID + * @param[in] dev_config CDC device configuration + * @param[in] interface_idx Interface number + * @return CdcAcmDevice Pointer to created and opened FTDI device + */ + FT23x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief Set Line Coding method + * + * @note Overrides default implementation in CDC-ACM driver + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Control Line State method + * + * @note Overrides default implementation in CDC-ACM driver + * @note Both signals are active low + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ + esp_err_t set_control_line_state(bool dtr, bool rts); + + // List of supported VIDs and PIDs + static constexpr uint16_t vid = FTDI_VID; + static constexpr std::array pids = {FT232_PID, FT231_PID}; + +private: + const uint8_t intf; + const cdc_acm_data_callback_t user_data_cb; + const cdc_acm_host_dev_callback_t user_event_cb; + void *user_arg; + uint16_t uart_state; + + /** + * @brief FT23x's RX data handler + * + * First two bytes are status bytes, the RX data start at data[2]. + * Coding of status bytes: + * Byte 0: + * Bit 0: Full Speed packet + * Bit 1: High Speed packet + * Bit 4: CTS + * Bit 5: DSR + * Bit 6: RI + * Bit 7: DCD + * Byte 1: + * Bit 1: RX overflow + * Bit 2: Parity error + * Bit 3: Framing error + * Bit 4: Break received + * Bit 5: Transmitter holding register empty + * Bit 6: Transmitter empty + * + * @todo When CTS is asserted, this driver should stop sending data. + * + * @param[in] data Received data + * @param[in] data_len Received data length + * @param[in] user_arg Pointer to FT23x class + */ + static bool ftdi_rx(const uint8_t *data, size_t data_len, void *user_arg); + + // Just a wrapper to recover user's argument + static void ftdi_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx); + + /** + * @brief Construct a new calculate baudrate object + * + * A Baud rate for the FT232R, FT2232 (UART mode) or FT232B is generated using the chips + * internal 48MHz clock. This is input to Baud rate generator circuitry where it is then divided by 16 + * and fed into a prescaler as a 3MHz reference clock. This 3MHz reference clock is then divided + * down to provide the required Baud rate for the device's on chip UART. The value of the Baud rate + * divisor is an integer plus a sub-integer prescaler. + * Allowed values for the Baud rate divisor are: + * Divisor = n + 0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875; where n is an integer between 2 and + * 16384 (214). + * + * Note: Divisor = 1 and Divisor = 0 are special cases. A divisor of 0 will give 3 MBaud, and a divisor + * of 1 will give 2 MBaud. Sub-integer divisors between 0 and 2 are not allowed. + * Therefore the value of the divisor needed for a given Baud rate is found by dividing 3000000 by the + * required Baud rate. + * + * @see FTDI AN232B-05 Configuring FT232R, FT2232 and FT232B Baud Rates + * @param[in] baudrate + * @param[out] wValue + * @param[out] wIndex + */ + static int calculate_baudrate(uint32_t baudrate, uint16_t *wValue, uint16_t *wIndex); + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; + using CdcAcmDevice::line_coding_get; // Not implemented + using CdcAcmDevice::send_break; // Not implemented +}; +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/usb_host_ftdi_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/usb_host_ftdi_vcp.cpp new file mode 100644 index 00000000..37fbe7e2 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_ftdi_vcp/usb_host_ftdi_vcp.cpp @@ -0,0 +1,176 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "usb/vcp_ftdi.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "sdkconfig.h" + +#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS +#error This component requires C++ exceptions +#endif + +#define FTDI_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_DIR_IN) +#define FTDI_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_DIR_OUT) + +namespace esp_usb { +FT23x::FT23x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) + : intf(interface_idx), user_data_cb(dev_config->data_cb), user_event_cb(dev_config->event_cb), + user_arg(dev_config->user_arg), uart_state(0) +{ + cdc_acm_host_device_config_t ftdi_config; + memcpy(&ftdi_config, dev_config, sizeof(cdc_acm_host_device_config_t)); + // FT23x reports modem status in first two bytes of RX data + // so here we override the RX handler with our own + + if (dev_config->data_cb) { + ftdi_config.data_cb = ftdi_rx; + ftdi_config.user_arg = this; + } + + if (dev_config->event_cb) { + ftdi_config.event_cb = ftdi_event; + ftdi_config.user_arg = this; + } + + esp_err_t err; + err = this->open_vendor_specific(vid, pid, this->intf, &ftdi_config); + if (err != ESP_OK) { + throw (err); + } + + // FT23x interface must be first reset and configured (115200 8N1) + err = this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_RESET, 0, this->intf + 1, 0, NULL); + if (err != ESP_OK) { + throw (err); + } + + cdc_acm_line_coding_t line_coding = { + .dwDTERate = 115200, + .bCharFormat = 0, + .bParityType = 0, + .bDataBits = 8, + }; + err = this->line_coding_set(&line_coding); + if (err != ESP_OK) { + throw (err); + } +}; + +esp_err_t FT23x::line_coding_set(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + if (line_coding->dwDTERate != 0) { + uint16_t wIndex, wValue; + calculate_baudrate(line_coding->dwDTERate, &wValue, &wIndex); + ESP_RETURN_ON_ERROR(this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_BAUDRATE, wValue, wIndex, 0, NULL), "FT23x",); + } + + if (line_coding->bDataBits != 0) { + const uint16_t wValue = (line_coding->bDataBits) | (line_coding->bParityType << 8) | (line_coding->bCharFormat << 11); + return this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_LINE_CTL, wValue, this->intf, 0, NULL); + } + return ESP_OK; +} + +esp_err_t FT23x::set_control_line_state(bool dtr, bool rts) +{ + ESP_RETURN_ON_ERROR(this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_MHS, dtr ? 0x11 : 0x10, this->intf, 0, NULL), "FT23x",); // DTR + return this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_MHS, rts ? 0x21 : 0x20, this->intf, 0, NULL); // RTS +} + +bool FT23x::ftdi_rx(const uint8_t *data, size_t data_len, void *user_arg) +{ + FT23x *this_ftdi = (FT23x *)user_arg; + + // Dispatch serial state if it has changed + if (this_ftdi->user_event_cb) { + cdc_acm_uart_state_t new_state; + new_state.val = 0; + new_state.bRxCarrier = data[0] & 0x80; // DCD + new_state.bTxCarrier = data[0] & 0x20; // DSR + new_state.bBreak = data[1] & 0x10; + new_state.bRingSignal = data[0] & 0x40; + new_state.bFraming = data[1] & 0x08; + new_state.bParity = data[1] & 0x04; + new_state.bOverRun = data[1] & 0x02; + + if (this_ftdi->uart_state != new_state.val) { + cdc_acm_host_dev_event_data_t serial_event; + serial_event.type = CDC_ACM_HOST_SERIAL_STATE; + serial_event.data.serial_state = new_state; + this_ftdi->user_event_cb(&serial_event, this_ftdi->user_arg); + this_ftdi->uart_state = new_state.val; + } + } + + // Dispatch data if any + if (data_len > 2) { + return this_ftdi->user_data_cb(&data[2], data_len - 2, this_ftdi->user_arg); + } + return true; +} + +void FT23x::ftdi_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +{ + FT23x *this_ftdi = (FT23x *)user_ctx; + this_ftdi->user_event_cb(event, this_ftdi->user_arg); +} + +int FT23x::calculate_baudrate(uint32_t baudrate, uint16_t *wValue, uint16_t *wIndex) +{ +#define FTDI_BASE_CLK (3000000) + + int baudrate_real; + if (baudrate > 2000000) { + // set to 3000000 + *wValue = 0; + *wIndex = 0; + baudrate_real = 3000000; + } else if (baudrate >= 1000000) { + // set to 1000000 + *wValue = 1; + *wIndex = 0; + baudrate_real = 1000000; + } else { + const float ftdi_fractal[] = {0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1}; + const uint8_t ftdi_fractal_bits[] = {0, 0x03, 0x02, 0x04, 0x01, 0x05, 0x06, 0x07}; + uint16_t divider_n = FTDI_BASE_CLK / baudrate; // integer value + int ftdi_fractal_idx = 0; + float divider = FTDI_BASE_CLK / (float)baudrate; // float value + float divider_fractal = divider - (float)divider_n; + + // Find closest bigger FT23x fractal divider + for (ftdi_fractal_idx = 0; ftdi_fractal[ftdi_fractal_idx] <= divider_fractal; ftdi_fractal_idx++) {}; + + // Calculate baudrate errors for two closest fractal divisors + int diff1 = baudrate - (int)(FTDI_BASE_CLK / (divider_n + ftdi_fractal[ftdi_fractal_idx])); // Greater than required baudrate + int diff2 = (int)(FTDI_BASE_CLK / (divider_n + ftdi_fractal[ftdi_fractal_idx - 1])) - baudrate; // Lesser than required baudrate + + // Chose divider and fractal divider with smallest error + if (diff2 < diff1) { + ftdi_fractal_idx--; + } else { + if (ftdi_fractal_idx == 8) { + ftdi_fractal_idx = 0; + divider_n++; + } + } + + baudrate_real = FTDI_BASE_CLK / (float)((float)divider_n + ftdi_fractal[ftdi_fractal_idx]); + *wValue = ((0x3FFFF) & divider_n) | (ftdi_fractal_bits[ftdi_fractal_idx] << 14); + *wIndex = ftdi_fractal_bits[ftdi_fractal_idx] >> 2; + } + ESP_LOGD("FT23x", "wValue: 0x%04X wIndex: 0x%04X", *wValue, *wIndex); + ESP_LOGI("FT23x", "Baudrate required: %" PRIu32", set: %d", baudrate, baudrate_real); + + return baudrate_real; +} +} // esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/.component_hash b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/.component_hash new file mode 100644 index 00000000..7fe9bcd9 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/.component_hash @@ -0,0 +1 @@ +f170d607a1b56c6f43f50a035e073c34350caafb2dc2c8e84bde8a0e96ad1de3 \ No newline at end of file diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/CHANGELOG.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/CHANGELOG.md new file mode 100644 index 00000000..1ee508e0 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.0.0 +- Initial version + +## 1.0.0~1 +- Claim compatibility with [CDC-ACM driver](https://components.espressif.com/components/espressif/usb_host_cdc_acm) v2 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/CMakeLists.txt b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/CMakeLists.txt new file mode 100644 index 00000000..e719094d --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register(SRCS "usb_host_vcp.cpp" + INCLUDE_DIRS "include") + +set_target_properties(${COMPONENT_LIB} PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON +) +target_compile_options(${COMPONENT_LIB} PRIVATE -fconcepts) diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/LICENSE b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/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/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/README.md b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/README.md new file mode 100644 index 00000000..160fda12 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/README.md @@ -0,0 +1,9 @@ +# Virtual COM Port Service + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_vcp/badge.svg)](https://components.espressif.com/components/espressif/usb_host_vcp) + +Virtual COM Port (VCP) service manages drivers to connected VCP devices - typically USB <-> UART converters. +In practice, you rarely care about specifics of the devices; you only want uniform interface for them all. + +VCP service does just that, after you register drivers for various VCP devices, you can just call VCP::open +and the service will load proper driver for device that was just plugged into USB port. diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/idf_component.yml b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/idf_component.yml new file mode 100644 index 00000000..99528fb3 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/idf_component.yml @@ -0,0 +1,9 @@ +dependencies: + espressif/usb_host_cdc_acm: + public: true + version: '>=1.0.4,<3.0.0' + idf: + version: '>=4.4' +description: USB Host Virtual COM Port Service +url: https://github.com/espressif/idf-extra-components/tree/master/usb/usb_host_vcp +version: 1.0.0~2 diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/include/usb/vcp.hpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/include/usb/vcp.hpp new file mode 100644 index 00000000..ece97f57 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/include/usb/vcp.hpp @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "usb/cdc_acm_host.h" + +namespace esp_usb { +/** + * @brief Virtual COM Port Service Class + * + * Virtual COM Port (VCP) service manages drivers to connected VCP devices - typically USB <-> UART converters. + * In practice, you rarely care about specifics of the devices; you only want uniform interface for them all. + * VCP service does just that, after you register drivers for various VCP devices, you can just call VCP::open + * and the service will load proper driver for device that was just plugged into USB port. + * + * Example usage: + * \code{.cpp} + * VCP::register_driver(); + * VCP::register_driver(); + * VCP::register_driver(); + * auto vcp = VCP::open(&dev_config); + * \endcode + * + * The example code assumes that you have USB Host Lib already installed. + */ +class VCP { +public: + /** + * @brief Register VCP driver to VCP service + * + * To fully leverage from VCP service functionalities, you must register VCP drivers first. + * The driver must contain the following public members/methods; + * #. vid: Supported VID + * #. pids: Array of supported PIDs + * # Constructor with (uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) input parameters + * + * @tparam T VCP driver type + */ + template static void + register_driver(void) + { + static_assert(T::pids.begin() != nullptr, "Every VCP driver must contain array of supported PIDs in 'pids' array"); + static_assert(T::vid != 0, "Every VCP driver must contain supported VID in'vid' integer"); + std::vector pids(T::pids.begin(), T::pids.end()); // Convert array to vector + vcp_driver new_driver = vcp_driver([](uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) { + return static_cast (new T(pid, dev_config, interface_idx)); // Lambda function: Open factory method + }, T::vid, pids); + drivers.push_back(new_driver); + } + + /** + * @brief VCP factory with VID and PID + * + * Use this function if you know VID and PID of the device. + * The VCP service will look for correct (already registered) driver and load it. + * + * @attention USB Host Library must be installed before calling this function! + * + * @param[in] _vid VID of the device + * @param[in] _pid PID of the device + * @param[in] dev_config Configuration of the device + * @param[in] interface_idx USB interface to use + * @return std::shared_ptr + */ + static CdcAcmDevice * + open(uint16_t _vid, uint16_t _pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief VCP factory + * + * Use this function when you want the VCP service to open any connected VCP device. + * The VCP service will look for correct (already registered) driver and load it. + * + * This function will block until a valid VCP device is found or + * until dev_config->connection_timeout_ms expires. Set timeout to 0 to wait forever. + * + * @note If there are more USB devices connected, the VCP service will return first successfully opened device + * @attention USB Host Library must be installed before calling this function! + * + * @param[in] dev_config Configuration of the device + * @param[in] interface_idx USB interface to use + * @return std::shared_ptr + */ + static CdcAcmDevice * + open(const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + +private: + // Default operators + VCP() = delete; // This driver acts as a service, you can't instantiate it + VCP(const VCP &) = delete; + VCP &operator=(VCP &) = delete; + bool operator== (const VCP ¶m) = delete; + bool operator!= (const VCP ¶m) = delete; + + /** + * @brief VCP driver structure + */ + typedef struct vcp_driver { + CdcAcmDevice *(*open)(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx); /*!< Factory method of this driver */ + uint16_t vid; /*!< VID this driver supports */ + std::vector pids; /*!< List of PIDs this driver supports */ + vcp_driver(auto open_func, const uint16_t _vid, const std::vector &_pids): open(open_func), vid(_vid), pids(_pids) {}; + } vcp_driver; + + /** + * @brief List of registered VCP drivers + */ + static std::vector drivers; +}; // VCP class +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/usb_host_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/usb_host_vcp.cpp new file mode 100644 index 00000000..43455a67 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/original_components/espressif__usb_host_vcp/usb_host_vcp.cpp @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "usb/vcp.hpp" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "VCP service"; + +namespace esp_usb { +std::vector VCP::drivers; +CdcAcmDevice *VCP::open(uint16_t _vid, uint16_t _pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) +{ + // In case user didn't install CDC-ACM driver, we try to install it here. + const esp_err_t err = cdc_acm_host_install(NULL); + switch (err) { + case ESP_OK: ESP_LOGD(TAG, "CDC-ACM driver installed"); break; + case ESP_ERR_INVALID_STATE: ESP_LOGD(TAG, "CDC-ACM driver already installed"); break; + default: ESP_LOGE(TAG, "Failed to install CDC-ACM driver"); return nullptr; + } + + for (vcp_driver drv : drivers) { + if (drv.vid == _vid) { + for (uint16_t p : drv.pids) { + if (p == _pid) { + try { + return drv.open(_pid, dev_config, interface_idx); + } catch (esp_err_t &e) { + switch (e) { + case ESP_ERR_NO_MEM: throw std::bad_alloc(); + case ESP_ERR_NOT_FOUND: // fallthrough + default: return nullptr; + } + } + } + } + } + } + return nullptr; +} + +CdcAcmDevice *VCP::open(const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) +{ + // Setup this function timeout + TickType_t timeout_ticks = (dev_config->connection_timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(dev_config->connection_timeout_ms); + TimeOut_t connection_timeout; + vTaskSetTimeOutState(&connection_timeout); + + // In case user didn't install CDC-ACM driver, we try to install it here. + esp_err_t err = cdc_acm_host_install(NULL); + switch (err) { + case ESP_OK: ESP_LOGD(TAG, "CDC-ACM driver installed"); break; + case ESP_ERR_INVALID_STATE: ESP_LOGD(TAG, "CDC-ACM driver already installed"); break; + default: ESP_LOGE(TAG, "Failed to install CDC-ACM driver"); return nullptr; + } + + // dev_config->connection_timeout_ms is normally meant for 1 device, + // but here it is a timeout for the whole function call + cdc_acm_host_device_config_t _config = *dev_config; + _config.connection_timeout_ms = 1; + + // Try opening all registered devices, return on first success + do { + for (vcp_driver drv : drivers) { + for (uint16_t pid : drv.pids) { + try { + return drv.open(pid, &_config, interface_idx); + } catch (esp_err_t &e) { + switch (e) { + case ESP_ERR_NOT_FOUND: break; + case ESP_ERR_NO_MEM: throw std::bad_alloc(); + default: return nullptr; + } + } + } + } + vTaskDelay(pdMS_TO_TICKS(50)); + } while (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) == pdFALSE); + return nullptr; +} +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/src/cdc_acm_host.cpp b/libraries/esp32-usb-serial-1.0.1/src/cdc_acm_host.cpp new file mode 100644 index 00000000..164c97c9 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/cdc_acm_host.cpp @@ -0,0 +1,1573 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * Modified by Luc Lebosse 2023 to be used in esp32-usb-serial library (porting C code to C++) + */ + +#include "usb/cdc_acm_host.h" + +#include +#include +#include + +#include "esp_check.h" +#include "esp_log.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "inttypes.h" +#include "usb/usb_host.h" + +static const char *TAG = "cdc_acm"; + +// CDC devices often implement Interface Association Descriptor (IAD). Parse IAD +// only when bDeviceClass = 0xEF (Miscellaneous Device Class), bDeviceSubClass = +// 0x02 (Common Class), bDeviceProtocol = 0x01 (Interface Association +// Descriptor), or when bDeviceClass, bDeviceSubClass, and bDeviceProtocol are +// 0x00 (Null class code triple), as per +// https://www.usb.org/defined-class-codes, "Base Class 00h (Device)" section +// @see USB Interface Association Descriptor: Device Class Code and Use Model +// rev 1.0, Table 1-1 +#define USB_SUBCLASS_NULL 0x00 +#define USB_SUBCLASS_COMMON 0x02 +#define USB_PROTOCOL_NULL 0x00 +#define USB_DEVICE_PROTOCOL_IAD 0x01 + +// 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) + +// CDC-ACM events +#define CDC_ACM_TEARDOWN BIT0 +#define CDC_ACM_TEARDOWN_COMPLETE BIT1 + +// CDC-ACM check macros +#define CDC_ACM_CHECK(cond, ret_val) \ + ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ + }) + +#define CDC_ACM_CHECK_FROM_CRIT(cond, ret_val) \ + ({ \ + if (!(cond)) { \ + CDC_ACM_EXIT_CRITICAL(); \ + return ret_val; \ + } \ + }) + +// CDC-ACM driver object +typedef struct { + usb_host_client_handle_t + cdc_acm_client_hdl; /*!< USB Host handle reused for all CDC-ACM devices in + the system */ + SemaphoreHandle_t open_close_mutex; + EventGroupHandle_t event_group; + cdc_acm_new_dev_callback_t new_dev_cb; + SLIST_HEAD(list_dev, cdc_dev_s) + cdc_devices_list; /*!< List of open pseudo devices */ +} cdc_acm_obj_t; + +static cdc_acm_obj_t *p_cdc_acm_obj = NULL; + +/** + * @brief Default CDC-ACM driver configuration + * + * This configuration is used when user passes NULL to config pointer during + * device open. + */ +static const cdc_acm_host_driver_config_t cdc_acm_driver_config_default = { + .driver_task_stack_size = 4096, + .driver_task_priority = 10, + .xCoreID = 0, + .new_dev_cb = NULL, +}; + +/** + * @brief USB CDC PSTN Call Descriptor + * + * @see Table 3, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t call_management : 1; // Device handles call management itself + uint8_t call_over_data_if : 1; // Device sends/receives call management + // information over Data Class interface + uint8_t reserved : 6; + }; + uint8_t val; + } bmCapabilities; + uint8_t bDataInterface; // Interface number of Data Class interface + // optionally used for call management +} __attribute__((packed)) cdc_acm_call_desc_t; + +/** + * @brief USB CDC PSTN Abstract Control Model Descriptor + * + * @see Table 4, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; + const cdc_desc_subtype_t bDescriptorSubtype; + union { + struct { + uint8_t + feature : 1; // Device supports Set/Clear/Get_Comm_Feature requests + uint8_t serial : 1; // Device supports Set/Get_Line_Coding, + // Set_Control_Line_State and Serial_State request + // and notifications + uint8_t send_break : 1; // Device supports Send_Break request + uint8_t network : 1; // Device supports Network_Connection notification + uint8_t reserved : 4; + }; + uint8_t val; + } bmCapabilities; +} __attribute__((packed)) cdc_acm_acm_desc_t; + +typedef struct cdc_dev_s cdc_dev_t; +struct cdc_dev_s { + usb_device_handle_t dev_hdl; // USB device handle + void *cb_arg; // Common argument for user's callbacks (data IN and + // Notification) + struct { + usb_transfer_t *out_xfer; // OUT data transfer + usb_transfer_t *in_xfer; // IN data transfer + cdc_acm_data_callback_t + in_cb; // User's callback for async (non-blocking) data IN + uint16_t in_mps; // IN endpoint Maximum Packet Size + uint8_t + *in_data_buffer_base; // Pointer to IN data buffer in usb_transfer_t + const usb_intf_desc_t *intf_desc; // Pointer to data interface descriptor + SemaphoreHandle_t out_mux; // OUT mutex + } data; + + struct { + usb_transfer_t *xfer; // IN notification transfer + const usb_intf_desc_t + *intf_desc; // Pointer to notification interface descriptor, can be + // NULL if there is no notification channel in the device + cdc_acm_host_dev_callback_t cb; // User's callback for device events + } notif; // Structure with Notif pipe data + + usb_transfer_t *ctrl_transfer; // CTRL (endpoint 0) transfer + SemaphoreHandle_t ctrl_mux; // CTRL mutex + cdc_acm_uart_state_t serial_state; // Serial State + cdc_comm_protocol_t comm_protocol; + cdc_data_protocol_t data_protocol; + int num_cdc_intf_desc; // Number of CDC Interface descriptors in following + // array + const usb_standard_desc_t **cdc_intf_desc; // CDC Interface descriptors + SLIST_ENTRY(cdc_dev_s) list_entry; +}; + +/** + * @brief Notification received callback + * + * Notification (interrupt) IN transfer is submitted at the end of this function + * to ensure periodic poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void notif_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data received callback + * + * Data (bulk) IN transfer is submitted at the end of this function to ensure + * continuous poll of IN endpoint. + * + * @param[in] transfer Transfer that triggered the callback + */ +static void in_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief Data send callback + * + * Reused for bulk OUT and CTRL transfers + * + * @param[in] transfer Transfer that triggered the callback + */ +static void out_xfer_cb(usb_transfer_t *transfer); + +/** + * @brief USB Host Client event callback + * + * Handling of USB device connection/disconnection to/from root HUB. + * + * @param[in] event_msg Event message type + * @param[in] arg Caller's argument (not used in this driver) + */ +static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, + void *arg); + +/** + * @brief Send CDC specific request + * + * Helper function that will send CDC specific request to default endpoint. + * Both IN and OUT requests are sent through this API, depending on the + * in_transfer parameter. + * + * @see Chapter 6.2, USB CDC specification rev. 1.2 + * @note CDC specific requests are only supported by devices that have dedicated + * management element. + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] in_transfer Direction of data phase. true: IN, false: OUT + * @param[in] request CDC request code + * @param[inout] data Pointer to data buffer. Input for OUT transfers, output + * for IN transfers. + * @param[in] data_len Length of data buffer + * @param[in] value Value to be set in bValue of Setup packet + * @return esp_err_t + */ +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, + cdc_request_code_t request, uint8_t *data, + uint16_t data_len, uint16_t value); + +/** + * @brief Reset IN transfer + * + * In in_xfer_cb() we can modify IN transfer parameters, this function resets + * the transfer to its defaults + * + * @param[in] cdc_dev Pointer to CDC device + */ +static void cdc_acm_reset_in_transfer(cdc_dev_t *cdc_dev) { + assert(cdc_dev->data.in_xfer); + usb_transfer_t *transfer = cdc_dev->data.in_xfer; + uint8_t **ptr = (uint8_t **)(&(transfer->data_buffer)); + *ptr = cdc_dev->data.in_data_buffer_base; + transfer->num_bytes = transfer->data_buffer_size; +} + +/** + * @brief CDC-ACM driver handling task + * + * USB host client registration and deregistration is handled here. + * + * @param[in] arg User's argument. Handle of a task that started this task. + */ +static void cdc_acm_client_task(void *arg) { + vTaskSuspend(NULL); // Task will be resumed from cdc_acm_host_install() + cdc_acm_obj_t *cdc_acm_obj = + p_cdc_acm_obj; // Make local copy of the driver's handle + assert(cdc_acm_obj->cdc_acm_client_hdl); + + // Start handling client's events + while (1) { + usb_host_client_handle_events(cdc_acm_obj->cdc_acm_client_hdl, + portMAX_DELAY); + EventBits_t events = xEventGroupGetBits(cdc_acm_obj->event_group); + if (events & CDC_ACM_TEARDOWN) { + break; + } + } + + ESP_LOGD(TAG, "Deregistering client"); + ESP_ERROR_CHECK(usb_host_client_deregister(cdc_acm_obj->cdc_acm_client_hdl)); + xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE); + vTaskDelete(NULL); +} + +/** + * @brief Cancel transfer and reset endpoint + * + * This function will cancel ongoing transfer a reset its endpoint to ready + * state. + * + * @param[in] dev_hdl USB device handle + * @param[in] transfer Transfer to be cancelled + * @return esp_err_t + */ +static esp_err_t cdc_acm_reset_transfer_endpoint(usb_device_handle_t dev_hdl, + usb_transfer_t *transfer) { + assert(dev_hdl); + assert(transfer); + + ESP_RETURN_ON_ERROR( + usb_host_endpoint_halt(dev_hdl, transfer->bEndpointAddress), TAG, ); + ESP_RETURN_ON_ERROR( + usb_host_endpoint_flush(dev_hdl, transfer->bEndpointAddress), TAG, ); + usb_host_endpoint_clear(dev_hdl, transfer->bEndpointAddress); + return ESP_OK; +} + +/** + * @brief Start CDC device + * + * After this call, USB host peripheral will continuously poll IN endpoints. + * + * @param cdc_dev + * @param[in] event_cb Device event callback + * @param[in] in_cb Data received callback + * @param[in] user_arg Optional user's argument, that will be passed to the + * callbacks + * @return esp_err_t + */ +static esp_err_t cdc_acm_start(cdc_dev_t *cdc_dev, + cdc_acm_host_dev_callback_t event_cb, + cdc_acm_data_callback_t in_cb, void *user_arg) { + esp_err_t ret = ESP_OK; + assert(cdc_dev); + + CDC_ACM_ENTER_CRITICAL(); + cdc_dev->notif.cb = event_cb; + cdc_dev->data.in_cb = in_cb; + cdc_dev->cb_arg = user_arg; + CDC_ACM_EXIT_CRITICAL(); + + // Claim data interface and start polling its IN endpoint + ESP_GOTO_ON_ERROR(usb_host_interface_claim( + p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, + cdc_dev->data.intf_desc->bInterfaceNumber, 0), + err, TAG, ); + if (cdc_dev->data.in_xfer) { + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->data.in_xfer)); + } + + // If notification are supported, claim its interface and start polling its IN + // endpoint + if (cdc_dev->notif.xfer) { + if (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc) { + ESP_GOTO_ON_ERROR(usb_host_interface_claim( + p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, + cdc_dev->notif.intf_desc->bInterfaceNumber, 0), + err, TAG, ); + } + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + ESP_ERROR_CHECK(usb_host_transfer_submit(cdc_dev->notif.xfer)); + } + + // Everything OK, add the device into list and return + CDC_ACM_ENTER_CRITICAL(); + SLIST_INSERT_HEAD(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, list_entry); + CDC_ACM_EXIT_CRITICAL(); + return ret; + +err: + usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, + cdc_dev->dev_hdl, + cdc_dev->data.intf_desc->bInterfaceNumber); + if (cdc_dev->notif.xfer && + (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + usb_host_interface_release(p_cdc_acm_obj->cdc_acm_client_hdl, + cdc_dev->dev_hdl, + cdc_dev->notif.intf_desc->bInterfaceNumber); + } + return ret; +} + +static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev); +/** + * @brief Helper function that releases resources claimed by CDC device + * + * Close underlying USB device, free device driver memory + * + * @note All interfaces claimed by this device must be release before calling + * this function + * @param cdc_dev CDC device handle to be removed + */ +static void cdc_acm_device_remove(cdc_dev_t *cdc_dev) { + assert(cdc_dev); + cdc_acm_transfers_free(cdc_dev); + free(cdc_dev->cdc_intf_desc); + // We don't check the error code of usb_host_device_close, as the close might + // fail, if someone else is still using the device (not all interfaces are + // released) + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, + cdc_dev->dev_hdl); // Gracefully continue on error + free(cdc_dev); +} + +/** + * @brief Open USB device with requested VID/PID + * + * This function has two regular return paths: + * 1. USB device with matching VID/PID is already opened by this driver: + * allocate new CDC device on top of the already opened USB device. + * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll + * USB connected devices until it is found. + * + * @note This function will block for timeout_ms, if the device is not + * enumerated at the moment of calling this function. + * @param[in] vid Vendor ID + * @param[in] pid Product ID + * @param[in] timeout_ms Connection timeout [ms] + * @param[out] dev CDC-ACM device + * @return esp_err_t + */ +static esp_err_t cdc_acm_find_and_open_usb_device(uint16_t vid, uint16_t pid, + int timeout_ms, + cdc_dev_t **dev) { + assert(p_cdc_acm_obj); + assert(dev); + + *dev =(cdc_dev_s*)calloc(1, sizeof(cdc_dev_t)); + if (*dev == NULL) { + return ESP_ERR_NO_MEM; + } + + // First, check list of already opened CDC devices + ESP_LOGD(TAG, "Checking list of opened USB devices"); + cdc_dev_t *cdc_dev; + SLIST_FOREACH(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry) { + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK( + usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // Return path 1: + (*dev)->dev_hdl = cdc_dev->dev_hdl; + return ESP_OK; + } + } + + // Second, poll connected devices until new device is connected or timeout + TickType_t timeout_ticks = + (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + TimeOut_t connection_timeout; + vTaskSetTimeOutState(&connection_timeout); + + do { + ESP_LOGD(TAG, "Checking list of connected USB devices"); + uint8_t dev_addr_list[10]; + int num_of_devices; + ESP_ERROR_CHECK(usb_host_device_addr_list_fill( + sizeof(dev_addr_list), dev_addr_list, &num_of_devices)); + + // Go through device address list and find the one we are looking for + for (int i = 0; i < num_of_devices; i++) { + usb_device_handle_t current_device; + // Open USB device + if (usb_host_device_open(p_cdc_acm_obj->cdc_acm_client_hdl, + dev_addr_list[i], ¤t_device) != ESP_OK) { + continue; // In case we failed to open this device, continue with next + // one in the list + } + assert(current_device); + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK( + usb_host_get_device_descriptor(current_device, &device_desc)); + if (device_desc->idVendor == vid && device_desc->idProduct == pid) { + // Return path 2: + (*dev)->dev_hdl = current_device; + return ESP_OK; + } + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, current_device); + } + vTaskDelay(pdMS_TO_TICKS(50)); + } while (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) == + pdFALSE); + + // Timeout was reached, clean-up + free(*dev); + *dev = NULL; + return ESP_ERR_NOT_FOUND; +} + +esp_err_t cdc_acm_host_install( + const cdc_acm_host_driver_config_t *driver_config) { + CDC_ACM_CHECK(!p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + + // Check driver configuration, use default if NULL is passed + if (driver_config == NULL) { + driver_config = &cdc_acm_driver_config_default; + } + + // Allocate all we need for this driver + esp_err_t ret = ESP_FAIL; + bool client_err = false; + bool err = false; + cdc_acm_obj_t *cdc_acm_obj =(cdc_acm_obj_t*) + heap_caps_calloc(1, sizeof(cdc_acm_obj_t), MALLOC_CAP_DEFAULT); + EventGroupHandle_t event_group = xEventGroupCreate(); + SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); + TaskHandle_t driver_task_h = NULL; + xTaskCreatePinnedToCore(cdc_acm_client_task, "USB-CDC", + driver_config->driver_task_stack_size, NULL, + driver_config->driver_task_priority, &driver_task_h, + driver_config->xCoreID); + + if (cdc_acm_obj == NULL || driver_task_h == NULL || event_group == NULL || + mutex == NULL) { + ret = ESP_ERR_NO_MEM; + err = true; + } + // Register USB Host client + usb_host_client_handle_t usb_client = NULL; +if (!err){ + + const usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = 3, + .async={ + .client_event_callback = usb_event_cb, + .callback_arg = NULL + }}; + + if (ESP_OK!=usb_host_client_register(&client_config, &usb_client)){ + err= true; + ESP_LOGE(TAG, "Failed to register USB host client"); + } else { + // Initialize CDC-ACM driver structure + SLIST_INIT(&(cdc_acm_obj->cdc_devices_list)); + cdc_acm_obj->event_group = event_group; + cdc_acm_obj->open_close_mutex = mutex; + cdc_acm_obj->cdc_acm_client_hdl = usb_client; + cdc_acm_obj->new_dev_cb = driver_config->new_dev_cb; + + // Between 1st call of this function and following section, another task might + // try to install this driver: Make sure that there is only one instance of + // this driver in the system + CDC_ACM_ENTER_CRITICAL(); + if (p_cdc_acm_obj) { + // Already created + ret = ESP_ERR_INVALID_STATE; + CDC_ACM_EXIT_CRITICAL(); + client_err = true; + } else { + p_cdc_acm_obj = cdc_acm_obj; + } + if (!client_err) { + CDC_ACM_EXIT_CRITICAL(); + + // Everything OK: Start CDC-Driver task and return + vTaskResume(driver_task_h); + return ESP_OK; + } + } +} + +if (client_err) { + usb_host_client_deregister(usb_client); + return ret; +} +if (err){ // Clean-up + free(cdc_acm_obj); + if (event_group) { + vEventGroupDelete(event_group); + } + if (driver_task_h) { + vTaskDelete(driver_task_h); + } + if (mutex) { + vSemaphoreDelete(mutex); + } +} +return ret; +} + +esp_err_t cdc_acm_host_uninstall() { + esp_err_t ret; + + CDC_ACM_ENTER_CRITICAL(); + CDC_ACM_CHECK_FROM_CRIT(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + cdc_acm_obj_t *cdc_acm_obj = + p_cdc_acm_obj; // Save Driver's handle to temporary handle + CDC_ACM_EXIT_CRITICAL(); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, + portMAX_DELAY); // Wait for all open/close calls to finish + + CDC_ACM_ENTER_CRITICAL(); + if (SLIST_EMPTY( + &p_cdc_acm_obj->cdc_devices_list)) { // Check that device list is + // empty (all devices closed) + p_cdc_acm_obj = NULL; // NULL static driver pointer: No open/close calls + // form this point + } else { + ret = ESP_ERR_INVALID_STATE; + CDC_ACM_EXIT_CRITICAL(); + goto unblock; + } + CDC_ACM_EXIT_CRITICAL(); + + // Signal to CDC task to stop, unblock it and wait for its deletion + xEventGroupSetBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN); + usb_host_client_unblock(cdc_acm_obj->cdc_acm_client_hdl); + ESP_GOTO_ON_FALSE( + xEventGroupWaitBits(cdc_acm_obj->event_group, CDC_ACM_TEARDOWN_COMPLETE, + pdFALSE, pdFALSE, pdMS_TO_TICKS(100)), + ESP_ERR_NOT_FINISHED, unblock, TAG, ); + + // Free remaining resources and return + vEventGroupDelete(cdc_acm_obj->event_group); + xSemaphoreGive(cdc_acm_obj->open_close_mutex); + vSemaphoreDelete(cdc_acm_obj->open_close_mutex); + free(cdc_acm_obj); + return ESP_OK; + +unblock: + xSemaphoreGive(cdc_acm_obj->open_close_mutex); + return ret; +} + +esp_err_t cdc_acm_host_register_new_dev_callback( + cdc_acm_new_dev_callback_t new_dev_cb) { + CDC_ACM_ENTER_CRITICAL(); + p_cdc_acm_obj->new_dev_cb = new_dev_cb; + CDC_ACM_EXIT_CRITICAL(); + return ESP_OK; +} + +/** + * @brief Free USB transfers used by this device + * + * @note There can be no transfers in flight, at the moment of calling this + * function. + * @param[in] cdc_dev Pointer to CDC device + */ +static void cdc_acm_transfers_free(cdc_dev_t *cdc_dev) { + assert(cdc_dev); + if (cdc_dev->notif.xfer != NULL) { + usb_host_transfer_free(cdc_dev->notif.xfer); + } + if (cdc_dev->data.in_xfer != NULL) { + cdc_acm_reset_in_transfer(cdc_dev); + usb_host_transfer_free(cdc_dev->data.in_xfer); + } + if (cdc_dev->data.out_xfer != NULL) { + if (cdc_dev->data.out_xfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->data.out_xfer->context); + } + if (cdc_dev->data.out_mux != NULL) { + vSemaphoreDelete(cdc_dev->data.out_mux); + } + usb_host_transfer_free(cdc_dev->data.out_xfer); + } + if (cdc_dev->ctrl_transfer != NULL) { + if (cdc_dev->ctrl_transfer->context != NULL) { + vSemaphoreDelete((SemaphoreHandle_t)cdc_dev->ctrl_transfer->context); + } + if (cdc_dev->ctrl_mux != NULL) { + vSemaphoreDelete(cdc_dev->ctrl_mux); + } + usb_host_transfer_free(cdc_dev->ctrl_transfer); + } +} + +/** + * @brief Allocate CDC transfers + * + * @param[in] cdc_dev Pointer to CDC device + * @param[in] notif_ep_desc Pointer to notification EP descriptor + * @param[in] in_ep_desc- Pointer to data IN EP descriptor + * @param[in] in_buf_len Length of data IN buffer + * @param[in] out_ep_desc Pointer to data OUT EP descriptor + * @param[in] out_buf_len Length of data OUT buffer + * @return + * - ESP_OK: Success + * - ESP_ERR_NO_MEM: Not enough memory for transfers and semaphores + * allocation + * - ESP_ERR_NOT_FOUND: IN or OUT endpoints were not found in the selected + * interface + */ +static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, + const usb_ep_desc_t *notif_ep_desc, + const usb_ep_desc_t *in_ep_desc, + size_t in_buf_len, + const usb_ep_desc_t *out_ep_desc, + size_t out_buf_len) { + esp_err_t ret; + + // 0. Check IN and OUT endpoints + // If your open functions fail here, it signals that you either selected wrong + // interface number or that you are trying to open IAD CDC device with + // cdc_acm_host_open_vendor_specific() instead of cdc_acm_host_open() Refer to + // README.md for more information + ESP_RETURN_ON_FALSE(in_ep_desc && out_ep_desc, ESP_ERR_NOT_FOUND, TAG, + "IN or OUT endpoint not found in this data interface"); + + // 1. Setup notification transfer if it is supported + if (notif_ep_desc) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(notif_ep_desc), 0, + &cdc_dev->notif.xfer), + err, TAG, ); + cdc_dev->notif.xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->notif.xfer->bEndpointAddress = notif_ep_desc->bEndpointAddress; + cdc_dev->notif.xfer->callback = notif_xfer_cb; + cdc_dev->notif.xfer->context = cdc_dev; + cdc_dev->notif.xfer->num_bytes = USB_EP_DESC_GET_MPS(notif_ep_desc); + } + + // 2. Setup control transfer + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(cdc_dev->dev_hdl, &dev_info)); + ESP_GOTO_ON_ERROR(usb_host_transfer_alloc(dev_info.bMaxPacketSize0, 0, + &cdc_dev->ctrl_transfer), + err, TAG, ); + cdc_dev->ctrl_transfer->timeout_ms = 1000; + cdc_dev->ctrl_transfer->bEndpointAddress = 0; + cdc_dev->ctrl_transfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->ctrl_transfer->context = cdc_dev; + cdc_dev->ctrl_transfer->callback = out_xfer_cb; + cdc_dev->ctrl_transfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->context, ESP_ERR_NO_MEM, err, + TAG, ); + cdc_dev->ctrl_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_mux, ESP_ERR_NO_MEM, err, TAG, ); + + // 3. Setup IN data transfer (if it is required (in_buf_len > 0)) + if (in_buf_len != 0) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(in_buf_len, 0, &cdc_dev->data.in_xfer), err, + TAG, ); + assert(cdc_dev->data.in_xfer); + cdc_dev->data.in_xfer->callback = in_xfer_cb; + cdc_dev->data.in_xfer->num_bytes = in_buf_len; + cdc_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress; + cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.in_xfer->context = cdc_dev; + cdc_dev->data.in_mps = USB_EP_DESC_GET_MPS(in_ep_desc); + cdc_dev->data.in_data_buffer_base = cdc_dev->data.in_xfer->data_buffer; + } + + // 4. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + if (out_buf_len != 0) { + ESP_GOTO_ON_ERROR( + usb_host_transfer_alloc(out_buf_len, 0, &cdc_dev->data.out_xfer), err, + TAG, ); + assert(cdc_dev->data.out_xfer); + cdc_dev->data.out_xfer->device_handle = cdc_dev->dev_hdl; + cdc_dev->data.out_xfer->context = xSemaphoreCreateBinary(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->context, ESP_ERR_NO_MEM, err, + TAG, ); + cdc_dev->data.out_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_mux, ESP_ERR_NO_MEM, err, TAG, ); + cdc_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress; + cdc_dev->data.out_xfer->callback = out_xfer_cb; + } + return ESP_OK; + +err: + cdc_acm_transfers_free(cdc_dev); + return ret; +} + +/** + * @brief Find CDC interface descriptor and its endpoint descriptors + * + * @note This function is called in open procedure of CDC compliant devices + * only. + * @param[in] cdc_dev Pointer to CDC device + * @param[in] intf_idx Index of CDC interface that should be used for this + * device + * @param[out] notif_ep Pointer to notification EP descriptor + * @param[out] in_ep Pointer to data IN EP descriptor + * @param[out] out_ep Pointer to data OUT EP descriptor + * @return + * - ESP_OK: Success + * - ESP_ERR_NOT_FOUND: Interfaces and endpoints NOT found + */ +static esp_err_t cdc_acm_find_intf_and_ep_desc(cdc_dev_t *cdc_dev, + uint8_t intf_idx, + const usb_ep_desc_t **notif_ep, + const usb_ep_desc_t **in_ep, + const usb_ep_desc_t **out_ep) { + bool interface_found = false; + const usb_config_desc_t *config_desc; + const usb_device_desc_t *device_desc; + int data_intf_idx, notif_intf_idx; + int desc_offset = 0; + + // Get required descriptors + ESP_ERROR_CHECK( + usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + ESP_ERROR_CHECK( + usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + + if (((device_desc->bDeviceClass == USB_CLASS_MISC) && + (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) && + (device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) || + ((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && + (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) && + (device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) { + // This is a composite device, that uses Interface Association Descriptor + const usb_standard_desc_t *this_desc = + (const usb_standard_desc_t *)config_desc; + do { + this_desc = usb_parse_next_descriptor_of_type( + this_desc, config_desc->wTotalLength, + USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset); + + if (this_desc == NULL) { + break; // Reached end of configuration descriptor + } + + const usb_iad_desc_t *iad_desc = (const usb_iad_desc_t *)this_desc; + if (iad_desc->bFirstInterface == intf_idx) { + // IAD with correct interface number was found: Check Class/Subclass + // codes, save Interface indexes + assert(iad_desc->bInterfaceCount == 2); + assert(iad_desc->bFunctionClass == USB_CLASS_COMM); + assert(iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM); + notif_intf_idx = iad_desc->bFirstInterface; + data_intf_idx = iad_desc->bFirstInterface + 1; + interface_found = true; + } + } while (!interface_found); + } else if ((device_desc->bDeviceClass == USB_CLASS_COMM) && (intf_idx == 0)) { + // This is a Communication Device Class + notif_intf_idx = 0; + data_intf_idx = 1; + interface_found = true; + } + + // Save found interfaces descriptors: + if (interface_found) { + // Notification IF and EP + cdc_dev->notif.intf_desc = usb_parse_interface_descriptor( + config_desc, notif_intf_idx, 0, &desc_offset); + assert(cdc_dev->notif.intf_desc); + + // CDC specific descriptors should be right after CDC-Communication + // interface descriptor Note: That's why we use usb_parse_next_descriptor + // instead of usb_parse_next_descriptor_of_type. The latter could return CDC + // specific descriptors that don't belong to this interface + const usb_standard_desc_t *cdc_desc = + (usb_standard_desc_t *)cdc_dev->notif.intf_desc; + do { + cdc_desc = usb_parse_next_descriptor(cdc_desc, config_desc->wTotalLength, + &desc_offset); + if ((cdc_desc == NULL) || + (cdc_desc->bDescriptorType != + ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE))) { + break; // We found all CDC specific descriptors + } + cdc_dev->num_cdc_intf_desc++; + cdc_dev->cdc_intf_desc =(const usb_standard_desc_t**) + realloc(cdc_dev->cdc_intf_desc, + cdc_dev->num_cdc_intf_desc * (sizeof(usb_standard_desc_t *))); + assert(cdc_dev->cdc_intf_desc); + cdc_dev->cdc_intf_desc[cdc_dev->num_cdc_intf_desc - 1] = cdc_desc; + } while (1); + *notif_ep = usb_parse_endpoint_descriptor_by_index( + cdc_dev->notif.intf_desc, 0, config_desc->wTotalLength, &desc_offset); + assert(notif_ep); + + // Data IF and EP + cdc_dev->data.intf_desc = usb_parse_interface_descriptor( + config_desc, data_intf_idx, 0, &desc_offset); + assert(cdc_dev->data.intf_desc); + int temp_offset = desc_offset; + for (int i = 0; i < 2; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index( + cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + *in_ep = this_ep; + } else { + *out_ep = this_ep; + } + desc_offset = temp_offset; + } + return ESP_OK; + } + return ESP_ERR_NOT_FOUND; +} + +esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, + const cdc_acm_host_device_config_t *dev_config, + cdc_acm_dev_hdl_t *cdc_hdl_ret) { + esp_err_t ret = ESP_FAIL; + bool exit_flag = false; + bool err = false; + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + // Find underlying USB device + cdc_dev_t *cdc_dev; + if(ESP_OK!=cdc_acm_find_and_open_usb_device( + vid, pid, dev_config->connection_timeout_ms, &cdc_dev)){ + exit_flag = true; + ESP_LOGD(TAG, "USB device with VID: 0x%04X, PID: 0x%04X not found", vid, pid); + + } else { + + // Find and save relevant interface and endpoint descriptors + const usb_ep_desc_t *notif_ep = NULL; + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + + if (ESP_OK != cdc_acm_find_intf_and_ep_desc(cdc_dev, interface_idx, + ¬if_ep, &in_ep, &out_ep)) { + err= true; + ESP_LOGE(TAG, "Could not find required interface"); + } else { + + // Check whether found Interfaces are really CDC-ACM + assert(cdc_dev->notif.intf_desc->bInterfaceClass == USB_CLASS_COMM); + assert(cdc_dev->notif.intf_desc->bInterfaceSubClass == USB_CDC_SUBCLASS_ACM); + assert(cdc_dev->notif.intf_desc->bNumEndpoints == 1); + assert(cdc_dev->data.intf_desc->bInterfaceClass == USB_CLASS_CDC_DATA); + assert(cdc_dev->data.intf_desc->bNumEndpoints == 2); + + // Save Communication and Data protocols + cdc_dev->comm_protocol = + (cdc_comm_protocol_t)cdc_dev->notif.intf_desc->bInterfaceProtocol; + cdc_dev->data_protocol = + (cdc_data_protocol_t)cdc_dev->data.intf_desc->bInterfaceProtocol; + + // The following line is here for backward compatibility with v1.0.* + // where fixed size of IN buffer (equal to IN Maximum Packe Size) was used + const size_t in_buf_size = + (dev_config->data_cb && (dev_config->in_buffer_size == 0)) + ? USB_EP_DESC_GET_MPS(in_ep) + : dev_config->in_buffer_size; + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + if (ESP_OK != cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, in_buf_size, out_ep, + dev_config->out_buffer_size)) { + err = true; + ESP_LOGE(TAG, "Failed to allocate USB transfers"); + } else { + if (ESP_OK != cdc_acm_start(cdc_dev, dev_config->event_cb, + dev_config->data_cb, dev_config->user_arg)){ + + err = true; + ESP_LOGE(TAG, "Failed to start CDC-ACM device"); + } else { + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; + } + } + } + } + +if (err) { + cdc_acm_device_remove(cdc_dev); +} +if (err || exit_flag) { + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + *cdc_hdl_ret = NULL; + } + return ret; +} + +esp_err_t cdc_acm_host_open_vendor_specific( + uint16_t vid, uint16_t pid, uint8_t interface_num, + const cdc_acm_host_device_config_t *dev_config, + cdc_acm_dev_hdl_t *cdc_hdl_ret) { + esp_err_t ret = ESP_FAIL; + bool err = false; + bool exit_flag = false; + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(dev_config, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_hdl_ret, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + // Find underlying USB device + cdc_dev_t *cdc_dev; + ret = cdc_acm_find_and_open_usb_device( + vid, pid, dev_config->connection_timeout_ms, &cdc_dev); + if (ESP_OK != ret) { + exit_flag = true; + ESP_LOGD(TAG, "USB device with VID: 0x%04X, PID: 0x%04X not found", vid, + pid); + } else { + + // Open procedure for CDC-ACM non-compliant devices: + const usb_config_desc_t *config_desc; + int desc_offset; + ESP_ERROR_CHECK( + usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + cdc_dev->data.intf_desc = usb_parse_interface_descriptor( + config_desc, interface_num, 0, &desc_offset); + + if (!(cdc_dev->data.intf_desc)){ + err = true; + ret = ESP_ERR_NOT_FOUND; + ESP_LOGE(TAG, "Required interface no %d was not found.", interface_num); + } else { + + const int temp_offset = desc_offset; // Save this offset for later + + // The interface can have 2-3 endpoints. 2 for data and 1 optional for + // notifications + const usb_ep_desc_t *in_ep = NULL; + const usb_ep_desc_t *out_ep = NULL; + const usb_ep_desc_t *notif_ep = NULL; + + // Go through all interface's endpoints and parse Interrupt and Bulk endpoints + for (int i = 0; i < cdc_dev->data.intf_desc->bNumEndpoints; i++) { + const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index( + cdc_dev->data.intf_desc, i, config_desc->wTotalLength, &desc_offset); + assert(this_ep); + + if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_INTR) { + // Notification channel does not have its dedicated interface (data and + // notif interface is the same) + cdc_dev->notif.intf_desc = cdc_dev->data.intf_desc; + notif_ep = this_ep; + } else if (USB_EP_DESC_GET_XFERTYPE(this_ep) == USB_TRANSFER_TYPE_BULK) { + if (USB_EP_DESC_GET_EP_DIR(this_ep)) { + in_ep = this_ep; + } else { + out_ep = this_ep; + } + } + desc_offset = temp_offset; + } + + // The following line is here for backward compatibility with v1.0.* + // where fixed size of IN buffer (equal to IN Maximum Packet Size) was used + const size_t in_buf_size = + (dev_config->data_cb && (dev_config->in_buffer_size == 0)) + ? USB_EP_DESC_GET_MPS(in_ep) + : dev_config->in_buffer_size; + + // Allocate USB transfers, claim CDC interfaces and return CDC-ACM handle + if(ESP_OK!= cdc_acm_transfers_allocate(cdc_dev, notif_ep, in_ep, in_buf_size, out_ep, + dev_config->out_buffer_size)){ + err = true; + ESP_LOGE(TAG, "Failed to allocate USB transfers"); + } else { + if(ESP_OK != cdc_acm_start(cdc_dev, dev_config->event_cb, + dev_config->data_cb, dev_config->user_arg)){ + err = true; + ESP_LOGE(TAG, "Failed to start CDC-ACM device"); + } else { + *cdc_hdl_ret = (cdc_acm_dev_hdl_t)cdc_dev; + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; + } + } + } + } + if (err) { + cdc_acm_device_remove(cdc_dev); + } + if (err || exit_flag){ + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + } + return ret; +} + +esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl) { + CDC_ACM_CHECK(p_cdc_acm_obj, ESP_ERR_INVALID_STATE); + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + + xSemaphoreTake(p_cdc_acm_obj->open_close_mutex, portMAX_DELAY); + + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + // Cancel polling of BULK IN and INTERRUPT IN + CDC_ACM_ENTER_CRITICAL(); + cdc_dev->notif.cb = NULL; + cdc_dev->data.in_cb = NULL; + CDC_ACM_EXIT_CRITICAL(); + if (cdc_dev->data.in_xfer) { + ESP_ERROR_CHECK(cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, + cdc_dev->data.in_xfer)); + } + if (cdc_dev->notif.xfer != NULL) { + ESP_ERROR_CHECK( + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->notif.xfer)); + } + + // Release all interfaces + ESP_ERROR_CHECK(usb_host_interface_release( + p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, + cdc_dev->data.intf_desc->bInterfaceNumber)); + if ((cdc_dev->notif.intf_desc != NULL) && + (cdc_dev->notif.intf_desc != cdc_dev->data.intf_desc)) { + ESP_ERROR_CHECK(usb_host_interface_release( + p_cdc_acm_obj->cdc_acm_client_hdl, cdc_dev->dev_hdl, + cdc_dev->notif.intf_desc->bInterfaceNumber)); + } + + CDC_ACM_ENTER_CRITICAL(); + SLIST_REMOVE(&p_cdc_acm_obj->cdc_devices_list, cdc_dev, cdc_dev_s, + list_entry); + CDC_ACM_EXIT_CRITICAL(); + + cdc_acm_device_remove(cdc_dev); + xSemaphoreGive(p_cdc_acm_obj->open_close_mutex); + return ESP_OK; +} + +/** + * @brief Print CDC specific descriptor in human readable form + * + * This is a callback function that is called from USB Host library, + * when it wants to print full configuration descriptor to stdout. + * + * @param[in] _desc CDC specific descriptor + */ +static void cdc_acm_print_desc(const usb_standard_desc_t *_desc) { + if (_desc->bDescriptorType != + ((USB_CLASS_COMM << 4) | USB_B_DESCRIPTOR_TYPE_INTERFACE)) { + // Quietly return in case that this descriptor is not CDC interface + // descriptor + return; + } + + switch (((cdc_header_desc_t *)_desc)->bDescriptorSubtype) { + case USB_CDC_DESC_SUBTYPE_HEADER: { + cdc_header_desc_t *desc = (cdc_header_desc_t *)_desc; + printf("\t*** CDC Header Descriptor ***\n"); + printf("\tbcdCDC: %d.%d0\n", ((desc->bcdCDC >> 8) & 0xF), + ((desc->bcdCDC >> 4) & 0xF)); + break; + } + case USB_CDC_DESC_SUBTYPE_CALL: { + cdc_acm_call_desc_t *desc = (cdc_acm_call_desc_t *)_desc; + printf("\t*** CDC Call Descriptor ***\n"); + printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + printf("\tbDataInterface: %d\n", desc->bDataInterface); + break; + } + case USB_CDC_DESC_SUBTYPE_ACM: { + cdc_acm_acm_desc_t *desc = (cdc_acm_acm_desc_t *)_desc; + printf("\t*** CDC ACM Descriptor ***\n"); + printf("\tbmCapabilities: 0x%02X\n", desc->bmCapabilities.val); + break; + } + case USB_CDC_DESC_SUBTYPE_UNION: { + cdc_union_desc_t *desc = (cdc_union_desc_t *)_desc; + printf("\t*** CDC Union Descriptor ***\n"); + printf("\tbControlInterface: %d\n", desc->bControlInterface); + printf("\tbSubordinateInterface[0]: %d\n", + desc->bSubordinateInterface[0]); + break; + } + default: + ESP_LOGW(TAG, "Unsupported CDC specific descriptor"); + break; + } +} + +void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl) { + assert(cdc_hdl); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + const usb_device_desc_t *device_desc; + const usb_config_desc_t *config_desc; + ESP_ERROR_CHECK_WITHOUT_ABORT( + usb_host_get_device_descriptor(cdc_dev->dev_hdl, &device_desc)); + ESP_ERROR_CHECK_WITHOUT_ABORT( + usb_host_get_active_config_descriptor(cdc_dev->dev_hdl, &config_desc)); + usb_print_device_descriptor(device_desc); + usb_print_config_descriptor(config_desc, cdc_acm_print_desc); +} + +/** + * @brief Check finished transfer status + * + * Return to on transfer completed OK. + * Cancel the transfer and issue user's callback in case of an error. + * + * @param[in] transfer Transfer to be checked + * @return true Transfer completed + * @return false Transfer NOT completed + */ +static bool cdc_acm_is_transfer_completed(usb_transfer_t *transfer) { + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + bool completed = false; + + switch (transfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + completed = true; + break; + case USB_TRANSFER_STATUS_NO_DEVICE: // User is notified about device + // disconnection from usb_event_cb + case USB_TRANSFER_STATUS_CANCELED: + break; + case USB_TRANSFER_STATUS_ERROR: + case USB_TRANSFER_STATUS_TIMED_OUT: + case USB_TRANSFER_STATUS_STALL: + case USB_TRANSFER_STATUS_OVERFLOW: + case USB_TRANSFER_STATUS_SKIPPED: + default: + // Transfer was not completed or cancelled by user. Inform user about this + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t error_event = { + .type = CDC_ACM_HOST_ERROR, + .data ={.error = (int)transfer->status}}; + cdc_dev->notif.cb(&error_event, cdc_dev->cb_arg); + } + } + return completed; +} + +static void in_xfer_cb(usb_transfer_t *transfer) { + ESP_LOGD("CDC_ACM", "in xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (!cdc_acm_is_transfer_completed(transfer)) { + return; + } + + if (cdc_dev->data.in_cb) { + const bool data_processed = cdc_dev->data.in_cb( + transfer->data_buffer, transfer->actual_num_bytes, cdc_dev->cb_arg); + + // Information for developers: + // In order to save RAM and CPU time, the application can indicate that the + // received data was not processed and that the application expects more + // data. In this case, the next received data must be appended to the + // existing buffer. Since the data_buffer in usb_transfer_t is a constant + // pointer, we must cast away to const qualifier. + if (!data_processed) { + // In case the received data was not processed, the next RX data must be + // appended to current buffer + uint8_t **ptr = (uint8_t **)(&(transfer->data_buffer)); + *ptr += transfer->actual_num_bytes; + + // Calculate remaining space in the buffer. Attention: pointer + // arithmetics! + size_t space_left = + transfer->data_buffer_size - + (transfer->data_buffer - cdc_dev->data.in_data_buffer_base); + uint16_t mps = cdc_dev->data.in_mps; + transfer->num_bytes = + (space_left / mps) * mps; // Round down to MPS for next transfer + + if (transfer->num_bytes == 0) { + // The IN buffer cannot accept more data, inform the user and reset the + // buffer + ESP_LOGW(TAG, "IN buffer overflow"); + cdc_dev->serial_state.bOverRun = true; + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t serial_state_event = { + .type = CDC_ACM_HOST_SERIAL_STATE, + .data = {.serial_state = cdc_dev->serial_state}}; + cdc_dev->notif.cb(&serial_state_event, cdc_dev->cb_arg); + } + + cdc_acm_reset_in_transfer(cdc_dev); + cdc_dev->serial_state.bOverRun = false; + } + } else { + cdc_acm_reset_in_transfer(cdc_dev); + } + } + + ESP_LOGD("CDC_ACM", "Submitting poll for BULK IN transfer"); + usb_host_transfer_submit(cdc_dev->data.in_xfer); +} + +static void notif_xfer_cb(usb_transfer_t *transfer) { + ESP_LOGD("CDC_ACM", "notif xfer cb"); + cdc_dev_t *cdc_dev = (cdc_dev_t *)transfer->context; + + if (cdc_acm_is_transfer_completed(transfer)) { + cdc_notification_t *notif = (cdc_notification_t *)transfer->data_buffer; + switch (notif->bNotificationCode) { + case USB_CDC_NOTIF_NETWORK_CONNECTION: { + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t net_conn_event = { + .type = CDC_ACM_HOST_NETWORK_CONNECTION, + .data = {.network_connected = (bool)notif->wValue}}; + cdc_dev->notif.cb(&net_conn_event, cdc_dev->cb_arg); + } + break; + } + case USB_CDC_NOTIF_SERIAL_STATE: { + cdc_dev->serial_state.val = *((uint16_t *)notif->Data); + if (cdc_dev->notif.cb) { + const cdc_acm_host_dev_event_data_t serial_state_event = { + .type = CDC_ACM_HOST_SERIAL_STATE, + .data = {.serial_state = cdc_dev->serial_state}}; + cdc_dev->notif.cb(&serial_state_event, cdc_dev->cb_arg); + } + break; + } + case USB_CDC_NOTIF_RESPONSE_AVAILABLE: // Encapsulated commands not + // implemented - fallthrough + default: + ESP_LOGW("CDC_ACM", "Unsupported notification type 0x%02X", + notif->bNotificationCode); + ESP_LOG_BUFFER_HEX("CDC_ACM", transfer->data_buffer, + transfer->actual_num_bytes); + break; + } + + // Start polling for new data again + ESP_LOGD("CDC_ACM", "Submitting poll for INTR IN transfer"); + usb_host_transfer_submit(cdc_dev->notif.xfer); + } +} + +static void out_xfer_cb(usb_transfer_t *transfer) { + ESP_LOGD("CDC_ACM", "out/ctrl xfer cb"); + assert(transfer->context); + xSemaphoreGive((SemaphoreHandle_t)transfer->context); +} + +static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, + void *arg) { + switch (event_msg->event) { + case USB_HOST_CLIENT_EVENT_NEW_DEV: { + // Guard p_cdc_acm_obj->new_dev_cb from concurrent access + ESP_LOGD(TAG, "New device connected"); + CDC_ACM_ENTER_CRITICAL(); + cdc_acm_new_dev_callback_t _new_dev_cb = p_cdc_acm_obj->new_dev_cb; + CDC_ACM_EXIT_CRITICAL(); + + if (_new_dev_cb) { + usb_device_handle_t new_dev; + if (usb_host_device_open(p_cdc_acm_obj->cdc_acm_client_hdl, + event_msg->new_dev.address, + &new_dev) != ESP_OK) { + break; + } + assert(new_dev); + _new_dev_cb(new_dev); + usb_host_device_close(p_cdc_acm_obj->cdc_acm_client_hdl, new_dev); + } + break; + } + case USB_HOST_CLIENT_EVENT_DEV_GONE: { + ESP_LOGD(TAG, "Device suddenly disconnected"); + // Find CDC pseudo-devices associated with this USB device and close them + cdc_dev_t *cdc_dev; + cdc_dev_t *tcdc_dev; + // We are using 'SAFE' version of 'SLIST_FOREACH' which enables user to + // close the disconnected device in the callback + SLIST_FOREACH_SAFE(cdc_dev, &p_cdc_acm_obj->cdc_devices_list, list_entry, + tcdc_dev) { + if (cdc_dev->dev_hdl == event_msg->dev_gone.dev_hdl && + cdc_dev->notif.cb) { + // The suddenly disconnected device was opened by this driver: inform + // user about this + const cdc_acm_host_dev_event_data_t disconn_event = { + .type = CDC_ACM_HOST_DEVICE_DISCONNECTED, + .data = {.cdc_hdl = (cdc_acm_dev_hdl_t)cdc_dev} + }; + cdc_dev->notif.cb(&disconn_event, cdc_dev->cb_arg); + } + } + break; + } + default: + assert(false); + break; + } +} + +esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, + const uint8_t *data, size_t data_len, + uint32_t timeout_ms) { + esp_err_t ret; + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + CDC_ACM_CHECK(data && (data_len > 0), ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_dev->data.out_xfer, + ESP_ERR_NOT_SUPPORTED); // Device was opened as read-only. + CDC_ACM_CHECK(data_len <= cdc_dev->data.out_xfer->data_buffer_size, + ESP_ERR_INVALID_SIZE); + + // Take OUT mutex and fill the OUT transfer + BaseType_t taken = + xSemaphoreTake(cdc_dev->data.out_mux, pdMS_TO_TICKS(timeout_ms)); + if (taken != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + ESP_LOGD("CDC_ACM", "Submitting BULK OUT transfer"); + memcpy(cdc_dev->data.out_xfer->data_buffer, data, data_len); + cdc_dev->data.out_xfer->num_bytes = data_len; + cdc_dev->data.out_xfer->timeout_ms = timeout_ms; + ESP_GOTO_ON_ERROR(usb_host_transfer_submit(cdc_dev->data.out_xfer), unblock, + TAG, ); + + // Wait for OUT transfer completion + taken = xSemaphoreTake((SemaphoreHandle_t)cdc_dev->data.out_xfer->context, + pdMS_TO_TICKS(timeout_ms)); + if (!taken) { + // Reset the endpoint + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->data.out_xfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE( + cdc_dev->data.out_xfer->status == USB_TRANSFER_STATUS_COMPLETED, + ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Bulk OUT transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->data.out_xfer->actual_num_bytes == data_len, + ESP_ERR_INVALID_RESPONSE, unblock, TAG, + "Incorrect number of bytes transferred"); + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->data.out_mux); + return ret; +} + +esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, + cdc_acm_line_coding_t *line_coding) { + CDC_ACM_CHECK(line_coding, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, true, USB_CDC_REQ_GET_LINE_CODING, + (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), + 0), + TAG, ); + ESP_LOGD(TAG, + "Line Get: Rate: %" PRIu32 + ", Stop bits: %d, Parity: %d, Databits: %d", + line_coding->dwDTERate, line_coding->bCharFormat, + line_coding->bParityType, line_coding->bDataBits); + return ESP_OK; +} + +esp_err_t cdc_acm_host_line_coding_set( + cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding) { + CDC_ACM_CHECK(line_coding, ESP_ERR_INVALID_ARG); + + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SET_LINE_CODING, + (uint8_t *)line_coding, sizeof(cdc_acm_line_coding_t), + 0), + TAG, ); + ESP_LOGD(TAG, + "Line Set: Rate: %" PRIu32 + ", Stop bits: %d, Parity: %d, Databits: %d", + line_coding->dwDTERate, line_coding->bCharFormat, + line_coding->bParityType, line_coding->bDataBits); + return ESP_OK; +} + +esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, + bool dtr, bool rts) { + const uint16_t ctrl_bitmap = (uint16_t)dtr | ((uint16_t)rts << 1); + + ESP_RETURN_ON_ERROR(send_cdc_request((cdc_dev_t *)cdc_hdl, false, + USB_CDC_REQ_SET_CONTROL_LINE_STATE, NULL, + 0, ctrl_bitmap), + TAG, ); + ESP_LOGD(TAG, "Control Line Set: DTR: %d, RTS: %d", dtr, rts); + return ESP_OK; +} + +esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, + uint16_t duration_ms) { + ESP_RETURN_ON_ERROR( + send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SEND_BREAK, + NULL, 0, duration_ms), + TAG, ); + + // Block until break is deasserted + vTaskDelay(pdMS_TO_TICKS(duration_ms + 1)); + return ESP_OK; +} + +esp_err_t cdc_acm_host_send_custom_request(cdc_acm_dev_hdl_t cdc_hdl, + uint8_t bmRequestType, + uint8_t bRequest, uint16_t wValue, + uint16_t wIndex, uint16_t wLength, + uint8_t *data) { + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + if (wLength > 0) { + CDC_ACM_CHECK(data, ESP_ERR_INVALID_ARG); + } + CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= wLength, + ESP_ERR_INVALID_SIZE); + + esp_err_t ret; + + // Take Mutex and fill the CTRL request + BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(5000)); + if (!taken) { + return ESP_ERR_TIMEOUT; + } + usb_setup_packet_t *req = + (usb_setup_packet_t *)(cdc_dev->ctrl_transfer->data_buffer); + uint8_t *start_of_data = (uint8_t *)req + sizeof(usb_setup_packet_t); + req->bmRequestType = bmRequestType; + req->bRequest = bRequest; + req->wValue = wValue; + req->wIndex = wIndex; + req->wLength = wLength; + + // For IN transfers we must transfer data ownership to CDC driver + const bool in_transfer = bmRequestType & USB_BM_REQUEST_TYPE_DIR_IN; + if (!in_transfer) { + memcpy(start_of_data, data, wLength); + } + + cdc_dev->ctrl_transfer->num_bytes = wLength + sizeof(usb_setup_packet_t); + ESP_GOTO_ON_ERROR( + usb_host_transfer_submit_control(p_cdc_acm_obj->cdc_acm_client_hdl, + cdc_dev->ctrl_transfer), + unblock, TAG, "CTRL transfer failed"); + + taken = xSemaphoreTake( + (SemaphoreHandle_t)cdc_dev->ctrl_transfer->context, + pdMS_TO_TICKS(5000)); // This is a fixed timeout. Every CDC device should + // be able to respond to CTRL transfer in 5 seconds + if (!taken) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + cdc_acm_reset_transfer_endpoint(cdc_dev->dev_hdl, cdc_dev->ctrl_transfer); + ret = ESP_ERR_TIMEOUT; + goto unblock; + } + + ESP_GOTO_ON_FALSE( + cdc_dev->ctrl_transfer->status == USB_TRANSFER_STATUS_COMPLETED, + ESP_ERR_INVALID_RESPONSE, unblock, TAG, "Control transfer error"); + ESP_GOTO_ON_FALSE(cdc_dev->ctrl_transfer->actual_num_bytes == + cdc_dev->ctrl_transfer->num_bytes, + ESP_ERR_INVALID_RESPONSE, unblock, TAG, + "Incorrect number of bytes transferred"); + + // For OUT transfers, we must transfer data ownership to user + if (in_transfer) { + memcpy(data, start_of_data, wLength); + } + ret = ESP_OK; + +unblock: + xSemaphoreGive(cdc_dev->ctrl_mux); + return ret; +} + +static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, + cdc_request_code_t request, uint8_t *data, + uint16_t data_len, uint16_t value) { + CDC_ACM_CHECK(cdc_dev, ESP_ERR_INVALID_ARG); + CDC_ACM_CHECK(cdc_dev->notif.intf_desc, ESP_ERR_NOT_SUPPORTED); + + uint8_t req_type = + USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + if (in_transfer) { + req_type |= USB_BM_REQUEST_TYPE_DIR_IN; + } else { + req_type |= USB_BM_REQUEST_TYPE_DIR_OUT; + } + return cdc_acm_host_send_custom_request( + (cdc_acm_dev_hdl_t)cdc_dev, req_type, request, value, + cdc_dev->notif.intf_desc->bInterfaceNumber, data_len, data); +} + +esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, + cdc_comm_protocol_t *comm, + cdc_data_protocol_t *data) { + CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); + cdc_dev_t *cdc_dev = (cdc_dev_t *)cdc_hdl; + + if (comm != NULL) { + *comm = cdc_dev->comm_protocol; + } + if (data != NULL) { + *data = cdc_dev->data_protocol; + } + return ESP_OK; +} diff --git a/libraries/esp32-usb-serial-1.0.1/src/esp32_usb_serial.cpp b/libraries/esp32-usb-serial-1.0.1/src/esp32_usb_serial.cpp new file mode 100644 index 00000000..95fb6f35 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/esp32_usb_serial.cpp @@ -0,0 +1,230 @@ +/* + esp32_usb_serial.cpp - esp32 usb serial library + Copyright (c) 2023 Luc Lebosse. All rights reserved. + + This code is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/********************* + * INCLUDES + *********************/ + +#include "esp32_usb_serial.h" +#include "esp_log.h" +#include "esp_private/usb_phy.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/usb_phy_types.h" +#include "usb/cdc_acm_host.h" +#include "usb/usb_host.h" +#include "usb/vcp.hpp" +#include "usb/vcp_ch34x.hpp" +#include "usb/vcp_cp210x.hpp" +#include "usb/vcp_ftdi.hpp" + + +static usb_phy_handle_t phy_hdl = NULL; +static TaskHandle_t usb_serial_xHandle = NULL; + +#ifndef ESP_USB_LIB_TASK_SIZE +#define ESP_USB_LIB_TASK_SIZE 4096 +#endif //ESP_USB_LIB_TASK_SIZE + +#ifndef ESP_USB_LIB_TASK_CORE +#define ESP_USB_LIB_TASK_CORE 1 +#endif //ESP_USB_LIB_TASK_CORE + +#ifndef ESP_USB_LIB_TASK_PRIORITY +#define ESP_USB_LIB_TASK_PRIORITY 10 +#endif //ESP_USB_LIB_TASK_PRIORITY + +uint16_t esp_usb::getVID(){ + return esp_usb::current_vid; +} + +const char * esp_usb::getVIDString(){ + switch (esp_usb::current_vid){ + case 0x10C4: + return "CP210X"; + break; + case 0x1A86: + return "CH34X"; + break; + case 0X0403: + return "FTDI"; + break; + default: + break; + }; + return "None"; +} + +uint16_t esp_usb::getPID(){ + return esp_usb::current_pid; +} + +const char * esp_usb::getPIDString() { + switch (esp_usb::current_pid){ + case 0x7522: + case 0x7523: + return "CH340"; + break; + case 0x341: + return "CH341"; + break; + case 0xEA60: + return "CP2101-CP2104"; + break; + case 0xEA70: + return "CP2105"; + break; + case 0xEA71: + return "CP2108"; + break; + case 0x6001: + return "FT232"; + break; + case 0x6015: + return "FT231"; + break; + default: + break; + }; + return "None"; +} + +/** + * @brief USB Host library handling task + * + * @param arg Unused + */ +void usb_lib_task(void *arg) { + while (1) { + // Start handling system events + uint32_t event_flags; + // exception must be enabled - so I guess there is a reason for it + // this generate random crash when disconnected + try { + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + } catch (...) { + ESP_LOGD("USB_SERIAL","Error handling usb event"); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + if (ESP_OK != usb_host_device_free_all()) { + ESP_LOGE("USB_SERIAL","Failed to free all devices"); + } + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGD("USB_SERIAL","USB: All devices freed"); + // Continue handling USB events to allow device reconnection + } + } +} + +esp_err_t usb_serial_deinit() { + esp_err_t ret; + if (usb_serial_xHandle) { + ret = usb_serial_delete_task(); + if (ret != ESP_OK) { + ESP_LOGE("USB_SERIAL","Failed to delete task for usb-host %s", + esp_err_to_name(ret)); + } + vTaskDelay(10); + } + ret = usb_host_device_free_all(); + if (ret != ESP_OK) { + ESP_LOGE("USB_SERIAL","Failed to free all usb device %s", esp_err_to_name(ret)); + } + ret = usb_host_uninstall(); + // this one failed with ESP_ERR_INVALID_STATE if USB is connected + if (ret != ESP_OK) { + ESP_LOGE("USB_SERIAL","Failed to unsinstall usb host %s", + esp_err_to_name(usb_host_uninstall())); + } + // Deinitialize the internal USB PHY + ret = usb_del_phy(phy_hdl); + if (ret != ESP_OK) { + ESP_LOGE("USB_SERIAL","Failed to delete PHY %s", esp_err_to_name(ret)); + } + phy_hdl = NULL; + return ESP_OK; +} + +esp_err_t usb_serial_init() { + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = + USB_PHY_SPEED_UNDEFINED, // In Host mode, the speed is determined by + // the connected device + .ext_io_conf = NULL, + .otg_io_conf = NULL, + }; + if (ESP_OK != usb_new_phy(&phy_config, &phy_hdl)) { + ESP_LOGE("USB_SERIAL","Failed to init USB PHY"); + } + + const usb_host_config_t host_config = { + .skip_phy_setup = true, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + // Install USB Host driver. Should only be called once in entire application + ESP_LOGD("USB_SERIAL","Installing USB Host"); + esp_err_t err = usb_host_install(&host_config); + if (err != ESP_OK) { + ESP_LOGE("USB_SERIAL","Failed to install USB Host %s", esp_err_to_name(err)); + return ESP_FAIL; + }; + return ESP_OK; +} + +esp_err_t usb_serial_delete_task() { + esp_err_t err = cdc_acm_host_uninstall(); + if (err != ESP_OK) { + vTaskDelete(usb_serial_xHandle); + usb_serial_xHandle = NULL; + return ESP_OK; + } + return err; +} + +esp_err_t usb_serial_create_task() { + // Create a task that will handle USB library events + + BaseType_t res = + xTaskCreatePinnedToCore(usb_lib_task, "usb_lib", ESP_USB_LIB_TASK_SIZE, + NULL, ESP_USB_LIB_TASK_PRIORITY, + &usb_serial_xHandle, ESP_USB_LIB_TASK_CORE); + if (res == pdPASS && usb_serial_xHandle) { + ESP_LOGD("USB_SERIAL","Installing CDC-ACM driver"); + if (cdc_acm_host_install(NULL) == ESP_OK) { + // Register VCP drivers to VCP service. + ESP_LOGD("USB_SERIAL","Registering FT23x driver"); + esp_usb::VCP::register_driver(); + ESP_LOGD("USB_SERIAL","Registering CP210x driver"); + esp_usb::VCP::register_driver(); + ESP_LOGD("USB_SERIAL","Registering CH34x driver"); + esp_usb::VCP::register_driver(); + return ESP_OK; + } else { + ESP_LOGE("USB_SERIAL","Failed to install CDC-ACM driver"); + } + } else { + ESP_LOGE("USB_SERIAL","Failed to create task USB Host"); + } + + return ESP_FAIL; +} diff --git a/libraries/esp32-usb-serial-1.0.1/src/esp32_usb_serial.h b/libraries/esp32-usb-serial-1.0.1/src/esp32_usb_serial.h new file mode 100644 index 00000000..a3af86cb --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/esp32_usb_serial.h @@ -0,0 +1,67 @@ +/* + esp32_usb_serial.h - esp32 usb serial library + Copyright (c) 2023 Luc Lebosse. All rights reserved. + + This code is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include "usb/cdc_acm_host.h" +#include "usb/usb_host.h" +#include "usb/vcp.hpp" +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include +#include + +#include "esp_err.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +esp_err_t usb_serial_init(); +esp_err_t usb_serial_deinit(); +esp_err_t usb_serial_create_task(); +esp_err_t usb_serial_delete_task(); + +namespace esp_usb{ +uint16_t getVID(); +const char* getVIDString(); +uint16_t getPID(); +const char* getPIDString(); +}; + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb/cdc_acm_host.h b/libraries/esp32-usb-serial-1.0.1/src/usb/cdc_acm_host.h new file mode 100644 index 00000000..7443d903 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb/cdc_acm_host.h @@ -0,0 +1,360 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "usb/usb_host.h" +#include "usb_types_cdc.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct cdc_dev_s *cdc_acm_dev_hdl_t; + +/** + * @brief Line Coding structure + * @see Table 17, USB CDC-PSTN specification rev. 1.2 + */ +typedef struct { + uint32_t dwDTERate; // in bits per second + uint8_t bCharFormat; // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits + uint8_t bParityType; // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space + uint8_t bDataBits; // 5, 6, 7, 8 or 16 +} __attribute__((packed)) cdc_acm_line_coding_t; + +/** + * @brief UART State Bitmap + * @see Table 31, USB CDC-PSTN specification rev. 1.2 + */ +typedef union { + struct { + uint16_t bRxCarrier : 1; // State of receiver carrier detection mechanism of device. This signal corresponds to V.24 signal 109 and RS-232 signal DCD. + uint16_t bTxCarrier : 1; // State of transmission carrier. This signal corresponds to V.24 signal 106 and RS-232 signal DSR. + uint16_t bBreak : 1; // State of break detection mechanism of the device. + uint16_t bRingSignal : 1; // State of ring signal detection of the device. + uint16_t bFraming : 1; // A framing error has occurred. + uint16_t bParity : 1; // A parity error has occurred. + uint16_t bOverRun : 1; // Received data has been discarded due to overrun in the device. + uint16_t reserved : 9; + }; + uint16_t val; +} cdc_acm_uart_state_t; + +/** + * @brief CDC-ACM Device Event types to upper layer + * + */ +typedef enum { + CDC_ACM_HOST_ERROR, + CDC_ACM_HOST_SERIAL_STATE, + CDC_ACM_HOST_NETWORK_CONNECTION, + CDC_ACM_HOST_DEVICE_DISCONNECTED +} cdc_acm_host_dev_event_t; + +/** + * @brief CDC-ACM Device Event data structure + * + */ +typedef struct { + cdc_acm_host_dev_event_t type; + union { + int error; //!< Error code from USB Host + cdc_acm_uart_state_t serial_state; //!< Serial (UART) state + bool network_connected; //!< Network connection event + cdc_acm_dev_hdl_t cdc_hdl; //!< Disconnection event + } data; +} cdc_acm_host_dev_event_data_t; + +/** + * @brief New USB device callback + * + * Provides already opened usb_dev, that will be closed after this callback returns. + * This is useful for peeking device's descriptors, e.g. peeking VID/PID and loading proper driver. + * + * @attention This callback is called from USB Host context, so the CDC device can't be opened here. + */ +typedef void (*cdc_acm_new_dev_callback_t)(usb_device_handle_t usb_dev); + +/** + * @brief Data receive callback type + * + * @param[in] data Pointer to received data + * @param[in] data_len Lenght of received data in bytes + * @param[in] user_arg User's argument passed to open function + * @return true Received data was processed -> Flush RX buffer + * @return false Received data was NOT processed -> Append new data to the buffer + */ +typedef bool (*cdc_acm_data_callback_t)(const uint8_t *data, size_t data_len, void *user_arg); + +/** + * @brief Device event callback type + * + * @param[in] event Event strucutre + * @param[in] user_arg User's argument passed to open function + */ +typedef void (*cdc_acm_host_dev_callback_t)(const cdc_acm_host_dev_event_data_t *event, void *user_ctx); + +/** + * @brief Configuration structure of USB Host CDC-ACM driver + * + */ +typedef struct { + size_t driver_task_stack_size; /**< Stack size of the driver's task */ + unsigned driver_task_priority; /**< Priority of the driver's task */ + int xCoreID; /**< Core affinity of the driver's task */ + cdc_acm_new_dev_callback_t new_dev_cb; /**< New USB device connected callback. Can be NULL. */ +} cdc_acm_host_driver_config_t; + +/** + * @brief Configuration structure of CDC-ACM device + * + */ +typedef struct { + uint32_t connection_timeout_ms; /**< Timeout for USB device connection in [ms] */ + size_t out_buffer_size; /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */ + size_t in_buffer_size; /**< Maximum size of USB bulk in transfer */ + cdc_acm_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */ + cdc_acm_data_callback_t data_cb; /**< Device's data RX callback function. Can be NULL for write-only devices */ + void *user_arg; /**< User's argument that will be passed to the callbacks */ +} cdc_acm_host_device_config_t; + +/** + * @brief Install CDC-ACM driver + * + * - USB Host Library must already be installed before calling this function (via usb_host_install()) + * - This function should be called before calling any other CDC driver functions + * + * @param[in] driver_config Driver configuration structure. If set to NULL, a default configuration will be used. + * @return esp_err_t + */ +esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config); + +/** + * @brief Uninstall CDC-ACM driver + * + * - Users must ensure that all CDC devices must be closed via cdc_acm_host_close() before calling this function + * + * @return esp_err_t + */ +esp_err_t cdc_acm_host_uninstall(void); + +/** + * @brief Register new USB device callback + * + * The callback will be called for every new USB device, not just CDC-ACM class. + * + * @param[in] new_dev_cb New device callback function + * @return esp_err_t + */ +esp_err_t cdc_acm_host_register_new_dev_callback(cdc_acm_new_dev_callback_t new_dev_cb); + +/** + * @brief Open CDC-ACM compliant device + * + * CDC-ACM compliant device must contain either an Interface Association Descriptor or CDC-Union descriptor, + * which are used for the driver's configuration. + * + * @param[in] vid Device's Vendor ID + * @param[in] pid Device's Product ID + * @param[in] interface_idx Index of device's interface used for CDC-ACM communication + * @param[in] dev_config Configuration structure of the device + * @param[out] cdc_hdl_ret CDC device handle + * @return esp_err_t + */ +esp_err_t cdc_acm_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); + +/** + * @brief Open CDC-ACM non-compliant device + * + * CDC-ACM non-compliant device acts as CDC-ACM device but doesn't support all its features. + * User must provide the interface index that will be used (zero for non-composite devices). + * + * @param[in] vid Device's Vendor ID + * @param[in] pid Device's Product ID + * @param[in] interface_idx Index of device's interface used for CDC-ACM like communication + * @param[in] dev_config Configuration structure of the device + * @param[out] cdc_hdl_ret CDC device handle + * @return esp_err_t + */ +esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_num, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret); + +/** + * @brief Close CDC device and release its resources + * + * @note All in-flight transfers will be prematurely canceled. + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @return esp_err_t + */ +esp_err_t cdc_acm_host_close(cdc_acm_dev_hdl_t cdc_hdl); + +/** + * @brief Transmit data - blocking mode + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] data Data to be sent + * @param[in] data_len Data length + * @param[in] timeout_ms Timeout in [ms] + * @return esp_err_t + */ +esp_err_t cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms); + +/** + * @brief SetLineCoding function + * + * @see Chapter 6.3.10, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ +esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_line_coding_t *line_coding); + +/** + * @brief GetLineCoding function + * + * @see Chapter 6.3.11, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[out] line_coding Line Coding structure to be filled + * @return esp_err_t + */ +esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_coding_t *line_coding); + +/** + * @brief SetControlLineState function + * + * @see Chapter 6.3.12, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ +esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts); + +/** + * @brief SendBreak function + * + * This function will block until the duration_ms has passed. + * + * @see Chapter 6.3.13, USB CDC-PSTN specification rev. 1.2 + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] duration_ms Duration of the Break signal in [ms] + * @return esp_err_t + */ +esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms); + +/** + * @brief Print device's descriptors + * + * Device and full Configuration descriptors are printed in human readable format to stdout. + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + */ +void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl); + +/** + * @brief Get protocols defined in USB-CDC interface descriptors + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[out] comm Communication protocol + * @param[out] data Data protocol + * @return esp_err_t + */ +esp_err_t cdc_acm_host_protocols_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_comm_protocol_t *comm, cdc_data_protocol_t *data); + +/** + * @brief Send command to CTRL endpoint + * + * Sends Control transfer as described in USB specification chapter 9. + * This function can be used by device drivers that use custom/vendor specific commands. + * These commands can either extend or replace commands defined in USB CDC-PSTN specification rev. 1.2. + * + * @param cdc_hdl CDC handle obtained from cdc_acm_host_open() + * @param[in] bmRequestType Field of USB control request + * @param[in] bRequest Field of USB control request + * @param[in] wValue Field of USB control request + * @param[in] wIndex Field of USB control request + * @param[in] wLength Field of USB control request + * @param[inout] data Field of USB control request + * @return esp_err_t + */ +esp_err_t cdc_acm_host_send_custom_request(cdc_acm_dev_hdl_t cdc_hdl, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data); + +#ifdef __cplusplus +} +class CdcAcmDevice { +public: + // Operators + CdcAcmDevice() : cdc_hdl(NULL) {}; + virtual ~CdcAcmDevice() + { + // Close CDC-ACM device, if it wasn't explicitly closed + if (this->cdc_hdl != NULL) { + this->close(); + } + } + + inline esp_err_t tx_blocking(uint8_t *data, size_t len, uint32_t timeout_ms = 100) + { + return cdc_acm_host_data_tx_blocking(this->cdc_hdl, data, len, timeout_ms); + } + + inline esp_err_t open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config) + { + return cdc_acm_host_open(vid, pid, interface_idx, dev_config, &this->cdc_hdl); + } + + inline esp_err_t open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t interface_idx, const cdc_acm_host_device_config_t *dev_config) + { + return cdc_acm_host_open_vendor_specific(vid, pid, interface_idx, dev_config, &this->cdc_hdl); + } + + inline esp_err_t close() + { + const esp_err_t err = cdc_acm_host_close(this->cdc_hdl); + if (err == ESP_OK) { + this->cdc_hdl = NULL; + } + return err; + } + + virtual inline esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding) const + { + return cdc_acm_host_line_coding_get(this->cdc_hdl, line_coding); + } + + virtual inline esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding) + { + return cdc_acm_host_line_coding_set(this->cdc_hdl, line_coding); + } + + virtual inline esp_err_t set_control_line_state(bool dtr, bool rts) + { + return cdc_acm_host_set_control_line_state(this->cdc_hdl, dtr, rts); + } + + virtual inline esp_err_t send_break(uint16_t duration_ms) + { + return cdc_acm_host_send_break(this->cdc_hdl, duration_ms); + } + + inline esp_err_t send_custom_request(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *data) + { + return cdc_acm_host_send_custom_request(this->cdc_hdl, bmRequestType, bRequest, wValue, wIndex, wLength, data); + } + +private: + CdcAcmDevice &operator= (const CdcAcmDevice &Copy); + bool operator== (const CdcAcmDevice ¶m) const; + bool operator!= (const CdcAcmDevice ¶m) const; + cdc_acm_dev_hdl_t cdc_hdl; +}; +#endif diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb/usb_types_cdc.h b/libraries/esp32-usb-serial-1.0.1/src/usb/usb_types_cdc.h new file mode 100644 index 00000000..bd8b1d07 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb/usb_types_cdc.h @@ -0,0 +1,206 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include + +/** + * @brief USB CDC Descriptor Subtypes + * + * @see Table 13, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_DESC_SUBTYPE_HEADER = 0x00, // Header Functional Descriptor + USB_CDC_DESC_SUBTYPE_CALL = 0x01, // Call Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_ACM = 0x02, // Abstract Control Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_DLM = 0x03, // Direct Line Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_RINGER = 0x04, // Telephone Ringer Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_CLSR = 0x05, // Telephone Call and Line State Reporting Capabilities Functional Descriptor + USB_CDC_DESC_SUBTYPE_UNION = 0x06, // Union Functional Descriptor + USB_CDC_DESC_SUBTYPE_COUNTRY = 0x07, // Country Selection Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_MODE = 0x08, // Telephone Operational Modes Functional Descriptor + USB_CDC_DESC_SUBTYPE_TERMINAL = 0x09, // USB Terminal + USB_CDC_DESC_SUBTYPE_NCHT = 0x0A, // Network Channel Terminal + USB_CDC_DESC_SUBTYPE_PROTOCOL = 0x08, // Protocol Unit + USB_CDC_DESC_SUBTYPE_EXTENSION = 0x0C, // Extension Unit + USB_CDC_DESC_SUBTYPE_MULTI_CHAN = 0x0D, // Multi-Channel Management Functional Descriptor + USB_CDC_DESC_SUBTYPE_CAPI = 0x0E, // CAPI Control + USB_CDC_DESC_SUBTYPE_ETH = 0x0F, // Ethernet Networking + USB_CDC_DESC_SUBTYPE_ATM = 0x10, // ATM Networking + USB_CDC_DESC_SUBTYPE_WHANDSET = 0x11, // Wireless Handset Control Model Functional Descriptor + USB_CDC_DESC_SUBTYPE_MDLM = 0x12, // Mobile Direct Line Model + USB_CDC_DESC_SUBTYPE_MDLM_DETAIL = 0x13, // MDLM Detail + USB_CDC_DESC_SUBTYPE_DMM = 0x14, // Device Management Model + USB_CDC_DESC_SUBTYPE_OBEX = 0x15, // OBEX Functional + USB_CDC_DESC_SUBTYPE_COMMAND_SET = 0x16, // Command Set + USB_CDC_DESC_SUBTYPE_COMMAND_SET_DETAIL = 0x17, // Command Set Detail Functional Descriptor + USB_CDC_DESC_SUBTYPE_TEL_CM = 0x18, // Telephone Control Model Functional Descriptor + USB_CDC_DESC_SUBTYPE_OBEX_SERVICE = 0x19, // OBEX Service Identifier Functional Descriptor + USB_CDC_DESC_SUBTYPE_NCM = 0x1A // NCM Functional Descriptor +} __attribute__((packed)) cdc_desc_subtype_t; + +/** + * @brief USB CDC Subclass codes + * + * @see Table 4, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_SUBCLASS_DLCM = 0x01, // Direct Line Control Model + USB_CDC_SUBCLASS_ACM = 0x02, // Abstract Control Model + USB_CDC_SUBCLASS_TCM = 0x03, // Telephone Control Model + USB_CDC_SUBCLASS_MCHCM = 0x04, // Multi-Channel Control Model + USB_CDC_SUBCLASS_CAPI = 0x05, // CAPI Control Model + USB_CDC_SUBCLASS_ECM = 0x06, // Ethernet Networking Control Model + USB_CDC_SUBCLASS_ATM = 0x07, // ATM Networking Model + USB_CDC_SUBCLASS_HANDSET = 0x08, // Wireless Handset Control Model + USB_CDC_SUBCLASS_DEV_MAN = 0x09, // Device Management + USB_CDC_SUBCLASS_MOBILE = 0x0A, // Mobile Direct Line Model + USB_CDC_SUBCLASS_OBEX = 0x0B, // OBEX + USB_CDC_SUBCLASS_EEM = 0x0C, // Ethernet Emulation Model + USB_CDC_SUBCLASS_NCM = 0x0D // Network Control Model +} __attribute__((packed)) cdc_subclass_t; + +/** + * @brief USB CDC Communications Protocol Codes + * + * @see Table 5, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_COMM_PROTOCOL_NONE = 0x00, // No class specific protocol required + USB_CDC_COMM_PROTOCOL_V250 = 0x01, // AT Commands: V.250 etc + USB_CDC_COMM_PROTOCOL_PCAA = 0x02, // AT Commands defined by PCCA-101 + USB_CDC_COMM_PROTOCOL_PCAA_A = 0x03, // AT Commands defined by PCAA-101 & Annex O + USB_CDC_COMM_PROTOCOL_GSM = 0x04, // AT Commands defined by GSM 07.07 + USB_CDC_COMM_PROTOCOL_3GPP = 0x05, // AT Commands defined by 3GPP 27.007 + USB_CDC_COMM_PROTOCOL_TIA = 0x06, // AT Commands defined by TIA for CDMA + USB_CDC_COMM_PROTOCOL_EEM = 0x07, // Ethernet Emulation Model + USB_CDC_COMM_PROTOCOL_EXT = 0xFE, // External Protocol: Commands defined by Command Set Functional Descriptor + USB_CDC_COMM_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_comm_protocol_t; + +/** + * @brief USB CDC Data Protocol Codes + * + * @see Table 7, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_DATA_PROTOCOL_NONE = 0x00, // No class specific protocol required + USB_CDC_DATA_PROTOCOL_NCM = 0x01, // Network Transfer Block + USB_CDC_DATA_PROTOCOL_I430 = 0x30, // Physical interface protocol for ISDN BRI + USB_CDC_DATA_PROTOCOL_HDLC = 0x31, // HDLC + USB_CDC_DATA_PROTOCOL_Q921M = 0x50, // Management protocol for Q.921 data link protocol + USB_CDC_DATA_PROTOCOL_Q921 = 0x51, // Data link protocol for Q.931 + USB_CDC_DATA_PROTOCOL_Q921TM = 0x52, // TEI-multiplexor for Q.921 data link protocol + USB_CDC_DATA_PROTOCOL_V42BIS = 0x90, // Data compression procedures + USB_CDC_DATA_PROTOCOL_Q931 = 0x91, // Euro-ISDN protocol control + USB_CDC_DATA_PROTOCOL_V120 = 0x92, // V.24 rate adaptation to ISDN + USB_CDC_DATA_PROTOCOL_CAPI = 0x93, // CAPI Commands + USB_CDC_DATA_PROTOCOL_VENDOR = 0xFF // Vendor-specific +} __attribute__((packed)) cdc_data_protocol_t; + +/** + * @brief USB CDC Request Codes + * + * @see Table 19, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_REQ_SEND_ENCAPSULATED_COMMAND = 0x00, + USB_CDC_REQ_GET_ENCAPSULATED_RESPONSE = 0x01, + USB_CDC_REQ_SET_COMM_FEATURE = 0x02, + USB_CDC_REQ_GET_COMM_FEATURE = 0x03, + USB_CDC_REQ_CLEAR_COMM_FEATURE = 0x04, + USB_CDC_REQ_SET_AUX_LINE_STATE = 0x10, + USB_CDC_REQ_SET_HOOK_STATE = 0x11, + USB_CDC_REQ_PULSE_SETUP = 0x12, + USB_CDC_REQ_SEND_PULSE = 0x13, + USB_CDC_REQ_SET_PULSE_TIME = 0x14, + USB_CDC_REQ_RING_AUX_JACK = 0x15, + USB_CDC_REQ_SET_LINE_CODING = 0x20, + USB_CDC_REQ_GET_LINE_CODING = 0x21, + USB_CDC_REQ_SET_CONTROL_LINE_STATE = 0x22, + USB_CDC_REQ_SEND_BREAK = 0x23, + USB_CDC_REQ_SET_RINGER_PARMS = 0x30, + USB_CDC_REQ_GET_RINGER_PARMS = 0x31, + USB_CDC_REQ_SET_OPERATION_PARMS = 0x32, + USB_CDC_REQ_GET_OPERATION_PARMS = 0x33, + USB_CDC_REQ_SET_LINE_PARMS = 0x34, + USB_CDC_REQ_GET_LINE_PARMS = 0x35, + USB_CDC_REQ_DIAL_DIGITS = 0x36, + USB_CDC_REQ_SET_UNIT_PARAMETER = 0x37, + USB_CDC_REQ_GET_UNIT_PARAMETER = 0x38, + USB_CDC_REQ_CLEAR_UNIT_PARAMETER = 0x39, + USB_CDC_REQ_GET_PROFILE = 0x3A, + USB_CDC_REQ_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + USB_CDC_REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + USB_CDC_REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + USB_CDC_REQ_SET_ETHERNET_PACKET_FILTER = 0x43, + USB_CDC_REQ_GET_ETHERNET_STATISTIC = 0x44, + USB_CDC_REQ_SET_ATM_DATA_FORMAT = 0x50, + USB_CDC_REQ_GET_ATM_DEVICE_STATISTICS = 0x51, + USB_CDC_REQ_SET_ATM_DEFAULT_VC = 0x52, + USB_CDC_REQ_GET_ATM_VC_STATISTICS = 0x53, + USB_CDC_REQ_GET_NTB_PARAMETERS = 0x80, + USB_CDC_REQ_GET_NET_ADDRESS = 0x81, + USB_CDC_REQ_SET_NET_ADDRESS = 0x82, + USB_CDC_REQ_GET_NTB_FORMAT = 0x83, + USB_CDC_REQ_SET_NTB_FORMAT = 0x84, + USB_CDC_REQ_GET_NTB_INPUT_SIZE = 0x85, + USB_CDC_REQ_SET_NTB_INPUT_SIZE = 0x86, + USB_CDC_REQ_GET_MAX_DATAGRAM_SIZE = 0x87, + USB_CDC_REQ_SET_MAX_DATAGRAM_SIZE = 0x88, + USB_CDC_REQ_GET_CRC_MODE = 0x89, + USB_CDC_REQ_SET_CRC_MODE = 0x8A +} __attribute__((packed)) cdc_request_code_t; + +/** + * @brief USB CDC Notification Codes + * + * @see Table 20, USB CDC specification rev. 1.2 + */ +typedef enum { + USB_CDC_NOTIF_NETWORK_CONNECTION = 0x00, + USB_CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, + USB_CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, + USB_CDC_NOTIF_RING_DETECT = 0x09, + USB_CDC_NOTIF_SERIAL_STATE = 0x20, + USB_CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + USB_CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + USB_CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A +} __attribute__((packed)) cdc_notification_code_t; + +typedef struct { + uint8_t bmRequestType; + cdc_notification_code_t bNotificationCode; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint8_t Data[]; +} __attribute__((packed)) cdc_notification_t; + +/** + * @brief USB CDC Header Functional Descriptor + * + * @see Table 15, USB CDC specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + uint16_t bcdCDC; // CDC version as binary-coded decimal. This driver is written for version 1.2 +} __attribute__((packed)) cdc_header_desc_t; + +/** + * @brief USB CDC Union Functional Descriptor + * + * @see Table 16, USB CDC specification rev. 1.2 + */ +typedef struct { + uint8_t bFunctionLength; + const uint8_t bDescriptorType; // Upper nibble: CDC code 0x02, Lower nibble: intf/ep descriptor type 0x04/0x05 + const cdc_desc_subtype_t bDescriptorSubtype; + const uint8_t bControlInterface; // Master/controlling interface + uint8_t bSubordinateInterface[]; // Slave/subordinate interfaces +} __attribute__((packed)) cdc_union_desc_t; diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb/vcp.hpp b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp.hpp new file mode 100644 index 00000000..fe287bd6 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp.hpp @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "usb/cdc_acm_host.h" + +namespace esp_usb { +extern uint16_t current_vid; +extern uint16_t current_pid; +/** + * @brief Virtual COM Port Service Class + * + * Virtual COM Port (VCP) service manages drivers to connected VCP devices - typically USB <-> UART converters. + * In practice, you rarely care about specifics of the devices; you only want uniform interface for them all. + * VCP service does just that, after you register drivers for various VCP devices, you can just call VCP::open + * and the service will load proper driver for device that was just plugged into USB port. + * + * Example usage: + * \code{.cpp} + * VCP::register_driver(); + * VCP::register_driver(); + * VCP::register_driver(); + * auto vcp = VCP::open(&dev_config); + * \endcode + * + * The example code assumes that you have USB Host Lib already installed. + */ +class VCP { +public: + /** + * @brief Register VCP driver to VCP service + * + * To fully leverage from VCP service functionalities, you must register VCP drivers first. + * The driver must contain the following public members/methods; + * #. vid: Supported VID + * #. pids: Array of supported PIDs + * # Constructor with (uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) input parameters + * + * @tparam T VCP driver type + */ + template static void + register_driver(void) + { + static_assert(T::pids.begin() != nullptr, "Every VCP driver must contain array of supported PIDs in 'pids' array"); + static_assert(T::vid != 0, "Every VCP driver must contain supported VID in'vid' integer"); + std::vector pids(T::pids.begin(), T::pids.end()); // Convert array to vector + vcp_driver new_driver = vcp_driver([](uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) { + return static_cast (new T(pid, dev_config, interface_idx)); // Lambda function: Open factory method + }, T::vid, pids); + drivers.push_back(new_driver); + } + + /** + * @brief VCP factory with VID and PID + * + * Use this function if you know VID and PID of the device. + * The VCP service will look for correct (already registered) driver and load it. + * + * @attention USB Host Library must be installed before calling this function! + * + * @param[in] _vid VID of the device + * @param[in] _pid PID of the device + * @param[in] dev_config Configuration of the device + * @param[in] interface_idx USB interface to use + * @return std::shared_ptr + */ + static CdcAcmDevice * + open(uint16_t _vid, uint16_t _pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief VCP factory + * + * Use this function when you want the VCP service to open any connected VCP device. + * The VCP service will look for correct (already registered) driver and load it. + * + * This function will block until a valid VCP device is found or + * until dev_config->connection_timeout_ms expires. Set timeout to 0 to wait forever. + * + * @note If there are more USB devices connected, the VCP service will return first successfully opened device + * @attention USB Host Library must be installed before calling this function! + * + * @param[in] dev_config Configuration of the device + * @param[in] interface_idx USB interface to use + * @return std::shared_ptr + */ + static CdcAcmDevice * + open(const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + +private: + // Default operators + VCP() = delete; // This driver acts as a service, you can't instantiate it + VCP(const VCP &) = delete; + VCP &operator=(VCP &) = delete; + bool operator== (const VCP ¶m) = delete; + bool operator!= (const VCP ¶m) = delete; + + /** + * @brief VCP driver structure + */ + typedef struct vcp_driver { + CdcAcmDevice *(*open)(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx); /*!< Factory method of this driver */ + uint16_t vid; /*!< VID this driver supports */ + std::vector pids; /*!< List of PIDs this driver supports */ + vcp_driver(auto open_func, const uint16_t _vid, const std::vector &_pids): open(open_func), vid(_vid), pids(_pids) {}; + } vcp_driver; + + /** + * @brief List of registered VCP drivers + */ + static std::vector drivers; +}; // VCP class +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_ch34x.hpp b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_ch34x.hpp new file mode 100644 index 00000000..7a819071 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_ch34x.hpp @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021 WCH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "usb/cdc_acm_host.h" + +#define NANJING_QINHENG_MICROE_VID (0x1A86) +#define CH340_PID (0x7522) +#define CH340_PID_1 (0x7523) +#define CH341_PID (0x5523) + +namespace esp_usb { +class CH34x : public CdcAcmDevice { +public: + /** + * @brief Constructor for this CH34x driver + * + * @note USB Host library and CDC-ACM driver must be already installed + * + * @param[in] pid PID eg. CH340_PID + * @param[in] dev_config CDC device configuration + * @param[in] interface_idx Interface number + * @return CdcAcmDevice Pointer to created and opened CH34x device + */ + CH34x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief Set Line Coding method + * + * @note Overrides default implementation in CDC-ACM driver + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Control Line State method + * + * @note Overrides default implementation in CDC-ACM driver + * @note Both signals are active low + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ + esp_err_t set_control_line_state(bool dtr, bool rts); + + // List of supported VIDs and PIDs + static constexpr uint16_t vid = NANJING_QINHENG_MICROE_VID; + static constexpr std::array pids = {CH340_PID, CH340_PID_1, CH341_PID}; + +private: + const uint8_t intf; + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; + using CdcAcmDevice::send_break; // Break is not supported by CH34x + using CdcAcmDevice::line_coding_get; // Manufacturer doesn't provide enough information to implement this + + // This function comes from official Linux driver + static int calculate_baud_divisor(unsigned int baud_rate, unsigned char *factor, unsigned char *divisor); +}; +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_cp210x.hpp b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_cp210x.hpp new file mode 100644 index 00000000..6a5f54d6 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_cp210x.hpp @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "usb/cdc_acm_host.h" + +#define SILICON_LABS_VID (0x10C4) +#define CP210X_PID (0xEA60) // Single i.e. CP2101 - CP2104 +#define CP2105_PID (0xEA70) // Dual +#define CP2108_PID (0xEA71) // Quad + +// @see AN571: CP210x Virtual COM Port Interface, chapter 5 +#define CP210X_CMD_IFC_ENABLE (0x00) // Enable or disable the interface +#define CP210X_CMD_SET_BAUDDIV (0x01) // Set the baud rate divisor +#define CP210X_CMD_GET_BAUDDIV (0x02) // Get the baud rate divisor +#define CP210X_CMD_SET_LINE_CTL (0x03) // Set the line control +#define CP210X_CMD_GET_LINE_CTL (0x04) // Get the line control +#define CP210X_CMD_SET_BREAK (0x05) // Set a BREAK +#define CP210X_CMD_IMM_CHAR (0x06) // Send character out of order +#define CP210X_CMD_SET_MHS (0x07) // Set modem handshaking +#define CP210X_CMD_GET_MDMSTS (0x08) // Get modem status +#define CP210X_CMD_SET_XON (0x09) // Emulate XON +#define CP210X_CMD_SET_XOFF (0x0A) // Emulate XOFF +#define CP210X_CMD_SET_EVENTMASK (0x0B) // Set the event mask +#define CP210X_CMD_GET_EVENTMASK (0x0C) // Get the event mask +#define CP210X_CMD_GET_EVENTSTATE (0x16) // Get the event state +#define CP210X_CMD_SET_RECEIVE (0x17) // Set receiver max timeout +#define CP210X_CMD_GET_RECEIVE (0x18) // Get receiver max timeout +#define CP210X_CMD_SET_CHAR (0x0D) // Set special character individually +#define CP210X_CMD_GET_CHARS (0x0E) // Get special characters +#define CP210X_CMD_GET_PROPS (0x0F) // Get properties +#define CP210X_CMD_GET_COMM_STATUS (0x10) // Get the serial status +#define CP210X_CMD_RESET (0x11) // Reset +#define CP210X_CMD_PURGE (0x12) // Purge +#define CP210X_CMD_SET_FLOW (0x13) // Set flow control +#define CP210X_CMD_GET_FLOW (0x14) // Get flow control +#define CP210X_CMD_EMBED_EVENTS (0x15) // Control embedding of events in the data stream +#define CP210X_CMD_GET_BAUDRATE (0x1D) // Get the baud rate +#define CP210X_CMD_SET_BAUDRATE (0x1E) // Set the baud rate +#define CP210X_CMD_SET_CHARS (0x19) // Set special characters +#define CP210X_CMD_VENDOR_SPECIFIC (0xFF) // Read/write latch values + +namespace esp_usb { +class CP210x : public CdcAcmDevice { +public: + /** + * @brief Constructor for this CP210x driver + * + * @note USB Host library and CDC-ACM driver must be already installed + * + * @param[in] pid PID eg. CP210X_PID + * @param[in] dev_config CDC device configuration + * @param[in] interface_idx Interface number + * @return CdcAcmDevice Pointer to created and opened CP210x device + */ + CP210x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief Get Line Coding method + * + * @see AN571: CP210x Virtual COM Port Interface chapters 5.6 and 5.8 + * @note Overrides default implementation in CDC-ACM driver + * @param[out] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Line Coding method + * + * @see AN571: CP210x Virtual COM Port Interface chapters 5.5 and 5.7 + * @note Overrides default implementation in CDC-ACM driver + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Control Line State method + * + * @see AN571: CP210x Virtual COM Port Interface chapter 5.9 + * @note Overrides default implementation in CDC-ACM driver + * @note Both signals are active low + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ + esp_err_t set_control_line_state(bool dtr, bool rts); + + /** + * @brief Send Break method + * + * @see AN571: CP210x Virtual COM Port Interface chapter 5.20 + * @note Overrides default implementation in CDC-ACM driver + * @param[in] duration_ms Duration of the break condition in [ms] + * @return esp_err_t + */ + esp_err_t send_break(uint16_t duration_ms); + + // List of supported VIDs and PIDs + static constexpr uint16_t vid = SILICON_LABS_VID; + static constexpr std::array pids = {CP210X_PID, CP2105_PID, CP2108_PID}; + +private: + const uint8_t intf; + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; +}; +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_ftdi.hpp b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_ftdi.hpp new file mode 100644 index 00000000..cfea297e --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb/vcp_ftdi.hpp @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "usb/cdc_acm_host.h" + +#define FTDI_VID (0x0403) +#define FT232_PID (0x6001) +#define FT231_PID (0x6015) + +#define FTDI_CMD_RESET (0x00) +#define FTDI_CMD_SET_FLOW (0x01) +#define FTDI_CMD_SET_MHS (0x02) // Modem handshaking +#define FTDI_CMD_SET_BAUDRATE (0x03) +#define FTDI_CMD_SET_LINE_CTL (0x04) +#define FTDI_CMD_GET_MDMSTS (0x05) // Modem status + +namespace esp_usb { +class FT23x : public CdcAcmDevice { +public: + /** + * @brief Constructor for this FTDI driver + * + * @note USB Host library and CDC-ACM driver must be already installed + * + * @param[in] pid PID eg. FTDI_FT232_PID + * @param[in] dev_config CDC device configuration + * @param[in] interface_idx Interface number + * @return CdcAcmDevice Pointer to created and opened FTDI device + */ + FT23x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + /** + * @brief Set Line Coding method + * + * @note Overrides default implementation in CDC-ACM driver + * @param[in] line_coding Line Coding structure + * @return esp_err_t + */ + esp_err_t line_coding_set(cdc_acm_line_coding_t *line_coding); + + /** + * @brief Set Control Line State method + * + * @note Overrides default implementation in CDC-ACM driver + * @note Both signals are active low + * @param[in] dtr Indicates to DCE if DTE is present or not. This signal corresponds to V.24 signal 108/2 and RS-232 signal Data Terminal Ready. + * @param[in] rts Carrier control for half duplex modems. This signal corresponds to V.24 signal 105 and RS-232 signal Request To Send. + * @return esp_err_t + */ + esp_err_t set_control_line_state(bool dtr, bool rts); + + // List of supported VIDs and PIDs + static constexpr uint16_t vid = FTDI_VID; + static constexpr std::array pids = {FT232_PID, FT231_PID}; + +private: + const uint8_t intf; + const cdc_acm_data_callback_t user_data_cb; + const cdc_acm_host_dev_callback_t user_event_cb; + void *user_arg; + uint16_t uart_state; + + /** + * @brief FT23x's RX data handler + * + * First two bytes are status bytes, the RX data start at data[2]. + * Coding of status bytes: + * Byte 0: + * Bit 0: Full Speed packet + * Bit 1: High Speed packet + * Bit 4: CTS + * Bit 5: DSR + * Bit 6: RI + * Bit 7: DCD + * Byte 1: + * Bit 1: RX overflow + * Bit 2: Parity error + * Bit 3: Framing error + * Bit 4: Break received + * Bit 5: Transmitter holding register empty + * Bit 6: Transmitter empty + * + * @todo When CTS is asserted, this driver should stop sending data. + * + * @param[in] data Received data + * @param[in] data_len Received data length + * @param[in] user_arg Pointer to FT23x class + */ + static bool ftdi_rx(const uint8_t *data, size_t data_len, void *user_arg); + + // Just a wrapper to recover user's argument + static void ftdi_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx); + + /** + * @brief Construct a new calculate baudrate object + * + * A Baud rate for the FT232R, FT2232 (UART mode) or FT232B is generated using the chips + * internal 48MHz clock. This is input to Baud rate generator circuitry where it is then divided by 16 + * and fed into a prescaler as a 3MHz reference clock. This 3MHz reference clock is then divided + * down to provide the required Baud rate for the device's on chip UART. The value of the Baud rate + * divisor is an integer plus a sub-integer prescaler. + * Allowed values for the Baud rate divisor are: + * Divisor = n + 0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875; where n is an integer between 2 and + * 16384 (214). + * + * Note: Divisor = 1 and Divisor = 0 are special cases. A divisor of 0 will give 3 MBaud, and a divisor + * of 1 will give 2 MBaud. Sub-integer divisors between 0 and 2 are not allowed. + * Therefore the value of the divisor needed for a given Baud rate is found by dividing 3000000 by the + * required Baud rate. + * + * @see FTDI AN232B-05 Configuring FT232R, FT2232 and FT232B Baud Rates + * @param[in] baudrate + * @param[out] wValue + * @param[out] wIndex + */ + static int calculate_baudrate(uint32_t baudrate, uint16_t *wValue, uint16_t *wIndex); + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; + using CdcAcmDevice::line_coding_get; // Not implemented + using CdcAcmDevice::send_break; // Not implemented +}; +} // namespace esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb_host_ch34x_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/src/usb_host_ch34x_vcp.cpp new file mode 100644 index 00000000..f0d9b4f5 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb_host_ch34x_vcp.cpp @@ -0,0 +1,210 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021 WCH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb/vcp_ch34x.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS +#error This component requires C++ exceptions +#endif + +#define CH34X_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_DEVICE | USB_BM_REQUEST_TYPE_DIR_IN) +#define CH34X_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_DEVICE | USB_BM_REQUEST_TYPE_DIR_OUT) + +#define CH34X_CMD_READ_TYPE 0xC0 +#define CH34X_CMD_READ 0x95 +#define CH34X_CMD_WRITE 0x9A +#define CH34X_CMD_SERIAL_INIT 0xA1 +#define CH34X_CMD_MODEM_OUT 0xA4 +#define CH34X_CMD_VERSION 0x5F + +// For CMD 0xA4 +#define CH34X_UART_CTS 0x01 +#define CH34X_UART_DSR 0x02 +#define CH34X_UART_RING 0x04 +#define CH34X_UART_DCD 0x08 +#define CH34X_CONTROL_OUT 0x10 +#define CH34X_CONTROL_DTR 0x20 +#define CH34X_CONTROL_RTS 0x40 + +// Uart state +#define CH34X_UART_STATE 0x00 +#define CH34X_UART_OVERRUN_ERROR 0x01 +#define CH34X_UART_BREAK_ERROR // no define +#define CH34X_UART_PARITY_ERROR 0x02 +#define CH34X_UART_FRAME_ERROR 0x06 +#define CH34X_UART_RECV_ERROR 0x02 +#define CH34X_UART_STATE_TRANSIENT_MASK 0x07 + +//CH34x Baud Rate +#define CH34x_BAUDRATE_FACTOR 1532620800 +#define CH34x_BAUDRATE_DIVMAX 3 + +// Line Coding Register (LCR) +#define CH34x_REG_LCR 0x18 +#define CH34x_LCR_ENABLE_RX 0x80 +#define CH34x_LCR_ENABLE_TX 0x40 +#define CH34x_LCR_MARK_SPACE 0x20 +#define CH34x_LCR_PAR_EVEN 0x10 +#define CH34x_LCR_ENABLE_PAR 0x08 +#define CH34x_LCR_STOP_BITS_2 0x04 +#define CH34x_LCR_CS8 0x03 +#define CH34x_LCR_CS7 0x02 +#define CH34x_LCR_CS6 0x01 +#define CH34x_LCR_CS5 0x00 + +static const char *TAG = "CH34X"; + +namespace esp_usb { +CH34x::CH34x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) + : intf(interface_idx) +{ + const esp_err_t err = this->open_vendor_specific(vid, pid, this->intf, dev_config); + if (err != ESP_OK) { + throw (err); + } +}; + +esp_err_t CH34x::line_coding_set(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + // Baudrate + if (line_coding->dwDTERate != 0) { + uint8_t factor, divisor; + if (calculate_baud_divisor(line_coding->dwDTERate, &factor, &divisor) != 0) { + return ESP_ERR_INVALID_ARG; + } + uint16_t baud_reg_val = (factor << 8) | divisor; + baud_reg_val |= BIT7; + ESP_RETURN_ON_ERROR(this->send_custom_request(CH34X_WRITE_REQ, CH34X_CMD_WRITE, 0x1312, baud_reg_val, 0, NULL), TAG, "Set baudrate failed"); + } + + // Line coding + if (line_coding->bDataBits != 0) { + uint8_t lcr = CH34x_LCR_ENABLE_RX | CH34x_LCR_ENABLE_TX; + + switch (line_coding->bDataBits) { + case 5: + lcr |= CH34x_LCR_CS5; + break; + case 6: + lcr |= CH34x_LCR_CS6; + break; + case 7: + lcr |= CH34x_LCR_CS7; + break; + case 8: + lcr |= CH34x_LCR_CS8; + break; + default: + return ESP_ERR_INVALID_ARG; + } + + switch (line_coding->bParityType) { + case 0: + break; + case 1: + lcr |= CH34x_LCR_ENABLE_PAR; + break; + case 2: + lcr |= CH34x_LCR_ENABLE_PAR | CH34x_LCR_PAR_EVEN; + break; + case 3: // Mark + case 4: + lcr |= CH34x_LCR_ENABLE_PAR | CH34x_LCR_MARK_SPACE; + break; + default: + return ESP_ERR_INVALID_ARG; + } + + switch (line_coding->bCharFormat) { + case 0: + break; // 1 Stop bit + case 2: + lcr |= CH34x_LCR_STOP_BITS_2; + break; + default: + return ESP_ERR_INVALID_ARG; // 1.5 stop bits not supported + } + + ESP_RETURN_ON_ERROR(this->send_custom_request(CH34X_WRITE_REQ, CH34X_CMD_WRITE, 0x2518, lcr, 0, NULL), TAG, + "Set line coding failed"); + } + + return ESP_OK; +} + +esp_err_t CH34x::set_control_line_state(bool dtr, bool rts) +{ + uint16_t wValue = 0; + if (dtr) { + wValue |= CH34X_CONTROL_DTR; + } + if (rts) { + wValue |= CH34X_CONTROL_RTS; + } + return this->send_custom_request(CH34X_WRITE_REQ, CH34X_CMD_MODEM_OUT, wValue, this->intf, 0, NULL); +} + +int CH34x::calculate_baud_divisor(unsigned int baud_rate, unsigned char *factor, unsigned char *divisor) +{ + unsigned char a; + unsigned char b; + unsigned long c; + + assert(factor); + assert(divisor); + + switch (baud_rate) { + case 921600: + a = 0xf3; + b = 7; + break; + case 307200: + a = 0xd9; + b = 7; + break; + default: + if (baud_rate > 6000000 / 255) { + b = 3; + c = 6000000; + } else if (baud_rate > 750000 / 255) { + b = 2; + c = 750000; + } else if (baud_rate > 93750 / 255) { + b = 1; + c = 93750; + } else { + b = 0; + c = 11719; + } + + a = (unsigned char)(c / baud_rate); + if (a == 0 || a == 0xFF) { + return -1; // Can't set required baud rate + } + // Deal with integer division + const int delta_0 = c / a - baud_rate; + const int delta_1 = baud_rate - c / (a + 1); + if (delta_0 > delta_1) { + a++; + } + a = 256 - a; + break; + } + + *factor = a; + *divisor = b; + return 0; +} +} diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb_host_cp210x_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/src/usb_host_cp210x_vcp.cpp new file mode 100644 index 00000000..4e466f2f --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb_host_cp210x_vcp.cpp @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb/vcp_cp210x.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS +#error This component requires C++ exceptions +#endif + +#define CP210X_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_INTERFACE | USB_BM_REQUEST_TYPE_DIR_IN) +#define CP210X_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_RECIP_INTERFACE | USB_BM_REQUEST_TYPE_DIR_OUT) + +namespace esp_usb { +CP210x::CP210x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) + : intf(interface_idx) +{ + esp_err_t err; + err = this->open_vendor_specific(vid, pid, this->intf, dev_config); + if (err != ESP_OK) { + throw (err); + } + + // CP210X interfaces must be explicitly enabled + err = this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_IFC_ENABLE, 1, this->intf, 0, NULL); + if (err != ESP_OK) { + throw (err); + } +}; + +esp_err_t CP210x::line_coding_get(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_READ_REQ, CP210X_CMD_GET_BAUDRATE, 0, this->intf, sizeof(line_coding->dwDTERate), (uint8_t *)&line_coding->dwDTERate), "CP210X",); + + uint8_t temp_data[2]; + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_READ_REQ, CP210X_CMD_GET_LINE_CTL, 0, this->intf, 2, temp_data), "CP210X",); + line_coding->bCharFormat = temp_data[0] & 0x0F; + line_coding->bParityType = (temp_data[0] & 0xF0) >> 4; + line_coding->bDataBits = temp_data[1]; + + return ESP_OK; +} + +esp_err_t CP210x::line_coding_set(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + if (line_coding->dwDTERate != 0) { + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_BAUDRATE, 0, this->intf, sizeof(line_coding->dwDTERate), (uint8_t *)&line_coding->dwDTERate), "CP210X",); + } + + if (line_coding->bDataBits != 0) { + const uint16_t wValue = line_coding->bCharFormat | (line_coding->bParityType << 4) | (line_coding->bDataBits << 8); + return this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_LINE_CTL, wValue, this->intf, 0, NULL); + } + return ESP_OK; +} + +esp_err_t CP210x::set_control_line_state(bool dtr, bool rts) +{ + const uint16_t wValue = (uint16_t)dtr | ((uint16_t)rts << 1) | 0x0300; + return this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_MHS, wValue, this->intf, 0, NULL); +} + +esp_err_t CP210x::send_break(uint16_t duration_ms) +{ + ESP_RETURN_ON_ERROR(this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_BREAK, 1, this->intf, 0, NULL), "CP210x",); + vTaskDelay(pdMS_TO_TICKS(duration_ms)); + return this->send_custom_request(CP210X_WRITE_REQ, CP210X_CMD_SET_BREAK, 0, this->intf, 0, NULL); +} +} diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb_host_ftdi_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/src/usb_host_ftdi_vcp.cpp new file mode 100644 index 00000000..37fbe7e2 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb_host_ftdi_vcp.cpp @@ -0,0 +1,176 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "usb/vcp_ftdi.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "sdkconfig.h" + +#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS +#error This component requires C++ exceptions +#endif + +#define FTDI_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_DIR_IN) +#define FTDI_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_DIR_OUT) + +namespace esp_usb { +FT23x::FT23x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) + : intf(interface_idx), user_data_cb(dev_config->data_cb), user_event_cb(dev_config->event_cb), + user_arg(dev_config->user_arg), uart_state(0) +{ + cdc_acm_host_device_config_t ftdi_config; + memcpy(&ftdi_config, dev_config, sizeof(cdc_acm_host_device_config_t)); + // FT23x reports modem status in first two bytes of RX data + // so here we override the RX handler with our own + + if (dev_config->data_cb) { + ftdi_config.data_cb = ftdi_rx; + ftdi_config.user_arg = this; + } + + if (dev_config->event_cb) { + ftdi_config.event_cb = ftdi_event; + ftdi_config.user_arg = this; + } + + esp_err_t err; + err = this->open_vendor_specific(vid, pid, this->intf, &ftdi_config); + if (err != ESP_OK) { + throw (err); + } + + // FT23x interface must be first reset and configured (115200 8N1) + err = this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_RESET, 0, this->intf + 1, 0, NULL); + if (err != ESP_OK) { + throw (err); + } + + cdc_acm_line_coding_t line_coding = { + .dwDTERate = 115200, + .bCharFormat = 0, + .bParityType = 0, + .bDataBits = 8, + }; + err = this->line_coding_set(&line_coding); + if (err != ESP_OK) { + throw (err); + } +}; + +esp_err_t FT23x::line_coding_set(cdc_acm_line_coding_t *line_coding) +{ + assert(line_coding); + + if (line_coding->dwDTERate != 0) { + uint16_t wIndex, wValue; + calculate_baudrate(line_coding->dwDTERate, &wValue, &wIndex); + ESP_RETURN_ON_ERROR(this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_BAUDRATE, wValue, wIndex, 0, NULL), "FT23x",); + } + + if (line_coding->bDataBits != 0) { + const uint16_t wValue = (line_coding->bDataBits) | (line_coding->bParityType << 8) | (line_coding->bCharFormat << 11); + return this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_LINE_CTL, wValue, this->intf, 0, NULL); + } + return ESP_OK; +} + +esp_err_t FT23x::set_control_line_state(bool dtr, bool rts) +{ + ESP_RETURN_ON_ERROR(this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_MHS, dtr ? 0x11 : 0x10, this->intf, 0, NULL), "FT23x",); // DTR + return this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_MHS, rts ? 0x21 : 0x20, this->intf, 0, NULL); // RTS +} + +bool FT23x::ftdi_rx(const uint8_t *data, size_t data_len, void *user_arg) +{ + FT23x *this_ftdi = (FT23x *)user_arg; + + // Dispatch serial state if it has changed + if (this_ftdi->user_event_cb) { + cdc_acm_uart_state_t new_state; + new_state.val = 0; + new_state.bRxCarrier = data[0] & 0x80; // DCD + new_state.bTxCarrier = data[0] & 0x20; // DSR + new_state.bBreak = data[1] & 0x10; + new_state.bRingSignal = data[0] & 0x40; + new_state.bFraming = data[1] & 0x08; + new_state.bParity = data[1] & 0x04; + new_state.bOverRun = data[1] & 0x02; + + if (this_ftdi->uart_state != new_state.val) { + cdc_acm_host_dev_event_data_t serial_event; + serial_event.type = CDC_ACM_HOST_SERIAL_STATE; + serial_event.data.serial_state = new_state; + this_ftdi->user_event_cb(&serial_event, this_ftdi->user_arg); + this_ftdi->uart_state = new_state.val; + } + } + + // Dispatch data if any + if (data_len > 2) { + return this_ftdi->user_data_cb(&data[2], data_len - 2, this_ftdi->user_arg); + } + return true; +} + +void FT23x::ftdi_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +{ + FT23x *this_ftdi = (FT23x *)user_ctx; + this_ftdi->user_event_cb(event, this_ftdi->user_arg); +} + +int FT23x::calculate_baudrate(uint32_t baudrate, uint16_t *wValue, uint16_t *wIndex) +{ +#define FTDI_BASE_CLK (3000000) + + int baudrate_real; + if (baudrate > 2000000) { + // set to 3000000 + *wValue = 0; + *wIndex = 0; + baudrate_real = 3000000; + } else if (baudrate >= 1000000) { + // set to 1000000 + *wValue = 1; + *wIndex = 0; + baudrate_real = 1000000; + } else { + const float ftdi_fractal[] = {0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1}; + const uint8_t ftdi_fractal_bits[] = {0, 0x03, 0x02, 0x04, 0x01, 0x05, 0x06, 0x07}; + uint16_t divider_n = FTDI_BASE_CLK / baudrate; // integer value + int ftdi_fractal_idx = 0; + float divider = FTDI_BASE_CLK / (float)baudrate; // float value + float divider_fractal = divider - (float)divider_n; + + // Find closest bigger FT23x fractal divider + for (ftdi_fractal_idx = 0; ftdi_fractal[ftdi_fractal_idx] <= divider_fractal; ftdi_fractal_idx++) {}; + + // Calculate baudrate errors for two closest fractal divisors + int diff1 = baudrate - (int)(FTDI_BASE_CLK / (divider_n + ftdi_fractal[ftdi_fractal_idx])); // Greater than required baudrate + int diff2 = (int)(FTDI_BASE_CLK / (divider_n + ftdi_fractal[ftdi_fractal_idx - 1])) - baudrate; // Lesser than required baudrate + + // Chose divider and fractal divider with smallest error + if (diff2 < diff1) { + ftdi_fractal_idx--; + } else { + if (ftdi_fractal_idx == 8) { + ftdi_fractal_idx = 0; + divider_n++; + } + } + + baudrate_real = FTDI_BASE_CLK / (float)((float)divider_n + ftdi_fractal[ftdi_fractal_idx]); + *wValue = ((0x3FFFF) & divider_n) | (ftdi_fractal_bits[ftdi_fractal_idx] << 14); + *wIndex = ftdi_fractal_bits[ftdi_fractal_idx] >> 2; + } + ESP_LOGD("FT23x", "wValue: 0x%04X wIndex: 0x%04X", *wValue, *wIndex); + ESP_LOGI("FT23x", "Baudrate required: %" PRIu32", set: %d", baudrate, baudrate_real); + + return baudrate_real; +} +} // esp_usb diff --git a/libraries/esp32-usb-serial-1.0.1/src/usb_host_vcp.cpp b/libraries/esp32-usb-serial-1.0.1/src/usb_host_vcp.cpp new file mode 100644 index 00000000..0aa04da9 --- /dev/null +++ b/libraries/esp32-usb-serial-1.0.1/src/usb_host_vcp.cpp @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "usb/vcp.hpp" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "VCP service"; + +namespace esp_usb { +std::vector VCP::drivers; +// To +uint16_t current_vid = 0; +uint16_t current_pid = 0; + +CdcAcmDevice *VCP::open(uint16_t _vid, uint16_t _pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) +{ + // In case user didn't install CDC-ACM driver, we try to install it here. + const esp_err_t err = cdc_acm_host_install(NULL); + switch (err) { + case ESP_OK: ESP_LOGD(TAG, "CDC-ACM driver installed"); break; + case ESP_ERR_INVALID_STATE: ESP_LOGD(TAG, "CDC-ACM driver already installed"); break; + default: ESP_LOGE(TAG, "Failed to install CDC-ACM driver"); return nullptr; + } + + for (vcp_driver drv : drivers) { + if (drv.vid == _vid) { + for (uint16_t p : drv.pids) { + if (p == _pid) { + try { + return drv.open(_pid, dev_config, interface_idx); + } catch (esp_err_t &e) { + switch (e) { + case ESP_ERR_NO_MEM: throw std::bad_alloc(); + case ESP_ERR_NOT_FOUND: // fallthrough + default: return nullptr; + } + } + } + } + } + } + return nullptr; +} + +CdcAcmDevice *VCP::open(const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) +{ + // Setup this function timeout + TickType_t timeout_ticks = (dev_config->connection_timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(dev_config->connection_timeout_ms); + TimeOut_t connection_timeout; + vTaskSetTimeOutState(&connection_timeout); + + // In case user didn't install CDC-ACM driver, we try to install it here. + esp_err_t err = cdc_acm_host_install(NULL); + switch (err) { + case ESP_OK: ESP_LOGD(TAG, "CDC-ACM driver installed"); break; + case ESP_ERR_INVALID_STATE: ESP_LOGD(TAG, "CDC-ACM driver already installed"); break; + default: ESP_LOGE(TAG, "Failed to install CDC-ACM driver"); return nullptr; + } + + // dev_config->connection_timeout_ms is normally meant for 1 device, + // but here it is a timeout for the whole function call + cdc_acm_host_device_config_t _config = *dev_config; + _config.connection_timeout_ms = 1; + + // Try opening all registered devices, return on first success + do { + for (vcp_driver drv : drivers) { + for (uint16_t pid : drv.pids) { + try { + CdcAcmDevice * ptr = drv.open(pid, &_config, interface_idx); + if (ptr){ + current_vid = drv.vid; + current_pid = pid; + } else { + current_vid = 0; + current_pid = 0; + } + return ptr; + } catch (esp_err_t &e) { + current_vid = 0; + current_pid = 0; + switch (e) { + case ESP_ERR_NOT_FOUND: break; + case ESP_ERR_NO_MEM: throw std::bad_alloc(); + default: return nullptr; + } + } + } + } + vTaskDelay(pdMS_TO_TICKS(50)); + } while (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) == pdFALSE); + return nullptr; +} +} // namespace esp_usb