ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
ft23xx.cpp
Go to the documentation of this file.
1#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
2#include "usb_uart.h"
3#include "usb/usb_host.h"
4#include "esphome/core/log.h"
6
8
9namespace esphome::usb_uart {
10
11using namespace bytebuffer;
12
13// FTDI chip family identifiers. These map to USB device bcdDevice values
14// and determine how baudrate divisors and clock sources are calculated.
25
26static int ftdi_to_clkbits_am(int baudrate, uint32_t *encoded_divisor) {
27 static const char FRAC_CODE[8] = {0, 3, 2, 4, 1, 5, 6, 7};
28 static const char AM_ADJUST_UP[8] = {0, 0, 0, 1, 0, 3, 2, 1};
29 static const char AM_ADJUST_DN[8] = {0, 0, 0, 1, 0, 1, 2, 3};
30 int divisor, best_divisor, best_baud, best_baud_diff;
31 int i;
32 divisor = 24000000 / baudrate;
33
34 divisor -= AM_ADJUST_DN[divisor & 7];
35
36 best_divisor = 0;
37 best_baud = 0;
38 best_baud_diff = 0;
39 for (i = 0; i < 2; i++) {
40 int try_divisor = divisor + i;
41 int baud_estimate;
42 int baud_diff;
43
44 if (try_divisor <= 8) {
45 try_divisor = 8;
46 } else if (divisor < 16) {
47 try_divisor = 16;
48 } else {
49 try_divisor += AM_ADJUST_UP[try_divisor & 7];
50 if (try_divisor > 0x1FFF8) {
51 // Round down to maximum supported divisor value (for AM)
52 try_divisor = 0x1FFF8;
53 }
54 }
55 baud_estimate = (24000000 + (try_divisor / 2)) / try_divisor;
56 if (baud_estimate < baudrate) {
57 baud_diff = baudrate - baud_estimate;
58 } else {
59 baud_diff = baud_estimate - baudrate;
60 }
61 if (i == 0 || baud_diff < best_baud_diff) {
62 best_divisor = try_divisor;
63 best_baud = baud_estimate;
64 best_baud_diff = baud_diff;
65 if (baud_diff == 0) {
66 break;
67 }
68 }
69 }
70 *encoded_divisor = (best_divisor >> 3) | (FRAC_CODE[best_divisor & 7] << 14);
71 if (*encoded_divisor == 1) {
72 *encoded_divisor = 0; // 3000000 baud
73 } else if (*encoded_divisor == 0x4001) {
74 *encoded_divisor = 1; // 2000000 baud (BM only)
75 }
76 return best_baud;
77}
78
79static int ftdi_to_clkbits(int baudrate, unsigned int clk, int clk_div, uint32_t *encoded_divisor) {
80 static const char FRAC_CODE[8] = {0, 3, 2, 4, 1, 5, 6, 7};
81 int best_baud = 0;
82 int divisor, best_divisor;
83 if (baudrate >= clk / clk_div) {
84 *encoded_divisor = 0;
85 best_baud = clk / clk_div;
86 } else if (baudrate >= clk / (clk_div + clk_div / 2)) {
87 *encoded_divisor = 1;
88 best_baud = clk / (clk_div + clk_div / 2);
89 } else if (baudrate >= clk / (2 * clk_div)) {
90 *encoded_divisor = 2;
91 best_baud = clk / (2 * clk_div);
92 } else {
93 divisor = clk * 16 / clk_div / baudrate;
94 if (divisor & 1) {
95 best_divisor = divisor / 2 + 1;
96 } else {
97 best_divisor = divisor / 2;
98 }
99 if (best_divisor > 0x20000)
100 best_divisor = 0x1ffff;
101 best_baud = clk * 16 / clk_div / best_divisor;
102 if (best_baud & 1) {
103 best_baud = best_baud / 2 + 1;
104 } else {
105 best_baud = best_baud / 2;
106 }
107 *encoded_divisor = (best_divisor >> 3) | (FRAC_CODE[best_divisor & 0x7] << 14);
108 }
109 return best_baud;
110}
111
112static int ftdi_convert_baudrate(int baudrate, uint8_t chip_type, uint8_t channel_index, uint16_t *value,
113 uint16_t *index) {
114 int best_baud;
115 uint32_t encoded_divisor;
116
117 if (baudrate <= 0) {
118 return -1;
119 }
120
121 static constexpr uint32_t H_CLK = 120000000;
122 static constexpr uint32_t C_CLK = 48000000;
123 if ((chip_type == TYPE_2232H) || (chip_type == TYPE_4232H) || (chip_type == TYPE_232H)) {
124 if (baudrate * 10 > H_CLK / 0x3fff) {
125 best_baud = ftdi_to_clkbits(baudrate, H_CLK, 10, &encoded_divisor);
126 encoded_divisor |= 0x20000; /* switch on CLK/10*/
127 } else {
128 best_baud = ftdi_to_clkbits(baudrate, C_CLK, 16, &encoded_divisor);
129 }
130 } else if ((chip_type == TYPE_BM) || (chip_type == TYPE_2232C) || (chip_type == TYPE_R) || (chip_type == TYPE_230X)) {
131 best_baud = ftdi_to_clkbits(baudrate, C_CLK, 16, &encoded_divisor);
132 } else {
133 best_baud = ftdi_to_clkbits_am(baudrate, &encoded_divisor);
134 }
135
136 *value = (uint16_t) (encoded_divisor & 0xFFFF);
137 if (chip_type == TYPE_2232H || chip_type == TYPE_4232H || chip_type == TYPE_232H) {
138 *index = (uint16_t) (encoded_divisor >> 8);
139 *index &= 0xFF00;
140 *index |= (channel_index + 1);
141 } else {
142 *index = (uint16_t) (encoded_divisor >> 16);
143 }
144
145 return best_baud;
146}
147
148static optional<CdcEps> get_uart(const usb_config_desc_t *config_desc, uint8_t intf_idx) {
149 int conf_offset, ep_offset;
150 CdcEps eps{};
151
152 const auto *intf_desc = usb_parse_interface_descriptor(config_desc, intf_idx, 0, &conf_offset);
153 if (!intf_desc) {
154 ESP_LOGD(TAG, "usb_parse_interface_descriptor failed for intf_idx=%d (end of interfaces)", intf_idx);
155 return nullopt;
156 }
157 ESP_LOGD(TAG,
158 "intf_desc [idx=%d]: bInterfaceClass=%02X, bInterfaceSubClass=%02X, bInterfaceProtocol=%02X, "
159 "bNumEndpoints=%d, bInterfaceNumber=%d",
160 intf_idx, intf_desc->bInterfaceClass, intf_desc->bInterfaceSubClass, intf_desc->bInterfaceProtocol,
161 intf_desc->bNumEndpoints, intf_desc->bInterfaceNumber);
162
163 std::vector<const usb_ep_desc_t *> endpoints;
164 for (uint8_t i = 0; i != intf_desc->bNumEndpoints; i++) {
165 ep_offset = conf_offset;
166 const auto *ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &ep_offset);
167 if (!ep) {
168 ESP_LOGE(TAG, "Ran out of endpoints at %d before finding all %d endpoints", i, intf_desc->bNumEndpoints);
169 return nullopt;
170 }
171 ESP_LOGD(TAG, "ep: bEndpointAddress=%02X, bmAttributes=%02X", ep->bEndpointAddress, ep->bmAttributes);
172
173 if (ep->bmAttributes != 0x2) {
174 ESP_LOGD(TAG, "Skipping non-bulk endpoint: %02X", ep->bEndpointAddress);
175 continue;
176 }
177 endpoints.push_back(ep);
178 }
179
180 const usb_ep_desc_t *ep1 = nullptr;
181 const usb_ep_desc_t *ep2 = nullptr;
182 for (const auto *ep : endpoints) {
183 if (ep1 == nullptr) {
184 ep1 = ep;
185 } else if (ep2 == nullptr) {
186 ep2 = ep;
187 break;
188 }
189 }
190
191 if (ep1 == nullptr || ep2 == nullptr) {
192 ESP_LOGD(TAG, "Interface %d has %zu endpoints (need 2 bulk endpoints)", intf_idx, endpoints.size());
193 return nullopt;
194 }
195
196 ESP_LOGD(TAG, "Interface %d: ep1=0x%02X, ep2=0x%02X", intf_idx, ep1->bEndpointAddress, ep2->bEndpointAddress);
197
198 if (ep1->bEndpointAddress & usb_host::USB_DIR_IN) {
199 eps.in_ep = ep1;
200 eps.out_ep = ep2;
201 ESP_LOGD(TAG, "ep1 is IN (RX): ep1=0x%02X (in_ep), ep2=0x%02X (out_ep)", ep1->bEndpointAddress,
202 ep2->bEndpointAddress);
203 } else {
204 eps.out_ep = ep1;
205 eps.in_ep = ep2;
206 ESP_LOGD(TAG, "ep1 is OUT (TX): ep1=0x%02X (out_ep), ep2=0x%02X (in_ep)", ep1->bEndpointAddress,
207 ep2->bEndpointAddress);
208 }
209
210 eps.bulk_interface_number = intf_desc->bInterfaceNumber;
211 return eps;
212}
213
214std::vector<CdcEps> USBUartTypeFT23XX::parse_descriptors(usb_device_handle_t dev_hdl) {
215 const usb_config_desc_t *config_desc;
216 const usb_device_desc_t *device_desc;
217 std::vector<CdcEps> cdc_devs{};
218 std::string type_string;
219
220 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
221 ESP_LOGE(TAG, "get_device_descriptor failed");
222 return {};
223 }
224 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
225 ESP_LOGE(TAG, "get_active_config_descriptor failed");
226 return {};
227 }
228 if (device_desc->bcdDevice == 0x400 || (device_desc->bcdDevice == 0x200 && device_desc->iSerialNumber == 0)) {
229 this->chip_type_ = TYPE_BM;
230 type_string = "BM type chip";
231 } else if (device_desc->bcdDevice == 0x200) {
232 this->chip_type_ = TYPE_AM;
233 type_string = "AM type chip";
234 } else if (device_desc->bcdDevice == 0x500) {
235 this->chip_type_ = TYPE_2232C;
236 type_string = "2232C chip";
237 } else if (device_desc->bcdDevice == 0x600) {
238 this->chip_type_ = TYPE_R;
239 type_string = "type R chip";
240 } else if (device_desc->bcdDevice == 0x700) {
241 this->chip_type_ = TYPE_2232H;
242 type_string = "2232H chip";
243 } else if (device_desc->bcdDevice == 0x800) {
244 this->chip_type_ = TYPE_4232H;
245 type_string = "4232H chip";
246 } else if (device_desc->bcdDevice == 0x900) {
247 this->chip_type_ = TYPE_232H;
248 type_string = "232H type chip";
249 } else if (device_desc->bcdDevice == 0x1000) {
250 this->chip_type_ = TYPE_230X;
251 type_string = "230x chip";
252 }
253
254 ESP_LOGD(TAG, "Found FTDI %s based device", type_string.c_str());
255 for (size_t intf_idx = 0; intf_idx < this->channels_.size(); intf_idx++) {
256 if (auto eps = get_uart(config_desc, static_cast<uint8_t>(intf_idx))) {
257 cdc_devs.push_back(*eps);
258 ESP_LOGD(TAG, "Found CDC interface at USB interface index %zu", intf_idx);
259 }
260 }
261 return cdc_devs;
262}
263
265 usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
266 if (!status.success) {
267 ESP_LOGE(TAG, "Reset failed, status=%s", esp_err_to_name(status.error_code));
268 channel->initialised_.store(false);
269 } else {
270 ESP_LOGD(TAG, "Reset successful, setting baudrate...");
271 this->set_baudrate_(channel);
272 }
273 };
274 bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x00, 0x00,
275 channel->cdc_dev_.bulk_interface_number + 1, callback);
276 if (!ok) {
277 ESP_LOGE(TAG, "Reset control_transfer submit failed");
278 channel->initialised_.store(false);
279 return -1;
280 }
281 return 0;
282}
283
285 usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
286 if (!status.success) {
287 ESP_LOGE(TAG, "Set baudrate failed, status=%s", esp_err_to_name(status.error_code));
288 channel->initialised_.store(false);
289 } else {
290 ESP_LOGD(TAG, "Baudrate %d set, setting line properties...", channel->baud_rate_);
291 this->set_line_properties_(channel);
292 }
293 };
294 if (baudrate == 0) {
295 baudrate = channel->baud_rate_;
296 }
297 uint16_t value, ftdi_index;
298 ftdi_convert_baudrate(baudrate, this->chip_type_, channel->index_, &value, &ftdi_index);
299 ESP_LOGD(TAG, "Baudrate: %d, value=0x%04X, ftdi_index=0x%04X", baudrate, value, ftdi_index);
300 uint16_t usb_index = (ftdi_index & 0xFF00) | (channel->cdc_dev_.bulk_interface_number + 1);
301 bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x03, value, usb_index, callback);
302 if (!ok) {
303 ESP_LOGE(TAG, "Set baudrate control_transfer submit failed");
304 channel->initialised_.store(false);
305 return -1;
306 }
307 return 0;
308}
309
311 usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
312 if (!status.success) {
313 ESP_LOGE(TAG, "Set line properties failed, status=%s", esp_err_to_name(status.error_code));
314 channel->initialised_.store(false);
315 return;
316 }
317 ESP_LOGD(TAG, "Line properties set, setting modem control...");
318 this->set_dtr_rts_(channel);
319 };
320
321 uint16_t value = channel->data_bits_;
322
323 switch (channel->parity_) {
325 value |= (0x00 << 8);
326 break;
328 value |= (0x01 << 8);
329 break;
331 value |= (0x02 << 8);
332 break;
334 value |= (0x03 << 8);
335 break;
337 value |= (0x04 << 8);
338 break;
339 }
340
341 switch (channel->stop_bits_) {
343 value |= (0x00 << 11);
344 break;
346 value |= (0x01 << 11);
347 break;
349 value |= (0x02 << 11);
350 break;
351 }
352
353 value |= (0x00 << 14);
354
355 bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x04, value,
356 channel->cdc_dev_.bulk_interface_number + 1, callback);
357 if (!ok) {
358 ESP_LOGE(TAG, "Set line properties control_transfer submit failed");
359 channel->initialised_.store(false);
360 return -1;
361 }
362 return 0;
363}
364
366 usb_host::transfer_cb_t callback = [channel, this](const usb_host::TransferStatus &status) {
367 if (!status.success) {
368 ESP_LOGE(TAG, "Set modem control failed, status=%s", esp_err_to_name(status.error_code));
369 channel->initialised_.store(false);
370 return;
371 }
372 ESP_LOGD(TAG, "Modem control set for channel %d, starting input...", channel->index_);
373 channel->initialised_.store(true);
374 this->start_input(channel);
375 uint8_t next_index = channel->index_ + 1;
376 if (next_index < this->channels_.size()) {
377 USBUartChannel *next_channel = this->channels_[next_index];
378 ESP_LOGD(TAG, "Configuring next channel %d", next_channel->index_);
379 this->reset_(next_channel);
380 return;
381 } else {
382 ESP_LOGI(TAG, "All channels configured");
383 }
384 };
385
386 bool ok = this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, 0x01, 0x0000,
387 channel->cdc_dev_.bulk_interface_number + 1, callback);
388 if (!ok) {
389 ESP_LOGE(TAG, "Set modem control control_transfer submit failed");
390 channel->initialised_.store(false);
391 return -1;
392 }
393 return 0;
394}
395
397 if (!channel->initialised_.load() || channel->input_started_.load())
398 return;
399
400 const auto *ep = channel->cdc_dev_.in_ep;
401
402 auto callback = [this, channel](const usb_host::TransferStatus &status) {
403 if (!status.success) {
404 ESP_LOGE(TAG, "RX Transfer failed, status=%s", esp_err_to_name(status.error_code));
405 channel->input_started_.store(false);
406 return;
407 }
408
409 size_t uart_data_len = (status.data_len > 2) ? (status.data_len - 2) : 0;
410
411 if (uart_data_len > 0) {
412 ESP_LOGV(TAG, "RX callback: Received %zu bytes, channel=%d", uart_data_len, channel->index_);
413 if (!channel->dummy_receiver_) {
414 // Copy the entire received UART payload into the ring buffer in one
415 // operation to avoid per-byte overhead and reduce the chance of
416 // heap activity in hot paths.
417 channel->input_buffer_.push(status.data + 2, uart_data_len);
418#ifdef USE_UART_DEBUGGER
419 if (channel->debug_) {
420 // Debug path creates a temporary vector for logging only; this is
421 // acceptable because debug mode is opt-in and not used in release.
423 std::vector<uint8_t>(status.data + 2, status.data + 2 + uart_data_len), ',',
424 channel->debug_prefix_);
425 }
426#endif
427 }
428 } else {
429 ESP_LOGVV(TAG, "RX: Status packet, modem=0x%02X line=0x%02X, ch=%d", status.data[0], status.data[1],
430 channel->index_);
431 }
432
433 channel->input_started_.store(false);
434 if (channel->dummy_receiver_ ||
435 channel->input_buffer_.get_free_space() >= channel->cdc_dev_.in_ep->wMaxPacketSize) {
436 this->start_input(channel);
437 }
438 };
439
440 channel->input_started_.store(true);
441 this->transfer_in(ep->bEndpointAddress, callback, ep->wMaxPacketSize);
442}
443
445 if (!this->channels_.empty() && this->channels_[0]->initialised_.load()) {
446 this->reset_(this->channels_[0]);
447 }
448
449 for (auto *channel : this->channels_) {
450 if (!channel->initialised_.load())
451 continue;
452 channel->input_started_.store(false);
453 channel->output_started_.store(false);
454 }
455}
456
457} // namespace esphome::usb_uart
458#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32P4
uint8_t status
Definition bl0942.h:8
static void log_hex(UARTDirection direction, std::vector< uint8_t > bytes, uint8_t separator, StringRef prefix=StringRef())
Log the bytes as hex values, separated by the provided separator character.
bool control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index, const transfer_cb_t &callback, const std::vector< uint8_t > &data={})
bool transfer_in(uint8_t ep_address, const transfer_cb_t &callback, uint16_t length)
Performs a transfer input operation.
void push(uint8_t item)
Definition usb_uart.cpp:108
size_t get_free_space() const
Definition usb_uart.h:92
std::atomic< bool > input_started_
Definition usb_uart.h:174
std::atomic< bool > initialised_
Definition usb_uart.h:176
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:202
void start_input(USBUartChannel *channel)
Definition ft23xx.cpp:396
int set_baudrate_(USBUartChannel *channel, uint32_t baudrate=0)
Definition ft23xx.cpp:284
int reset_(USBUartChannel *channel)
Definition ft23xx.cpp:264
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition ft23xx.cpp:214
int set_dtr_rts_(USBUartChannel *channel)
Definition ft23xx.cpp:365
int set_line_properties_(USBUartChannel *channel)
Definition ft23xx.cpp:310
std::function< void(const TransferStatus &)> transfer_cb_t
Definition usb_host.h:85
@ UART_CONFIG_STOP_BITS_1_5
Definition usb_uart.h:78
static void uint32_t
const usb_ep_desc_t * in_ep
Definition usb_uart.h:32