mirror of
https://github.com/esphome/esphome.git
synced 2026-05-28 13:37:24 +08:00
[usb_host][usb_uart] Add configurable max packet size (#14584)
This commit is contained in:
@@ -10,6 +10,7 @@ from esphome.components.esp32 import (
|
|||||||
)
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_DEVICES, CONF_ID
|
from esphome.const import CONF_DEVICES, CONF_ID
|
||||||
|
from esphome.core import CORE
|
||||||
from esphome.cpp_types import Component
|
from esphome.cpp_types import Component
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
@@ -19,14 +20,15 @@ DEPENDENCIES = ["esp32"]
|
|||||||
usb_host_ns = cg.esphome_ns.namespace("usb_host")
|
usb_host_ns = cg.esphome_ns.namespace("usb_host")
|
||||||
USBHost = usb_host_ns.class_("USBHost", Component)
|
USBHost = usb_host_ns.class_("USBHost", Component)
|
||||||
USBClient = usb_host_ns.class_("USBClient", Component)
|
USBClient = usb_host_ns.class_("USBClient", Component)
|
||||||
|
DOMAIN = "usb_host"
|
||||||
CONF_VID = "vid"
|
CONF_VID = "vid"
|
||||||
CONF_PID = "pid"
|
CONF_PID = "pid"
|
||||||
CONF_ENABLE_HUBS = "enable_hubs"
|
CONF_ENABLE_HUBS = "enable_hubs"
|
||||||
CONF_MAX_TRANSFER_REQUESTS = "max_transfer_requests"
|
CONF_MAX_TRANSFER_REQUESTS = "max_transfer_requests"
|
||||||
|
CONF_MAX_PACKET_SIZE = "max_packet_size"
|
||||||
|
|
||||||
|
|
||||||
def usb_device_schema(cls=USBClient, vid: int = None, pid: [int] = None) -> cv.Schema:
|
def usb_device_schema(cls=USBClient, vid: int = None, pid: int = None) -> cv.Schema:
|
||||||
schema = cv.COMPONENT_SCHEMA.extend(
|
schema = cv.COMPONENT_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(cls),
|
cv.GenerateID(): cv.declare_id(cls),
|
||||||
@@ -43,6 +45,17 @@ def usb_device_schema(cls=USBClient, vid: int = None, pid: [int] = None) -> cv.S
|
|||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
|
def _set_max_packet_size(config: dict) -> dict:
|
||||||
|
CORE.data.setdefault(DOMAIN, {})[CONF_MAX_PACKET_SIZE] = config[
|
||||||
|
CONF_MAX_PACKET_SIZE
|
||||||
|
]
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def get_max_packet_size() -> int:
|
||||||
|
return CORE.data.get(DOMAIN, {}).get(CONF_MAX_PACKET_SIZE, 64)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.COMPONENT_SCHEMA.extend(
|
cv.COMPONENT_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
@@ -51,10 +64,14 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_MAX_TRANSFER_REQUESTS, default=16): cv.int_range(
|
cv.Optional(CONF_MAX_TRANSFER_REQUESTS, default=16): cv.int_range(
|
||||||
min=1, max=32
|
min=1, max=32
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_MAX_PACKET_SIZE, default=64): cv.one_of(
|
||||||
|
64, 128, 256, 512, 1024, int=True
|
||||||
|
),
|
||||||
cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()),
|
cv.Optional(CONF_DEVICES): cv.ensure_list(usb_device_schema()),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
only_on_variant(supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3]),
|
only_on_variant(supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3]),
|
||||||
|
_set_max_packet_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -72,8 +89,8 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
if config.get(CONF_ENABLE_HUBS):
|
if config.get(CONF_ENABLE_HUBS):
|
||||||
add_idf_sdkconfig_option("CONFIG_USB_HOST_HUBS_SUPPORTED", True)
|
add_idf_sdkconfig_option("CONFIG_USB_HOST_HUBS_SUPPORTED", True)
|
||||||
|
|
||||||
max_requests = config[CONF_MAX_TRANSFER_REQUESTS]
|
cg.add_define("USB_HOST_MAX_REQUESTS", config[CONF_MAX_TRANSFER_REQUESTS])
|
||||||
cg.add_define("USB_HOST_MAX_REQUESTS", max_requests)
|
cg.add_define("USB_HOST_MAX_PACKET_SIZE", config[CONF_MAX_PACKET_SIZE])
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ static_assert(MAX_REQUESTS >= 1 && MAX_REQUESTS <= 32, "MAX_REQUESTS must be bet
|
|||||||
using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
|
using trq_bitmask_t = std::conditional<(MAX_REQUESTS <= 16), uint16_t, uint32_t>::type;
|
||||||
static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1;
|
static constexpr trq_bitmask_t ALL_REQUESTS_IN_USE = MAX_REQUESTS == 32 ? ~0 : (1 << MAX_REQUESTS) - 1;
|
||||||
|
|
||||||
|
static constexpr size_t USB_MAX_PACKET_SIZE =
|
||||||
|
USB_HOST_MAX_PACKET_SIZE; // Max USB packet size (64 for FS, 512 for P4 HS)
|
||||||
static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
|
static constexpr size_t USB_EVENT_QUEUE_SIZE = 32; // Size of event queue between USB task and main loop
|
||||||
static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
|
static constexpr size_t USB_TASK_STACK_SIZE = 4096; // Stack size for USB task (same as ESP-IDF USB examples)
|
||||||
static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than main loop (tskIDLE_PRIORITY + 5)
|
static constexpr UBaseType_t USB_TASK_PRIORITY = 5; // Higher priority than main loop (tskIDLE_PRIORITY + 5)
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ void USBClient::setup() {
|
|||||||
// Pre-allocate USB transfer buffers for all slots at startup
|
// Pre-allocate USB transfer buffers for all slots at startup
|
||||||
// This avoids any dynamic allocation during runtime
|
// This avoids any dynamic allocation during runtime
|
||||||
for (auto &request : this->requests_) {
|
for (auto &request : this->requests_) {
|
||||||
usb_host_transfer_alloc(64, 0, &request.transfer);
|
usb_host_transfer_alloc(USB_MAX_PACKET_SIZE, 0, &request.transfer);
|
||||||
request.client = this; // Set once, never changes
|
request.client = this; // Set once, never changes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
|
from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS
|
||||||
from esphome.components.uart import CONF_DEBUG_PREFIX, CONF_FLUSH_TIMEOUT, UARTComponent
|
from esphome.components.uart import CONF_DEBUG_PREFIX, CONF_FLUSH_TIMEOUT, UARTComponent
|
||||||
from esphome.components.usb_host import register_usb_client, usb_device_schema
|
from esphome.components.usb_host import (
|
||||||
|
get_max_packet_size,
|
||||||
|
register_usb_client,
|
||||||
|
usb_device_schema,
|
||||||
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_BAUD_RATE,
|
CONF_BAUD_RATE,
|
||||||
@@ -118,14 +122,14 @@ CONFIG_SCHEMA = cv.ensure_list(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
# The output chunk pool/queue are compile-time-sized templates shared by all
|
# The output chunk pool/queue are compile-time-sized templates shared by all
|
||||||
# USBUartChannel instances, so use the largest buffer_size across every channel
|
# USBUartChannel instances, so use the largest buffer_size across every channel
|
||||||
# of every device. Each chunk is 64 bytes (USB FS MPS); add one extra slot
|
# of every device. Add one extra slot because LockFreeQueue<T,N> is a ring
|
||||||
# because LockFreeQueue<T,N> is a ring buffer that wastes one entry.
|
# buffer that wastes one entry.
|
||||||
max_buffer_size = max(
|
max_buffer_size = max(
|
||||||
channel[CONF_BUFFER_SIZE]
|
channel[CONF_BUFFER_SIZE]
|
||||||
for device in config
|
for device in config
|
||||||
for channel in device[CONF_CHANNELS]
|
for channel in device[CONF_CHANNELS]
|
||||||
)
|
)
|
||||||
output_chunk_count = max_buffer_size // 64 + 1
|
output_chunk_count = max(max_buffer_size // get_max_packet_size(), 2) + 1
|
||||||
cg.add_define("USB_UART_OUTPUT_CHUNK_COUNT", output_chunk_count)
|
cg.add_define("USB_UART_OUTPUT_CHUNK_COUNT", output_chunk_count)
|
||||||
|
|
||||||
for device in config:
|
for device in config:
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ void USBUartChannel::write_array(const uint8_t *data, size_t len) {
|
|||||||
ESP_LOGE(TAG, "Output pool full - lost %zu bytes", len);
|
ESP_LOGE(TAG, "Output pool full - lost %zu bytes", len);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
size_t chunk_len = std::min(len, UsbOutputChunk::MAX_CHUNK_SIZE);
|
uint16_t chunk_len = std::min(len, UsbOutputChunk::MAX_CHUNK_SIZE);
|
||||||
memcpy(chunk->data, data, chunk_len);
|
memcpy(chunk->data, data, chunk_len);
|
||||||
chunk->length = static_cast<uint8_t>(chunk_len);
|
chunk->length = static_cast<uint8_t>(chunk_len);
|
||||||
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
|
// Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
|
||||||
@@ -222,7 +222,7 @@ void USBUartComponent::loop() {
|
|||||||
|
|
||||||
#ifdef USE_UART_DEBUGGER
|
#ifdef USE_UART_DEBUGGER
|
||||||
if (channel->debug_) {
|
if (channel->debug_) {
|
||||||
char buf[4 + format_hex_pretty_size(UsbDataChunk::MAX_CHUNK_SIZE)]; // "<<< " + hex
|
char buf[4 + format_hex_pretty_size(usb_host::USB_MAX_PACKET_SIZE)]; // "<<< " + hex
|
||||||
memcpy(buf, "<<< ", 4);
|
memcpy(buf, "<<< ", 4);
|
||||||
format_hex_pretty_to(buf + 4, sizeof(buf) - 4, chunk->data, chunk->length, ',');
|
format_hex_pretty_to(buf + 4, sizeof(buf) - 4, chunk->data, chunk->length, ',');
|
||||||
ESP_LOGD(TAG, "%s%s", channel->debug_prefix_.c_str(), buf);
|
ESP_LOGD(TAG, "%s%s", channel->debug_prefix_.c_str(), buf);
|
||||||
@@ -377,7 +377,7 @@ void USBUartComponent::start_output(USBUartChannel *channel) {
|
|||||||
this->start_output(channel);
|
this->start_output(channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint8_t len = chunk->length;
|
const auto len = chunk->length;
|
||||||
if (!this->transfer_out(ep->bEndpointAddress, callback, chunk->data, len)) {
|
if (!this->transfer_out(ep->bEndpointAddress, callback, chunk->data, len)) {
|
||||||
// Transfer submission failed — return chunk and release flag so callers can retry.
|
// Transfer submission failed — return chunk and release flag so callers can retry.
|
||||||
channel->output_pool_.release(chunk);
|
channel->output_pool_.release(chunk);
|
||||||
@@ -394,10 +394,10 @@ void USBUartComponent::start_output(USBUartChannel *channel) {
|
|||||||
static void fix_mps(const usb_ep_desc_t *ep) {
|
static void fix_mps(const usb_ep_desc_t *ep) {
|
||||||
if (ep != nullptr) {
|
if (ep != nullptr) {
|
||||||
auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
|
auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
|
||||||
if (ep->wMaxPacketSize > 64) {
|
if (ep->wMaxPacketSize > usb_host::USB_MAX_PACKET_SIZE) {
|
||||||
ESP_LOGW(TAG, "Corrected MPS of EP 0x%02X from %u to 64", static_cast<uint8_t>(ep->bEndpointAddress & 0xFF),
|
ESP_LOGW(TAG, "Corrected MPS of EP 0x%02X from %u to %u", static_cast<uint8_t>(ep->bEndpointAddress & 0xFF),
|
||||||
ep->wMaxPacketSize);
|
ep->wMaxPacketSize, usb_host::USB_MAX_PACKET_SIZE);
|
||||||
ep_mutable->wMaxPacketSize = 64;
|
ep_mutable->wMaxPacketSize = usb_host::USB_MAX_PACKET_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,20 +106,19 @@ class RingBuffer {
|
|||||||
|
|
||||||
// Structure for queuing received USB data chunks
|
// Structure for queuing received USB data chunks
|
||||||
struct UsbDataChunk {
|
struct UsbDataChunk {
|
||||||
static constexpr size_t MAX_CHUNK_SIZE = 64; // USB packet size
|
uint8_t data[usb_host::USB_MAX_PACKET_SIZE];
|
||||||
uint8_t data[MAX_CHUNK_SIZE];
|
uint16_t length;
|
||||||
uint8_t length; // Max 64 bytes, so uint8_t is sufficient
|
|
||||||
USBUartChannel *channel;
|
USBUartChannel *channel;
|
||||||
|
|
||||||
// Required for EventPool - no cleanup needed for POD types
|
// Required for EventPool - no cleanup needed for POD types
|
||||||
void release() {}
|
void release() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Structure for queuing outgoing USB data chunks (one per USB FS packet)
|
// Structure for queuing outgoing USB data chunks (one per USB packet)
|
||||||
struct UsbOutputChunk {
|
struct UsbOutputChunk {
|
||||||
static constexpr size_t MAX_CHUNK_SIZE = 64; // USB FS MPS
|
static constexpr size_t MAX_CHUNK_SIZE = usb_host::USB_MAX_PACKET_SIZE;
|
||||||
uint8_t data[MAX_CHUNK_SIZE];
|
uint8_t data[MAX_CHUNK_SIZE];
|
||||||
uint8_t length;
|
uint16_t length;
|
||||||
|
|
||||||
// Required for EventPool - no cleanup needed for POD types
|
// Required for EventPool - no cleanup needed for POD types
|
||||||
void release() {}
|
void release() {}
|
||||||
|
|||||||
Reference in New Issue
Block a user