#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 "hal/uart_ll.h" #include "esp_private/gpio.h" #include "soc/gpio_num.h" #include "soc/soc_caps.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 { ESPHOME_LOG_TAG(TAG, "uart"); /// 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; } // clock_source_ is stored in a byte; every uart_sclk_t value is a soc_module_clk_t below SOC_MOD_CLK_INVALID static_assert(SOC_MOD_CLK_INVALID <= UINT8_MAX, "uart_sclk_t no longer fits in uint8_t clock_source_"); static const LogString *clock_source_to_str(uart_sclk_t clock_source) { switch (clock_source) { #if SOC_UART_SUPPORT_APB_CLK case UART_SCLK_APB: return LOG_STR("APB"); #endif #if SOC_UART_SUPPORT_XTAL_CLK case UART_SCLK_XTAL: return LOG_STR("XTAL"); #endif #if SOC_UART_SUPPORT_RTC_CLK case UART_SCLK_RTC: return LOG_STR("RTC"); #endif #if SOC_UART_SUPPORT_REF_TICK case UART_SCLK_REF_TICK: return LOG_STR("REF_TICK"); #endif default: return clock_source == UART_SCLK_DEFAULT ? LOG_STR("DEFAULT") : LOG_STR("UNKNOWN"); } } 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 = static_cast(this->clock_source_); 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 this->tx_buffer_size_, // TX ring buffer size; 0 makes uart_write_bytes() block until // the FIFO has taken everything 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; } // uart_param_config must be called after uart_driver_install and before any // other uart_set_*() calls. The driver installation resets the UART peripheral // registers to their default state, overwriting any previously configured baud // rate or framing settings. Calling uart_param_config here ensures the requested // settings are applied after the reset and before pin routing, inversion, and // threshold configuration. 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; } this->last_good_framing_ = this->framing_(); 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_); } // Must precede uart_set_pin() so an inverted TX line never shows the wrong idle // level; apply_line_settings_() repeats it later for the reset registers. err = uart_set_line_inverse(this->uart_num_, this->line_inversion_mask_()); 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; } if (this->apply_line_settings_() != ESP_OK) { this->mark_failed(); return; } #ifdef USE_GPIO_HOLD // Release held pins so the UART peripheral can drive them this->set_pins_hold_(false); #endif #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(); } } uint32_t IDFUARTComponent::line_inversion_mask_() { 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; } return invert; } esp_err_t IDFUARTComponent::apply_line_settings_() { // uart_param_config() resets these; call after every use of it. esp_err_t err = uart_set_line_inverse(this->uart_num_, this->line_inversion_mask_()); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_line_inverse failed: %s", esp_err_to_name(err)); return err; } 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)); return err; } 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)); return err; } // Per ESP-IDF docs, uart_set_mode() must be called only after uart_driver_install(). auto mode = this->flow_control_pin_ != nullptr ? UART_MODE_RS485_HALF_DUPLEX : UART_MODE_UART; err = uart_set_mode(this->uart_num_, mode); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_set_mode failed: %s", esp_err_to_name(err)); return err; } return ESP_OK; } void IDFUARTComponent::set_framing_(const Framing &framing) { this->baud_rate_ = framing.baud_rate; this->data_bits_ = framing.data_bits; this->stop_bits_ = framing.stop_bits; this->parity_ = framing.parity; this->rx_full_threshold_ = framing.rx_full_threshold; } esp_err_t IDFUARTComponent::apply_settings_live() { if (this->is_failed()) { return ESP_ERR_INVALID_STATE; } // No driver yet: nothing to reconfigure in place. if (!uart_is_driver_installed(this->uart_num_)) { this->load_settings(false); return this->is_failed() ? ESP_FAIL : ESP_OK; } // Keeps the driver ring buffers; flushes both hardware FIFOs (in-flight bytes lost). uart_config_t uart_config = this->get_config_(); esp_err_t err = uart_param_config(this->uart_num_, &uart_config); if (err != ESP_OK) { // Failure leaves the registers reset; put back the last accepted framing so the // getters still describe the hardware. if (this->last_good_framing_.baud_rate == 0) { ESP_LOGE(TAG, "uart_param_config (live) failed: %s; no previous framing to restore", esp_err_to_name(err)); this->mark_failed(); return err; } ESP_LOGW(TAG, "uart_param_config (live) failed: %s; restoring %" PRIu32 " baud", esp_err_to_name(err), this->last_good_framing_.baud_rate); this->set_framing_(this->last_good_framing_); uart_config = this->get_config_(); esp_err_t restore_err = uart_param_config(this->uart_num_, &uart_config); if (restore_err != ESP_OK) { ESP_LOGE(TAG, "UART left unconfigured after failed live reconfigure: %s", esp_err_to_name(restore_err)); this->mark_failed(); return err; } // Previous framing is live again; report the refusal (line-setting errors log). this->apply_line_settings_(); return err; } this->last_good_framing_ = this->framing_(); // The new framing is live; a line-setting failure here only logs. this->apply_line_settings_(); return ESP_OK; } 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->tx_buffer_size_ > 0) { ESP_LOGCONFIG(TAG, " TX Buffer Size: %zu", this->tx_buffer_size_); } 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\n" " Clock Source: %s" #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_, LOG_STR_ARG(clock_source_to_str(static_cast(this->clock_source_)))); 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; } size_t IDFUARTComponent::available_for_write() { if (this->uart_num_ == UART_NUM_MAX || !uart_is_driver_installed(this->uart_num_)) return 0; if (this->tx_buffer_size_ == 0) { return uart_ll_get_txfifo_len(UART_LL_GET_HW(this->uart_num_)); } // The driver's figure already deducts its ring item headers, so a write of this size fits size_t free = 0; uart_get_tx_buffer_free_size(this->uart_num_, &free); return free; } 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 void IDFUARTComponent::on_shutdown() { if (this->uart_num_ == UART_NUM_MAX || !uart_is_driver_installed(this->uart_num_)) return; uart_wait_tx_done(this->uart_num_, pdMS_TO_TICKS(100)); // Keep the peripheral quiet across a soft reset so ROM output does not reach the attached device (#15472) esp_err_t err = uart_driver_delete(this->uart_num_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_delete failed: %s", esp_err_to_name(err)); } #ifdef USE_GPIO_HOLD // Hold the pins again so they keep their state through the reset this->set_pins_hold_(true); #endif } #ifdef USE_GPIO_HOLD void IDFUARTComponent::set_pins_hold_(bool hold) { for (auto *pin : {this->tx_pin_, this->rx_pin_, this->flow_control_pin_}) { if (pin != nullptr) pin->set_hold(hold); } } #endif } // namespace esphome::uart #endif // USE_ESP32