ESPHome 2026.2.1
Loading...
Searching...
No Matches
uart_component_esp_idf.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
4#include <cinttypes>
8#include "esphome/core/log.h"
9#include "esphome/core/gpio.h"
10#include "driver/gpio.h"
11#include "soc/gpio_num.h"
12#include "soc/uart_pins.h"
13
14#ifdef USE_LOGGER
16#endif
17
18namespace esphome::uart {
19
20static const char *const TAG = "uart.idf";
21
25static constexpr bool is_default_uart0_pin(int8_t pin_num) {
26 return pin_num == U0TXD_GPIO_NUM || pin_num == U0RXD_GPIO_NUM;
27}
28
30 uart_parity_t parity = UART_PARITY_DISABLE;
31 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
32 parity = UART_PARITY_EVEN;
33 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
34 parity = UART_PARITY_ODD;
35 }
36
37 uart_word_length_t data_bits;
38 switch (this->data_bits_) {
39 case 5:
40 data_bits = UART_DATA_5_BITS;
41 break;
42 case 6:
43 data_bits = UART_DATA_6_BITS;
44 break;
45 case 7:
46 data_bits = UART_DATA_7_BITS;
47 break;
48 case 8:
49 data_bits = UART_DATA_8_BITS;
50 break;
51 default:
52 data_bits = UART_DATA_BITS_MAX;
53 break;
54 }
55
56 uart_config_t uart_config{};
57 uart_config.baud_rate = this->baud_rate_;
58 uart_config.data_bits = data_bits;
59 uart_config.parity = parity;
60 uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
61 uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
62 uart_config.source_clk = UART_SCLK_DEFAULT;
63 uart_config.rx_flow_ctrl_thresh = 122;
64
65 return uart_config;
66}
67
69 static uint8_t next_uart_num = 0;
70
71#ifdef USE_LOGGER
72 bool logger_uses_hardware_uart = true;
73
74#ifdef USE_LOGGER_USB_CDC
76 // this is not a hardware UART, ignore it
77 logger_uses_hardware_uart = false;
78 }
79#endif // USE_LOGGER_USB_CDC
80
81#ifdef USE_LOGGER_USB_SERIAL_JTAG
83 // this is not a hardware UART, ignore it
84 logger_uses_hardware_uart = false;
85 }
86#endif // USE_LOGGER_USB_SERIAL_JTAG
87
88 if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
89 logger::global_logger->get_uart_num() == next_uart_num) {
90 next_uart_num++;
91 }
92#endif // USE_LOGGER
93
94 if (next_uart_num >= SOC_UART_NUM) {
95 ESP_LOGW(TAG, "Maximum number of UART components created already");
96 this->mark_failed();
97 return;
98 }
99 this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
100
101#if (SOC_UART_LP_NUM >= 1)
102 size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN);
103#else
104 size_t fifo_len = SOC_UART_FIFO_LEN;
105#endif
106 if (this->rx_buffer_size_ <= fifo_len) {
107 ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len);
108 this->rx_buffer_size_ = fifo_len * 2;
109 }
110
111 this->load_settings(false);
112}
113
114void IDFUARTComponent::load_settings(bool dump_config) {
115 esp_err_t err;
116
117 if (uart_is_driver_installed(this->uart_num_)) {
118#ifdef USE_UART_WAKE_LOOP_ON_RX
119 if (this->rx_event_task_handle_ != nullptr) {
120 vTaskDelete(this->rx_event_task_handle_);
121 this->rx_event_task_handle_ = nullptr;
122 }
123#endif
124 err = uart_driver_delete(this->uart_num_);
125 if (err != ESP_OK) {
126 ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
127 this->mark_failed();
128 return;
129 }
130 }
131#ifdef USE_UART_WAKE_LOOP_ON_RX
132 constexpr int event_queue_size = 20;
133 QueueHandle_t *event_queue_ptr = &this->uart_event_queue_;
134#else
135 constexpr int event_queue_size = 0;
136 QueueHandle_t *event_queue_ptr = nullptr;
137#endif
138 err = uart_driver_install(this->uart_num_, // UART number
139 this->rx_buffer_size_, // RX ring buffer size
140 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
141 // block task until all data has been sent out
142 event_queue_size, // event queue size/depth
143 event_queue_ptr, // event queue
144 0 // Flags used to allocate the interrupt
145 );
146 if (err != ESP_OK) {
147 ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
148 this->mark_failed();
149 return;
150 }
151
152 int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
153 int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
154 int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
155
156 // Workaround for ESP-IDF issue: https://github.com/espressif/esp-idf/issues/17459
157 // Commit 9ed617fb17 removed gpio_func_sel() calls from uart_set_pin(), which breaks
158 // UART on default UART0 pins that may have residual state from boot console.
159 // Reset these pins before configuring UART to ensure they're in a clean state.
160 if (is_default_uart0_pin(tx)) {
161 gpio_reset_pin(static_cast<gpio_num_t>(tx));
162 }
163 if (is_default_uart0_pin(rx)) {
164 gpio_reset_pin(static_cast<gpio_num_t>(rx));
165 }
166
167 // Setup pins after reset to configure GPIO direction and pull resistors.
168 // For UART0 default pins, setup() must always be called because gpio_reset_pin()
169 // above sets GPIO_MODE_DISABLE which disables the input buffer. Without setup(),
170 // uart_set_pin() on ESP-IDF 5.4.2+ does not re-enable the input buffer for
171 // IOMUX-connected pins, so the RX pin cannot receive data (see issue #10132).
172 // For other pins, only call setup() if pull or open-drain flags are set to avoid
173 // disturbing the default pin state which breaks some external components (#11823).
174 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
175 if (!pin) {
176 return;
177 }
179 if (is_default_uart0_pin(pin->get_pin()) || (pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
180 pin->setup();
181 }
182 };
183
184 setup_pin_if_needed(this->rx_pin_);
185 if (this->rx_pin_ != this->tx_pin_) {
186 setup_pin_if_needed(this->tx_pin_);
187 }
188
189 uint32_t invert = 0;
190 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
191 invert |= UART_SIGNAL_TXD_INV;
192 }
193 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
194 invert |= UART_SIGNAL_RXD_INV;
195 }
196
197 err = uart_set_line_inverse(this->uart_num_, invert);
198 if (err != ESP_OK) {
199 ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
200 this->mark_failed();
201 return;
202 }
203
204 err = uart_set_pin(this->uart_num_, tx, rx, flow_control, UART_PIN_NO_CHANGE);
205 if (err != ESP_OK) {
206 ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
207 this->mark_failed();
208 return;
209 }
210
211 err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
212 if (err != ESP_OK) {
213 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
214 this->mark_failed();
215 return;
216 }
217
218 err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_);
219 if (err != ESP_OK) {
220 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
221 this->mark_failed();
222 return;
223 }
224
225 auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
226 err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install()
227 if (err != ESP_OK) {
228 ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
229 this->mark_failed();
230 return;
231 }
232
233 uart_config_t uart_config = this->get_config_();
234 err = uart_param_config(this->uart_num_, &uart_config);
235 if (err != ESP_OK) {
236 ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
237 this->mark_failed();
238 return;
239 }
240
241#ifdef USE_UART_WAKE_LOOP_ON_RX
242 // Start the RX event task to enable low-latency data notifications
243 this->start_rx_event_task_();
244#endif // USE_UART_WAKE_LOOP_ON_RX
245
246 if (dump_config) {
247 ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
248 this->dump_config();
249 }
250}
251
253 ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
254 LOG_PIN(" TX Pin: ", this->tx_pin_);
255 LOG_PIN(" RX Pin: ", this->rx_pin_);
256 LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
257 if (this->rx_pin_ != nullptr) {
258 ESP_LOGCONFIG(TAG,
259 " RX Buffer Size: %u\n"
260 " RX Full Threshold: %u\n"
261 " RX Timeout: %u",
263 }
264 ESP_LOGCONFIG(TAG,
265 " Baud Rate: %" PRIu32 " baud\n"
266 " Data Bits: %u\n"
267 " Parity: %s\n"
268 " Stop bits: %u"
269#ifdef USE_UART_WAKE_LOOP_ON_RX
270 "\n Wake on data RX: ENABLED"
271#endif
272 ,
273 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
274 this->check_logger_conflict();
275}
276
277void IDFUARTComponent::set_rx_full_threshold(size_t rx_full_threshold) {
278 if (this->is_ready()) {
279 esp_err_t err = uart_set_rx_full_threshold(this->uart_num_, rx_full_threshold);
280 if (err != ESP_OK) {
281 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
282 return;
283 }
284 }
285 this->rx_full_threshold_ = rx_full_threshold;
286}
287
288void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) {
289 if (this->is_ready()) {
290 esp_err_t err = uart_set_rx_timeout(this->uart_num_, rx_timeout);
291 if (err != ESP_OK) {
292 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
293 return;
294 }
295 }
296 this->rx_timeout_ = rx_timeout;
297}
298
299void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
300 int32_t write_len = uart_write_bytes(this->uart_num_, data, len);
301 if (write_len != (int32_t) len) {
302 ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len);
303 this->mark_failed();
304 }
305#ifdef USE_UART_DEBUGGER
306 for (size_t i = 0; i < len; i++) {
307 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
308 }
309#endif
310}
311
312bool IDFUARTComponent::peek_byte(uint8_t *data) {
313 if (!this->check_read_timeout_())
314 return false;
315 if (this->has_peek_) {
316 *data = this->peek_byte_;
317 } else {
318 int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_PERIOD_MS);
319 if (len == 0) {
320 *data = 0;
321 } else {
322 this->has_peek_ = true;
323 this->peek_byte_ = *data;
324 }
325 }
326 return true;
327}
328
329bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
330 size_t length_to_read = len;
331 int32_t read_len = 0;
332 if (!this->check_read_timeout_(len))
333 return false;
334 if (this->has_peek_) {
335 length_to_read--;
336 *data = this->peek_byte_;
337 data++;
338 this->has_peek_ = false;
339 }
340 if (length_to_read > 0)
341 read_len = uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS);
342#ifdef USE_UART_DEBUGGER
343 for (size_t i = 0; i < len; i++) {
344 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
345 }
346#endif
347 return read_len == (int32_t) length_to_read;
348}
349
351 size_t available = 0;
352 esp_err_t err;
353
354 err = uart_get_buffered_data_len(this->uart_num_, &available);
355
356 if (err != ESP_OK) {
357 ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err));
358 this->mark_failed();
359 }
360 if (this->has_peek_) {
361 available++;
362 }
363 return available;
364}
365
367 ESP_LOGVV(TAG, " Flushing");
368 uart_wait_tx_done(this->uart_num_, portMAX_DELAY);
369}
370
372
373#ifdef USE_UART_WAKE_LOOP_ON_RX
375 // Create FreeRTOS task to monitor UART events
376 BaseType_t result = xTaskCreate(rx_event_task_func, // Task function
377 "uart_rx_evt", // Task name (max 16 chars)
378 2240, // Stack size in bytes (~2.2KB); increase if needed for logging
379 this, // Task parameter (this pointer)
380 tskIDLE_PRIORITY + 1, // Priority (low, just above idle)
381 &this->rx_event_task_handle_ // Task handle
382 );
383
384 if (result != pdPASS) {
385 ESP_LOGE(TAG, "Failed to create RX event task");
386 return;
387 }
388
389 ESP_LOGV(TAG, "RX event task started");
390}
391
392// FreeRTOS task that relays UART ISR events to the main loop.
393// This task exists because wake_loop_threadsafe() is not ISR-safe (it uses a
394// UDP loopback socket), so we need a task as an ISR-to-main-loop trampoline.
395// IMPORTANT: This task must NOT call any UART wrapper methods (read_array,
396// write_array, peek_byte, etc.) or touch has_peek_/peek_byte_ — all reading
397// is done by the main loop. This task only reads from the event queue and
398// calls App.wake_loop_threadsafe().
400 auto *self = static_cast<IDFUARTComponent *>(param);
401 uart_event_t event;
402
403 ESP_LOGV(TAG, "RX event task running");
404
405 // Run forever - task lifecycle matches component lifecycle
406 while (true) {
407 // Wait for UART events (blocks efficiently)
408 if (xQueueReceive(self->uart_event_queue_, &event, portMAX_DELAY) == pdTRUE) {
409 switch (event.type) {
410 case UART_DATA:
411 // Data available in UART RX buffer - wake the main loop
412 ESP_LOGVV(TAG, "Data event: %d bytes", event.size);
413#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
415#endif
416 break;
417
418 case UART_FIFO_OVF:
419 case UART_BUFFER_FULL:
420 // Don't call uart_flush_input() here — this task does not own the read side.
421 // ESP-IDF examples flush on overflow because the same task handles both events
422 // and reads, so flush and read are serialized. Here, reads happen on the main
423 // loop, so flushing from this task races with read_array() and can destroy data
424 // mid-read. The driver self-heals without an explicit flush: uart_read_bytes()
425 // calls uart_check_buf_full() after each chunk, which moves stashed FIFO bytes
426 // into the ring buffer and re-enables RX interrupts once space is freed.
427 ESP_LOGW(TAG, "FIFO overflow or ring buffer full");
428#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
430#endif
431 break;
432
433 default:
434 // Ignore other event types
435 ESP_LOGVV(TAG, "Event type: %d", event.type);
436 break;
437 }
438 }
439 }
440}
441#endif // USE_UART_WAKE_LOOP_ON_RX
442
443} // namespace esphome::uart
444#endif // USE_ESP32
BedjetMode mode
BedJet operating mode.
void wake_loop_threadsafe()
Wake the main event loop from a FreeRTOS task Thread-safe, can be called from task context to immedia...
virtual void mark_failed()
Mark this component as failed.
bool is_ready() const
virtual uint8_t get_pin() const =0
virtual bool is_inverted() const =0
ESP-IDF UART driver wrapper.
void set_rx_timeout(size_t rx_timeout) override
static void rx_event_task_func(void *param)
bool peek_byte(uint8_t *data) override
void write_array(const uint8_t *data, size_t len) override
void set_rx_full_threshold(size_t rx_full_threshold) override
bool read_array(uint8_t *data, size_t len) override
bool check_read_timeout_(size_t len=1)
InternalGPIOPin * flow_control_pin_
CallbackManager< void(UARTDirection, uint8_t)> debug_callback_
@ FLAG_OPEN_DRAIN
Definition gpio.h:29
@ FLAG_NONE
Definition gpio.h:26
@ FLAG_PULLUP
Definition gpio.h:30
@ FLAG_PULLDOWN
Definition gpio.h:31
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:120
@ UART_SELECTION_USB_CDC
Definition logger.h:117
Logger * global_logger
Definition logger.cpp:279
const char *const TAG
Definition spi.cpp:7
const LogString * parity_to_str(UARTParityOptions parity)
Definition uart.cpp:36
std::string size_t len
Definition helpers.h:692
Application App
Global storage of Application pointer - only one Application can exist.