ESPHome 2026.3.3
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 "esp_private/gpio.h"
11#include "soc/gpio_num.h"
12#include "soc/uart_pins.h"
13
14#ifdef USE_UART_WAKE_LOOP_ON_RX
16#endif
17
18#ifdef USE_LOGGER
20#endif
21
22namespace esphome::uart {
23
24static const char *const TAG = "uart.idf";
25
36static constexpr bool is_default_uart0_pin(int8_t pin_num) {
37 return pin_num == U0TXD_GPIO_NUM || pin_num == U0RXD_GPIO_NUM;
38}
39
41 uart_parity_t parity = UART_PARITY_DISABLE;
42 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
43 parity = UART_PARITY_EVEN;
44 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
45 parity = UART_PARITY_ODD;
46 }
47
48 uart_word_length_t data_bits;
49 switch (this->data_bits_) {
50 case 5:
51 data_bits = UART_DATA_5_BITS;
52 break;
53 case 6:
54 data_bits = UART_DATA_6_BITS;
55 break;
56 case 7:
57 data_bits = UART_DATA_7_BITS;
58 break;
59 case 8:
60 data_bits = UART_DATA_8_BITS;
61 break;
62 default:
63 data_bits = UART_DATA_BITS_MAX;
64 break;
65 }
66
67 uart_config_t uart_config{};
68 uart_config.baud_rate = this->baud_rate_;
69 uart_config.data_bits = data_bits;
70 uart_config.parity = parity;
71 uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
72 uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
73 uart_config.source_clk = UART_SCLK_DEFAULT;
74 uart_config.rx_flow_ctrl_thresh = 122;
75
76 return uart_config;
77}
78
80 static uint8_t next_uart_num = 0;
81
82#ifdef USE_LOGGER
83 bool logger_uses_hardware_uart = true;
84
85#ifdef USE_LOGGER_USB_CDC
87 // this is not a hardware UART, ignore it
88 logger_uses_hardware_uart = false;
89 }
90#endif // USE_LOGGER_USB_CDC
91
92#ifdef USE_LOGGER_USB_SERIAL_JTAG
94 // this is not a hardware UART, ignore it
95 logger_uses_hardware_uart = false;
96 }
97#endif // USE_LOGGER_USB_SERIAL_JTAG
98
99 if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
100 logger::global_logger->get_uart_num() == next_uart_num) {
101 next_uart_num++;
102 }
103#endif // USE_LOGGER
104
105 if (next_uart_num >= SOC_UART_NUM) {
106 ESP_LOGW(TAG, "Maximum number of UART components created already");
107 this->mark_failed();
108 return;
109 }
110 this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
111
112#if (SOC_UART_LP_NUM >= 1)
113 size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN);
114#else
115 size_t fifo_len = SOC_UART_FIFO_LEN;
116#endif
117 if (this->rx_buffer_size_ <= fifo_len) {
118 ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len);
119 this->rx_buffer_size_ = fifo_len * 2;
120 }
121
122 this->load_settings(false);
123}
124
125void IDFUARTComponent::load_settings(bool dump_config) {
126 esp_err_t err;
127
128 if (uart_is_driver_installed(this->uart_num_)) {
129 err = uart_driver_delete(this->uart_num_);
130 if (err != ESP_OK) {
131 ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
132 this->mark_failed();
133 return;
134 }
135 }
136 err = uart_driver_install(this->uart_num_, // UART number
137 this->rx_buffer_size_, // RX ring buffer size
138 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
139 // block task until all data has been sent out
140 0, // event queue size/depth
141 nullptr, // event queue
142 0 // Flags used to allocate the interrupt
143 );
144 if (err != ESP_OK) {
145 ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
146 this->mark_failed();
147 return;
148 }
149
150 // uart_param_config must be called after uart_driver_install and before any
151 // other uart_set_*() calls. The driver installation resets the UART peripheral
152 // registers to their default state, overwriting any previously configured baud
153 // rate or framing settings. Calling uart_param_config here ensures the requested
154 // settings are applied after the reset and before pin routing, inversion, and
155 // threshold configuration.
156 uart_config_t uart_config = this->get_config_();
157 err = uart_param_config(this->uart_num_, &uart_config);
158 if (err != ESP_OK) {
159 ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
160 this->mark_failed();
161 return;
162 }
163
164 int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
165 int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
166 int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
167
168 // Clear residual IOMUX function on UART0 default pins left by the ROM bootloader.
169 // See is_default_uart0_pin() comment for details on the ESP-IDF uart_set_pin() bug.
170 if (is_default_uart0_pin(tx)) {
171 gpio_func_sel(static_cast<gpio_num_t>(tx), PIN_FUNC_GPIO);
172 }
173 if (is_default_uart0_pin(rx)) {
174 gpio_func_sel(static_cast<gpio_num_t>(rx), PIN_FUNC_GPIO);
175 }
176
177 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
178 if (!pin) {
179 return;
180 }
182 if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
183 pin->setup();
184 }
185 };
186
187 setup_pin_if_needed(this->rx_pin_);
188 if (this->rx_pin_ != this->tx_pin_) {
189 setup_pin_if_needed(this->tx_pin_);
190 }
191
192 uint32_t invert = 0;
193 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
194 invert |= UART_SIGNAL_TXD_INV;
195 }
196 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
197 invert |= UART_SIGNAL_RXD_INV;
198 }
199 if (this->flow_control_pin_ != nullptr && this->flow_control_pin_->is_inverted()) {
200 invert |= UART_SIGNAL_RTS_INV;
201 }
202
203 err = uart_set_line_inverse(this->uart_num_, invert);
204 if (err != ESP_OK) {
205 ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
206 this->mark_failed();
207 return;
208 }
209
210 err = uart_set_pin(this->uart_num_, tx, rx, flow_control, UART_PIN_NO_CHANGE);
211 if (err != ESP_OK) {
212 ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
213 this->mark_failed();
214 return;
215 }
216
217 err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
218 if (err != ESP_OK) {
219 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
220 this->mark_failed();
221 return;
222 }
223
224 err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_);
225 if (err != ESP_OK) {
226 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
227 this->mark_failed();
228 return;
229 }
230
231 // Per ESP-IDF docs, uart_set_mode() must be called only after uart_driver_install().
232 auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
233 err = uart_set_mode(this->uart_num_, mode);
234 if (err != ESP_OK) {
235 ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
236 this->mark_failed();
237 return;
238 }
239
240#ifdef USE_UART_WAKE_LOOP_ON_RX
241 // Register ISR callback to wake the main loop when UART data arrives.
242 // The callback runs in ISR context and uses vTaskNotifyGiveFromISR() to
243 // wake the main loop task directly — no queue or FreeRTOS task needed.
244 uart_set_select_notif_callback(this->uart_num_, IDFUARTComponent::uart_rx_isr_callback);
245#endif // USE_UART_WAKE_LOOP_ON_RX
246
247 if (dump_config) {
248 ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
249 this->dump_config();
250 }
251}
252
254 ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
255 LOG_PIN(" TX Pin: ", this->tx_pin_);
256 LOG_PIN(" RX Pin: ", this->rx_pin_);
257 LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
258 if (this->rx_pin_ != nullptr) {
259 ESP_LOGCONFIG(TAG,
260 " RX Buffer Size: %u\n"
261 " RX Full Threshold: %u\n"
262 " RX Timeout: %u",
264 }
265 if (this->flush_timeout_ms_ > 0) {
266 ESP_LOGCONFIG(TAG, " Flush Timeout: %" PRIu32 " ms", this->flush_timeout_ms_);
267 }
268 ESP_LOGCONFIG(TAG,
269 " Baud Rate: %" PRIu32 " baud\n"
270 " Data Bits: %u\n"
271 " Parity: %s\n"
272 " Stop bits: %u"
273#ifdef USE_UART_WAKE_LOOP_ON_RX
274 "\n Wake on data RX: ENABLED"
275#endif
276 ,
277 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
278 this->check_logger_conflict();
279}
280
281void IDFUARTComponent::set_rx_full_threshold(size_t rx_full_threshold) {
282 if (this->is_ready()) {
283 esp_err_t err = uart_set_rx_full_threshold(this->uart_num_, rx_full_threshold);
284 if (err != ESP_OK) {
285 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
286 return;
287 }
288 }
289 this->rx_full_threshold_ = rx_full_threshold;
290}
291
292void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) {
293 if (this->is_ready()) {
294 esp_err_t err = uart_set_rx_timeout(this->uart_num_, rx_timeout);
295 if (err != ESP_OK) {
296 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
297 return;
298 }
299 }
300 this->rx_timeout_ = rx_timeout;
301}
302
303void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
304 int32_t write_len = uart_write_bytes(this->uart_num_, data, len);
305 if (write_len != (int32_t) len) {
306 ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len);
307 this->mark_failed();
308 }
309#ifdef USE_UART_DEBUGGER
310 for (size_t i = 0; i < len; i++) {
311 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
312 }
313#endif
314}
315
316bool IDFUARTComponent::peek_byte(uint8_t *data) {
317 if (!this->check_read_timeout_())
318 return false;
319 if (this->has_peek_) {
320 *data = this->peek_byte_;
321 } else {
322 int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_PERIOD_MS);
323 if (len == 0) {
324 *data = 0;
325 } else {
326 this->has_peek_ = true;
327 this->peek_byte_ = *data;
328 }
329 }
330 return true;
331}
332
333bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
334 if (len == 0) {
335 return false;
336 }
337 size_t length_to_read = len;
338 int32_t read_len = 0;
339 if (!this->check_read_timeout_(len))
340 return false;
341 if (this->has_peek_) {
342 length_to_read--;
343 *data = this->peek_byte_;
344 this->has_peek_ = false;
345 }
346 if (length_to_read > 0)
347 read_len = uart_read_bytes(this->uart_num_, data + (len - length_to_read), length_to_read, 20 / portTICK_PERIOD_MS);
348#ifdef USE_UART_DEBUGGER
349 for (size_t i = 0; i < len; i++) {
350 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
351 }
352#endif
353 return read_len == (int32_t) length_to_read;
354}
355
357 size_t available = 0;
358 esp_err_t err;
359
360 err = uart_get_buffered_data_len(this->uart_num_, &available);
361
362 if (err != ESP_OK) {
363 ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err));
364 this->mark_failed();
365 }
366 if (this->has_peek_) {
367 available++;
368 }
369 return available;
370}
371
373 ESP_LOGVV(TAG, " Flushing");
374 TickType_t ticks = this->flush_timeout_ms_ == 0 ? portMAX_DELAY : pdMS_TO_TICKS(this->flush_timeout_ms_);
375 esp_err_t err = uart_wait_tx_done(this->uart_num_, ticks);
376 if (err == ESP_OK)
378 if (err == ESP_ERR_TIMEOUT)
380 return FlushResult::FAILED;
381}
382
384
385#ifdef USE_UART_WAKE_LOOP_ON_RX
386// ISR callback invoked by the ESP-IDF UART driver when data arrives.
387// Wakes the main loop directly via vTaskNotifyGiveFromISR() — no queue or task needed.
388void IRAM_ATTR IDFUARTComponent::uart_rx_isr_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif,
389 BaseType_t *task_woken) {
390 if (uart_select_notif == UART_SELECT_READ_NOTIF) {
392 }
393}
394#endif // USE_UART_WAKE_LOOP_ON_RX
395
396} // namespace esphome::uart
397#endif // USE_ESP32
BedjetMode mode
BedJet operating mode.
static void IRAM_ATTR wake_loop_isrsafe(int *px_higher_priority_task_woken)
Wake the main event loop from an ISR.
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
void set_rx_timeout(size_t rx_timeout) override
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
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)
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:119
@ UART_SELECTION_USB_CDC
Definition logger.h:116
Logger * global_logger
const char *const TAG
Definition spi.cpp:7
const LogString * parity_to_str(UARTParityOptions parity)
Definition uart.cpp:36
FlushResult
Result of a flush() call.
@ TIMEOUT
Confirmed: timed out before TX completed.
@ FAILED
Confirmed: driver or hardware error.
@ SUCCESS
Confirmed: all bytes left the TX FIFO.
std::string size_t len
Definition helpers.h:892
static void uint32_t