ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ch34x.cpp
Go to the documentation of this file.
1#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
2 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
3#include "usb_uart.h"
4#include "usb/usb_host.h"
5#include "esphome/core/log.h"
6
8
9#include <algorithm>
10#include <iterator>
11
12namespace esphome::usb_uart {
13
14using namespace bytebuffer;
15
16static constexpr uint8_t CH341_REQ_MODEM_CTRL = 0xA4;
17// Modem control bits, sent inverted
18static constexpr uint8_t CH341_BIT_DTR = 1 << 5;
19static constexpr uint8_t CH341_BIT_RTS = 1 << 6;
20
21static constexpr uint8_t CH341_REQ_WRITE_REG = 0x9A;
22static constexpr uint8_t CH341_REG_FLOW_CTL = 0x27;
23static constexpr uint8_t CH341_FLOW_CTL_NONE = 0x00;
24static constexpr uint8_t CH341_FLOW_CTL_RTSCTS = 0x01;
25// The single-port chips, which have the flow control register
26static constexpr uint16_t CH341_PIDS[] = {0x5523, 0x7522, 0x7523};
27
28struct CH34xEntry {
29 const char *name;
30 uint16_t pid;
31 uint8_t byte_idx; // which status.data[] byte to inspect
32 uint8_t mask; // bitmask applied before comparison
33 uint8_t match; // 0xFF = wildcard (default/fallthrough for this PID)
34 CH34xChipType chiptype;
35 uint8_t num_ports;
36};
37
38static const CH34xEntry CH34X_TABLE[] = {
39 {"CH342K", 0x55D2, 1, 0xFF, 0x41, CHIP_CH342K, 2},
40 {"CH342F", 0x55D2, 1, 0xFF, 0xFF, CHIP_CH342F, 2},
41 {"CH343J", 0x55D3, 1, 0xFF, 0x02, CHIP_CH343J, 1},
42 {"CH343K", 0x55D3, 1, 0xFF, 0x01, CHIP_CH343K, 1},
43 {"CH343G_AUTOBAUD", 0x55D3, 1, 0xFF, 0x18, CHIP_CH343G_AUTOBAUD, 1},
44 {"CH343GP", 0x55D3, 1, 0xFF, 0xFF, CHIP_CH343GP, 1},
45 {"CH9102X", 0x55D4, 1, 0xFF, 0x09, CHIP_CH9102X, 1},
46 {"CH9102F", 0x55D4, 1, 0xFF, 0xFF, CHIP_CH9102F, 1},
47 {"CH344L", 0x55D5, 1, 0xFF, 0xC0, CHIP_CH344L, 4}, // CH344L vs CH344L_V2 resolved below
48 {"CH344Q", 0x55D5, 1, 0xFF, 0xFF, CHIP_CH344Q, 4},
49 {"CH9103M", 0x55D7, 1, 0xFF, 0xFF, CHIP_CH9103M, 2},
50 {"CH9101RY", 0x55D8, 1, 0xFF, 0x0A, CHIP_CH9101RY, 1},
51 {"CH9101UH", 0x55D8, 1, 0xFF, 0xFF, CHIP_CH9101UH, 1},
52 {"CH347TF", 0x55DB, 1, 0xFF, 0xFF, CHIP_CH347TF, 1},
53 {"CH347TF", 0x55DD, 1, 0xFF, 0xFF, CHIP_CH347TF, 1},
54 {"CH347TF", 0x55DA, 1, 0xFF, 0xFF, CHIP_CH347TF, 2},
55 {"CH347TF", 0x55DE, 1, 0xFF, 0xFF, CHIP_CH347TF, 2},
56 {"CH339W", 0x55E7, 1, 0xFF, 0xFF, CHIP_CH339W, 1},
57 {"CH9104L", 0x55DF, 1, 0xFF, 0xFF, CHIP_CH9104L, 4},
58 {"CH9111L_M0", 0x55E9, 1, 0xFF, 0xFF, CHIP_CH9111L_M0, 1},
59 {"CH9111L_M1", 0x55EA, 1, 0xFF, 0xFF, CHIP_CH9111L_M1, 1},
60 {"CH9114L", 0x55E8, 2, 0xFF, 0x48, CHIP_CH9114L, 4},
61 {"CH9114W", 0x55E8, 2, 0xFF, 0x49, CHIP_CH9114W, 4},
62 {"CH9114F", 0x55E8, 2, 0xFF, 0x4A, CHIP_CH9114F, 4},
63 {"CH346C_M1", 0x55EB, 4, 0x01, 0x01, CHIP_CH346C_M1, 1},
64 {"CH346C_M0", 0x55EB, 4, 0x01, 0xFF, CHIP_CH346C_M0, 1},
65 {"CH346C_M2", 0x55EC, 1, 0xFF, 0xFF, CHIP_CH346C_M2, 2},
66};
67
68bool USBUartTypeCH34X::config_device_step(uint8_t step, bool ok, const uint8_t *response) {
69 if (step == 0) {
70 // Vendor-specific GET_CHIP_VERSION request (bRequest=0x5F): returns chip ID bytes
71 // used to distinguish CH34x variants sharing the same PID.
72 this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_IN, 0x5F, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0});
73 return true;
74 }
75 // step 1: parse the chip-version response (falling back to "unknown" on failure).
76 if (!ok) {
77 ESP_LOGE(TAG, "CH34x chip detection failed");
78 return false;
79 }
80 CH34xChipType chiptype = CHIP_UNKNOWN;
81 uint8_t num_ports = 1;
82 for (const auto &e : CH34X_TABLE) {
83 if (e.pid != this->pid_)
84 continue;
85 if (e.match != 0xFF && (response[e.byte_idx] & e.mask) != e.match)
86 continue;
87 chiptype = e.chiptype;
88 num_ports = e.num_ports;
89 break;
90 }
91 // CH344L vs CH344L_V2 requires chipver (data[0]) in addition to chiptype (data[1])
92 if (chiptype == CHIP_CH344L && (response[0] & 0xF0) != 0x40)
93 chiptype = CHIP_CH344L_V2;
94 const char *name = "unknown";
95 for (const auto &e : CH34X_TABLE) {
96 if (e.chiptype == chiptype) {
97 name = e.name;
98 break;
99 }
100 }
101 this->chiptype_ = chiptype;
102 this->chip_name_ = name;
103 this->num_ports_ = num_ports;
104 ESP_LOGD(TAG, "CH34x chip: %s, ports: %u", name, this->num_ports_);
105 return false;
106}
107
110 ESP_LOGCONFIG(TAG, " CH34x chip: %s", this->chip_name_);
111}
112
113bool USBUartTypeCH34X::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
114 const uint8_t *response) {
115 // TODO: flow control on the multi-port chips (PIDs 0x55Dx)
116 const bool has_flow_ctl_reg =
117 std::find(std::begin(CH341_PIDS), std::end(CH341_PIDS), this->pid_) != std::end(CH341_PIDS);
118 if (!has_flow_ctl_reg && step >= 1)
119 step++;
120
121 uint8_t cmd = 0xA1 + channel->index_;
122 if (channel->index_ >= 2)
123 cmd += 0xE;
124 switch (step) {
125 case 0: {
126 uint8_t divisor = 7;
127 uint32_t clk = 12000000;
128
129 auto baud_rate = channel->baud_rate_;
130 if (baud_rate < 256000) {
131 if (baud_rate > 6000000 / 255) {
132 divisor = 3;
133 clk = 6000000;
134 } else if (baud_rate > 750000 / 255) {
135 divisor = 2;
136 clk = 750000;
137 } else if (baud_rate > 93750 / 255) {
138 divisor = 1;
139 clk = 93750;
140 } else {
141 divisor = 0;
142 clk = 11719;
143 }
144 }
145 ESP_LOGV(TAG, "baud_rate: %" PRIu32 ", divisor: %d, clk: %" PRIu32, baud_rate, divisor, clk);
146 auto factor = static_cast<uint8_t>(clk / baud_rate);
147 if (factor == 0 || factor == 0xFF) {
148 ESP_LOGE(TAG, "Invalid baud rate %" PRIu32, baud_rate);
149 return false;
150 }
151 if ((clk / factor - baud_rate) > (baud_rate - clk / (factor + 1)))
152 factor++;
153 factor = 256 - factor;
154
155 uint16_t value = 0xC0;
157 value |= 4;
158 // Parity enable, then 0 = odd, 1 = even, 2 = mark, 3 = space
159 const LineParity parity = channel->line_parity_();
160 if (parity != LineParity::LINE_PARITY_NONE)
161 value |= 8 | ((static_cast<uint8_t>(parity) - 1) << 4);
162 value |= channel->data_bits_ - 5;
163 value <<= 8;
164 value |= 0x8C;
165 this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor);
166 return true;
167 }
168 case 1: {
169 // Re-applied on every settings change. The chip has no XON/XOFF.
170 const bool rts_cts = channel->flow_control_ == uart::UART_FLOW_CONTROL_RTS_CTS;
171 const uint8_t flow_ctl = rts_cts ? CH341_FLOW_CTL_RTSCTS : CH341_FLOW_CTL_NONE;
172 ESP_LOGD(TAG, "FLOW_CTL: RTS/CTS=%s", ONOFF(rts_cts));
173 this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, CH341_REQ_WRITE_REG,
174 (CH341_REG_FLOW_CTL << 8) | CH341_REG_FLOW_CTL, (flow_ctl << 8) | flow_ctl);
175 return true;
176 }
177 case 2:
178 this->modem_control_transfer(channel);
179 return true;
180 default:
181 return false;
182 }
183}
184
186 uint8_t request = CH341_REQ_MODEM_CTRL + channel->index_;
187 if (channel->index_ >= 2)
188 request += 0xE;
189 const uint8_t control = (channel->dtr_ ? CH341_BIT_DTR : 0) | (channel->rts_ ? CH341_BIT_RTS : 0);
190 ESP_LOGD(TAG, "MODEM_CTRL: DTR=%s RTS=%s", ONOFF(channel->dtr_), ONOFF(channel->rts_));
191 // Integer promotion makes this 16 bits wide, as in Linux: 0xFF9F with both lines on
192 this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, request, static_cast<uint16_t>(~control), 0);
193}
194
195} // namespace esphome::usb_uart
196
197#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
198 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
LineStopBits line_stop_bits_() const
Definition usb_uart.cpp:744
void config_transfer_(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const std::vector< uint8_t > &data={}, uint8_t *dest=nullptr)
Definition usb_uart.cpp:604
void modem_control_transfer(USBUartChannelBase *channel) override
Definition ch34x.cpp:185
bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override
Definition ch34x.cpp:68
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition ch34x.cpp:113
static void uint32_t