1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-29 08:32:26 +01:00

[usb_uart] Implement USB Host mode UART (#8334)

This commit is contained in:
Clyde Stubbs
2025-05-22 11:54:40 +10:00
committed by GitHub
parent d1e55252d0
commit 1ec57a74b5
13 changed files with 1464 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
import esphome.codegen as cg
from esphome.components.uart import (
CONF_DATA_BITS,
CONF_PARITY,
CONF_STOP_BITS,
UARTComponent,
)
from esphome.components.usb_host import register_usb_client, usb_device_schema
import esphome.config_validation as cv
from esphome.const import (
CONF_BAUD_RATE,
CONF_BUFFER_SIZE,
CONF_CHANNELS,
CONF_DEBUG,
CONF_DUMMY_RECEIVER,
CONF_ID,
)
from esphome.cpp_types import Component
AUTO_LOAD = ["uart", "usb_host", "bytebuffer"]
CODEOWNERS = ["@clydebarrow"]
usb_uart_ns = cg.esphome_ns.namespace("usb_uart")
USBUartComponent = usb_uart_ns.class_("USBUartComponent", Component)
USBUartChannel = usb_uart_ns.class_("USBUartChannel", UARTComponent)
UARTParityOptions = usb_uart_ns.enum("UARTParityOptions")
UART_PARITY_OPTIONS = {
"NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE,
"EVEN": UARTParityOptions.UART_CONFIG_PARITY_EVEN,
"ODD": UARTParityOptions.UART_CONFIG_PARITY_ODD,
"MARK": UARTParityOptions.UART_CONFIG_PARITY_MARK,
"SPACE": UARTParityOptions.UART_CONFIG_PARITY_SPACE,
}
UARTStopBitsOptions = usb_uart_ns.enum("UARTStopBitsOptions")
UART_STOP_BITS_OPTIONS = {
"1": UARTStopBitsOptions.UART_CONFIG_STOP_BITS_1,
"1.5": UARTStopBitsOptions.UART_CONFIG_STOP_BITS_1_5,
"2": UARTStopBitsOptions.UART_CONFIG_STOP_BITS_2,
}
DEFAULT_BAUD_RATE = 9600
class Type:
def __init__(self, name, vid, pid, cls, max_channels=1, baud_rate_required=True):
self.name = name
cls = cls or name
self.vid = vid
self.pid = pid
self.cls = usb_uart_ns.class_(f"USBUartType{cls}", USBUartComponent)
self.max_channels = max_channels
self.baud_rate_required = baud_rate_required
uart_types = (
Type("CH34X", 0x1A86, 0x55D5, "CH34X", 3),
Type("CH340", 0x1A86, 0x7523, "CH34X", 1),
Type("ESP_JTAG", 0x303A, 0x1001, "CdcAcm", 1, baud_rate_required=False),
Type("STM32_VCP", 0x0483, 0x5740, "CdcAcm", 1, baud_rate_required=False),
Type("CDC_ACM", 0, 0, "CdcAcm", 1, baud_rate_required=False),
Type("CP210X", 0x10C4, 0xEA60, "CP210X", 3),
)
def channel_schema(channels, baud_rate_required):
return cv.Schema(
{
cv.Required(CONF_CHANNELS): cv.All(
cv.ensure_list(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(USBUartChannel),
cv.Optional(CONF_BUFFER_SIZE, default=256): cv.int_range(
min=64, max=8192
),
(
cv.Required(CONF_BAUD_RATE)
if baud_rate_required
else cv.Optional(
CONF_BAUD_RATE, default=DEFAULT_BAUD_RATE
)
): cv.int_range(min=300, max=1000000),
cv.Optional(CONF_STOP_BITS, default="1"): cv.enum(
UART_STOP_BITS_OPTIONS, upper=True
),
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
UART_PARITY_OPTIONS, upper=True
),
cv.Optional(CONF_DATA_BITS, default=8): cv.int_range(
min=5, max=8
),
cv.Optional(CONF_DUMMY_RECEIVER, default=False): cv.boolean,
cv.Optional(CONF_DEBUG, default=False): cv.boolean,
}
)
),
cv.Length(max=channels),
)
}
)
CONFIG_SCHEMA = cv.ensure_list(
cv.typed_schema(
{
it.name: usb_device_schema(it.cls, it.vid, it.pid).extend(
channel_schema(it.max_channels, it.baud_rate_required)
)
for it in uart_types
},
upper=True,
)
)
async def to_code(config):
for device in config:
var = await register_usb_client(device)
for index, channel in enumerate(device[CONF_CHANNELS]):
chvar = cg.new_Pvariable(channel[CONF_ID], index, channel[CONF_BUFFER_SIZE])
await cg.register_parented(chvar, var)
cg.add(chvar.set_rx_buffer_size(channel[CONF_BUFFER_SIZE]))
cg.add(chvar.set_stop_bits(channel[CONF_STOP_BITS]))
cg.add(chvar.set_data_bits(channel[CONF_DATA_BITS]))
cg.add(chvar.set_parity(channel[CONF_PARITY]))
cg.add(chvar.set_baud_rate(channel[CONF_BAUD_RATE]))
cg.add(chvar.set_dummy_receiver(channel[CONF_DUMMY_RECEIVER]))
cg.add(chvar.set_debug(channel[CONF_DEBUG]))
cg.add(var.add_channel(chvar))
if channel[CONF_DEBUG]:
cg.add_define("USE_UART_DEBUGGER")

View File

@@ -0,0 +1,80 @@
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "usb_uart.h"
#include "usb/usb_host.h"
#include "esphome/core/log.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
namespace esphome {
namespace usb_uart {
using namespace bytebuffer;
/**
* CH34x
*/
void USBUartTypeCH34X::enable_channels() {
// enable the channels
for (auto channel : this->channels_) {
if (!channel->initialised_)
continue;
usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
channel->initialised_ = false;
}
};
uint8_t divisor = 7;
uint32_t clk = 12000000;
auto baud_rate = channel->baud_rate_;
if (baud_rate < 256000) {
if (baud_rate > 6000000 / 255) {
divisor = 3;
clk = 6000000;
} else if (baud_rate > 750000 / 255) {
divisor = 2;
clk = 750000;
} else if (baud_rate > 93750 / 255) {
divisor = 1;
clk = 93750;
} else {
divisor = 0;
clk = 11719;
}
}
ESP_LOGV(TAG, "baud_rate: %" PRIu32 ", divisor: %d, clk: %" PRIu32, baud_rate, divisor, clk);
auto factor = static_cast<uint8_t>(clk / baud_rate);
if (factor == 0 || factor == 0xFF) {
ESP_LOGE(TAG, "Invalid baud rate %" PRIu32, baud_rate);
channel->initialised_ = false;
continue;
}
if ((clk / factor - baud_rate) > (baud_rate - clk / (factor + 1)))
factor++;
factor = 256 - factor;
uint16_t value = 0xC0;
if (channel->stop_bits_ == UART_CONFIG_STOP_BITS_2)
value |= 4;
switch (channel->parity_) {
case UART_CONFIG_PARITY_NONE:
break;
default:
value |= 8 | ((channel->parity_ - 1) << 4);
break;
}
value |= channel->data_bits_ - 5;
value <<= 8;
value |= 0x8C;
uint8_t cmd = 0xA1 + channel->index_;
if (channel->index_ >= 2)
cmd += 0xE;
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor, callback);
}
USBUartTypeCdcAcm::enable_channels();
}
} // namespace usb_uart
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3

View File

@@ -0,0 +1,126 @@
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "usb_uart.h"
#include "usb/usb_host.h"
#include "esphome/core/log.h"
#include "esphome/components/bytebuffer/bytebuffer.h"
namespace esphome {
namespace usb_uart {
using namespace bytebuffer;
/**
* Silabs CP210x Commands
*/
static constexpr uint8_t IFC_ENABLE = 0x00; // Enable or disable the interface.
static constexpr uint8_t SET_BAUDDIV = 0x01; // Set the baud rate divisor.
static constexpr uint8_t GET_BAUDDIV = 0x02; // Get the baud rate divisor.
static constexpr uint8_t SET_LINE_CTL = 0x03; // Set the line control.
static constexpr uint8_t GET_LINE_CTL = 0x04; // Get the line control.
static constexpr uint8_t SET_BREAK = 0x05; // Set a BREAK.
static constexpr uint8_t IMM_CHAR = 0x06; // Send character out of order.
static constexpr uint8_t SET_MHS = 0x07; // Set modem handshaking.
static constexpr uint8_t GET_MDMSTS = 0x08; // Get modem status.
static constexpr uint8_t SET_XON = 0x09; // Emulate XON.
static constexpr uint8_t SET_XOFF = 0x0A; // Emulate XOFF.
static constexpr uint8_t SET_EVENTMASK = 0x0B; // Set the event mask.
static constexpr uint8_t GET_EVENTMASK = 0x0C; // Get the event mask.
static constexpr uint8_t GET_EVENTSTATE = 0x16; // Get the event state.
static constexpr uint8_t SET_RECEIVE = 0x17; // Set receiver max timeout.
static constexpr uint8_t GET_RECEIVE = 0x18; // Get receiver max timeout.
static constexpr uint8_t SET_CHAR = 0x0D; // Set special character individually.
static constexpr uint8_t GET_CHARS = 0x0E; // Get special characters.
static constexpr uint8_t GET_PROPS = 0x0F; // Get properties.
static constexpr uint8_t GET_COMM_STATUS = 0x10; // Get the serial status.
static constexpr uint8_t RESET = 0x11; // Reset.
static constexpr uint8_t PURGE = 0x12; // Purge.
static constexpr uint8_t SET_FLOW = 0x13; // Set flow control.
static constexpr uint8_t GET_FLOW = 0x14; // Get flow control.
static constexpr uint8_t EMBED_EVENTS = 0x15; // Control embedding of events in the data stream.
static constexpr uint8_t GET_BAUDRATE = 0x1D; // Get the baud rate.
static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate.
static constexpr uint8_t SET_CHARS = 0x19; // Set special characters.
static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command.
std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors_(usb_device_handle_t dev_hdl) {
const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc;
int conf_offset = 0, ep_offset;
std::vector<CdcEps> cdc_devs{};
// Get required descriptors
if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
ESP_LOGE(TAG, "get_device_descriptor failed");
return {};
}
if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
ESP_LOGE(TAG, "get_active_config_descriptor failed");
return {};
}
ESP_LOGD(TAG, "bDeviceClass: %u, bDeviceSubClass: %u", device_desc->bDeviceClass, device_desc->bDeviceSubClass);
ESP_LOGD(TAG, "bNumInterfaces: %u", config_desc->bNumInterfaces);
if (device_desc->bDeviceClass != 0) {
ESP_LOGE(TAG, "bDeviceClass != 0");
return {};
}
for (uint8_t i = 0; i != config_desc->bNumInterfaces; i++) {
auto data_desc = usb_parse_interface_descriptor(config_desc, 0, 0, &conf_offset);
if (!data_desc) {
ESP_LOGE(TAG, "data_desc: usb_parse_interface_descriptor failed");
break;
}
if (data_desc->bNumEndpoints != 2 || data_desc->bInterfaceClass != USB_CLASS_VENDOR_SPEC) {
ESP_LOGE(TAG, "data_desc: bInterfaceClass == %u, bInterfaceSubClass == %u, bNumEndpoints == %u",
data_desc->bInterfaceClass, data_desc->bInterfaceSubClass, data_desc->bNumEndpoints);
continue;
}
ep_offset = conf_offset;
auto out_ep = usb_parse_endpoint_descriptor_by_index(data_desc, 0, config_desc->wTotalLength, &ep_offset);
if (!out_ep) {
ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
continue;
}
ep_offset = conf_offset;
auto in_ep = usb_parse_endpoint_descriptor_by_index(data_desc, 1, config_desc->wTotalLength, &ep_offset);
if (!in_ep) {
ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
continue;
}
if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN) {
cdc_devs.push_back({CdcEps{nullptr, in_ep, out_ep, data_desc->bInterfaceNumber}});
} else {
cdc_devs.push_back({CdcEps{nullptr, out_ep, in_ep, data_desc->bInterfaceNumber}});
}
}
return cdc_devs;
}
void USBUartTypeCP210X::enable_channels() {
// enable the channels
for (auto channel : this->channels_) {
if (!channel->initialised_)
continue;
usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) {
if (!status.success) {
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
channel->initialised_ = false;
}
};
this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, IFC_ENABLE, 1, channel->index_, callback);
uint16_t line_control = channel->stop_bits_;
line_control |= static_cast<uint8_t>(channel->parity_) << 4;
line_control |= channel->data_bits_ << 8;
ESP_LOGD(TAG, "Line control value 0x%X", line_control);
this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_LINE_CTL, line_control, channel->index_,
callback);
auto baud = ByteBuffer::wrap(channel->baud_rate_, LITTLE);
this->control_transfer(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_BAUDRATE, 0, channel->index_, callback,
baud.get_data());
}
USBUartTypeCdcAcm::enable_channels();
}
} // namespace usb_uart
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3

View File

@@ -0,0 +1,325 @@
// Should not be needed, but it's required to pass CI clang-tidy checks
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "usb_uart.h"
#include "esphome/core/log.h"
#include "esphome/components/uart/uart_debugger.h"
#include <cinttypes>
namespace esphome {
namespace usb_uart {
/**
*
* Given a configuration, look for the required interfaces defining a CDC-ACM device
* @param config_desc The configuration descriptor
* @param intf_idx The index of the interface to be examined
* @return
*/
static optional<CdcEps> get_cdc(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
int conf_offset, ep_offset;
const usb_ep_desc_t *notify_ep{}, *in_ep{}, *out_ep{};
uint8_t interface_number = 0;
// look for an interface with one interrupt endpoint (notify), and an interface with two bulk endpoints (data in/out)
for (;;) {
auto intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx++, 0, &conf_offset);
if (!intf_desc) {
ESP_LOGE(TAG, "usb_parse_interface_descriptor failed");
return nullopt;
}
if (intf_desc->bNumEndpoints == 1) {
ep_offset = conf_offset;
notify_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
if (!notify_ep) {
ESP_LOGE(TAG, "notify_ep: usb_parse_endpoint_descriptor_by_index failed");
return nullopt;
}
if (notify_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_INT)
notify_ep = nullptr;
} else if (USB_CLASS_CDC_DATA && intf_desc->bNumEndpoints == 2) {
interface_number = intf_desc->bInterfaceNumber;
ep_offset = conf_offset;
out_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 0, config_desc->wTotalLength, &ep_offset);
if (!out_ep) {
ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
return nullopt;
}
if (out_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
out_ep = nullptr;
ep_offset = conf_offset;
in_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, 1, config_desc->wTotalLength, &ep_offset);
if (!in_ep) {
ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
return nullopt;
}
if (in_ep->bmAttributes != USB_BM_ATTRIBUTES_XFER_BULK)
in_ep = nullptr;
}
if (in_ep != nullptr && out_ep != nullptr && notify_ep != nullptr)
break;
}
if (in_ep->bEndpointAddress & usb_host::USB_DIR_IN)
return CdcEps{notify_ep, in_ep, out_ep, interface_number};
return CdcEps{notify_ep, out_ep, in_ep, interface_number};
}
std::vector<CdcEps> USBUartTypeCdcAcm::parse_descriptors_(usb_device_handle_t dev_hdl) {
const usb_config_desc_t *config_desc;
const usb_device_desc_t *device_desc;
int desc_offset = 0;
std::vector<CdcEps> cdc_devs{};
// Get required descriptors
if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
ESP_LOGE(TAG, "get_device_descriptor failed");
return {};
}
if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
ESP_LOGE(TAG, "get_active_config_descriptor failed");
return {};
}
if (device_desc->bDeviceClass == USB_CLASS_COMM) {
// single CDC-ACM device
if (auto eps = get_cdc(config_desc, 0)) {
ESP_LOGV(TAG, "Found CDC-ACM device");
cdc_devs.push_back(*eps);
}
return cdc_devs;
}
if (((device_desc->bDeviceClass == USB_CLASS_MISC) && (device_desc->bDeviceSubClass == USB_SUBCLASS_COMMON) &&
(device_desc->bDeviceProtocol == USB_DEVICE_PROTOCOL_IAD)) ||
((device_desc->bDeviceClass == USB_CLASS_PER_INTERFACE) && (device_desc->bDeviceSubClass == USB_SUBCLASS_NULL) &&
(device_desc->bDeviceProtocol == USB_PROTOCOL_NULL))) {
// This is a composite device, that uses Interface Association Descriptor
const auto *this_desc = reinterpret_cast<const usb_standard_desc_t *>(config_desc);
for (;;) {
this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength,
USB_B_DESCRIPTOR_TYPE_INTERFACE_ASSOCIATION, &desc_offset);
if (!this_desc)
break;
const auto *iad_desc = reinterpret_cast<const usb_iad_desc_t *>(this_desc);
if (iad_desc->bFunctionClass == USB_CLASS_COMM && iad_desc->bFunctionSubClass == USB_CDC_SUBCLASS_ACM) {
ESP_LOGV(TAG, "Found CDC-ACM device in composite device");
if (auto eps = get_cdc(config_desc, iad_desc->bFirstInterface))
cdc_devs.push_back(*eps);
}
}
}
return cdc_devs;
}
void RingBuffer::push(uint8_t item) {
this->buffer_[this->insert_pos_] = item;
this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
}
void RingBuffer::push(const uint8_t *data, size_t len) {
for (size_t i = 0; i != len; i++) {
this->buffer_[this->insert_pos_] = *data++;
this->insert_pos_ = (this->insert_pos_ + 1) % this->buffer_size_;
}
}
uint8_t RingBuffer::pop() {
uint8_t item = this->buffer_[this->read_pos_];
this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
return item;
}
size_t RingBuffer::pop(uint8_t *data, size_t len) {
len = std::min(len, this->get_available());
for (size_t i = 0; i != len; i++) {
*data++ = this->buffer_[this->read_pos_];
this->read_pos_ = (this->read_pos_ + 1) % this->buffer_size_;
}
return len;
}
void USBUartChannel::write_array(const uint8_t *data, size_t len) {
if (!this->initialised_) {
ESP_LOGV(TAG, "Channel not initialised - write ignored");
return;
}
while (this->output_buffer_.get_free_space() != 0 && len-- != 0) {
this->output_buffer_.push(*data++);
}
len++;
if (len > 0) {
ESP_LOGE(TAG, "Buffer full - failed to write %d bytes", len);
}
this->parent_->start_output(this);
}
bool USBUartChannel::peek_byte(uint8_t *data) {
if (this->input_buffer_.is_empty()) {
return false;
}
*data = this->input_buffer_.peek();
return true;
}
bool USBUartChannel::read_array(uint8_t *data, size_t len) {
if (!this->initialised_) {
ESP_LOGV(TAG, "Channel not initialised - read ignored");
return false;
}
auto available = this->available();
bool status = true;
if (len > available) {
ESP_LOGV(TAG, "underflow: requested %zu but returned %d, bytes", len, available);
len = available;
status = false;
}
for (size_t i = 0; i != len; i++) {
*data++ = this->input_buffer_.pop();
}
this->parent_->start_input(this);
return status;
}
void USBUartComponent::setup() { USBClient::setup(); }
void USBUartComponent::loop() { USBClient::loop(); }
void USBUartComponent::dump_config() {
USBClient::dump_config();
for (auto &channel : this->channels_) {
ESP_LOGCONFIG(TAG, " UART Channel %d", channel->index_);
ESP_LOGCONFIG(TAG, " Baud Rate: %" PRIu32 " baud", channel->baud_rate_);
ESP_LOGCONFIG(TAG, " Data Bits: %u", channel->data_bits_);
ESP_LOGCONFIG(TAG, " Parity: %s", PARITY_NAMES[channel->parity_]);
ESP_LOGCONFIG(TAG, " Stop bits: %s", STOP_BITS_NAMES[channel->stop_bits_]);
ESP_LOGCONFIG(TAG, " Debug: %s", YESNO(channel->debug_));
ESP_LOGCONFIG(TAG, " Dummy receiver: %s", YESNO(channel->dummy_receiver_));
}
}
void USBUartComponent::start_input(USBUartChannel *channel) {
if (!channel->initialised_ || channel->input_started_ ||
channel->input_buffer_.get_free_space() < channel->cdc_dev_.in_ep->wMaxPacketSize)
return;
auto ep = channel->cdc_dev_.in_ep;
auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Transfer result: length: %u; status %X", status.data_len, status.error_code);
if (!status.success) {
ESP_LOGE(TAG, "Control transfer failed, status=%s", esp_err_to_name(status.error_code));
return;
}
#ifdef USE_UART_DEBUGGER
if (channel->debug_) {
uart::UARTDebug::log_hex(uart::UART_DIRECTION_RX,
std::vector<uint8_t>(status.data, status.data + status.data_len), ','); // NOLINT()
}
#endif
channel->input_started_ = false;
if (!channel->dummy_receiver_) {
for (size_t i = 0; i != status.data_len; i++) {
channel->input_buffer_.push(status.data[i]);
}
}
if (channel->input_buffer_.get_free_space() >= channel->cdc_dev_.in_ep->wMaxPacketSize) {
this->defer([this, channel] { this->start_input(channel); });
}
};
channel->input_started_ = true;
this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize);
}
void USBUartComponent::start_output(USBUartChannel *channel) {
if (channel->output_started_)
return;
if (channel->output_buffer_.is_empty()) {
return;
}
auto ep = channel->cdc_dev_.out_ep;
auto callback = [this, channel](const usb_host::TransferStatus &status) {
ESP_LOGV(TAG, "Output Transfer result: length: %u; status %X", status.data_len, status.error_code);
channel->output_started_ = false;
this->defer([this, channel] { this->start_output(channel); });
};
channel->output_started_ = true;
uint8_t data[ep->wMaxPacketSize];
auto len = channel->output_buffer_.pop(data, ep->wMaxPacketSize);
this->transfer_out(ep->bEndpointAddress, callback, data, len);
#ifdef USE_UART_DEBUGGER
if (channel->debug_) {
uart::UARTDebug::log_hex(uart::UART_DIRECTION_TX, std::vector<uint8_t>(data, data + len), ','); // NOLINT()
}
#endif
ESP_LOGV(TAG, "Output %d bytes started", len);
}
/**
* Hacky fix for some devices that report incorrect MPS values
* @param ep The endpoint descriptor
*/
static void fix_mps(const usb_ep_desc_t *ep) {
if (ep != nullptr) {
auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
if (ep->wMaxPacketSize > 64) {
ESP_LOGW(TAG, "Corrected MPS of EP %u from %u to 64", ep->bEndpointAddress, ep->wMaxPacketSize);
ep_mutable->wMaxPacketSize = 64;
}
}
}
void USBUartTypeCdcAcm::on_connected() {
auto cdc_devs = this->parse_descriptors_(this->device_handle_);
if (cdc_devs.empty()) {
this->status_set_error("No CDC-ACM device found");
this->disconnect();
return;
}
ESP_LOGD(TAG, "Found %zu CDC-ACM devices", cdc_devs.size());
auto i = 0;
for (auto channel : this->channels_) {
if (i == cdc_devs.size()) {
ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
this->status_set_warning("No configuration found for channel");
break;
}
channel->cdc_dev_ = cdc_devs[i++];
fix_mps(channel->cdc_dev_.in_ep);
fix_mps(channel->cdc_dev_.out_ep);
channel->initialised_ = true;
auto err = usb_host_interface_claim(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "usb_host_interface_claim failed: %s, channel=%d, intf=%d", esp_err_to_name(err), channel->index_,
channel->cdc_dev_.interface_number);
this->status_set_error("usb_host_interface_claim failed");
this->disconnect();
return;
}
}
this->enable_channels();
}
void USBUartTypeCdcAcm::on_disconnected() {
for (auto channel : this->channels_) {
if (channel->cdc_dev_.in_ep != nullptr) {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.in_ep->bEndpointAddress);
}
if (channel->cdc_dev_.out_ep != nullptr) {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.out_ep->bEndpointAddress);
}
if (channel->cdc_dev_.notify_ep != nullptr) {
usb_host_endpoint_halt(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
usb_host_endpoint_flush(this->device_handle_, channel->cdc_dev_.notify_ep->bEndpointAddress);
}
usb_host_interface_release(this->handle_, this->device_handle_, channel->cdc_dev_.interface_number);
channel->initialised_ = false;
channel->input_started_ = false;
channel->output_started_ = false;
channel->input_buffer_.clear();
channel->output_buffer_.clear();
}
USBClient::on_disconnected();
}
void USBUartTypeCdcAcm::enable_channels() {
for (auto channel : this->channels_) {
if (!channel->initialised_)
continue;
channel->input_started_ = false;
channel->output_started_ = false;
this->start_input(channel);
}
}
} // namespace usb_uart
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3

View File

@@ -0,0 +1,151 @@
#pragma once
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/uart/uart_component.h"
#include "esphome/components/usb_host/usb_host.h"
namespace esphome {
namespace usb_uart {
class USBUartTypeCdcAcm;
class USBUartComponent;
static const char *const TAG = "usb_uart";
static constexpr uint8_t USB_CDC_SUBCLASS_ACM = 0x02;
static constexpr uint8_t USB_SUBCLASS_COMMON = 0x02;
static constexpr uint8_t USB_SUBCLASS_NULL = 0x00;
static constexpr uint8_t USB_PROTOCOL_NULL = 0x00;
static constexpr uint8_t USB_DEVICE_PROTOCOL_IAD = 0x01;
static constexpr uint8_t USB_VENDOR_IFC = usb_host::USB_TYPE_VENDOR | usb_host::USB_RECIP_INTERFACE;
static constexpr uint8_t USB_VENDOR_DEV = usb_host::USB_TYPE_VENDOR | usb_host::USB_RECIP_DEVICE;
struct CdcEps {
const usb_ep_desc_t *notify_ep;
const usb_ep_desc_t *in_ep;
const usb_ep_desc_t *out_ep;
uint8_t interface_number;
};
enum UARTParityOptions {
UART_CONFIG_PARITY_NONE = 0,
UART_CONFIG_PARITY_ODD,
UART_CONFIG_PARITY_EVEN,
UART_CONFIG_PARITY_MARK,
UART_CONFIG_PARITY_SPACE,
};
enum UARTStopBitsOptions {
UART_CONFIG_STOP_BITS_1 = 0,
UART_CONFIG_STOP_BITS_1_5,
UART_CONFIG_STOP_BITS_2,
};
static const char *const PARITY_NAMES[] = {"NONE", "ODD", "EVEN", "MARK", "SPACE"};
static const char *const STOP_BITS_NAMES[] = {"1", "1.5", "2"};
class RingBuffer {
public:
RingBuffer(uint16_t buffer_size) : buffer_size_(buffer_size), buffer_(new uint8_t[buffer_size]) {}
bool is_empty() const { return this->read_pos_ == this->insert_pos_; }
size_t get_available() const {
return (this->insert_pos_ + this->buffer_size_ - this->read_pos_) % this->buffer_size_;
};
size_t get_free_space() const { return this->buffer_size_ - 1 - this->get_available(); }
uint8_t peek() const { return this->buffer_[this->read_pos_]; }
void push(uint8_t item);
void push(const uint8_t *data, size_t len);
uint8_t pop();
size_t pop(uint8_t *data, size_t len);
void clear() { this->read_pos_ = this->insert_pos_ = 0; }
protected:
uint16_t insert_pos_ = 0;
uint16_t read_pos_ = 0;
uint16_t buffer_size_;
uint8_t *buffer_;
};
class USBUartChannel : public uart::UARTComponent, public Parented<USBUartComponent> {
friend class USBUartComponent;
friend class USBUartTypeCdcAcm;
friend class USBUartTypeCP210X;
friend class USBUartTypeCH34X;
public:
USBUartChannel(uint8_t index, uint16_t buffer_size)
: index_(index), input_buffer_(RingBuffer(buffer_size)), output_buffer_(RingBuffer(buffer_size)) {}
void write_array(const uint8_t *data, size_t len) override;
;
bool peek_byte(uint8_t *data) override;
;
bool read_array(uint8_t *data, size_t len) override;
int available() override { return static_cast<int>(this->input_buffer_.get_available()); }
void flush() override {}
void check_logger_conflict() override {}
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
void set_debug(bool debug) { this->debug_ = debug; }
void set_dummy_receiver(bool dummy_receiver) { this->dummy_receiver_ = dummy_receiver; }
protected:
const uint8_t index_;
RingBuffer input_buffer_;
RingBuffer output_buffer_;
UARTParityOptions parity_{UART_CONFIG_PARITY_NONE};
bool input_started_{true};
bool output_started_{true};
CdcEps cdc_dev_{};
bool debug_{};
bool dummy_receiver_{};
bool initialised_{};
};
class USBUartComponent : public usb_host::USBClient {
public:
USBUartComponent(uint16_t vid, uint16_t pid) : usb_host::USBClient(vid, pid) {}
void setup() override;
void loop() override;
void dump_config() override;
std::vector<USBUartChannel *> get_channels() { return this->channels_; }
void add_channel(USBUartChannel *channel) { this->channels_.push_back(channel); }
void start_input(USBUartChannel *channel);
void start_output(USBUartChannel *channel);
protected:
std::vector<USBUartChannel *> channels_{};
};
class USBUartTypeCdcAcm : public USBUartComponent {
public:
USBUartTypeCdcAcm(uint16_t vid, uint16_t pid) : USBUartComponent(vid, pid) {}
protected:
virtual std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl);
void on_connected() override;
virtual void enable_channels();
void on_disconnected() override;
};
class USBUartTypeCP210X : public USBUartTypeCdcAcm {
public:
USBUartTypeCP210X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
protected:
std::vector<CdcEps> parse_descriptors_(usb_device_handle_t dev_hdl) override;
void enable_channels() override;
};
class USBUartTypeCH34X : public USBUartTypeCdcAcm {
public:
USBUartTypeCH34X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {}
protected:
void enable_channels() override;
};
} // namespace usb_uart
} // namespace esphome
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3