#ifdef USE_ESP32 #include "uart_component_esp_idf.h" #include #include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" #include "esphome/core/gpio.h" #include "driver/gpio.h" #include "esp_private/gpio.h" #include "soc/gpio_num.h" #include "soc/uart_pins.h" #ifdef USE_UART_WAKE_LOOP_ON_RX #include "esphome/core/application.h" #endif #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif namespace esphome::uart { static const char *const TAG = "uart.idf"; /// Check if a pin number matches one of the default UART0 GPIO pins. /// These pins may have residual IOMUX state from the ROM bootloader that /// must be cleared before UART reconfiguration. /// /// ESP-IDF's uart_set_pin() has an asymmetry: when routing TX via GPIO matrix, /// it calls gpio_func_sel(PIN_FUNC_GPIO) to clear IOMUX, but for RX it only /// calls gpio_input_enable() which does NOT clear the IOMUX function select. /// If a default UART0 TX pin (configured as TX via IOMUX during boot) is later /// reassigned as RX via GPIO matrix, the old IOMUX TX function remains active, /// causing TX data to loop back into RX on the same pin. static constexpr bool is_default_uart0_pin(int8_t pin_num) { return pin_num == U0TXD_GPIO_NUM || pin_num == U0RXD_GPIO_NUM; } uart_config_t IDFUARTComponent::get_config_() { uart_parity_t parity = UART_PARITY_DISABLE; if (this->parity_ == UART_CONFIG_PARITY_EVEN) { parity = UART_PARITY_EVEN; } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { parity = UART_PARITY_ODD; } uart_word_length_t data_bits; switch (this->data_bits_) { case 5: data_bits = UART_DATA_5_BITS; break; case 6: data_bits = UART_DATA_6_BITS; break; case 7: data_bits = UART_DATA_7_BITS; break; case 8: data_bits = UART_DATA_8_BITS; break; default: data_bits = UART_DATA_BITS_MAX; break; } uart_config_t uart_config{}; uart_config.baud_rate = this->baud_rate_; uart_config.data_bits = data_bits; uart_config.parity = parity; uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; uart_config.source_clk = UART_SCLK_DEFAULT; uart_config.rx_flow_ctrl_thresh = 122; return uart_config; } void IDFUARTComponent::setup() { static uint8_t next_uart_num = 0; #ifdef USE_LOGGER bool logger_uses_hardware_uart = true; #ifdef USE_LOGGER_USB_CDC if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_CDC) { // this is not a hardware UART, ignore it logger_uses_hardware_uart = false; } #endif // USE_LOGGER_USB_CDC #ifdef USE_LOGGER_USB_SERIAL_JTAG if (logger::global_logger->get_uart() == logger::UART_SELECTION_USB_SERIAL_JTAG) { // this is not a hardware UART, ignore it logger_uses_hardware_uart = false; } #endif // USE_LOGGER_USB_SERIAL_JTAG if (logger_uses_hardware_uart && logger::global_logger->get_baud_rate() > 0 && logger::global_logger->get_uart_num() == next_uart_num) { next_uart_num++; } #endif // USE_LOGGER if (next_uart_num >= SOC_UART_NUM) { ESP_LOGW(TAG, "Maximum number of UART components created already"); this->mark_failed(); return; } this->uart_num_ = static_cast(next_uart_num++); #if (SOC_UART_LP_NUM >= 1) size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN); #else size_t fifo_len = SOC_UART_FIFO_LEN; #endif if (this->rx_buffer_size_ <= fifo_len) { ESP_LOGW(TAG, "rx_buffer_size is too small, must be greater than %zu", fifo_len); this->rx_buffer_size_ = fifo_len * 2; } this->load_settings(false); } void IDFUARTComponent::load_settings(bool dump_config) { esp_err_t err; if (uart_is_driver_installed(this->uart_num_)) { err = uart_driver_delete(this->uart_num_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } } err = uart_driver_install(this->uart_num_, // UART number this->rx_buffer_size_, // RX ring buffer size 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will // block task until all data has been sent out 0, // event queue size/depth nullptr, // event queue 0 // Flags used to allocate the interrupt ); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } int8_t tx = this->tx_pin_ != nullptr ? this->tx_pin_->get_pin() : -1; int8_t rx = this->rx_pin_ != nullptr ? this->rx_pin_->get_pin() : -1; int8_t flow_control = this->flow_control_pin_ != nullptr ? this->flow_control_pin_->get_pin() : -1; // Clear residual IOMUX function on UART0 default pins left by the ROM bootloader. // See is_default_uart0_pin() comment for details on the ESP-IDF uart_set_pin() bug. if (is_default_uart0_pin(tx)) { gpio_func_sel(static_cast(tx), PIN_FUNC_GPIO); } if (is_default_uart0_pin(rx)) { gpio_func_sel(static_cast(rx), PIN_FUNC_GPIO); } auto setup_pin_if_needed = [](InternalGPIOPin *pin) { if (!pin) { return; } const auto mask = gpio::Flags::FLAG_OPEN_DRAIN | gpio::Flags::FLAG_PULLUP | gpio::Flags::FLAG_PULLDOWN; if ((pin->get_flags() & mask) != gpio::Flags::FLAG_NONE) { pin->setup(); } }; setup_pin_if_needed(this->rx_pin_); if (this->rx_pin_ != this->tx_pin_) { setup_pin_if_needed(this->tx_pin_); } uint32_t invert = 0; if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) { invert |= UART_SIGNAL_TXD_INV; } if (this->rx_pin_ != nullptr && this->rx_pin_->is_inverted()) { invert |= UART_SIGNAL_RXD_INV; } if (this->flow_control_pin_ != nullptr && this->flow_control_pin_->is_inverted()) { invert |= UART_SIGNAL_RTS_INV; } err = uart_set_line_inverse(this->uart_num_, invert); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } err = uart_set_pin(this->uart_num_, tx, rx, flow_control, UART_PIN_NO_CHANGE); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_pin failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } err = uart_set_rx_full_threshold(this->uart_num_, this->rx_full_threshold_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } err = uart_set_rx_timeout(this->uart_num_, this->rx_timeout_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART; err = uart_set_mode(this->uart_num_, mode); // per docs, must be called only after uart_driver_install() if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } uart_config_t uart_config = this->get_config_(); err = uart_param_config(this->uart_num_, &uart_config); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_param_config failed: %s", esp_err_to_name(err)); this->mark_failed(); return; } #ifdef USE_UART_WAKE_LOOP_ON_RX // Register ISR callback to wake the main loop when UART data arrives. // The callback runs in ISR context and uses vTaskNotifyGiveFromISR() to // wake the main loop task directly — no queue or FreeRTOS task needed. uart_set_select_notif_callback(this->uart_num_, IDFUARTComponent::uart_rx_isr_callback); #endif // USE_UART_WAKE_LOOP_ON_RX if (dump_config) { ESP_LOGCONFIG(TAG, "Reloaded UART %u", this->uart_num_); this->dump_config(); } } void IDFUARTComponent::dump_config() { ESP_LOGCONFIG(TAG, "UART Bus %u:", this->uart_num_); LOG_PIN(" TX Pin: ", this->tx_pin_); LOG_PIN(" RX Pin: ", this->rx_pin_); LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_); if (this->rx_pin_ != nullptr) { ESP_LOGCONFIG(TAG, " RX Buffer Size: %u\n" " RX Full Threshold: %u\n" " RX Timeout: %u", this->rx_buffer_size_, this->rx_full_threshold_, this->rx_timeout_); } if (this->flush_timeout_ms_ > 0) { ESP_LOGCONFIG(TAG, " Flush Timeout: %" PRIu32 " ms", this->flush_timeout_ms_); } ESP_LOGCONFIG(TAG, " Baud Rate: %" PRIu32 " baud\n" " Data Bits: %u\n" " Parity: %s\n" " Stop bits: %u" #ifdef USE_UART_WAKE_LOOP_ON_RX "\n Wake on data RX: ENABLED" #endif , this->baud_rate_, this->data_bits_, LOG_STR_ARG(parity_to_str(this->parity_)), this->stop_bits_); this->check_logger_conflict(); } void IDFUARTComponent::set_rx_full_threshold(size_t rx_full_threshold) { if (this->is_ready()) { esp_err_t err = uart_set_rx_full_threshold(this->uart_num_, rx_full_threshold); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_rx_full_threshold failed: %s", esp_err_to_name(err)); return; } } this->rx_full_threshold_ = rx_full_threshold; } void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) { if (this->is_ready()) { esp_err_t err = uart_set_rx_timeout(this->uart_num_, rx_timeout); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_rx_timeout failed: %s", esp_err_to_name(err)); return; } } this->rx_timeout_ = rx_timeout; } void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { int32_t write_len = uart_write_bytes(this->uart_num_, data, len); if (write_len != (int32_t) len) { ESP_LOGW(TAG, "uart_write_bytes failed: %" PRId32 " != %zu", write_len, len); this->mark_failed(); } #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_TX, data[i]); } #endif } bool IDFUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; if (this->has_peek_) { *data = this->peek_byte_; } else { int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_PERIOD_MS); if (len == 0) { *data = 0; } else { this->has_peek_ = true; this->peek_byte_ = *data; } } return true; } bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { if (len == 0) { return false; } size_t length_to_read = len; int32_t read_len = 0; if (!this->check_read_timeout_(len)) return false; if (this->has_peek_) { length_to_read--; *data = this->peek_byte_; this->has_peek_ = false; } if (length_to_read > 0) read_len = uart_read_bytes(this->uart_num_, data + (len - length_to_read), length_to_read, 20 / portTICK_PERIOD_MS); #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_RX, data[i]); } #endif return read_len == (int32_t) length_to_read; } size_t IDFUARTComponent::available() { size_t available = 0; esp_err_t err; err = uart_get_buffered_data_len(this->uart_num_, &available); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err)); this->mark_failed(); } if (this->has_peek_) { available++; } return available; } UARTFlushResult IDFUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing"); TickType_t ticks = this->flush_timeout_ms_ == 0 ? portMAX_DELAY : pdMS_TO_TICKS(this->flush_timeout_ms_); esp_err_t err = uart_wait_tx_done(this->uart_num_, ticks); if (err == ESP_OK) return UARTFlushResult::UART_FLUSH_RESULT_SUCCESS; if (err == ESP_ERR_TIMEOUT) return UARTFlushResult::UART_FLUSH_RESULT_TIMEOUT; return UARTFlushResult::UART_FLUSH_RESULT_FAILED; } void IDFUARTComponent::check_logger_conflict() {} #ifdef USE_UART_WAKE_LOOP_ON_RX // ISR callback invoked by the ESP-IDF UART driver when data arrives. // Wakes the main loop directly via vTaskNotifyGiveFromISR() — no queue or task needed. void IRAM_ATTR IDFUARTComponent::uart_rx_isr_callback(uart_port_t uart_num, uart_select_notif_t uart_select_notif, BaseType_t *task_woken) { if (uart_select_notif == UART_SELECT_READ_NOTIF) { Application::wake_loop_isrsafe(task_woken); } } #endif // USE_UART_WAKE_LOOP_ON_RX } // namespace esphome::uart #endif // USE_ESP32