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

USB Host: Expand VCP example

Closes IDFGH-7370 and IDFGH-8156

See merge request espressif/esp-idf!18940
This commit is contained in:
Tomas Rezucha
2022-12-08 15:14:51 +08:00
9 changed files with 76 additions and 552 deletions

View File

@@ -5,20 +5,22 @@
(See the README.md file in the upper level 'examples' directory for more information about examples.) (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, This example shows how to extend CDC-ACM driver for Virtual Communication Port (VCP) devices,
such as CP210x or FTDI FT23x devices. such as CP210x, FTDI FT23x or CP34x devices.
The drivers are fetched from [IDF Component Registry](https://components.espressif.com/) together with VCP service that automatically loads correct driver for plugged-in device.
## How to use example ## 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` 1. Connect your USB<->UART converter to ESP32-S2/S3, the device will be automatically enumerated and correct driver will be picked
2. Change baudrate and other line coding parameters in `cdc_acm_vcp.cpp` to match your needs 2. Change baudrate and other line coding parameters in [cdc_acm_vcp_example_main.cpp](main/cdc_acm_vcp_example_main.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 3. Now you can use the usual CDC-ACM 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 4. Try disconnecting and then reconnecting of the USB device to experiment with USB hotplugging
### Hardware Required ### Hardware Required
* ESP board with USB-OTG supported * ESP board with USB-OTG supported
* Silicon Labs CP210x or FTDI FT23x USB to UART converter * Silicon Labs CP210x, FTDI FT23x or CP34x 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. Connect USB_D+, USB_D-, GND and +5V signals of your ESP chip to matching signals on USB to UART converter.

View File

@@ -1,3 +1,4 @@
idf_component_register(SRCS "cdc_acm_vcp.cpp" "cp210x_usb.cpp" "ftdi_usb.cpp" idf_component_register(
INCLUDE_DIRS ".") SRCS "cdc_acm_vcp_example_main.cpp"
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") INCLUDE_DIRS "."
)

View File

@@ -1,15 +0,0 @@
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

@@ -6,15 +6,19 @@
#include <stdio.h> #include <stdio.h>
#include <string.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 "esp_log.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "usb/cdc_acm_host.h"
#include "usb/vcp_ch34x.hpp"
#include "usb/vcp_cp210x.hpp"
#include "usb/vcp_ftdi.hpp"
#include "usb/vcp.hpp"
#include "usb/usb_host.h"
using namespace esp_usb; using namespace esp_usb;
// Change these values to match your needs // Change these values to match your needs
@@ -23,16 +27,26 @@ using namespace esp_usb;
#define EXAMPLE_PARITY (0) // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space #define EXAMPLE_PARITY (0) // 0: None, 1: Odd, 2: Even, 3: Mark, 4: Space
#define EXAMPLE_DATA_BITS (8) #define EXAMPLE_DATA_BITS (8)
static const char *TAG = "VCP example"; namespace {
const char *TAG = "VCP example";
SemaphoreHandle_t device_disconnected_sem;
static SemaphoreHandle_t device_disconnected_sem; /**
* @brief Data received callback
static void handle_rx(uint8_t *data, size_t data_len, void *arg) *
* Just pass received data to stdout
*/
void handle_rx(uint8_t *data, size_t data_len, void *arg)
{ {
printf("%.*s", data_len, data); printf("%.*s", data_len, data);
} }
static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) /**
* @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) { switch (event->type) {
case CDC_ACM_HOST_ERROR: case CDC_ACM_HOST_ERROR:
@@ -43,13 +57,18 @@ static void handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_
xSemaphoreGive(device_disconnected_sem); xSemaphoreGive(device_disconnected_sem);
break; break;
case CDC_ACM_HOST_SERIAL_STATE: case CDC_ACM_HOST_SERIAL_STATE:
ESP_LOGI(TAG, "serial state notif 0x%04X", event->data.serial_state.val); ESP_LOGI(TAG, "Serial state notif 0x%04X", event->data.serial_state.val);
break; break;
case CDC_ACM_HOST_NETWORK_CONNECTION: case CDC_ACM_HOST_NETWORK_CONNECTION:
default: break; default: break;
} }
} }
/**
* @brief USB Host library handling task
*
* @param arg Unused
*/
void usb_lib_task(void *arg) void usb_lib_task(void *arg)
{ {
while (1) { while (1) {
@@ -65,7 +84,13 @@ void usb_lib_task(void *arg)
} }
} }
} }
}
/**
* @brief Main application
*
* This function shows how you can use VCP drivers
*/
extern "C" void app_main(void) extern "C" void app_main(void)
{ {
device_disconnected_sem = xSemaphoreCreateBinary(); device_disconnected_sem = xSemaphoreCreateBinary();
@@ -80,37 +105,36 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(usb_host_install(&host_config)); ESP_ERROR_CHECK(usb_host_install(&host_config));
// Create a task that will handle USB library events // Create a task that will handle USB library events
xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, NULL); BaseType_t task_created = xTaskCreate(usb_lib_task, "usb_lib", 4096, NULL, 10, NULL);
assert(task_created == pdTRUE);
ESP_LOGI(TAG, "Installing CDC-ACM driver"); ESP_LOGI(TAG, "Installing CDC-ACM driver");
ESP_ERROR_CHECK(cdc_acm_host_install(NULL)); ESP_ERROR_CHECK(cdc_acm_host_install(NULL));
// Register VCP drivers to VCP service.
VCP::register_driver<FT23x>();
VCP::register_driver<CP210x>();
VCP::register_driver<CH34x>();
// Do everything else in a loop, so we can demonstrate USB device reconnections
while (true) { while (true) {
const cdc_acm_host_device_config_t dev_config = { const cdc_acm_host_device_config_t dev_config = {
.connection_timeout_ms = 10000, .connection_timeout_ms = 5000, // 5 seconds, enough time to plug the device in or experiment with timeout
.out_buffer_size = 64, .out_buffer_size = 64,
.event_cb = handle_event, .event_cb = handle_event,
.data_cb = handle_rx, .data_cb = handle_rx,
.user_arg = NULL, .user_arg = NULL,
}; };
#if defined(CONFIG_EXAMPLE_USE_FTDI) // 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
FT23x *vcp; ESP_LOGI(TAG, "Opening any VCP device...");
try { auto vcp = std::unique_ptr<CdcAcmDevice>(VCP::open(&dev_config));
ESP_LOGI(TAG, "Opening FT232 UART device");
vcp = FT23x::open_ftdi(FTDI_FT232_PID, &dev_config); if (vcp == nullptr) {
} ESP_LOGI(TAG, "Failed to open VCP device");
#else continue;
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;
} }
vTaskDelay(10);
ESP_LOGI(TAG, "Setting up line coding"); ESP_LOGI(TAG, "Setting up line coding");
cdc_acm_line_coding_t line_coding = { cdc_acm_line_coding_t line_coding = {
@@ -129,8 +153,14 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(vcp->tx_blocking((uint8_t *)"Test string", 12)); ESP_ERROR_CHECK(vcp->tx_blocking((uint8_t *)"Test string", 12));
*/ */
// Send some dummy data
ESP_LOGI(TAG, "Sending data through CdcAcmDevice");
uint8_t data[] = "test_string";
ESP_ERROR_CHECK(vcp->tx_blocking(data, sizeof(data)));
ESP_ERROR_CHECK(vcp->set_control_line_state(true, true));
// We are done. Wait for device disconnection and start over // We are done. Wait for device disconnection and start over
ESP_LOGI(TAG, "Done. You can reconnect the VCP device to run again.");
xSemaphoreTake(device_disconnected_sem, portMAX_DELAY); xSemaphoreTake(device_disconnected_sem, portMAX_DELAY);
delete vcp;
} }
} }

View File

@@ -1,82 +0,0 @@
/*
* 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

@@ -1,114 +0,0 @@
/*
* 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

@@ -1,175 +0,0 @@
/*
* 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

@@ -1,126 +0,0 @@
/*
* 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

@@ -1,4 +1,7 @@
## IDF Component Manager Manifest File ## IDF Component Manager Manifest File
dependencies: dependencies:
usb_host_cdc_acm: "1.*" usb_host_ch34x_vcp: "^1"
idf: ">=4.4" usb_host_cp210x_vcp: "^1"
usb_host_ftdi_vcp: "^1"
usb_host_vcp: "^1"
idf: ">=5.1.0"