ESPHome 2026.6.2
Loading...
Searching...
No Matches
pl2303.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#include "usb_uart.h"
3#include "usb/usb_host.h"
4#include "esphome/core/log.h"
5
6namespace esphome::usb_uart {
7
8// Control request types
9static constexpr uint8_t SET_LINE_REQUEST_TYPE = 0x21;
10static constexpr uint8_t SET_LINE_REQUEST = 0x20;
11
12static constexpr uint8_t SET_CONTROL_REQUEST_TYPE = 0x21;
13static constexpr uint8_t SET_CONTROL_REQUEST = 0x22;
14static constexpr uint8_t CONTROL_DTR = 0x01;
15static constexpr uint8_t CONTROL_RTS = 0x02;
16
17static constexpr uint8_t VENDOR_WRITE_REQUEST_TYPE = 0x40;
18static constexpr uint8_t VENDOR_WRITE_REQUEST = 0x01;
19
20static constexpr uint8_t VENDOR_READ_REQUEST_TYPE = 0xc0;
21static constexpr uint8_t VENDOR_READ_REQUEST = 0x01;
22
23// Supported standard baud rates for direct encoding (TYPE_H, TYPE_HX, TYPE_HXD, TYPE_HXN)
24static const uint32_t SUPPORTED_BAUD_RATES[] = {
25 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200,
26 28800, 38400, 57600, 115200, 230400, 460800, 614400, 921600, 1228800, 2457600, 3000000, 6000000,
27};
28
29static const char *pl2303_type_name(Pl2303ChipType type) {
30 switch (type) {
31 case PL2303_TYPE_H:
32 return "H (legacy)";
33 case PL2303_TYPE_HX:
34 return "HX";
35 case PL2303_TYPE_TA:
36 return "TA";
37 case PL2303_TYPE_TB:
38 return "TB";
39 case PL2303_TYPE_HXD:
40 return "HXD";
41 case PL2303_TYPE_HXN:
42 return "G/HXN (newer)";
43 default:
44 return "unknown";
45 }
46}
47
48// Find nearest supported baud rate for direct encoding
49static uint32_t nearest_supported_baud(uint32_t baud) {
50 size_t n = sizeof(SUPPORTED_BAUD_RATES) / sizeof(SUPPORTED_BAUD_RATES[0]);
51 for (size_t i = 0; i < n; i++) {
52 if (SUPPORTED_BAUD_RATES[i] > baud) {
53 if (i == 0)
54 return SUPPORTED_BAUD_RATES[0];
55 uint32_t lower = SUPPORTED_BAUD_RATES[i - 1];
56 uint32_t upper = SUPPORTED_BAUD_RATES[i];
57 return (upper - baud) > (baud - lower) ? lower : upper;
58 }
59 }
60 return SUPPORTED_BAUD_RATES[n - 1];
61}
62
63// Direct encoding: little-endian 32-bit baud rate value
64static void encode_baud_direct(uint8_t buf[4], uint32_t baud) {
65 buf[0] = baud & 0xFF;
66 buf[1] = (baud >> 8) & 0xFF;
67 buf[2] = (baud >> 16) & 0xFF;
68 buf[3] = (baud >> 24) & 0xFF;
69}
70
71// Divisor encoding for TYPE_HX, TYPE_HXD: baudrate = 12M*32 / (mantissa * 4^exponent)
72static void encode_baud_divisor(uint8_t buf[4], uint32_t baud) {
73 static constexpr uint32_t BASELINE = 12000000 * 32;
74 uint32_t mantissa = BASELINE / baud;
75 if (mantissa == 0)
76 mantissa = 1;
77 uint8_t exponent = 0;
78 while (mantissa >= 512) {
79 if (exponent < 7) {
80 mantissa >>= 2;
81 exponent++;
82 } else {
83 mantissa = 511;
84 break;
85 }
86 }
87 buf[3] = 0x80;
88 buf[2] = 0;
89 buf[1] = (exponent << 1) | (mantissa >> 8);
90 buf[0] = mantissa & 0xFF;
91}
92
93// Alt divisor encoding for TYPE_TA, TYPE_TB: baudrate = 12M*32 / (mantissa * 2^exponent)
94static void encode_baud_divisor_alt(uint8_t buf[4], uint32_t baud) {
95 static constexpr uint32_t BASELINE = 12000000 * 32;
96 uint32_t mantissa = BASELINE / baud;
97 if (mantissa == 0)
98 mantissa = 1;
99 uint8_t exponent = 0;
100 while (mantissa >= 2048) {
101 if (exponent < 15) {
102 mantissa >>= 1;
103 exponent++;
104 } else {
105 mantissa = 2047;
106 break;
107 }
108 }
109 buf[3] = 0x80;
110 buf[2] = exponent & 0x01;
111 buf[1] = ((exponent & ~0x01) << 4) | (mantissa >> 8);
112 buf[0] = mantissa & 0xFF;
113}
114
115std::vector<CdcEps> USBUartTypePL2303::parse_descriptors(usb_device_handle_t dev_hdl) {
116 const usb_config_desc_t *config_desc;
117 const usb_device_desc_t *device_desc;
118 std::vector<CdcEps> cdc_devs{};
119
120 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
121 ESP_LOGE(TAG, "PL2303: get_device_descriptor failed");
122 return {};
123 }
124 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
125 ESP_LOGE(TAG, "PL2303: get_active_config_descriptor failed");
126 return {};
127 }
128
129 // Detect chip type from USB descriptor fields (mirrors pl2303_detect_type in Linux driver)
130 uint16_t bcd_device = device_desc->bcdDevice;
131 uint16_t bcd_usb = device_desc->bcdUSB;
132 uint8_t bmax_packet = device_desc->bMaxPacketSize0;
133 uint8_t bdev_class = device_desc->bDeviceClass;
134
135 if (bdev_class == 0x02 || bmax_packet != 0x40) {
137 } else {
138 switch (bcd_usb) {
139 case 0x0101:
140 case 0x0110:
141 this->chip_type_ = (bcd_device == 0x0400) ? PL2303_TYPE_HXD : PL2303_TYPE_HX;
142 break;
143 default:
144 // TA and TB are distinguishable by bcdDevice without any USB probe.
145 if (bcd_device == 0x0300) {
147 } else if (bcd_device == 0x0500) {
149 } else {
151 }
152 break;
153 }
154 }
155
156 ESP_LOGI(TAG, "PL2303 chip type: %s (bcdUSB=0x%04X bcdDevice=0x%04X bMaxPkt=%u)", pl2303_type_name(this->chip_type_),
157 bcd_usb, bcd_device, bmax_packet);
158
159 // PL2303 is single-port: find first interface with 2 bulk endpoints
160 int conf_offset = 0;
161 for (uint8_t i = 0; i < config_desc->bNumInterfaces; i++) {
162 int ep_offset = conf_offset;
163 const auto *intf = usb_parse_interface_descriptor(config_desc, i, 0, &conf_offset);
164 if (!intf)
165 break;
166 if (intf->bNumEndpoints < 2)
167 continue;
168
169 const usb_ep_desc_t *in_ep = nullptr;
170 const usb_ep_desc_t *out_ep = nullptr;
171 const usb_ep_desc_t *notify_ep = nullptr;
172
173 for (uint8_t e = 0; e < intf->bNumEndpoints; e++) {
174 ep_offset = conf_offset;
175 const auto *ep = usb_parse_endpoint_descriptor_by_index(intf, e, config_desc->wTotalLength, &ep_offset);
176 if (!ep)
177 break;
178 if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_BULK) {
179 if (ep->bEndpointAddress & usb_host::USB_DIR_IN) {
180 in_ep = ep;
181 } else {
182 out_ep = ep;
183 }
184 } else if (ep->bmAttributes == USB_BM_ATTRIBUTES_XFER_INT) {
185 notify_ep = ep;
186 }
187 }
188
189 if (in_ep && out_ep) {
190 cdc_devs.push_back(CdcEps{notify_ep, in_ep, out_ep, intf->bInterfaceNumber, intf->bInterfaceNumber});
191 break; // PL2303 is single-port
192 }
193 }
194
195 if (cdc_devs.empty())
196 ESP_LOGE(TAG, "PL2303: failed to find bulk IN+OUT endpoints");
197
198 return cdc_devs;
199}
200
202 if (this->channels_.empty())
203 return;
204
205 auto *channel = this->channels_[0];
206 bool is_legacy = (this->chip_type_ == PL2303_TYPE_H);
207 bool is_hxn = (this->chip_type_ == PL2303_TYPE_HXN);
208
210 if (!status.success)
211 ESP_LOGW(TAG, "PL2303: vendor init transfer failed");
212 };
213
214 // Init sequence for non-HXN chips (mirrors pl2303_startup in Linux driver):
215 // Read 0x8484, write 0x0404=0, read 0x8484, read 0x8383, read 0x8484,
216 // write 0x0404=1, read 0x8484, read 0x8383,
217 // write 0=1, write 1=0, write 2=0x24 (legacy) or 0x44 (HX+)
218 if (!is_hxn) {
219 uint8_t req = VENDOR_READ_REQUEST;
220 uint8_t wreq = VENDOR_WRITE_REQUEST;
221
222 // Fire-and-forget vendor reads: result discarded, chip requires this sequence.
223 // Pass a 1-byte buffer to set wLength=1 so the IN data stage is performed.
224 this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
225 this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 0x0404, 0, nop_cb);
226 this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
227 this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8383, 0, nop_cb, {0});
228 this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
229 this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 0x0404, 1, nop_cb);
230 this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8484, 0, nop_cb, {0});
231 this->control_transfer(VENDOR_READ_REQUEST_TYPE, req, 0x8383, 0, nop_cb, {0});
232 this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 0, 1, nop_cb);
233 this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 1, 0, nop_cb);
234 this->control_transfer(VENDOR_WRITE_REQUEST_TYPE, wreq, 2, is_legacy ? 0x24 : 0x44, nop_cb);
235 }
236
237 // Build 7-byte line coding structure:
238 // [0-3] baud rate (LE32), [4] stop bits, [5] parity, [6] data bits
239 uint8_t line_coding[7] = {};
240 uint32_t baud = channel->get_baud_rate();
241
242 // Choose baud encoding based on chip type
243 uint32_t nearest = nearest_supported_baud(baud);
244 if (baud == nearest || this->chip_type_ == PL2303_TYPE_HXN) {
245 encode_baud_direct(line_coding, baud);
246 } else if (this->chip_type_ == PL2303_TYPE_TA || this->chip_type_ == PL2303_TYPE_TB) {
247 encode_baud_divisor_alt(line_coding, baud);
248 } else {
249 encode_baud_divisor(line_coding, baud);
250 }
251
252 // Stop bits: 0=1, 1=1.5, 2=2
253 switch (channel->get_stop_bits()) {
254 case 2:
255 line_coding[4] = 2;
256 break;
257 default:
258 line_coding[4] = 0;
259 break;
260 }
261
262 // Parity: 0=none, 1=odd, 2=even, 3=mark, 4=space
263 switch (channel->parity_) {
265 line_coding[5] = 1;
266 break;
268 line_coding[5] = 2;
269 break;
271 line_coding[5] = 3;
272 break;
274 line_coding[5] = 4;
275 break;
276 default:
277 line_coding[5] = 0;
278 break;
279 }
280
281 // Data bits
282 line_coding[6] = channel->get_data_bits();
283
284 ESP_LOGD(TAG, "PL2303: SET_LINE_REQUEST baud=%u stop=%u parity=%u data=%u", baud, line_coding[4], line_coding[5],
285 line_coding[6]);
286
287 std::vector<uint8_t> lc_vec(line_coding, line_coding + 7);
288 uint16_t iface = channel->cdc_dev_.bulk_interface_number;
289 this->control_transfer(SET_LINE_REQUEST_TYPE, SET_LINE_REQUEST, 0, iface, nop_cb, lc_vec);
290
291 // Assert DTR + RTS
292 this->control_transfer(SET_CONTROL_REQUEST_TYPE, SET_CONTROL_REQUEST, CONTROL_DTR | CONTROL_RTS, iface, nop_cb);
293
294 this->start_channels_();
295}
296
297} // namespace esphome::usb_uart
298#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
uint8_t status
Definition bl0942.h:8
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={})
std::vector< USBUartChannel * > channels_
Definition usb_uart.h:204
void start_channels_()
Resets per-channel transfer flags and posts the first bulk IN transfer.
Definition usb_uart.cpp:532
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition pl2303.cpp:115
uint16_t type
std::function< void(const TransferStatus &)> transfer_cb_t
Definition usb_host.h:85
static void uint32_t