ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
remote_transmitter_rmt.cpp
Go to the documentation of this file.
2#include "esphome/core/log.h"
4
5#ifdef USE_ESP32
6#include <soc/soc_caps.h>
7#if SOC_RMT_SUPPORTED
8#include <driver/gpio.h>
9
11
12ESPHOME_LOG_TAG(TAG, "remote_transmitter");
13
14static constexpr uint32_t COMPLETE_TIMEOUT_ID = 0;
15
16// Maximum RMT symbol duration (15-bit field)
17static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
18
19#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
20static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
21 rmt_symbol_word_t *symbols, bool *done, void *arg) {
22 auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
23 const auto *encoded = static_cast<const rmt_symbol_half_t *>(data);
24 size_t length = size / sizeof(rmt_symbol_half_t);
25 size_t count = 0;
26
27 // copy symbols
28 for (size_t i = 0; i < free; i++) {
29 uint16_t sym_0 = encoded[store->index++].val;
30 if (store->index >= length) {
31 store->index = 0;
32 store->times--;
33 if (store->times == 0) {
34 *done = true;
35 symbols[count++].val = sym_0;
36 return count;
37 }
38 }
39 uint16_t sym_1 = encoded[store->index++].val;
40 if (store->index >= length) {
41 store->index = 0;
42 store->times--;
43 if (store->times == 0) {
44 *done = true;
45 symbols[count++].val = sym_0 | (sym_1 << 16);
46 return count;
47 }
48 }
49 symbols[count++].val = sym_0 | (sym_1 << 16);
50 }
51 *done = false;
52 return count;
53}
54#endif
55
56void RemoteTransmitterComponent::fail_(esp_err_t error, const LogString *reason) {
57 ESP_LOGE(TAG, "RMT driver failed: %s", esp_err_to_name(error));
58 this->mark_failed(reason);
59}
60
62 this->inverted_ = this->pin_->is_inverted();
63 this->configure_rmt_();
64}
65
67 ESP_LOGCONFIG(TAG, "Remote Transmitter:");
68 ESP_LOGCONFIG(TAG,
69 " Clock resolution: %" PRIu32 " hz\n"
70 " RMT symbols: %" PRIu32,
71 this->clock_resolution_, this->rmt_symbols_);
72 LOG_PIN(" Pin: ", this->pin_);
73
74 if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) {
75 ESP_LOGCONFIG(TAG, " Carrier Duty: %u%%", this->carrier_duty_percent_);
76 }
77}
78
80#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
81 rmt_symbol_half_t symbol = {
82 .duration = 1,
83 .level = value,
84 };
85 rmt_transmit_config_t config;
86 memset(&config, 0, sizeof(config));
87 config.flags.eot_level = value;
88 this->store_.times = 1;
89 this->store_.index = 0;
90#else
91 rmt_symbol_word_t symbol = {
92 .duration0 = 1,
93 .level0 = value,
94 .duration1 = 0,
95 .level1 = value,
96 };
97 rmt_transmit_config_t config;
98 memset(&config, 0, sizeof(config));
99 config.flags.eot_level = value;
100#endif
101 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
102 if (error != ESP_OK) {
103 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
104 this->status_set_warning();
105 }
106 error = rmt_tx_wait_all_done(this->channel_, -1);
107 if (error != ESP_OK) {
108 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
109 this->status_set_warning();
110 }
111}
112
114 esp_err_t error;
115
116 if (!this->initialized_) {
117 bool open_drain = (this->pin_->get_flags() & gpio::FLAG_OPEN_DRAIN) != 0;
118 rmt_tx_channel_config_t channel;
119 memset(&channel, 0, sizeof(channel));
120 channel.clk_src = RMT_CLK_SRC_DEFAULT;
121 channel.resolution_hz = this->clock_resolution_;
122 channel.gpio_num = gpio_num_t(this->pin_->get_pin());
123 channel.mem_block_symbols = this->rmt_symbols_;
124 channel.trans_queue_depth = 1;
125 channel.flags.invert_out = 0;
126 channel.flags.with_dma = this->with_dma_;
127 channel.intr_priority = 0;
128#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
129 channel.flags.io_loop_back = open_drain;
130 channel.flags.io_od_mode = open_drain;
131#endif
132 error = rmt_new_tx_channel(&channel, &this->channel_);
133 if (error != ESP_OK) {
134 this->fail_(error,
135 error == ESP_ERR_NOT_FOUND ? LOG_STR("out of RMT symbol memory") : LOG_STR("in rmt_new_tx_channel"));
136 return;
137 }
138#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
139 if (open_drain) {
140 gpio_num_t gpio = gpio_num_t(this->pin_->get_pin());
141 gpio_od_enable(gpio);
142 gpio_input_enable(gpio);
143 }
144#endif
145 if (this->pin_->get_flags() & gpio::FLAG_PULLUP) {
146 gpio_pullup_en(gpio_num_t(this->pin_->get_pin()));
147 } else {
148 gpio_pullup_dis(gpio_num_t(this->pin_->get_pin()));
149 }
150
151#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
152 rmt_simple_encoder_config_t encoder;
153 memset(&encoder, 0, sizeof(encoder));
154 encoder.callback = encoder_callback;
155 encoder.arg = &this->store_;
156 encoder.min_chunk_size = 1;
157 error = rmt_new_simple_encoder(&encoder, &this->encoder_);
158 if (error != ESP_OK) {
159 this->fail_(error, LOG_STR("in rmt_new_simple_encoder"));
160 return;
161 }
162#else
163 rmt_copy_encoder_config_t encoder;
164 memset(&encoder, 0, sizeof(encoder));
165 error = rmt_new_copy_encoder(&encoder, &this->encoder_);
166 if (error != ESP_OK) {
167 this->fail_(error, LOG_STR("in rmt_new_copy_encoder"));
168 return;
169 }
170#endif
171
172 error = rmt_enable(this->channel_);
173 if (error != ESP_OK) {
174 this->fail_(error, LOG_STR("in rmt_enable"));
175 return;
176 }
177 this->digital_write(open_drain || this->inverted_);
178 this->initialized_ = true;
179 }
180
181 if (this->current_carrier_frequency_ == 0 || this->carrier_duty_percent_ == 100) {
182 error = rmt_apply_carrier(this->channel_, nullptr);
183 } else {
184 rmt_carrier_config_t carrier;
185 memset(&carrier, 0, sizeof(carrier));
186 carrier.frequency_hz = this->current_carrier_frequency_;
187 carrier.duty_cycle = (float) this->carrier_duty_percent_ / 100.0f;
188 carrier.flags.polarity_active_low = this->inverted_;
189 carrier.flags.always_on = 1;
190 error = rmt_apply_carrier(this->channel_, &carrier);
191 }
192 if (error != ESP_OK) {
193 this->fail_(error, LOG_STR("in rmt_apply_carrier"));
194 return;
195 }
196}
197
199 esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1);
200 if (error != ESP_OK) {
201 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
202 this->status_set_warning();
203 }
204
205 this->fire_complete_(error == ESP_OK);
206}
207
208#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
210 // a frame still on the wire is waited out, and its completion reported, before the next one
211 if (this->non_blocking_ && this->cancel_timeout(COMPLETE_TIMEOUT_ID)) {
212 this->wait_for_rmt_();
213 }
214}
215
217 uint64_t total_duration = 0;
218
219 if (this->is_failed()) {
220 // both triggers still fire, so a paced API client or on_complete automation is not left waiting
222 this->fire_complete_(false);
223 return;
224 }
225
228 this->configure_rmt_();
229 if (this->is_failed()) { // the carrier change failed, there is no channel to send on
231 this->fire_complete_(false);
232 return;
233 }
234 }
235
236 this->rmt_temp_.clear();
237 this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
238
239 // encode any delay at the start of the buffer to simplify the encoder callback
240 // this will be skipped the first time around
241 total_duration += send_wait * (send_times - 1);
242 send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
243 while (send_wait > 0) {
244 int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
245 this->rmt_temp_.push_back({
246 .duration = static_cast<uint16_t>(duration),
247 .level = static_cast<uint16_t>(this->eot_level_),
248 });
249 send_wait -= duration;
250 }
251
252 // encode data
253 size_t offset = this->rmt_temp_.size();
254 for (int32_t value : this->temp_.get_data()) {
255 bool level = value >= 0;
256 if (!level) {
257 value = -value;
258 }
259 total_duration += value * send_times;
260 value = this->from_microseconds_(static_cast<uint32_t>(value));
261 while (value > 0) {
262 int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
263 this->rmt_temp_.push_back({
264 .duration = static_cast<uint16_t>(duration),
265 .level = static_cast<uint16_t>(level ^ this->inverted_),
266 });
267 value -= duration;
268 }
269 }
270
271 if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
272 ESP_LOGE(TAG, "Empty data");
274 this->fire_complete_(false);
275 return;
276 }
277
279
280 rmt_transmit_config_t config;
281 memset(&config, 0, sizeof(config));
282 config.flags.eot_level = this->eot_level_;
283 this->store_.times = send_times;
284 this->store_.index = offset;
285 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
286 this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
287 if (!this->status_comm_result(error == ESP_OK, nullptr)) {
288 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
289 // nothing was queued, so there is no frame to wait for
290 this->fire_complete_(false);
291 return;
292 }
293
294 if (this->non_blocking_) {
295 this->set_timeout(COMPLETE_TIMEOUT_ID, total_duration / 1000, [this]() { this->wait_for_rmt_(); });
296 } else {
297 this->wait_for_rmt_();
298 }
299}
300#else
302
304 if (this->is_failed()) {
306 this->fire_complete_(false);
307 return;
308 }
309
312 this->configure_rmt_();
313 if (this->is_failed()) { // the carrier change failed, there is no channel to send on
315 this->fire_complete_(false);
316 return;
317 }
318 }
319
320 this->rmt_temp_.clear();
321 this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2);
322 uint32_t rmt_i = 0;
323 rmt_symbol_word_t rmt_item;
324
325 for (int32_t val : this->temp_.get_data()) {
326 bool level = val >= 0;
327 if (!level)
328 val = -val;
329 val = this->from_microseconds_(static_cast<uint32_t>(val));
330
331 do {
332 int32_t item = std::min(val, int32_t(RMT_SYMBOL_DURATION_MAX));
333 val -= item;
334
335 if (rmt_i % 2 == 0) {
336 rmt_item.level0 = static_cast<uint32_t>(level ^ this->inverted_);
337 rmt_item.duration0 = static_cast<uint32_t>(item);
338 } else {
339 rmt_item.level1 = static_cast<uint32_t>(level ^ this->inverted_);
340 rmt_item.duration1 = static_cast<uint32_t>(item);
341 this->rmt_temp_.push_back(rmt_item);
342 }
343 rmt_i++;
344 } while (val != 0);
345 }
346
347 if (rmt_i % 2 == 1) {
348 rmt_item.level1 = 0;
349 rmt_item.duration1 = 0;
350 this->rmt_temp_.push_back(rmt_item);
351 }
352
353 if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.empty()) {
354 ESP_LOGE(TAG, "Empty data");
356 this->fire_complete_(false);
357 return;
358 }
360 bool sent = send_times != 0; // same answer as the ISR backend for a frame sent zero times
361 for (uint32_t i = 0; i < send_times; i++) {
362 rmt_transmit_config_t config;
363 memset(&config, 0, sizeof(config));
364 config.flags.eot_level = this->eot_level_;
365 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
366 this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
367 if (error != ESP_OK) {
368 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
369 this->status_set_warning();
370 sent = false;
371 }
372 error = rmt_tx_wait_all_done(this->channel_, -1);
373 if (error != ESP_OK) {
374 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
375 this->status_set_warning();
376 sent = false;
377 }
378 if (i + 1 < send_times)
379 delayMicroseconds(send_wait);
380 }
381 // a later repeat must not clear the warning a failed one raised
382 if (sent)
383 this->status_clear_warning();
384 this->fire_complete_(sent);
385}
386#endif
387
388} // namespace esphome::remote_transmitter
389
390#endif // SOC_RMT_SUPPORTED
391#endif // USE_ESP32
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
bool status_comm_result(bool ok)
Clears the warning when ok, else sets it with message (default "Communication failed"); returns ok.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void status_clear_warning()
Definition component.h:296
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:556
gpio::Flags get_flags() const override
Definition gpio.h:34
uint32_t from_microseconds_(uint32_t us)
const RawTimings & get_data() const
Definition remote_base.h:66
RemoteTransmitData temp_
Use same vector for all transmits, avoids many allocations.
void send_internal(uint32_t send_times, uint32_t send_wait) override
void fail_(esp_err_t error, const LogString *reason)
unsigned count
Definition gif.cpp:51
mopeka_std_values val[3]
uint8_t duration
Definition msa3xx.h:0
const char * esp_err_to_name(esp_err_t err)
std::span< const uint8_t > data
ESPHOME_LOG_TAG(TAG, "remote_transmitter")
value[LAYOUT.day_index()]
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition hal.cpp:48
uint16_t size
Definition helpers.cpp:34
int written
Definition helpers.h:1262
static void uint32_t
uint16_t length
Definition tt21100.cpp:0