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