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