ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
uart_component_esp_idf.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
4#include <cinttypes>
7#include "esphome/core/log.h"
8#include "esphome/core/gpio.h"
9#include "driver/gpio.h"
10#include "hal/uart_ll.h"
11#include "esp_private/gpio.h"
12#include "soc/gpio_num.h"
13#include "soc/soc_caps.h"
14#include "soc/uart_pins.h"
15
16#ifdef USE_UART_WAKE_LOOP_ON_RX
18#endif
19
20#ifdef USE_LOGGER
22#endif
23
24namespace esphome::uart {
25
26ESPHOME_LOG_TAG(TAG, "uart");
27
38static constexpr bool is_default_uart0_pin(int8_t pin_num) {
39 return pin_num == U0TXD_GPIO_NUM || pin_num == U0RXD_GPIO_NUM;
40}
41
42// clock_source_ is stored in a byte; every uart_sclk_t value is a soc_module_clk_t below SOC_MOD_CLK_INVALID
43static_assert(SOC_MOD_CLK_INVALID <= UINT8_MAX, "uart_sclk_t no longer fits in uint8_t clock_source_");
44
45static const LogString *clock_source_to_str(uart_sclk_t clock_source) {
46 switch (clock_source) {
47#if SOC_UART_SUPPORT_APB_CLK
48 case UART_SCLK_APB:
49 return LOG_STR("APB");
50#endif
51#if SOC_UART_SUPPORT_XTAL_CLK
52 case UART_SCLK_XTAL:
53 return LOG_STR("XTAL");
54#endif
55#if SOC_UART_SUPPORT_RTC_CLK
56 case UART_SCLK_RTC:
57 return LOG_STR("RTC");
58#endif
59#if SOC_UART_SUPPORT_REF_TICK
60 case UART_SCLK_REF_TICK:
61 return LOG_STR("REF_TICK");
62#endif
63 default:
64 return clock_source == UART_SCLK_DEFAULT ? LOG_STR("DEFAULT") : LOG_STR("UNKNOWN");
65 }
66}
67
69 uart_parity_t parity = UART_PARITY_DISABLE;
70 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
71 parity = UART_PARITY_EVEN;
72 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
73 parity = UART_PARITY_ODD;
74 }
75
76 uart_word_length_t data_bits;
77 switch (this->data_bits_) {
78 case 5:
79 data_bits = UART_DATA_5_BITS;
80 break;
81 case 6:
82 data_bits = UART_DATA_6_BITS;
83 break;
84 case 7:
85 data_bits = UART_DATA_7_BITS;
86 break;
87 case 8:
88 data_bits = UART_DATA_8_BITS;
89 break;
90 default:
91 data_bits = UART_DATA_BITS_MAX;
92 break;
93 }
94
95 uart_config_t uart_config{};
96 uart_config.baud_rate = this->baud_rate_;
97 uart_config.data_bits = data_bits;
98 uart_config.parity = parity;
99 uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
100 uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
101 uart_config.source_clk = static_cast<uart_sclk_t>(this->clock_source_);
102 uart_config.rx_flow_ctrl_thresh = 122;
103
104 return uart_config;
105}
106
108 static uint8_t next_uart_num = 0;
109
110#ifdef USE_LOGGER
111 bool logger_uses_hardware_uart = true;
112
113#ifdef USE_LOGGER_USB_CDC
115 // this is not a hardware UART, ignore it
116 logger_uses_hardware_uart = false;
117 }
118#endif // USE_LOGGER_USB_CDC
119
120#ifdef USE_LOGGER_USB_SERIAL_JTAG
122 // this is not a hardware UART, ignore it
123 logger_uses_hardware_uart = false;
124 }
125#endif // USE_LOGGER_USB_SERIAL_JTAG
126
127 if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
128 logger::global_logger->get_uart_num() == next_uart_num) {
129 next_uart_num++;
130 }
131#endif // USE_LOGGER
132
133 if (next_uart_num >= SOC_UART_NUM) {
134 ESP_LOGW(TAG, "Maximum number of UART components created already");
135 this->mark_failed();
136 return;
137 }
138 this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
139
140#if (SOC_UART_LP_NUM >= 1)
141 size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN);
142#else
143 size_t fifo_len = SOC_UART_FIFO_LEN;
144#endif
145 if (this->rx_buffer_size_ <= fifo_len) {
146 ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len);
147 this->rx_buffer_size_ = fifo_len * 2;
148 }
149
150 this->load_settings(false);
151}
152
153void IDFUARTComponent::load_settings(bool dump_config) {
154 esp_err_t err;
155
156 if (uart_is_driver_installed(this->uart_num_)) {
157 err = uart_driver_delete(this->uart_num_);
158 if (err != ESP_OK) {
159 ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
160 this->mark_failed();
161 return;
162 }
163 }
164 err = uart_driver_install(this->uart_num_, // UART number
165 this->rx_buffer_size_, // RX ring buffer size
166 this->tx_buffer_size_, // TX ring buffer size; 0 makes uart_write_bytes() block until
167 // the FIFO has taken everything
168 0, // event queue size/depth
169 nullptr, // event queue
170 0 // Flags used to allocate the interrupt
171 );
172 if (err != ESP_OK) {
173 ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
174 this->mark_failed();
175 return;
176 }
177
178 // uart_param_config must be called after uart_driver_install and before any
179 // other uart_set_*() calls. The driver installation resets the UART peripheral
180 // registers to their default state, overwriting any previously configured baud
181 // rate or framing settings. Calling uart_param_config here ensures the requested
182 // settings are applied after the reset and before pin routing, inversion, and
183 // threshold configuration.
184 uart_config_t uart_config = this->get_config_();
185 err = uart_param_config(this->uart_num_, &uart_config);
186 if (err != ESP_OK) {
187 ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
188 this->mark_failed();
189 return;
190 }
191 this->last_good_framing_ = this->framing_();
192
193 int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
194 int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
195 int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
196
197 // Clear residual IOMUX function on UART0 default pins left by the ROM bootloader.
198 // See is_default_uart0_pin() comment for details on the ESP-IDF uart_set_pin() bug.
199 if (is_default_uart0_pin(tx)) {
200 gpio_func_sel(static_cast<gpio_num_t>(tx), PIN_FUNC_GPIO);
201 }
202 if (is_default_uart0_pin(rx)) {
203 gpio_func_sel(static_cast<gpio_num_t>(rx), PIN_FUNC_GPIO);
204 }
205
206 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
207 if (!pin) {
208 return;
209 }
211 if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
212 pin->setup();
213 }
214 };
215
216 setup_pin_if_needed(this->rx_pin_);
217 if (this->rx_pin_ != this->tx_pin_) {
218 setup_pin_if_needed(this->tx_pin_);
219 }
220
221 // Must precede uart_set_pin() so an inverted TX line never shows the wrong idle
222 // level; apply_line_settings_() repeats it later for the reset registers.
223 err = uart_set_line_inverse(this->uart_num_, this->line_inversion_mask_());
224 if (err != ESP_OK) {
225 ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
226 this->mark_failed();
227 return;
228 }
229
230 err = uart_set_pin(this->uart_num_, tx, rx, flow_control, UART_PIN_NO_CHANGE);
231 if (err != ESP_OK) {
232 ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
233 this->mark_failed();
234 return;
235 }
236
237 if (this->apply_line_settings_() != ESP_OK) {
238 this->mark_failed();
239 return;
240 }
241#ifdef USE_GPIO_HOLD
242 // Release held pins so the UART peripheral can drive them
243 this->set_pins_hold_(false);
244#endif
245
246#ifdef USE_UART_WAKE_LOOP_ON_RX
247 // Register ISR callback to wake the main loop when UART data arrives.
248 // The callback runs in ISR context and uses vTaskNotifyGiveFromISR() to
249 // wake the main loop task directly — no queue or FreeRTOS task needed.
250 uart_set_select_notif_callback(this->uart_num_, IDFUARTComponent::uart_rx_isr_callback);
251#endif // USE_UART_WAKE_LOOP_ON_RX
252
253 if (dump_config) {
254 ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
255 this->dump_config();
256 }
257}
258
260 uint32_t invert = 0;
261 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
262 invert |= UART_SIGNAL_TXD_INV;
263 }
264 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
265 invert |= UART_SIGNAL_RXD_INV;
266 }
267 if (this->flow_control_pin_ != nullptr && this->flow_control_pin_->is_inverted()) {
268 invert |= UART_SIGNAL_RTS_INV;
269 }
270 return invert;
271}
272
274 // uart_param_config() resets these; call after every use of it.
275 esp_err_t err = uart_set_line_inverse(this->uart_num_, this->line_inversion_mask_());
276 if (err != ESP_OK) {
277 ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
278 return err;
279 }
280
281 err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
282 if (err != ESP_OK) {
283 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
284 return err;
285 }
286
287 err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_);
288 if (err != ESP_OK) {
289 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
290 return err;
291 }
292
293 // Per ESP-IDF docs, uart_set_mode() must be called only after uart_driver_install().
294 auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
295 err = uart_set_mode(this->uart_num_, mode);
296 if (err != ESP_OK) {
297 ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
298 return err;
299 }
300
301 return ESP_OK;
302}
303
305 this->baud_rate_ = framing.baud_rate;
306 this->data_bits_ = framing.data_bits;
307 this->stop_bits_ = framing.stop_bits;
308 this->parity_ = framing.parity;
310}
311
313 if (this->is_failed()) {
314 return ESP_ERR_INVALID_STATE;
315 }
316 // No driver yet: nothing to reconfigure in place.
317 if (!uart_is_driver_installed(this->uart_num_)) {
318 this->load_settings(false);
319 return this->is_failed() ? ESP_FAIL : ESP_OK;
320 }
321 // Keeps the driver ring buffers; flushes both hardware FIFOs (in-flight bytes lost).
322 uart_config_t uart_config = this->get_config_();
323 esp_err_t err = uart_param_config(this->uart_num_, &uart_config);
324 if (err != ESP_OK) {
325 // Failure leaves the registers reset; put back the last accepted framing so the
326 // getters still describe the hardware.
327 if (this->last_good_framing_.baud_rate == 0) {
328 ESP_LOGE(TAG, "uart_param_config (live) failed: %s; no previous framing to restore", esp_err_to_name(err));
329 this->mark_failed();
330 return err;
331 }
332 ESP_LOGW(TAG, "uart_param_config (live) failed: %s; restoring %" PRIu32 " baud", esp_err_to_name(err),
334 this->set_framing_(this->last_good_framing_);
335 uart_config = this->get_config_();
336 esp_err_t restore_err = uart_param_config(this->uart_num_, &uart_config);
337 if (restore_err != ESP_OK) {
338 ESP_LOGE(TAG, "UART left unconfigured after failed live reconfigure: %s", esp_err_to_name(restore_err));
339 this->mark_failed();
340 return err;
341 }
342 // Previous framing is live again; report the refusal (line-setting errors log).
343 this->apply_line_settings_();
344 return err;
345 }
346 this->last_good_framing_ = this->framing_();
347 // The new framing is live; a line-setting failure here only logs.
348 this->apply_line_settings_();
349 return ESP_OK;
350}
351
353 ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
354 LOG_PIN(" TX Pin: ", this->tx_pin_);
355 LOG_PIN(" RX Pin: ", this->rx_pin_);
356 LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
357 if (this->rx_pin_ != nullptr) {
358 ESP_LOGCONFIG(TAG,
359 " RX Buffer Size: %u\n"
360 " RX Full Threshold: %u\n"
361 " RX Timeout: %u",
363 }
364 if (this->tx_buffer_size_ > 0) {
365 ESP_LOGCONFIG(TAG, " TX Buffer Size: %zu", this->tx_buffer_size_);
366 }
367 if (this->flush_timeout_ms_ > 0) {
368 ESP_LOGCONFIG(TAG, " Flush Timeout: %" PRIu32 " ms", this->flush_timeout_ms_);
369 }
370 ESP_LOGCONFIG(TAG,
371 " Baud Rate: %" PRIu32 " baud\n"
372 " Data Bits: %u\n"
373 " Parity: %s\n"
374 " Stop bits: %u\n"
375 " Clock Source: %s"
376#ifdef USE_UART_WAKE_LOOP_ON_RX
377 "\n Wake on data RX: ENABLED"
378#endif
379 ,
380 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_,
381 LOG_STR_ARG(clock_source_to_str(static_cast<uart_sclk_t>(this->clock_source_))));
382 this->check_logger_conflict();
383}
384
385void IDFUARTComponent::set_rx_full_threshold(size_t rx_full_threshold) {
386 if (this->is_ready()) {
387 esp_err_t err = uart_set_rx_full_threshold(this->uart_num_, rx_full_threshold);
388 if (err != ESP_OK) {
389 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
390 return;
391 }
392 }
393 this->rx_full_threshold_ = rx_full_threshold;
394}
395
396void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) {
397 if (this->is_ready()) {
398 esp_err_t err = uart_set_rx_timeout(this->uart_num_, rx_timeout);
399 if (err != ESP_OK) {
400 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
401 return;
402 }
403 }
404 this->rx_timeout_ = rx_timeout;
405}
406
408 if (this->uart_num_ == UART_NUM_MAX || !uart_is_driver_installed(this->uart_num_))
409 return 0;
410 if (this->tx_buffer_size_ == 0) {
411 return uart_ll_get_txfifo_len(UART_LL_GET_HW(this->uart_num_));
412 }
413 // The driver's figure already deducts its ring item headers, so a write of this size fits
414 size_t free = 0;
415 uart_get_tx_buffer_free_size(this->uart_num_, &free);
416 return free;
417}
418
419void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
420 int32_t write_len = uart_write_bytes(this->uart_num_, data, len);
421 if (write_len != (int32_t) len) {
422 ESP_LOGW(TAG, "uart_write_bytes failed: %" PRId32 " != %zu", write_len, len);
423 this->mark_failed();
424 }
425#ifdef USE_UART_DEBUGGER
426 for (size_t i = 0; i < len; i++) {
427 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
428 }
429#endif
430}
431
432bool IDFUARTComponent::peek_byte(uint8_t *data) {
433 if (!this->check_read_timeout_())
434 return false;
435 if (this->has_peek_) {
436 *data = this->peek_byte_;
437 } else {
438 int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_PERIOD_MS);
439 if (len == 0) {
440 *data = 0;
441 } else {
442 this->has_peek_ = true;
443 this->peek_byte_ = *data;
444 }
445 }
446 return true;
447}
448
449bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
450 if (len == 0) {
451 return false;
452 }
453 size_t length_to_read = len;
454 int32_t read_len = 0;
455 if (!this->check_read_timeout_(len))
456 return false;
457 if (this->has_peek_) {
458 length_to_read--;
459 *data = this->peek_byte_;
460 this->has_peek_ = false;
461 }
462 if (length_to_read > 0)
463 read_len = uart_read_bytes(this->uart_num_, data + (len - length_to_read), length_to_read, 20 / portTICK_PERIOD_MS);
464#ifdef USE_UART_DEBUGGER
465 for (size_t i = 0; i < len; i++) {
466 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
467 }
468#endif
469 return read_len == (int32_t) length_to_read;
470}
471
473 size_t available = 0;
474 esp_err_t err;
475
476 err = uart_get_buffered_data_len(this->uart_num_, &available);
477
478 if (err != ESP_OK) {
479 ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err));
480 this->mark_failed();
481 }
482 if (this->has_peek_) {
483 available++;
484 }
485 return available;
486}
487
489 ESP_LOGVV(TAG, " Flushing");
490 TickType_t ticks = this->flush_timeout_ms_ == 0 ? portMAX_DELAY : pdMS_TO_TICKS(this->flush_timeout_ms_);
491 esp_err_t err = uart_wait_tx_done(this->uart_num_, ticks);
492 if (err == ESP_OK)
494 if (err == ESP_ERR_TIMEOUT)
497}
498
500
501#ifdef USE_UART_WAKE_LOOP_ON_RX
502// ISR callback invoked by the ESP-IDF UART driver when data arrives.
503// Wakes the main loop directly via vTaskNotifyGiveFromISR() — no queue or task needed.
504void IRAM_ATTR IDFUARTComponent::uart_rx_isr_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif,
505 BaseType_t *task_woken) {
506 if (uart_select_notif == UART_SELECT_READ_NOTIF) {
508 }
509}
510#endif // USE_UART_WAKE_LOOP_ON_RX
511
513 if (this->uart_num_ == UART_NUM_MAX || !uart_is_driver_installed(this->uart_num_))
514 return;
515 uart_wait_tx_done(this->uart_num_, pdMS_TO_TICKS(100));
516 // Keep the peripheral quiet across a soft reset so ROM output does not reach the attached device (#15472)
517 esp_err_t err = uart_driver_delete(this->uart_num_);
518 if (err != ESP_OK) {
519 ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
520 }
521#ifdef USE_GPIO_HOLD
522 // Hold the pins again so they keep their state through the reset
523 this->set_pins_hold_(true);
524#endif
525}
526
527#ifdef USE_GPIO_HOLD
529 for (auto *pin : {this->tx_pin_, this->rx_pin_, this->flow_control_pin_}) {
530 if (pin != nullptr)
531 pin->set_hold(hold);
532 }
533}
534#endif
535
536} // namespace esphome::uart
537#endif // USE_ESP32
BedjetMode mode
BedJet operating mode.
static void IRAM_ATTR ESPHOME_ALWAYS_INLINE wake_loop_isrsafe()
Wake from ISR (ESP8266). No task_woken arg — no FreeRTOS. Caller must be IRAM_ATTR.
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
bool is_ready() const
void set_rx_timeout(size_t rx_timeout) override
void set_framing_(const Framing &framing)
bool peek_byte(uint8_t *data) override
uint8_t clock_source_
uart_sclk_t stored in a byte; the IDF values are all small.
void load_settings()
Load the UART settings.
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
uint32_t flush_timeout_ms_
0 means wait indefinitely (portMAX_DELAY).
static void uart_rx_isr_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken)
esp_err_t apply_settings_live()
Apply the current framing (baud rate, parity, data/stop bits) to the installed driver in place,...
bool check_read_timeout_(size_t len=1)
InternalGPIOPin * flow_control_pin_
CallbackManager< void(UARTDirection, uint8_t)> debug_callback_
@ UART_SELECTION_USB_SERIAL_JTAG
Definition logger.h:124
@ UART_SELECTION_USB_CDC
Definition logger.h:121
Logger * global_logger
Definition logger.cpp:276
ESPHOME_LOG_TAG(TAG, "uart.binary_sensor")
const LogString * parity_to_str(UARTParityOptions parity)
Definition uart.cpp:43
UARTFlushResult
Result of a flush() call.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_FAILED
Confirmed: driver or hardware error.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
const void size_t len
Definition hal.h:68
static void uint32_t