ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
cp210x.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 <cinttypes>
10#include <iterator>
11
12namespace esphome::usb_uart {
13
14using namespace bytebuffer;
19static constexpr uint8_t IFC_ENABLE = 0x00; // Enable or disable the interface.
20static constexpr uint8_t SET_BAUDDIV = 0x01; // Set the baud rate divisor.
21static constexpr uint8_t GET_BAUDDIV = 0x02; // Get the baud rate divisor.
22static constexpr uint8_t SET_LINE_CTL = 0x03; // Set the line control.
23static constexpr uint8_t GET_LINE_CTL = 0x04; // Get the line control.
24static constexpr uint8_t SET_BREAK = 0x05; // Set a BREAK.
25static constexpr uint8_t IMM_CHAR = 0x06; // Send character out of order.
26static constexpr uint8_t SET_MHS = 0x07; // Set modem handshaking.
27static constexpr uint8_t GET_MDMSTS = 0x08; // Get modem status.
28static constexpr uint8_t SET_XON = 0x09; // Emulate XON.
29static constexpr uint8_t SET_XOFF = 0x0A; // Emulate XOFF.
30static constexpr uint8_t SET_EVENTMASK = 0x0B; // Set the event mask.
31static constexpr uint8_t GET_EVENTMASK = 0x0C; // Get the event mask.
32static constexpr uint8_t GET_EVENTSTATE = 0x16; // Get the event state.
33static constexpr uint8_t SET_RECEIVE = 0x17; // Set receiver max timeout.
34static constexpr uint8_t GET_RECEIVE = 0x18; // Get receiver max timeout.
35static constexpr uint8_t SET_CHAR = 0x0D; // Set special character individually.
36static constexpr uint8_t GET_CHARS = 0x0E; // Get special characters.
37static constexpr uint8_t GET_PROPS = 0x0F; // Get properties.
38static constexpr uint8_t GET_COMM_STATUS = 0x10; // Get the serial status.
39static constexpr uint8_t RESET = 0x11; // Reset.
40static constexpr uint8_t PURGE = 0x12; // Purge.
41static constexpr uint8_t SET_FLOW = 0x13; // Set flow control.
42static constexpr uint8_t GET_FLOW = 0x14; // Get flow control.
43static constexpr uint8_t EMBED_EVENTS = 0x15; // Control embedding of events in the data stream.
44static constexpr uint8_t GET_BAUDRATE = 0x1D; // Get the baud rate.
45static constexpr uint8_t SET_BAUDRATE = 0x1E; // Set the baud rate.
46static constexpr uint8_t SET_CHARS = 0x19; // Set special characters.
47static constexpr uint8_t VENDOR_SPECIFIC = 0xFF; // Vendor specific command.
48
49// VENDOR_SPECIFIC wValue reads
50static constexpr uint16_t GET_FW_VER_2N = 0x0010;
51static constexpr uint16_t GET_PARTNUM = 0x370B;
52
53static constexpr uint8_t PARTNUM_CP2102N_QFN28 = 0x20;
54static constexpr uint8_t PARTNUM_CP2102N_QFN24 = 0x21;
55static constexpr uint8_t PARTNUM_CP2102N_QFN20 = 0x22;
56
57// CP2102N firmware up to 1.0.4 reads xon_limit as flow_replace (erratum E104)
58static constexpr uint32_t CP2102N_LAST_FW_WITHOUT_FLOW_CONTROL = 0x010004;
59
60// SET_MHS value bits. The high byte selects which lines the low byte changes.
61static constexpr uint16_t CONTROL_DTR = 0x0001;
62static constexpr uint16_t CONTROL_RTS = 0x0002;
63static constexpr uint16_t CONTROL_WRITE_DTR = 0x0100;
64static constexpr uint16_t CONTROL_WRITE_RTS = 0x0200;
65
66// CP210xFlowBlock::control_handshake bits
67static constexpr uint32_t SERIAL_DTR_MASK = 0x03;
68static constexpr uint32_t SERIAL_DTR_INACTIVE = 0x00;
69static constexpr uint32_t SERIAL_DTR_ACTIVE = 0x01;
70static constexpr uint32_t SERIAL_CTS_HANDSHAKE = 1 << 3;
71static constexpr uint32_t SERIAL_DSR_HANDSHAKE = 1 << 4;
72static constexpr uint32_t SERIAL_DCD_HANDSHAKE = 1 << 5;
73static constexpr uint32_t SERIAL_DSR_SENSITIVITY = 1 << 6;
74
75// CP210xFlowBlock::flow_replace bits
76static constexpr uint32_t SERIAL_AUTO_TRANSMIT = 1 << 0;
77static constexpr uint32_t SERIAL_AUTO_RECEIVE = 1 << 1;
78static constexpr uint32_t SERIAL_RTS_MASK = 0xC0;
79static constexpr uint32_t SERIAL_RTS_INACTIVE = 0x00;
80static constexpr uint32_t SERIAL_RTS_ACTIVE = 0x40;
81static constexpr uint32_t SERIAL_RTS_FLOW_CTL = 0x80;
82
83// Receive queue levels at which the chip sends XON and XOFF
84static constexpr uint32_t XON_XOFF_LIMIT = 128;
85// SET_CHARS block: EOF, error, break, event, XON and XOFF characters. XON and XOFF are the
86// termios defaults DC1 and DC3.
87static constexpr uint8_t SPECIAL_CHARS[] = {0, 0, 0, 0, 0x11, 0x13};
88
89// Put DTR and RTS into the block. Under RTS/CTS an asserted RTS hands the line to the chip.
90static void set_block_modem_lines(CP210xFlowBlock &block, bool dtr, bool rts) {
91 block.control_handshake =
92 (block.control_handshake & ~SERIAL_DTR_MASK) | (dtr ? SERIAL_DTR_ACTIVE : SERIAL_DTR_INACTIVE);
93 uint32_t rts_bits = SERIAL_RTS_INACTIVE;
94 if (rts)
95 rts_bits = (block.control_handshake & SERIAL_CTS_HANDSHAKE) ? SERIAL_RTS_FLOW_CTL : SERIAL_RTS_ACTIVE;
96 block.flow_replace = (block.flow_replace & ~SERIAL_RTS_MASK) | rts_bits;
97}
98
99std::vector<CdcEps> USBUartTypeCP210X::parse_descriptors(usb_device_handle_t dev_hdl) {
100 const usb_config_desc_t *config_desc;
101 const usb_device_desc_t *device_desc;
102 int conf_offset = 0, ep_offset;
103 std::vector<CdcEps> cdc_devs{};
104
105 // Get required descriptors
106 if (usb_host_get_device_descriptor(dev_hdl, &device_desc) != ESP_OK) {
107 ESP_LOGE(TAG, "get_device_descriptor failed");
108 return {};
109 }
110 if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) != ESP_OK) {
111 ESP_LOGE(TAG, "get_active_config_descriptor failed");
112 return {};
113 }
114 ESP_LOGD(TAG, "bDeviceClass: %u, bDeviceSubClass: %u, bNumInterfaces: %u", device_desc->bDeviceClass,
115 device_desc->bDeviceSubClass, config_desc->bNumInterfaces);
116 if (device_desc->bDeviceClass != 0) {
117 ESP_LOGE(TAG, "bDeviceClass != 0");
118 return {};
119 }
120
121 for (uint8_t i = 0; i != config_desc->bNumInterfaces; i++) {
122 const auto *data_desc = usb_parse_interface_descriptor(config_desc, i, 0, &conf_offset);
123 if (!data_desc) {
124 ESP_LOGE(TAG, "data_desc: usb_parse_interface_descriptor failed");
125 break;
126 }
127 if (data_desc->bNumEndpoints != 2 || data_desc->bInterfaceClass != USB_CLASS_VENDOR_SPEC) {
128 ESP_LOGE(TAG, "data_desc: bInterfaceClass == %u, bInterfaceSubClass == %u, bNumEndpoints == %u",
129 data_desc->bInterfaceClass, data_desc->bInterfaceSubClass, data_desc->bNumEndpoints);
130 continue;
131 }
132 ep_offset = conf_offset;
133 const auto *out_ep = usb_parse_endpoint_descriptor_by_index(data_desc, 0, config_desc->wTotalLength, &ep_offset);
134 if (!out_ep) {
135 ESP_LOGE(TAG, "out_ep: usb_parse_endpoint_descriptor_by_index failed");
136 continue;
137 }
138 ep_offset = conf_offset;
139 const auto *in_ep = usb_parse_endpoint_descriptor_by_index(data_desc, 1, config_desc->wTotalLength, &ep_offset);
140 if (!in_ep) {
141 ESP_LOGE(TAG, "in_ep: usb_parse_endpoint_descriptor_by_index failed");
142 continue;
143 }
144 CdcEps eps = in_ep->bEndpointAddress & usb_host::USB_DIR_IN
145 ? CdcEps{nullptr, in_ep, out_ep, data_desc->bInterfaceNumber}
146 : CdcEps{nullptr, out_ep, in_ep, data_desc->bInterfaceNumber};
147 eps.bulk_interface_class = data_desc->bInterfaceClass;
148 cdc_devs.push_back(eps);
149 }
150 return cdc_devs;
151}
152
153bool USBUartTypeCP210X::config_device_step(uint8_t step, bool ok, const uint8_t *response) {
154 switch (step) {
155 case 0:
156 // A fresh device: the previous one's cached blocks must not steer modem_control_transfer()
157 this->no_flow_control_ = false;
158 this->flow_blocks_.fill({});
159 this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_IN, VENDOR_SPECIFIC, GET_PARTNUM, 0,
160 std::vector<uint8_t>(1), this->cfg_response_);
161 return true;
162 case 1:
163 if (!ok)
164 return false;
165 ESP_LOGD(TAG, "Part number 0x%02X", response[0]);
166 if (response[0] != PARTNUM_CP2102N_QFN28 && response[0] != PARTNUM_CP2102N_QFN24 &&
167 response[0] != PARTNUM_CP2102N_QFN20)
168 return false;
169 this->config_transfer_(USB_VENDOR_DEV | usb_host::USB_DIR_IN, VENDOR_SPECIFIC, GET_FW_VER_2N, 0,
170 std::vector<uint8_t>(3), this->cfg_response_);
171 return true;
172 case 2: {
173 if (!ok)
174 return false;
175 const uint32_t version = (response[0] << 16) | (response[1] << 8) | response[2];
176 ESP_LOGD(TAG, "CP2102N firmware %u.%u.%u", response[0], response[1], response[2]);
177 this->no_flow_control_ = version <= CP2102N_LAST_FW_WITHOUT_FLOW_CONTROL;
178 if (this->no_flow_control_) {
179 ESP_LOGW(TAG, "CP2102N firmware 1.0.4 and older cannot do flow control (erratum E104), using none");
180 }
181 return false;
182 }
183 default:
184 return false;
185 }
186}
187
188bool USBUartTypeCP210X::config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok,
189 const uint8_t *response) {
190 // On reload, skip the one-time IFC_ENABLE step (the interface is already enabled).
191 if (reload)
192 step++;
193 const bool xon_xoff = channel->flow_control_ == uart::UART_FLOW_CONTROL_XON_XOFF;
194 if (step >= 3) {
195 if (this->no_flow_control_) {
196 // Firmware with erratum E104 takes flow_replace from xon_limit, so a SET_FLOW can drop RTS.
197 // Skip the flow control steps; the lines then stay on SET_MHS as the cached block has no CTS.
198 step += 3;
199 } else if (!xon_xoff) {
200 // SET_CHARS is only needed for XON/XOFF
201 step++;
202 }
203 }
204 switch (step) {
205 case 0:
206 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, IFC_ENABLE, 1, channel->index_);
207 return true;
208 case 1: {
209 uint16_t line_control = static_cast<uint8_t>(channel->line_stop_bits_());
210 line_control |= static_cast<uint8_t>(channel->line_parity_()) << 4;
211 line_control |= channel->data_bits_ << 8;
212 ESP_LOGD(TAG, "Line control value 0x%X", line_control);
213 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_LINE_CTL, line_control, channel->index_);
214 return true;
215 }
216 case 2: {
217 auto baud = ByteBuffer::wrap(channel->baud_rate_, LITTLE);
218 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_BAUDRATE, 0, channel->index_, baud.get_data());
219 return true;
220 }
221 case 3:
222 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_CHARS, 0, channel->index_,
223 std::vector<uint8_t>(std::begin(SPECIAL_CHARS), std::end(SPECIAL_CHARS)));
224 return true;
225 case 4: {
226 // Read into the channel's cached block, which the next step edits and writes back
227 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_IN, GET_FLOW, 0, channel->index_,
228 std::vector<uint8_t>(sizeof(CP210xFlowBlock)),
229 reinterpret_cast<uint8_t *>(&this->flow_blocks_[channel->index_]));
230 return true;
231 }
232 case 5: {
233 // Set flow control, re-applied on every settings change. The block also carries DTR and RTS,
234 // so this puts them on the wire too.
235 auto &block = this->flow_blocks_[channel->index_];
236 block.control_handshake &=
237 ~(SERIAL_DSR_HANDSHAKE | SERIAL_DCD_HANDSHAKE | SERIAL_DSR_SENSITIVITY | SERIAL_CTS_HANDSHAKE);
239 block.control_handshake |= SERIAL_CTS_HANDSHAKE;
240 assign_bits(block.flow_replace, SERIAL_AUTO_TRANSMIT | SERIAL_AUTO_RECEIVE, xon_xoff);
241 if (xon_xoff) {
242 block.xon_limit = XON_XOFF_LIMIT;
243 block.xoff_limit = XON_XOFF_LIMIT;
244 }
245 set_block_modem_lines(block, channel->dtr_, channel->rts_);
246 this->set_flow_transfer_(channel);
247 return true;
248 }
249 case 6:
250 // Raise the lines on open (init only)
251 if (reload)
252 return false;
253 this->modem_control_transfer(channel);
254 return true;
255 default:
256 return false;
257 }
258}
259
261 // Under RTS/CTS the chip drives RTS, so the lines go through SET_FLOW
262 auto &block = this->flow_blocks_[channel->index_];
263 if (block.control_handshake & SERIAL_CTS_HANDSHAKE) {
264 set_block_modem_lines(block, channel->dtr_, channel->rts_);
265 this->set_flow_transfer_(channel);
266 return;
267 }
268 const uint16_t value =
269 CONTROL_WRITE_DTR | CONTROL_WRITE_RTS | (channel->dtr_ ? CONTROL_DTR : 0) | (channel->rts_ ? CONTROL_RTS : 0);
270 ESP_LOGD(TAG, "SET_MHS: DTR=%s RTS=%s", ONOFF(channel->dtr_), ONOFF(channel->rts_));
271 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_MHS, value, channel->index_);
272}
273
275 const auto &block = this->flow_blocks_[channel->index_];
276 ESP_LOGD(TAG, "SET_FLOW: control handshake 0x%02" PRIX32 ", flow replace 0x%02" PRIX32, block.control_handshake,
277 block.flow_replace);
278 const auto *bytes = reinterpret_cast<const uint8_t *>(&block);
279 this->config_transfer_(USB_VENDOR_IFC | usb_host::USB_DIR_OUT, SET_FLOW, 0, channel->index_,
280 std::vector<uint8_t>(bytes, bytes + sizeof(block)));
281}
282} // namespace esphome::usb_uart
283
284#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 ||
285 // USE_ESP32_VARIANT_ESP32S31 || USE_ESP32_VARIANT_ESP32H4
static ByteBuffer wrap(T value, Endian endianness=LITTLE)
Definition bytebuffer.h:155
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 cp210x.cpp:260
bool config_device_step(uint8_t step, bool ok, const uint8_t *response) override
Definition cp210x.cpp:153
void set_flow_transfer_(USBUartChannelBase *channel)
Definition cp210x.cpp:274
std::vector< CdcEps > parse_descriptors(usb_device_handle_t dev_hdl) override
Definition cp210x.cpp:99
bool config_step(USBUartChannelBase *channel, uint8_t step, bool reload, bool ok, const uint8_t *response) override
Definition cp210x.cpp:188
std::array< CP210xFlowBlock, MAX_CHANNELS > flow_blocks_
Definition usb_uart.h:377
ESPHOME_ALWAYS_INLINE constexpr void assign_bits(T &value, std::type_identity_t< T > mask, bool on)
Set the bits of mask in value when on is true, clear them otherwise.
Definition helpers.h:1072
static void uint32_t