diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 4db4e80a60..69f8ab554a 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -64,6 +64,9 @@ class RemoteTransmitterComponent final : public remote_base::RemoteTransmitterBa #if defined(USE_ESP32) && SOC_RMT_SUPPORTED void set_with_dma(bool with_dma) { this->with_dma_ = with_dma; } void set_eot_level(bool eot_level) { this->eot_level_ = eot_level; } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) + void loop() override; +#endif #endif #if (defined(USE_ESP32) && SOC_RMT_SUPPORTED) || defined(USE_LIBRETINY_VARIANT_RTL8720C) || \ defined(REMOTE_TRANSMITTER_BK_PWM) @@ -145,21 +148,32 @@ class RemoteTransmitterComponent final : public remote_base::RemoteTransmitterBa void wait_for_rmt_(); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) + static bool tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *event, void *arg); + size_t encode_symbols_(rmt_symbol_half_t *out, uint32_t send_wait, uint32_t *offset); + void wait_all_done_(); + void deliver_completion_(); + RemoteTransmitterComponentStore store_{}; std::vector rmt_temp_; #else std::vector rmt_temp_; #endif uint32_t current_carrier_frequency_{38000}; - bool initialized_{false}; - bool with_dma_{false}; - bool eot_level_{false}; rmt_channel_handle_t channel_{NULL}; rmt_encoder_handle_t encoder_{NULL}; esp_err_t error_code_{ESP_OK}; std::string error_string_; + bool initialized_{false}; + bool with_dma_{false}; + bool eot_level_{false}; bool inverted_{false}; bool non_blocking_{false}; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) + // set when a frame is handed to the hardware, cleared once its completion is reported; + // the transmit done interrupt sets tx_done_ + bool tx_active_{false}; + volatile bool tx_done_{false}; +#endif #endif uint8_t carrier_duty_percent_{50}; diff --git a/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp b/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp index 3c9a12d472..9b4796249b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_rmt.cpp @@ -15,6 +15,9 @@ static const char *const TAG = "remote_transmitter"; static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) +// How long a blocking wait sleeps between watchdog feeds +static constexpr int RMT_WAIT_SLICE_MS = 50; + static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free, rmt_symbol_word_t *symbols, bool *done, void *arg) { auto *store = static_cast(arg); @@ -49,6 +52,31 @@ static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size *done = false; return count; } + +// Splits a duration into 15-bit symbols; with out == nullptr only counts them +static size_t write_symbols(rmt_symbol_half_t *out, size_t pos, uint32_t ticks, bool level) { + size_t count = 0; + while (ticks > 0) { + uint32_t duration = std::min(ticks, RMT_SYMBOL_DURATION_MAX); + if (out != nullptr) { + out[pos + count] = { + .duration = static_cast(duration), + .level = static_cast(level), + }; + } + ticks -= duration; + count++; + } + return count; +} + +bool IRAM_ATTR HOT RemoteTransmitterComponent::tx_done_callback(rmt_channel_handle_t channel, + const rmt_tx_done_event_data_t *event, void *arg) { + auto *self = static_cast(arg); + self->tx_done_ = true; + self->enable_loop_soon_any_context(); + return false; +} #endif void RemoteTransmitterComponent::setup() { @@ -83,8 +111,12 @@ void RemoteTransmitterComponent::digital_write(bool value) { rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); config.flags.eot_level = value; + config.flags.queue_nonblocking = 1; + // a frame still on the wire finishes first and reports its completion + this->wait_for_rmt_(); this->store_.times = 1; this->store_.index = 0; + rmt_encoder_handle_t encoder = this->encoder_; #else rmt_symbol_word_t symbol = { .duration0 = 1, @@ -95,17 +127,24 @@ void RemoteTransmitterComponent::digital_write(bool value) { rmt_transmit_config_t config; memset(&config, 0, sizeof(config)); config.flags.eot_level = value; + rmt_encoder_handle_t encoder = this->encoder_; #endif - esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config); + esp_err_t error = rmt_transmit(this->channel_, encoder, &symbol, sizeof(symbol), &config); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); this->status_set_warning(); } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) + this->wait_all_done_(); + // a level write is not a frame, so its completion is not reported + this->tx_done_ = false; +#else error = rmt_tx_wait_all_done(this->channel_, -1); if (error != ESP_OK) { ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); this->status_set_warning(); } +#endif } void RemoteTransmitterComponent::configure_rmt_() { @@ -152,6 +191,17 @@ void RemoteTransmitterComponent::configure_rmt_() { } #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) + rmt_tx_event_callbacks_t callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.on_trans_done = tx_done_callback; + error = rmt_tx_register_event_callbacks(this->channel_, &callbacks, this); + if (error != ESP_OK) { + this->error_code_ = error; + this->error_string_ = "in rmt_tx_register_event_callbacks"; + this->mark_failed(); + return; + } + rmt_simple_encoder_config_t encoder; memset(&encoder, 0, sizeof(encoder)); encoder.callback = encoder_callback; @@ -206,6 +256,118 @@ void RemoteTransmitterComponent::configure_rmt_() { } } +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) +// Blocks until the hardware is idle, feeding the watchdog while waiting +void RemoteTransmitterComponent::wait_all_done_() { + esp_err_t error; + while ((error = rmt_tx_wait_all_done(this->channel_, RMT_WAIT_SLICE_MS)) == ESP_ERR_TIMEOUT) { + App.feed_wdt(); + } + if (error != ESP_OK) { + ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error)); + this->status_set_warning(); + } +} + +void RemoteTransmitterComponent::deliver_completion_() { + this->tx_done_ = false; + this->tx_active_ = false; + this->complete_trigger_.trigger(); +} + +// Blocks until any frame on the wire has gone out and reports its completion +void RemoteTransmitterComponent::wait_for_rmt_() { + this->wait_all_done_(); + if (this->tx_active_) + this->deliver_completion_(); +} + +void RemoteTransmitterComponent::loop() { + if (this->tx_done_) + this->deliver_completion_(); + // the transmit done interrupt re-enables the loop + this->disable_loop(); +} + +// Encodes the repeat gap followed by the frame; with out == nullptr only counts symbols. +// The gap leads the buffer so the encoder skips it on the first pass and replays it +// before every repeat; offset receives the index of the first frame symbol. +size_t RemoteTransmitterComponent::encode_symbols_(rmt_symbol_half_t *out, uint32_t send_wait, uint32_t *offset) { + size_t count = write_symbols(out, 0, this->from_microseconds_(send_wait), this->eot_level_); + *offset = count; + for (int32_t value : this->temp_.get_data()) { + bool level = value >= 0; + if (!level) { + value = -value; + } + count += write_symbols(out, count, this->from_microseconds_(static_cast(value)), level ^ this->inverted_); + } + return count; +} + +void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { + if (this->is_failed()) { + return; + } + + if (this->tx_active_ && this->tx_done_) { + // finished, but loop() has not run yet + this->deliver_completion_(); + } + + if (send_times == 0 || this->tx_active_) { + // nothing is sent, but both triggers still fire so an on_complete-sequenced automation + // does not stall; a zero repeat count would never finish in the encoder, and a frame that + // arrives while another is on the wire is dropped rather than blocking the loop + if (this->tx_active_) { + ESP_LOGW(TAG, "Transmitter busy, dropping"); + this->status_set_warning(); + } + this->transmit_trigger_.trigger(); + this->complete_trigger_.trigger(); + return; + } + + if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { + this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); + this->configure_rmt_(); + } + + uint32_t offset; + size_t count = this->encode_symbols_(nullptr, send_wait, &offset); + if (count <= offset) { + ESP_LOGE(TAG, "Empty data"); + return; + } + this->rmt_temp_.resize(count); + this->encode_symbols_(this->rmt_temp_.data(), send_wait, &offset); + this->store_.times = send_times; + this->store_.index = offset; + + this->transmit_trigger_.trigger(); + + rmt_transmit_config_t config; + memset(&config, 0, sizeof(config)); + config.flags.eot_level = this->eot_level_; + config.flags.queue_nonblocking = 1; + this->tx_done_ = false; + this->tx_active_ = true; + esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(), + this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config); + if (error != ESP_OK) { + ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); + this->status_set_warning(); + // nothing will complete, so report it now + this->deliver_completion_(); + return; + } + this->status_clear_warning(); + + if (!this->non_blocking_) { + this->wait_for_rmt_(); + } +} +#else void RemoteTransmitterComponent::wait_for_rmt_() { esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1); if (error != ESP_OK) { @@ -216,87 +378,6 @@ void RemoteTransmitterComponent::wait_for_rmt_() { this->complete_trigger_.trigger(); } -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) -void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { - uint64_t total_duration = 0; - - if (this->is_failed()) { - return; - } - - // if the timeout was cancelled, block until the tx is complete - if (this->non_blocking_ && this->cancel_timeout("complete")) { - this->wait_for_rmt_(); - } - - if (this->current_carrier_frequency_ != this->temp_.get_carrier_frequency()) { - this->current_carrier_frequency_ = this->temp_.get_carrier_frequency(); - this->configure_rmt_(); - } - - this->rmt_temp_.clear(); - this->rmt_temp_.reserve(this->temp_.get_data().size() + 1); - - // encode any delay at the start of the buffer to simplify the encoder callback - // this will be skipped the first time around - total_duration += send_wait * (send_times - 1); - send_wait = this->from_microseconds_(static_cast(send_wait)); - while (send_wait > 0) { - int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX)); - this->rmt_temp_.push_back({ - .duration = static_cast(duration), - .level = static_cast(this->eot_level_), - }); - send_wait -= duration; - } - - // encode data - size_t offset = this->rmt_temp_.size(); - for (int32_t value : this->temp_.get_data()) { - bool level = value >= 0; - if (!level) { - value = -value; - } - total_duration += value * send_times; - value = this->from_microseconds_(static_cast(value)); - while (value > 0) { - int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX)); - this->rmt_temp_.push_back({ - .duration = static_cast(duration), - .level = static_cast(level ^ this->inverted_), - }); - value -= duration; - } - } - - if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) { - ESP_LOGE(TAG, "Empty data"); - return; - } - - this->transmit_trigger_.trigger(); - - rmt_transmit_config_t config; - memset(&config, 0, sizeof(config)); - config.flags.eot_level = this->eot_level_; - this->store_.times = send_times; - this->store_.index = offset; - esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(), - this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config); - if (error != ESP_OK) { - ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error)); - this->status_set_warning(); - } else { - this->status_clear_warning(); - } - - if (this->non_blocking_) { - this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); }); - } else { - this->wait_for_rmt_(); - } -} -#else void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) { if (this->is_failed()) return;