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