ESPHome 2026.3.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 "soc/gpio_num.h"
11
12#ifdef USE_UART_WAKE_LOOP_ON_RX
14#endif
15
16#ifdef USE_LOGGER
18#endif
19
20namespace esphome::uart {
21
22static const char *const TAG = "uart.idf";
23
25 uart_parity_t parity = UART_PARITY_DISABLE;
26 if (this->parity_ == UART_CONFIG_PARITY_EVEN) {
27 parity = UART_PARITY_EVEN;
28 } else if (this->parity_ == UART_CONFIG_PARITY_ODD) {
29 parity = UART_PARITY_ODD;
30 }
31
32 uart_word_length_t data_bits;
33 switch (this->data_bits_) {
34 case 5:
35 data_bits = UART_DATA_5_BITS;
36 break;
37 case 6:
38 data_bits = UART_DATA_6_BITS;
39 break;
40 case 7:
41 data_bits = UART_DATA_7_BITS;
42 break;
43 case 8:
44 data_bits = UART_DATA_8_BITS;
45 break;
46 default:
47 data_bits = UART_DATA_BITS_MAX;
48 break;
49 }
50
51 uart_config_t uart_config{};
52 uart_config.baud_rate = this->baud_rate_;
53 uart_config.data_bits = data_bits;
54 uart_config.parity = parity;
55 uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
56 uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
57 uart_config.source_clk = UART_SCLK_DEFAULT;
58 uart_config.rx_flow_ctrl_thresh = 122;
59
60 return uart_config;
61}
62
64 static uint8_t next_uart_num = 0;
65
66#ifdef USE_LOGGER
67 bool logger_uses_hardware_uart = true;
68
69#ifdef USE_LOGGER_USB_CDC
71 // this is not a hardware UART, ignore it
72 logger_uses_hardware_uart = false;
73 }
74#endif // USE_LOGGER_USB_CDC
75
76#ifdef USE_LOGGER_USB_SERIAL_JTAG
78 // this is not a hardware UART, ignore it
79 logger_uses_hardware_uart = false;
80 }
81#endif // USE_LOGGER_USB_SERIAL_JTAG
82
83 if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 &&
84 logger::global_logger->get_uart_num() == next_uart_num) {
85 next_uart_num++;
86 }
87#endif // USE_LOGGER
88
89 if (next_uart_num >= SOC_UART_NUM) {
90 ESP_LOGW(TAG, "Maximum number of UART components created already");
91 this->mark_failed();
92 return;
93 }
94 this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
95
96#if (SOC_UART_LP_NUM >= 1)
97 size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN);
98#else
99 size_t fifo_len = SOC_UART_FIFO_LEN;
100#endif
101 if (this->rx_buffer_size_ <= fifo_len) {
102 ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len);
103 this->rx_buffer_size_ = fifo_len * 2;
104 }
105
106 this->load_settings(false);
107}
108
109void IDFUARTComponent::load_settings(bool dump_config) {
110 esp_err_t err;
111
112 if (uart_is_driver_installed(this->uart_num_)) {
113 err = uart_driver_delete(this->uart_num_);
114 if (err != ESP_OK) {
115 ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err));
116 this->mark_failed();
117 return;
118 }
119 }
120 err = uart_driver_install(this->uart_num_, // UART number
121 this->rx_buffer_size_, // RX ring buffer size
122 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will
123 // block task until all data has been sent out
124 0, // event queue size/depth
125 nullptr, // event queue
126 0 // Flags used to allocate the interrupt
127 );
128 if (err != ESP_OK) {
129 ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
130 this->mark_failed();
131 return;
132 }
133
134 auto setup_pin_if_needed = [](InternalGPIOPin *pin) {
135 if (!pin) {
136 return;
137 }
139 if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) {
140 pin->setup();
141 }
142 };
143
144 setup_pin_if_needed(this->rx_pin_);
145 if (this->rx_pin_ != this->tx_pin_) {
146 setup_pin_if_needed(this->tx_pin_);
147 }
148
149 int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1;
150 int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1;
151 int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1;
152
153 uint32_t invert = 0;
154 if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) {
155 invert |= UART_SIGNAL_TXD_INV;
156 }
157 if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) {
158 invert |= UART_SIGNAL_RXD_INV;
159 }
160 if (this->flow_control_pin_ != nullptr && this->flow_control_pin_->is_inverted()) {
161 invert |= UART_SIGNAL_RTS_INV;
162 }
163
164 err = uart_set_line_inverse(this->uart_num_, invert);
165 if (err != ESP_OK) {
166 ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err));
167 this->mark_failed();
168 return;
169 }
170
171 err = uart_set_pin(this->uart_num_, tx, rx, flow_control, UART_PIN_NO_CHANGE);
172 if (err != ESP_OK) {
173 ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
174 this->mark_failed();
175 return;
176 }
177
178 err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_);
179 if (err != ESP_OK) {
180 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
181 this->mark_failed();
182 return;
183 }
184
185 err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_);
186 if (err != ESP_OK) {
187 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
188 this->mark_failed();
189 return;
190 }
191
192 auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART;
193 err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install()
194 if (err != ESP_OK) {
195 ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err));
196 this->mark_failed();
197 return;
198 }
199
200 uart_config_t uart_config = this->get_config_();
201 err = uart_param_config(this->uart_num_, &uart_config);
202 if (err != ESP_OK) {
203 ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
204 this->mark_failed();
205 return;
206 }
207
208#ifdef USE_UART_WAKE_LOOP_ON_RX
209 // Register ISR callback to wake the main loop when UART data arrives.
210 // The callback runs in ISR context and uses vTaskNotifyGiveFromISR() to
211 // wake the main loop task directly — no queue or FreeRTOS task needed.
212 uart_set_select_notif_callback(this->uart_num_, IDFUARTComponent::uart_rx_isr_callback);
213#endif // USE_UART_WAKE_LOOP_ON_RX
214
215 if (dump_config) {
216 ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_);
217 this->dump_config();
218 }
219}
220
222 ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_);
223 LOG_PIN(" TX Pin: ", this->tx_pin_);
224 LOG_PIN(" RX Pin: ", this->rx_pin_);
225 LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
226 if (this->rx_pin_ != nullptr) {
227 ESP_LOGCONFIG(TAG,
228 " RX Buffer Size: %u\n"
229 " RX Full Threshold: %u\n"
230 " RX Timeout: %u",
232 }
233 ESP_LOGCONFIG(TAG,
234 " Baud Rate: %" PRIu32 " baud\n"
235 " Data Bits: %u\n"
236 " Parity: %s\n"
237 " Stop bits: %u"
238#ifdef USE_UART_WAKE_LOOP_ON_RX
239 "\n Wake on data RX: ENABLED"
240#endif
241 ,
242 this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_);
243 this->check_logger_conflict();
244}
245
246void IDFUARTComponent::set_rx_full_threshold(size_t rx_full_threshold) {
247 if (this->is_ready()) {
248 esp_err_t err = uart_set_rx_full_threshold(this->uart_num_, rx_full_threshold);
249 if (err != ESP_OK) {
250 ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err));
251 return;
252 }
253 }
254 this->rx_full_threshold_ = rx_full_threshold;
255}
256
257void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) {
258 if (this->is_ready()) {
259 esp_err_t err = uart_set_rx_timeout(this->uart_num_, rx_timeout);
260 if (err != ESP_OK) {
261 ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err));
262 return;
263 }
264 }
265 this->rx_timeout_ = rx_timeout;
266}
267
268void IDFUARTComponent::write_array(const uint8_t *data, size_t len) {
269 int32_t write_len = uart_write_bytes(this->uart_num_, data, len);
270 if (write_len != (int32_t) len) {
271 ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len);
272 this->mark_failed();
273 }
274#ifdef USE_UART_DEBUGGER
275 for (size_t i = 0; i < len; i++) {
276 this->debug_callback_.call(UART_DIRECTION_TX, data[i]);
277 }
278#endif
279}
280
281bool IDFUARTComponent::peek_byte(uint8_t *data) {
282 if (!this->check_read_timeout_())
283 return false;
284 if (this->has_peek_) {
285 *data = this->peek_byte_;
286 } else {
287 int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_PERIOD_MS);
288 if (len == 0) {
289 *data = 0;
290 } else {
291 this->has_peek_ = true;
292 this->peek_byte_ = *data;
293 }
294 }
295 return true;
296}
297
298bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
299 size_t length_to_read = len;
300 int32_t read_len = 0;
301 if (!this->check_read_timeout_(len))
302 return false;
303 if (this->has_peek_) {
304 length_to_read--;
305 *data = this->peek_byte_;
306 data++;
307 this->has_peek_ = false;
308 }
309 if (length_to_read > 0)
310 read_len = uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS);
311#ifdef USE_UART_DEBUGGER
312 for (size_t i = 0; i < len; i++) {
313 this->debug_callback_.call(UART_DIRECTION_RX, data[i]);
314 }
315#endif
316 return read_len == (int32_t) length_to_read;
317}
318
320 size_t available = 0;
321 esp_err_t err;
322
323 err = uart_get_buffered_data_len(this->uart_num_, &available);
324
325 if (err != ESP_OK) {
326 ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err));
327 this->mark_failed();
328 }
329 if (this->has_peek_) {
330 available++;
331 }
332 return available;
333}
334
336 ESP_LOGVV(TAG, " Flushing");
337 uart_wait_tx_done(this->uart_num_, portMAX_DELAY);
338}
339
341
342#ifdef USE_UART_WAKE_LOOP_ON_RX
343// ISR callback invoked by the ESP-IDF UART driver when data arrives.
344// Wakes the main loop directly via vTaskNotifyGiveFromISR() — no queue or task needed.
345void IRAM_ATTR IDFUARTComponent::uart_rx_isr_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif,
346 BaseType_t *task_woken) {
347 if (uart_select_notif == UART_SELECT_READ_NOTIF) {
349 }
350}
351#endif // USE_UART_WAKE_LOOP_ON_RX
352
353} // namespace esphome::uart
354#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
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
Definition logger.cpp:278
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:817