ESPHome 2026.5.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
12static const char *const TAG = "remote_transmitter";
13
14// Maximum RMT symbol duration (15-bit field)
15static constexpr uint32_t RMT_SYMBOL_DURATION_MAX = 0x7FFF;
16
17#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
18static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t written, size_t free,
19 rmt_symbol_word_t *symbols, bool *done, void *arg) {
20 auto *store = static_cast<RemoteTransmitterComponentStore *>(arg);
21 const auto *encoded = static_cast<const rmt_symbol_half_t *>(data);
22 size_t length = size / sizeof(rmt_symbol_half_t);
23 size_t count = 0;
24
25 // copy symbols
26 for (size_t i = 0; i < free; i++) {
27 uint16_t sym_0 = encoded[store->index++].val;
28 if (store->index >= length) {
29 store->index = 0;
30 store->times--;
31 if (store->times == 0) {
32 *done = true;
33 symbols[count++].val = sym_0;
34 return count;
35 }
36 }
37 uint16_t sym_1 = encoded[store->index++].val;
38 if (store->index >= length) {
39 store->index = 0;
40 store->times--;
41 if (store->times == 0) {
42 *done = true;
43 symbols[count++].val = sym_0 | (sym_1 << 16);
44 return count;
45 }
46 }
47 symbols[count++].val = sym_0 | (sym_1 << 16);
48 }
49 *done = false;
50 return count;
51}
52#endif
53
55 this->inverted_ = this->pin_->is_inverted();
56 this->configure_rmt_();
57}
58
60 ESP_LOGCONFIG(TAG, "Remote Transmitter:");
61 ESP_LOGCONFIG(TAG,
62 " Clock resolution: %" PRIu32 " hz\n"
63 " RMT symbols: %" PRIu32,
64 this->clock_resolution_, this->rmt_symbols_);
65 LOG_PIN(" Pin: ", this->pin_);
66
67 if (this->current_carrier_frequency_ != 0 && this->carrier_duty_percent_ != 100) {
68 ESP_LOGCONFIG(TAG, " Carrier Duty: %u%%", this->carrier_duty_percent_);
69 }
70
71 if (this->is_failed()) {
72 ESP_LOGE(TAG, "Configuring RMT driver failed: %s (%s)", esp_err_to_name(this->error_code_),
73 this->error_string_.c_str());
74 }
75}
76
78#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
79 rmt_symbol_half_t symbol = {
80 .duration = 1,
81 .level = value,
82 };
83 rmt_transmit_config_t config;
84 memset(&config, 0, sizeof(config));
85 config.flags.eot_level = value;
86 this->store_.times = 1;
87 this->store_.index = 0;
88#else
89 rmt_symbol_word_t symbol = {
90 .duration0 = 1,
91 .level0 = value,
92 .duration1 = 0,
93 .level1 = value,
94 };
95 rmt_transmit_config_t config;
96 memset(&config, 0, sizeof(config));
97 config.flags.eot_level = value;
98#endif
99 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, &symbol, sizeof(symbol), &config);
100 if (error != ESP_OK) {
101 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
102 this->status_set_warning();
103 }
104 error = rmt_tx_wait_all_done(this->channel_, -1);
105 if (error != ESP_OK) {
106 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
107 this->status_set_warning();
108 }
109}
110
112 esp_err_t error;
113
114 if (!this->initialized_) {
115 bool open_drain = (this->pin_->get_flags() & gpio::FLAG_OPEN_DRAIN) != 0;
116 rmt_tx_channel_config_t channel;
117 memset(&channel, 0, sizeof(channel));
118 channel.clk_src = RMT_CLK_SRC_DEFAULT;
119 channel.resolution_hz = this->clock_resolution_;
120 channel.gpio_num = gpio_num_t(this->pin_->get_pin());
121 channel.mem_block_symbols = this->rmt_symbols_;
122 channel.trans_queue_depth = 1;
123 channel.flags.invert_out = 0;
124 channel.flags.with_dma = this->with_dma_;
125 channel.intr_priority = 0;
126#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
127 channel.flags.io_loop_back = open_drain;
128 channel.flags.io_od_mode = open_drain;
129#endif
130 error = rmt_new_tx_channel(&channel, &this->channel_);
131 if (error != ESP_OK) {
132 this->error_code_ = error;
133 if (error == ESP_ERR_NOT_FOUND) {
134 this->error_string_ = "out of RMT symbol memory";
135 } else {
136 this->error_string_ = "in rmt_new_tx_channel";
137 }
138 this->mark_failed();
139 return;
140 }
141#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
142 if (open_drain) {
143 gpio_num_t gpio = gpio_num_t(this->pin_->get_pin());
144 gpio_od_enable(gpio);
145 gpio_input_enable(gpio);
146 }
147#endif
148 if (this->pin_->get_flags() & gpio::FLAG_PULLUP) {
149 gpio_pullup_en(gpio_num_t(this->pin_->get_pin()));
150 } else {
151 gpio_pullup_dis(gpio_num_t(this->pin_->get_pin()));
152 }
153
154#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
155 rmt_simple_encoder_config_t encoder;
156 memset(&encoder, 0, sizeof(encoder));
157 encoder.callback = encoder_callback;
158 encoder.arg = &this->store_;
159 encoder.min_chunk_size = 1;
160 error = rmt_new_simple_encoder(&encoder, &this->encoder_);
161 if (error != ESP_OK) {
162 this->error_code_ = error;
163 this->error_string_ = "in rmt_new_simple_encoder";
164 this->mark_failed();
165 return;
166 }
167#else
168 rmt_copy_encoder_config_t encoder;
169 memset(&encoder, 0, sizeof(encoder));
170 error = rmt_new_copy_encoder(&encoder, &this->encoder_);
171 if (error != ESP_OK) {
172 this->error_code_ = error;
173 this->error_string_ = "in rmt_new_copy_encoder";
174 this->mark_failed();
175 return;
176 }
177#endif
178
179 error = rmt_enable(this->channel_);
180 if (error != ESP_OK) {
181 this->error_code_ = error;
182 this->error_string_ = "in rmt_enable";
183 this->mark_failed();
184 return;
185 }
186 this->digital_write(open_drain || this->inverted_);
187 this->initialized_ = true;
188 }
189
190 if (this->current_carrier_frequency_ == 0 || this->carrier_duty_percent_ == 100) {
191 error = rmt_apply_carrier(this->channel_, nullptr);
192 } else {
193 rmt_carrier_config_t carrier;
194 memset(&carrier, 0, sizeof(carrier));
195 carrier.frequency_hz = this->current_carrier_frequency_;
196 carrier.duty_cycle = (float) this->carrier_duty_percent_ / 100.0f;
197 carrier.flags.polarity_active_low = this->inverted_;
198 carrier.flags.always_on = 1;
199 error = rmt_apply_carrier(this->channel_, &carrier);
200 }
201 if (error != ESP_OK) {
202 this->error_code_ = error;
203 this->error_string_ = "in rmt_apply_carrier";
204 this->mark_failed();
205 return;
206 }
207}
208
210 esp_err_t error = rmt_tx_wait_all_done(this->channel_, -1);
211 if (error != ESP_OK) {
212 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
213 this->status_set_warning();
214 }
215
217}
218
219#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
221 uint64_t total_duration = 0;
222
223 if (this->is_failed()) {
224 return;
225 }
226
227 // if the timeout was cancelled, block until the tx is complete
228 if (this->non_blocking_ && this->cancel_timeout("complete")) {
229 this->wait_for_rmt_();
230 }
231
234 this->configure_rmt_();
235 }
236
237 this->rmt_temp_.clear();
238 this->rmt_temp_.reserve(this->temp_.get_data().size() + 1);
239
240 // encode any delay at the start of the buffer to simplify the encoder callback
241 // this will be skipped the first time around
242 total_duration += send_wait * (send_times - 1);
243 send_wait = this->from_microseconds_(static_cast<uint32_t>(send_wait));
244 while (send_wait > 0) {
245 int32_t duration = std::min(send_wait, uint32_t(RMT_SYMBOL_DURATION_MAX));
246 this->rmt_temp_.push_back({
247 .duration = static_cast<uint16_t>(duration),
248 .level = static_cast<uint16_t>(this->eot_level_),
249 });
250 send_wait -= duration;
251 }
252
253 // encode data
254 size_t offset = this->rmt_temp_.size();
255 for (int32_t value : this->temp_.get_data()) {
256 bool level = value >= 0;
257 if (!level) {
258 value = -value;
259 }
260 total_duration += value * send_times;
261 value = this->from_microseconds_(static_cast<uint32_t>(value));
262 while (value > 0) {
263 int32_t duration = std::min(value, int32_t(RMT_SYMBOL_DURATION_MAX));
264 this->rmt_temp_.push_back({
265 .duration = static_cast<uint16_t>(duration),
266 .level = static_cast<uint16_t>(level ^ this->inverted_),
267 });
268 value -= duration;
269 }
270 }
271
272 if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.size() <= offset) {
273 ESP_LOGE(TAG, "Empty data");
274 return;
275 }
276
278
279 rmt_transmit_config_t config;
280 memset(&config, 0, sizeof(config));
281 config.flags.eot_level = this->eot_level_;
282 this->store_.times = send_times;
283 this->store_.index = offset;
284 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
285 this->rmt_temp_.size() * sizeof(rmt_symbol_half_t), &config);
286 if (error != ESP_OK) {
287 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
288 this->status_set_warning();
289 } else {
290 this->status_clear_warning();
291 }
292
293 if (this->non_blocking_) {
294 this->set_timeout("complete", total_duration / 1000, [this]() { this->wait_for_rmt_(); });
295 } else {
296 this->wait_for_rmt_();
297 }
298}
299#else
301 if (this->is_failed())
302 return;
303
306 this->configure_rmt_();
307 }
308
309 this->rmt_temp_.clear();
310 this->rmt_temp_.reserve((this->temp_.get_data().size() + 1) / 2);
311 uint32_t rmt_i = 0;
312 rmt_symbol_word_t rmt_item;
313
314 for (int32_t val : this->temp_.get_data()) {
315 bool level = val >= 0;
316 if (!level)
317 val = -val;
318 val = this->from_microseconds_(static_cast<uint32_t>(val));
319
320 do {
321 int32_t item = std::min(val, int32_t(RMT_SYMBOL_DURATION_MAX));
322 val -= item;
323
324 if (rmt_i % 2 == 0) {
325 rmt_item.level0 = static_cast<uint32_t>(level ^ this->inverted_);
326 rmt_item.duration0 = static_cast<uint32_t>(item);
327 } else {
328 rmt_item.level1 = static_cast<uint32_t>(level ^ this->inverted_);
329 rmt_item.duration1 = static_cast<uint32_t>(item);
330 this->rmt_temp_.push_back(rmt_item);
331 }
332 rmt_i++;
333 } while (val != 0);
334 }
335
336 if (rmt_i % 2 == 1) {
337 rmt_item.level1 = 0;
338 rmt_item.duration1 = 0;
339 this->rmt_temp_.push_back(rmt_item);
340 }
341
342 if ((this->rmt_temp_.data() == nullptr) || this->rmt_temp_.empty()) {
343 ESP_LOGE(TAG, "Empty data");
344 return;
345 }
347 for (uint32_t i = 0; i < send_times; i++) {
348 rmt_transmit_config_t config;
349 memset(&config, 0, sizeof(config));
350 config.flags.eot_level = this->eot_level_;
351 esp_err_t error = rmt_transmit(this->channel_, this->encoder_, this->rmt_temp_.data(),
352 this->rmt_temp_.size() * sizeof(rmt_symbol_word_t), &config);
353 if (error != ESP_OK) {
354 ESP_LOGW(TAG, "rmt_transmit failed: %s", esp_err_to_name(error));
355 this->status_set_warning();
356 } else {
357 this->status_clear_warning();
358 }
359 error = rmt_tx_wait_all_done(this->channel_, -1);
360 if (error != ESP_OK) {
361 ESP_LOGW(TAG, "rmt_tx_wait_all_done failed: %s", esp_err_to_name(error));
362 this->status_set_warning();
363 }
364 if (i + 1 < send_times)
365 delayMicroseconds(send_wait);
366 }
368}
369#endif
370
371} // namespace esphome::remote_transmitter
372
373#endif // SOC_RMT_SUPPORTED
374#endif // USE_ESP32
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:284
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition component.h:510
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_timeout(const std boo cancel_timeout)(const char *name)
Cancel a timeout function.
Definition component.h:532
void status_clear_warning()
Definition component.h:306
virtual gpio::Flags get_flags() const =0
Retrieve GPIO pin flags.
virtual uint8_t get_pin() const =0
virtual bool is_inverted() const =0
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:482
uint32_t from_microseconds_(uint32_t us)
const RawTimings & get_data() const
Definition remote_base.h:32
RemoteTransmitData temp_
Use same vector for all transmits, avoids many allocations.
void send_internal(uint32_t send_times, uint32_t send_wait) override
mopeka_std_values val[3]
uint8_t duration
Definition msa3xx.h:0
@ FLAG_OPEN_DRAIN
Definition gpio.h:29
@ FLAG_PULLUP
Definition gpio.h:30
const std::vector< uint8_t > & data
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:30
uint16_t size
Definition helpers.cpp:25
int written
Definition helpers.h:1089
static void uint32_t
uint16_t length
Definition tt21100.cpp:0