From a9b5f95c768c58aeb4e117a94effe0121fdb77c2 Mon Sep 17 00:00:00 2001 From: Oliver Kleinecke Date: Sun, 8 Mar 2026 11:24:39 +0100 Subject: [PATCH 1/2] [usb_uart] ch34x chip-type & port-count enumeration (#14544) --- esphome/components/usb_uart/ch34x.cpp | 94 ++++++++++++++++++++++++-- esphome/components/usb_uart/usb_uart.h | 37 ++++++++++ 2 files changed, 126 insertions(+), 5 deletions(-) diff --git a/esphome/components/usb_uart/ch34x.cpp b/esphome/components/usb_uart/ch34x.cpp index e6e52a9e2a..d5428cc8d7 100644 --- a/esphome/components/usb_uart/ch34x.cpp +++ b/esphome/components/usb_uart/ch34x.cpp @@ -8,13 +8,97 @@ namespace esphome::usb_uart { using namespace bytebuffer; -/** - * CH34x - */ + +struct CH34xEntry { + uint16_t pid; + uint8_t byte_idx; // which status.data[] byte to inspect + uint8_t mask; // bitmask applied before comparison + uint8_t match; // 0xFF = wildcard (default/fallthrough for this PID) + CH34xChipType chiptype; + const char *name; + uint8_t num_ports; +}; + +static const CH34xEntry CH34X_TABLE[] = { + {0x55D2, 1, 0xFF, 0x41, CHIP_CH342K, "CH342K", 2}, + {0x55D2, 1, 0xFF, 0xFF, CHIP_CH342F, "CH342F", 2}, + {0x55D3, 1, 0xFF, 0x02, CHIP_CH343J, "CH343J", 1}, + {0x55D3, 1, 0xFF, 0x01, CHIP_CH343K, "CH343K", 1}, + {0x55D3, 1, 0xFF, 0x18, CHIP_CH343G_AUTOBAUD, "CH343G_AUTOBAUD", 1}, + {0x55D3, 1, 0xFF, 0xFF, CHIP_CH343GP, "CH343GP", 1}, + {0x55D4, 1, 0xFF, 0x09, CHIP_CH9102X, "CH9102X", 1}, + {0x55D4, 1, 0xFF, 0xFF, CHIP_CH9102F, "CH9102F", 1}, + {0x55D5, 1, 0xFF, 0xC0, CHIP_CH344L, "CH344L", 4}, // CH344L vs CH344L_V2 resolved below + {0x55D5, 1, 0xFF, 0xFF, CHIP_CH344Q, "CH344Q", 4}, + {0x55D7, 1, 0xFF, 0xFF, CHIP_CH9103M, "CH9103M", 2}, + {0x55D8, 1, 0xFF, 0x0A, CHIP_CH9101RY, "CH9101RY", 1}, + {0x55D8, 1, 0xFF, 0xFF, CHIP_CH9101UH, "CH9101UH", 1}, + {0x55DB, 1, 0xFF, 0xFF, CHIP_CH347TF, "CH347TF", 1}, + {0x55DD, 1, 0xFF, 0xFF, CHIP_CH347TF, "CH347TF", 1}, + {0x55DA, 1, 0xFF, 0xFF, CHIP_CH347TF, "CH347TF", 2}, + {0x55DE, 1, 0xFF, 0xFF, CHIP_CH347TF, "CH347TF", 2}, + {0x55E7, 1, 0xFF, 0xFF, CHIP_CH339W, "CH339W", 1}, + {0x55DF, 1, 0xFF, 0xFF, CHIP_CH9104L, "CH9104L", 4}, + {0x55E9, 1, 0xFF, 0xFF, CHIP_CH9111L_M0, "CH9111L_M0", 1}, + {0x55EA, 1, 0xFF, 0xFF, CHIP_CH9111L_M1, "CH9111L_M1", 1}, + {0x55E8, 2, 0xFF, 0x48, CHIP_CH9114L, "CH9114L", 4}, + {0x55E8, 2, 0xFF, 0x49, CHIP_CH9114W, "CH9114W", 4}, + {0x55E8, 2, 0xFF, 0x4A, CHIP_CH9114F, "CH9114F", 4}, + {0x55EB, 4, 0x01, 0x01, CHIP_CH346C_M1, "CH346C_M1", 1}, + {0x55EB, 4, 0x01, 0xFF, CHIP_CH346C_M0, "CH346C_M0", 1}, + {0x55EC, 1, 0xFF, 0xFF, CHIP_CH346C_M2, "CH346C_M2", 2}, +}; void USBUartTypeCH34X::enable_channels() { - // enable the channels - for (auto channel : this->channels_) { + usb_host::transfer_cb_t cb = [this](const usb_host::TransferStatus &status) { + if (!status.success) { + this->defer([this, error_code = status.error_code]() { + ESP_LOGE(TAG, "CH34x chip detection failed: %s", esp_err_to_name(error_code)); + this->apply_line_settings_(); + }); + return; + } + CH34xChipType chiptype = CHIP_UNKNOWN; + uint8_t num_ports = 1; + for (const auto &e : CH34X_TABLE) { + if (e.pid != this->pid_) + continue; + if (e.match != 0xFF && (status.data[e.byte_idx] & e.mask) != e.match) + continue; + chiptype = e.chiptype; + num_ports = e.num_ports; + break; + } + // CH344L vs CH344L_V2 requires chipver (data[0]) in addition to chiptype (data[1]) + if (chiptype == CHIP_CH344L && (status.data[0] & 0xF0) != 0x40) + chiptype = CHIP_CH344L_V2; + const char *name = "unknown"; + for (const auto &e : CH34X_TABLE) { + if (e.chiptype == chiptype) { + name = e.name; + break; + } + } + this->defer([this, chiptype, num_ports, name]() { + this->chiptype_ = chiptype; + this->chip_name_ = name; + this->num_ports_ = num_ports; + ESP_LOGD(TAG, "CH34x chip: %s, ports: %u", name, this->num_ports_); + this->apply_line_settings_(); + }); + }; + // Vendor-specific GET_CHIP_VERSION request (bRequest=0x5F): returns chip ID bytes + // used to distinguish CH34x variants sharing the same PID. + this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_IN, 0x5F, 0, 0, cb, {0, 0, 0, 0, 0, 0, 0, 0}); +} + +void USBUartTypeCH34X::dump_config() { + USBUartTypeCdcAcm::dump_config(); + ESP_LOGCONFIG(TAG, " CH34x chip: %s", this->chip_name_); +} + +void USBUartTypeCH34X::apply_line_settings_() { + for (auto *channel : this->channels_) { if (!channel->initialised_.load()) continue; usb_host::transfer_cb_t callback = [=](const usb_host::TransferStatus &status) { diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index b1748aebf2..16469df7f6 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -35,6 +35,36 @@ struct CdcEps { uint8_t interrupt_interface_number; }; +enum CH34xChipType : uint8_t { + CHIP_CH342F = 0, + CHIP_CH342K, + CHIP_CH343GP, + CHIP_CH343G_AUTOBAUD, + CHIP_CH343K, + CHIP_CH343J, + CHIP_CH344L, + CHIP_CH344L_V2, + CHIP_CH344Q, + CHIP_CH347TF, + CHIP_CH9101UH, + CHIP_CH9101RY, + CHIP_CH9102F, + CHIP_CH9102X, + CHIP_CH9103M, + CHIP_CH9104L, + CHIP_CH340B, + CHIP_CH339W, + CHIP_CH9111L_M0, + CHIP_CH9111L_M1, + CHIP_CH9114L, + CHIP_CH9114W, + CHIP_CH9114F, + CHIP_CH346C_M0, + CHIP_CH346C_M1, + CHIP_CH346C_M2, + CHIP_UNKNOWN = 0xFF, +}; + enum UARTParityOptions { UART_CONFIG_PARITY_NONE = 0, UART_CONFIG_PARITY_ODD, @@ -192,10 +222,17 @@ class USBUartTypeCP210X : public USBUartTypeCdcAcm { class USBUartTypeCH34X : public USBUartTypeCdcAcm { public: USBUartTypeCH34X(uint16_t vid, uint16_t pid) : USBUartTypeCdcAcm(vid, pid) {} + void dump_config() override; protected: void enable_channels() override; std::vector parse_descriptors(usb_device_handle_t dev_hdl) override; + + private: + void apply_line_settings_(); + CH34xChipType chiptype_{CHIP_UNKNOWN}; + const char *chip_name_{"unknown"}; + uint8_t num_ports_{1}; }; } // namespace esphome::usb_uart From 76a35df85a8be1d56bc3887652ca0fc1ecb50550 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 8 Mar 2026 01:03:31 -1000 Subject: [PATCH 2/2] [rp2040] Wrap printf/vprintf/fprintf to eliminate _vfprintf_r (~8.9 KB flash) Apply the same linker wrap technique used on ESP32 (PR #14362) to RP2040. ESPHome logging uses snprintf/vsnprintf, not libc printf, so the FILE*-based printf path (_vfprintf_r) is dead code at runtime. The stubs redirect printf/vprintf/fprintf through vsnprintf + fwrite, allowing the linker to GC _vfprintf_r (~8.9 KB). --- esphome/components/rp2040/__init__.py | 5 ++ esphome/components/rp2040/printf_stubs.cpp | 71 ++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 esphome/components/rp2040/printf_stubs.cpp diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 1442a0a7f7..7ee8381ca5 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -190,6 +190,11 @@ async def to_code(config): ], ) + # Wrap FILE*-based printf functions to eliminate newlib's _vfprintf_r + # (~8.9 KB). See printf_stubs.cpp for implementation. + for symbol in ("vprintf", "printf", "fprintf"): + cg.add_build_flag(f"-Wl,--wrap={symbol}") + cg.add_platformio_option("board_build.core", "earlephilhower") cg.add_platformio_option("board_build.filesystem_size", "1m") diff --git a/esphome/components/rp2040/printf_stubs.cpp b/esphome/components/rp2040/printf_stubs.cpp new file mode 100644 index 0000000000..83d916cd2c --- /dev/null +++ b/esphome/components/rp2040/printf_stubs.cpp @@ -0,0 +1,71 @@ +/* + * Linker wrap stubs for FILE*-based printf functions. + * + * The RP2040 Arduino framework and libraries may reference printf(), + * vprintf(), and fprintf() which pull in newlib's _vfprintf_r (~8.9 KB). + * ESPHome never uses these — all logging goes through the logger component + * which uses snprintf/vsnprintf, so the libc FILE*-based printf path is + * dead code. + * + * These stubs redirect through vsnprintf() (which is already in the binary) + * and fwrite(), allowing the linker to dead-code eliminate _vfprintf_r. + * + * Saves ~8.9 KB of flash. + */ + +#if defined(USE_RP2040) && !defined(USE_FULL_PRINTF) +#include +#include +#include + +namespace esphome::rp2040 {} + +static constexpr size_t PRINTF_BUFFER_SIZE = 512; + +// These stubs are essentially dead code at runtime — ESPHome uses its own +// logging through snprintf/vsnprintf, not libc printf. +// The buffer overflow check is purely defensive and should never trigger. +static int write_printf_buffer(FILE *stream, char *buf, int len) { + if (len < 0) { + return len; + } + size_t write_len = len; + if (write_len >= PRINTF_BUFFER_SIZE) { + fwrite(buf, 1, PRINTF_BUFFER_SIZE - 1, stream); + abort(); + } + if (fwrite(buf, 1, write_len, stream) < write_len || ferror(stream)) { + return -1; + } + return len; +} + +// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) +extern "C" { + +int __wrap_vprintf(const char *fmt, va_list ap) { + char buf[PRINTF_BUFFER_SIZE]; + return write_printf_buffer(stdout, buf, vsnprintf(buf, sizeof(buf), fmt, ap)); +} + +int __wrap_printf(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int len = __wrap_vprintf(fmt, ap); + va_end(ap); + return len; +} + +int __wrap_fprintf(FILE *stream, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + char buf[PRINTF_BUFFER_SIZE]; + int len = write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap)); + va_end(ap); + return len; +} + +} // extern "C" +// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming) + +#endif // USE_RP2040 && !USE_FULL_PRINTF