espressif_idf-extra-components/usb/usb_host_ftdi_vcp/usb_host_ftdi_vcp.cpp

176 lines
6.2 KiB
C++

/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include "usb/vcp_ftdi.hpp"
#include "usb/usb_types_ch9.h"
#include "esp_log.h"
#include "esp_check.h"
#include "sdkconfig.h"
#ifndef CONFIG_COMPILER_CXX_EXCEPTIONS
#error This component requires C++ exceptions
#endif
#define FTDI_READ_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_DIR_IN)
#define FTDI_WRITE_REQ (USB_BM_REQUEST_TYPE_TYPE_VENDOR | USB_BM_REQUEST_TYPE_DIR_OUT)
namespace esp_usb {
FT23x::FT23x(uint16_t pid, const cdc_acm_host_device_config_t *dev_config, uint8_t interface_idx)
: intf(interface_idx), user_data_cb(dev_config->data_cb), user_event_cb(dev_config->event_cb),
user_arg(dev_config->user_arg), uart_state(0)
{
cdc_acm_host_device_config_t ftdi_config;
memcpy(&ftdi_config, dev_config, sizeof(cdc_acm_host_device_config_t));
// FT23x reports modem status in first two bytes of RX data
// so here we override the RX handler with our own
if (dev_config->data_cb) {
ftdi_config.data_cb = ftdi_rx;
ftdi_config.user_arg = this;
}
if (dev_config->event_cb) {
ftdi_config.event_cb = ftdi_event;
ftdi_config.user_arg = this;
}
esp_err_t err;
err = this->open_vendor_specific(vid, pid, this->intf, &ftdi_config);
if (err != ESP_OK) {
throw (err);
}
// FT23x interface must be first reset and configured (115200 8N1)
err = this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_RESET, 0, this->intf + 1, 0, NULL);
if (err != ESP_OK) {
throw (err);
}
cdc_acm_line_coding_t line_coding = {
.dwDTERate = 115200,
.bCharFormat = 0,
.bParityType = 0,
.bDataBits = 8,
};
err = this->line_coding_set(&line_coding);
if (err != ESP_OK) {
throw (err);
}
};
esp_err_t FT23x::line_coding_set(cdc_acm_line_coding_t *line_coding)
{
assert(line_coding);
if (line_coding->dwDTERate != 0) {
uint16_t wIndex, wValue;
calculate_baudrate(line_coding->dwDTERate, &wValue, &wIndex);
ESP_RETURN_ON_ERROR(this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_BAUDRATE, wValue, wIndex, 0, NULL), "FT23x",);
}
if (line_coding->bDataBits != 0) {
const uint16_t wValue = (line_coding->bDataBits) | (line_coding->bParityType << 8) | (line_coding->bCharFormat << 11);
return this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_LINE_CTL, wValue, this->intf, 0, NULL);
}
return ESP_OK;
}
esp_err_t FT23x::set_control_line_state(bool dtr, bool rts)
{
ESP_RETURN_ON_ERROR(this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_MHS, dtr ? 0x11 : 0x10, this->intf, 0, NULL), "FT23x",); // DTR
return this->send_custom_request(FTDI_WRITE_REQ, FTDI_CMD_SET_MHS, rts ? 0x21 : 0x20, this->intf, 0, NULL); // RTS
}
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: %" PRIu32", set: %d", baudrate, baudrate_real);
return baudrate_real;
}
} // esp_usb