Merge branch 'feature/usb_host/cdc_custom_commands' into 'master'

usb_host: Add Virtual COM Port example

Closes IDF-4816, IDFGH-7027, and IDFGH-7159

See merge request espressif/esp-idf!17560
This commit is contained in:
Tomas Rezucha 2022-04-24 20:41:51 +08:00
commit 9663a282b3
13 changed files with 896 additions and 56 deletions

View File

@ -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)

View File

@ -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.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "cdc_acm_vcp.cpp" "cp210x_usb.cpp" "ftdi_usb.cpp"
INCLUDE_DIRS ".")

View File

@ -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

View File

@ -0,0 +1,136 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -0,0 +1,175 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <string.h>
#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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -7,6 +7,7 @@
#pragma once
#include <stdbool.h>
#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);

View File

@ -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