diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/CMakeLists.txt b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/CMakeLists.txt new file mode 100644 index 0000000000..93aadf0f61 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/CMakeLists.txt @@ -0,0 +1,9 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(cdc_acm_vcp) diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/README.md b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/README.md new file mode 100644 index 0000000000..6c459fb6a0 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/README.md @@ -0,0 +1,41 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# USB CDC-ACM Virtual Com Port example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example shows how to extend CDC-ACM driver for Virtual Communication Port devices, +such as CP210x or FTDI FT23x devices. + +## How to use example + +1. Pick your USB-to-UART device by executing `idf.py menuconfig` and navigating to `Example Configuration -> USB-to-UART device type` +2. Change baudrate and other line coding parameters in `cdc_acm_vcp.cpp` to match your needs +3. Now you can use the CDC-ACM to API to control the device and send data. Data are received in `handle_rx` callback +4. Try disconnecting and then reconnecting of the USB device to experiment with USB hotplugging + +### Hardware Required + +* ESP board with USB-OTG supported +* Silicon Labs CP210x or FTDI FT23x USB to UART converter + +Connect USB_D+, USB_D-, GND and +5V signals of your ESP chip to matching signals on USB to UART converter. + +#### Pin Assignment + +See common pin assignments for USB Device examples from [upper level](../../../README.md#common-pin-assignments). + +### Build and Flash + +Build this project and flash it to the USB host board, then run monitor tool to view serial output: + +```bash +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt new file mode 100644 index 0000000000..baa6d445e4 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "cdc_acm_vcp.cpp" "cp210x_usb.cpp" "ftdi_usb.cpp" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/Kconfig.projbuild b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/Kconfig.projbuild new file mode 100644 index 0000000000..b7e335e3af --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "Example Configuration" + + choice + prompt "USB-to-UART device type" + default EXAMPLE_USE_CP210X + help + Type of UART converter to use in this example. + + config EXAMPLE_USE_FTDI + bool "FT232" + config EXAMPLE_USE_CP210X + bool "CP2012" + endchoice + +endmenu diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cdc_acm_vcp.cpp b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cdc_acm_vcp.cpp new file mode 100644 index 0000000000..e61b89d96f --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cdc_acm_vcp.cpp @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#include "sdkconfig.h" +#include "cp210x_usb.hpp" +#include "ftdi_usb.hpp" +#include "usb/usb_host.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +using namespace esp_usb; + +// Change these values to match your needs +#define EXAMPLE_BAUDRATE (115200) +#define EXAMPLE_STOP_BITS (0) // 0: 1 stopbit, 1: 1.5 stopbits, 2: 2 stopbits +#define EXAMPLE_PARITY (0) // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space +#define EXAMPLE_DATA_BITS (8) + +static const char *TAG = "VCP example"; + +static SemaphoreHandle_t device_disconnected_sem; + +static void handle_rx(uint8_t *data, size_t data_len, void *arg) +{ + printf("%.*s", data_len, data); +} + +static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +{ + switch (event->type) { + case CDC_ACM_HOST_ERROR: + ESP_LOGE(TAG, "CDC-ACM error has occurred, err_no = %d", event->data.error); + break; + case CDC_ACM_HOST_DEVICE_DISCONNECTED: + ESP_LOGI(TAG, "Device suddenly disconnected"); + xSemaphoreGive(device_disconnected_sem); + break; + case CDC_ACM_HOST_SERIAL_STATE: + ESP_LOGI(TAG, "serial state notif 0x%04X", event->data.serial_state.val); + break; + case CDC_ACM_HOST_NETWORK_CONNECTION: + default: break; + } +} + +void usb_lib_task(void *arg) +{ + while (1) { + // 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) { + ESP_ERROR_CHECK(usb_host_device_free_all()); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGI(TAG, "USB: All devices freed"); + // Continue handling USB events to allow device reconnection + } + } +} + +extern "C" void app_main(void) +{ + device_disconnected_sem = xSemaphoreCreateBinary(); + assert(device_disconnected_sem); + + //Install USB Host driver. Should only be called once in entire application + ESP_LOGI(TAG, "Installing USB Host"); + const usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + + // Create a task that will handle USB library events + xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, NULL); + + ESP_LOGI(TAG, "Installing CDC-ACM driver"); + ESP_ERROR_CHECK(cdc_acm_host_install(NULL)); + + while (true) { + const cdc_acm_host_device_config_t dev_config = { + .connection_timeout_ms = 10000, + .out_buffer_size = 64, + .event_cb = handle_event, + .data_cb = handle_rx, + .user_arg = NULL, + }; + +#if defined(CONFIG_EXAMPLE_USE_FTDI) + FT23x *vcp; + try { + ESP_LOGI(TAG, "Opening FT232 UART device"); + vcp = FT23x::open_ftdi(FTDI_FT232_PID, &dev_config); + } +#else + CP210x *vcp; + try { + ESP_LOGI(TAG, "Opening CP210X device"); + vcp = CP210x::open_cp210x(CP210X_PID, &dev_config); + } +#endif + catch (esp_err_t err) { + ESP_LOGE(TAG, "The required device was not opened.\nExiting..."); + return; + } + + ESP_LOGI(TAG, "Setting up line coding"); + cdc_acm_line_coding_t line_coding = { + .dwDTERate = EXAMPLE_BAUDRATE, + .bCharFormat = EXAMPLE_STOP_BITS, + .bParityType = EXAMPLE_PARITY, + .bDataBits = EXAMPLE_DATA_BITS, + }; + ESP_ERROR_CHECK(vcp->line_coding_set(&line_coding)); + + /* + Now the USB-to-UART converter is configured and receiving data. + You can use standard CDC-ACM API to interact with it. E.g. + + ESP_ERROR_CHECK(vcp->set_control_line_state(false, true)); + ESP_ERROR_CHECK(vcp->tx_blocking((uint8_t *)"Test string", 12)); + */ + + // We are done. Wait for device disconnection and start over + xSemaphoreTake(device_disconnected_sem, portMAX_DELAY); + delete vcp; + } +} diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cp210x_usb.cpp b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cp210x_usb.cpp new file mode 100644 index 0000000000..080a76c474 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cp210x_usb.cpp @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "cp210x_usb.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define SILICON_LABS_VID (0x10C4) +#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::open_cp210x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) +{ + return new CP210x(pid, dev_config, interface_idx); +} + +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(SILICON_LABS_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/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cp210x_usb.hpp b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cp210x_usb.hpp new file mode 100644 index 0000000000..a8395077d5 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/cp210x_usb.hpp @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#pragma once + +#include "usb/cdc_acm_host.h" + +#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 Factory method 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 CP210x Pointer to created and opened CP210x device + */ + static CP210x *open_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); + +private: + const uint8_t intf; + + // Constructors are private, use factory method to create this object + CP210x(); + CP210x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; +}; +} // namespace esp_usb diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/ftdi_usb.cpp b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/ftdi_usb.cpp new file mode 100644 index 0000000000..1df5050010 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/ftdi_usb.cpp @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include "ftdi_usb.hpp" +#include "usb/usb_types_ch9.h" +#include "esp_log.h" +#include "esp_check.h" + +#define FTDI_VID (0x0403) +#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::open_ftdi(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx) +{ + return new FT23x(pid, dev_config, interface_idx); +} + +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(FTDI_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 +} + +void FT23x::ftdi_rx(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) { + this_ftdi->user_data_cb(&data[2], data_len - 2, this_ftdi->user_arg); + } +} + +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: %d, set: %d", baudrate, baudrate_real); + + return baudrate_real; +} +} // esp_usb diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/ftdi_usb.hpp b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/ftdi_usb.hpp new file mode 100644 index 0000000000..b8927ac364 --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/main/ftdi_usb.hpp @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#pragma once + +#include "usb/cdc_acm_host.h" + +#define FTDI_FT232_PID (0x6001) +#define FTDI_FT231_PID (0x6015) + +#define FTDI_CMD_RESET (0x00) +#define FTDI_CMD_SET_FLOW (0x01) +#define FTDI_CMD_SET_MHS (0x02) // Modem hanshaking +#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 Factory method 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 FT23x Pointer to created and opened FTDI device + */ + static FT23x *open_ftdi(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); + +private: + /** + * @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 void ftdi_rx(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); + + // Constructors are private, use factory method open_ftdi() to create this object + FT23x(); + FT23x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx = 0); + + // Make open functions from CdcAcmDevice class private + using CdcAcmDevice::open; + using CdcAcmDevice::open_vendor_specific; + + 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; +}; +} // namespace esp_usb diff --git a/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults new file mode 100644 index 0000000000..990777805d --- /dev/null +++ b/examples/peripherals/usb/host/cdc/cdc_acm_vcp/sdkconfig.defaults @@ -0,0 +1,4 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c index 5a613913b7..4d8cfd3f9e 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/cdc_acm_host.c @@ -53,6 +53,7 @@ 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; @@ -66,7 +67,8 @@ static cdc_acm_obj_t *p_cdc_acm_obj = NULL; static const cdc_acm_host_driver_config_t cdc_acm_driver_config_default = { .driver_task_stack_size = 4096, .driver_task_priority = 10, - .xCoreID = 0 + .xCoreID = 0, + .new_dev_cb = NULL, }; /** @@ -429,6 +431,7 @@ esp_err_t cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config 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 @@ -548,7 +551,7 @@ static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_des { esp_err_t ret; - // 1. Setup notification and control transfers if they are supported + // 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), @@ -558,24 +561,25 @@ static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_des 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); - - 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,); } - // 2. Setup IN data transfer + // 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 ESP_GOTO_ON_ERROR( usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(in_ep_desc), 0, &cdc_dev->data.in_xfer), err, TAG, @@ -587,7 +591,7 @@ static esp_err_t cdc_acm_transfers_allocate(cdc_dev_t *cdc_dev, const usb_ep_des cdc_dev->data.in_xfer->device_handle = cdc_dev->dev_hdl; cdc_dev->data.in_xfer->context = cdc_dev; - // 3. Setup OUT bulk transfer (if it is required (out_buf_len > 0)) + // 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), @@ -771,8 +775,10 @@ esp_err_t cdc_acm_host_open_vendor_specific(uint16_t vid, uint16_t pid, uint8_t 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 interfece no %d was not found.", interface_num); const int temp_offset = desc_offset; // Save this offset for later - assert(cdc_dev->data.intf_desc); // The interface can have 2-3 endpoints. 2 for data and 1 optional for notifications const usb_ep_desc_t *in_ep = NULL; @@ -940,7 +946,7 @@ static bool cdc_acm_is_transfer_completed(usb_transfer_t *transfer) .type = CDC_ACM_HOST_ERROR, .data.error = (int) transfer->status }; - cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &error_event, cdc_dev->cb_arg); + cdc_dev->notif.cb(&error_event, cdc_dev->cb_arg); } } return completed; @@ -975,7 +981,7 @@ static void notif_xfer_cb(usb_transfer_t *transfer) .type = CDC_ACM_HOST_NETWORK_CONNECTION, .data.network_connected = (bool) notif->wValue }; - cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &net_conn_event, cdc_dev->cb_arg); + cdc_dev->notif.cb(&net_conn_event, cdc_dev->cb_arg); } break; } @@ -986,7 +992,7 @@ static void notif_xfer_cb(usb_transfer_t *transfer) .type = CDC_ACM_HOST_SERIAL_STATE, .data.serial_state = cdc_dev->serial_state }; - cdc_dev->notif.cb((cdc_acm_dev_hdl_t) cdc_dev, &serial_state_event, cdc_dev->cb_arg); + cdc_dev->notif.cb(&serial_state_event, cdc_dev->cb_arg); } break; } @@ -1015,6 +1021,16 @@ 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: ESP_LOGD(TAG, "New device connected"); + if (p_cdc_acm_obj->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) { + ESP_LOGW(TAG, "Couldn't open the new device"); + break; + } + assert(new_dev); + p_cdc_acm_obj->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"); @@ -1027,8 +1043,9 @@ static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg // 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((cdc_acm_dev_hdl_t) cdc_dev, &disconn_event, cdc_dev->cb_arg); + cdc_dev->notif.cb(&disconn_event, cdc_dev->cb_arg); } } break; @@ -1080,7 +1097,7 @@ unblock: 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(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG); + 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), @@ -1092,7 +1109,7 @@ esp_err_t cdc_acm_host_line_coding_get(cdc_acm_dev_hdl_t cdc_hdl, cdc_acm_line_c 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(cdc_hdl && line_coding, ESP_ERR_INVALID_ARG); + 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), @@ -1104,8 +1121,6 @@ esp_err_t cdc_acm_host_line_coding_set(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_ esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dtr, bool rts) { - CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); - const uint16_t ctrl_bitmap = (uint16_t)dtr | ((uint16_t)rts << 1); ESP_RETURN_ON_ERROR( @@ -1117,8 +1132,6 @@ esp_err_t cdc_acm_host_set_control_line_state(cdc_acm_dev_hdl_t cdc_hdl, bool dt esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_ms) { - CDC_ACM_CHECK(cdc_hdl, ESP_ERR_INVALID_ARG); - ESP_RETURN_ON_ERROR( send_cdc_request((cdc_dev_t *)cdc_hdl, false, USB_CDC_REQ_SEND_BREAK, NULL, 0, duration_ms), TAG,); @@ -1128,37 +1141,42 @@ esp_err_t cdc_acm_host_send_break(cdc_acm_dev_hdl_t cdc_hdl, uint16_t duration_m return ESP_OK; } -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) +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; - CDC_ACM_CHECK(cdc_dev->ctrl_transfer, ESP_ERR_NOT_SUPPORTED); - CDC_ACM_CHECK(cdc_dev->ctrl_transfer->data_buffer_size >= data_len, ESP_ERR_INVALID_SIZE); // Take Mutex and fill the CTRL request - BaseType_t taken = xSemaphoreTake(cdc_dev->ctrl_mux, pdMS_TO_TICKS(1000)); + 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 = USB_BM_REQUEST_TYPE_DIR_OUT | USB_BM_REQUEST_TYPE_TYPE_CLASS | USB_BM_REQUEST_TYPE_RECIP_INTERFACE; - req->bRequest = request; - req->wValue = value; - req->wIndex = cdc_dev->notif.intf_desc->bInterfaceNumber; - req->wLength = data_len; + req->bmRequestType = bmRequestType; + req->bRequest = bRequest; + req->wValue = wValue; + req->wIndex = wIndex; + req->wLength = wLength; - if (in_transfer) { - req->bmRequestType |= USB_BM_REQUEST_TYPE_DIR_IN; - } else { - memcpy(start_of_data, data, data_len); + // 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 = data_len + sizeof(usb_setup_packet_t); + 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(1000)); // This is a fixed timeout. Every CDC device should be able to respond to CTRL transfer in 1 second + 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); @@ -1169,8 +1187,9 @@ static esp_err_t send_cdc_request(cdc_dev_t *cdc_dev, bool in_transfer, cdc_requ 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, data_len); + memcpy(data, start_of_data, wLength); } ret = ESP_OK; @@ -1179,6 +1198,20 @@ unblock: 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); diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h index 3724c919c6..952ecb85cb 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/include/usb/cdc_acm_host.h @@ -7,6 +7,7 @@ #pragma once #include +#include "usb/usb_host.h" #include "usb_types_cdc.h" #include "esp_err.h" @@ -63,12 +64,23 @@ typedef enum { 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 + 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 */ @@ -76,9 +88,9 @@ typedef void (*cdc_acm_data_callback_t)(uint8_t* data, size_t data_len, void *us /** * @brief Device event callback type - * @see cdc_acm_host_dev_event_t + * @see cdc_acm_host_dev_event_data_t */ -typedef void (*cdc_acm_host_dev_callback_t)(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx); +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 @@ -88,6 +100,7 @@ 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; /** @@ -238,6 +251,24 @@ void cdc_acm_host_desc_print(cdc_acm_dev_hdl_t cdc_hdl); */ 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 @@ -268,10 +299,13 @@ public: return cdc_acm_host_open_vendor_specific(vid, pid, interface_idx, dev_config, &this->cdc_hdl); } - inline void close() + inline esp_err_t close() { - cdc_acm_host_close(this->cdc_hdl); - this->cdc_hdl = NULL; + esp_err_t err = cdc_acm_host_close(this->cdc_hdl); + if (err == ESP_OK) { + this->cdc_hdl = NULL; + } + return err; } inline esp_err_t line_coding_get(cdc_acm_line_coding_t *line_coding) @@ -294,6 +328,11 @@ public: 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(const CdcAcmDevice &Copy); CdcAcmDevice &operator= (const CdcAcmDevice &Copy); diff --git a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c index a340ac8158..1ba2bcf231 100644 --- a/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c +++ b/examples/peripherals/usb/host/cdc/common/cdc_acm_host/test/test_cdc_acm_host.c @@ -110,7 +110,7 @@ static void handle_rx2(uint8_t *data, size_t data_len, void *arg) TEST_ASSERT_EQUAL_STRING_LEN(data, arg, data_len); } -static void notif_cb(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_data_t *event, void *user_ctx) +static void notif_cb(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) { switch (event->type) { case CDC_ACM_HOST_ERROR: @@ -122,7 +122,7 @@ static void notif_cb(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_dat break; case CDC_ACM_HOST_DEVICE_DISCONNECTED: printf("Disconnection event\n"); - TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(cdc_hdl)); + TEST_ASSERT_EQUAL(ESP_OK, cdc_acm_host_close(event->data.cdc_hdl)); xTaskNotifyGive(user_ctx); break; default: @@ -130,6 +130,19 @@ static void notif_cb(cdc_acm_dev_hdl_t cdc_hdl, const cdc_acm_host_dev_event_dat } } +static bool new_dev_cb_called = 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 */ @@ -373,11 +386,62 @@ TEST_CASE("error_handling", "[cdc_acm]") 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", "[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); + + printf("Installing CDC-ACM driver\n"); + const 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(80); + TEST_ASSERT_TRUE_MESSAGE(new_dev_cb_called, "New device callback was not called\n"); + + // Clean-up + 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