11#include "esp_idf_version.h"
13#if !defined(ESP_IDF_VERSION)
14#error "ESP_IDF_VERSION macro is not defined. Is <esp_idf_version.h> included?"
15#elif ESP_IDF_VERSION_MAJOR < 5
16#error "This wrapper requires ESP‑IDF v5.0 or newer (uses the new RMT driver)."
27#include "esp_private/esp_clk.h"
29#include "driver/rmt_encoder.h"
30#include "driver/rmt_rx.h"
31#include "driver/rmt_tx.h"
32#include "freertos/FreeRTOS.h"
33#include "freertos/queue.h"
38[[nodiscard]]
inline esp_err_t
log_if_error(esp_err_t err,
const char* tag,
const char* msg) {
40 ESP_LOGE(tag,
"%s: %s", msg, esp_err_to_name(err));
63 explicit RmtTx(gpio_num_t gpio, uint32_t resolution_hz = 10'000'000,
size_t mem_symbols = 64,
64 bool with_dma =
false, uint32_t queue_depth = 4,
65 rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT,
int channel_id = -1) {
66 constexpr char TAG[] =
"RmtTx";
72 rmt_tx_channel_config_t cfg = {};
74 cfg.clk_src = clk_src;
75 cfg.mem_block_symbols = mem_symbols;
76 cfg.resolution_hz = resolution_hz;
77 cfg.trans_queue_depth = queue_depth;
78 cfg.flags.invert_out =
false;
79 cfg.flags.with_dma = with_dma;
82 "failed to create TX channel"));
86 rmt_copy_encoder_config_t copy_cfg = {};
87 ESP_ERROR_CHECK(rmt_new_copy_encoder(©_cfg, ©_encoder_));
94 *
this = std::move(other);
97 std::swap(handle_, other.handle_);
98 std::swap(copy_encoder_, other.copy_encoder_);
99 std::swap(ws_encoder_, other.ws_encoder_);
105 rmt_del_encoder(ws_encoder_);
107 rmt_del_encoder(copy_encoder_);
109 rmt_disable(handle_), rmt_del_channel(handle_);
116 esp_err_t
Transmit(
const rmt_symbol_word_t* symbols,
size_t count,
117 TickType_t timeout = portMAX_DELAY)
const {
118 rmt_transmit_config_t tx_cfg = {};
119 tx_cfg.loop_count = 0;
121 rmt_transmit(handle_, copy_encoder_, symbols, count *
sizeof(rmt_symbol_word_t), &tx_cfg);
124 return rmt_tx_wait_all_done(handle_, timeout);
134 esp_err_t
TransmitBytes(
const uint8_t* data,
size_t length,
const rmt_symbol_word_t& bit0,
135 const rmt_symbol_word_t& bit1, TickType_t timeout = portMAX_DELAY) {
137 rmt_bytes_encoder_config_t be_cfg = {};
140 be_cfg.flags.msb_first =
true;
141 rmt_encoder_handle_t bytes_enc =
nullptr;
142 ESP_RETURN_ON_ERROR(rmt_new_bytes_encoder(&be_cfg, &bytes_enc),
"RmtTx",
"new bytes enc");
144 rmt_transmit_config_t tx_cfg = {};
145 tx_cfg.loop_count = 0;
146 esp_err_t err = rmt_transmit(handle_, bytes_enc, data, length, &tx_cfg);
148 err = rmt_tx_wait_all_done(handle_, timeout);
149 rmt_del_encoder(bytes_enc);
158 esp_err_t
TransmitWs2812(
const uint8_t* grb,
size_t length, TickType_t timeout = portMAX_DELAY) {
160 rmt_bytes_encoder_config_t ws_cfg = {};
161 ws_cfg.bit0 = makeWs2812Bit0();
162 ws_cfg.bit1 = makeWs2812Bit1();
163 ws_cfg.flags.msb_first =
true;
164 ESP_RETURN_ON_ERROR(rmt_new_bytes_encoder(&ws_cfg, &ws_encoder_),
"RmtTx",
"new ws2812 enc");
166 rmt_transmit_config_t tx_cfg = {};
167 tx_cfg.loop_count = 0;
168 esp_err_t err = rmt_transmit(handle_, ws_encoder_, grb, length, &tx_cfg);
170 err = rmt_tx_wait_all_done(handle_, timeout);
179 static constexpr rmt_symbol_word_t makeWs2812Bit0(uint32_t resolution_hz = 10'000'000) {
181 uint32_t ticks_h = (400'000'000ULL / resolution_hz + 9) / 10;
182 uint32_t ticks_l = (850'000'000ULL / resolution_hz + 9) / 10;
184 .duration0 = (uint16_t)ticks_h, .level0 = 1, .duration1 = (uint16_t)ticks_l, .level1 = 0};
187 static constexpr rmt_symbol_word_t makeWs2812Bit1(uint32_t resolution_hz = 10'000'000) {
189 uint32_t ticks_h = (800'000'000ULL / resolution_hz + 9) / 10;
190 uint32_t ticks_l = (450'000'000ULL / resolution_hz + 9) / 10;
192 .duration0 = (uint16_t)ticks_h, .level0 = 1, .duration1 = (uint16_t)ticks_l, .level1 = 0};
195 rmt_channel_handle_t handle_ =
nullptr;
196 rmt_encoder_handle_t copy_encoder_ =
nullptr;
197 rmt_encoder_handle_t ws_encoder_ =
nullptr;
205 explicit RmtRx(gpio_num_t gpio, uint32_t resolution_hz = 10'000'000,
size_t mem_symbols = 64,
206 uint32_t idle_threshold_us = 1000, uint32_t filter_ns = 200,
207 rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT,
int channel_id = -1) {
208 constexpr char TAG[] =
"RmtRx";
210 if (channel_id < 0) {
214 rmt_rx_channel_config_t cfg = {};
216 cfg.clk_src = clk_src;
217 cfg.mem_block_symbols = mem_symbols;
218 cfg.resolution_hz = resolution_hz;
219 cfg.flags.invert_in =
false;
222 rmt_rx_event_callbacks_t cbs = {};
223 cbs.on_recv_done = &RmtRx::rx_done_cb_static;
224 ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(_handle, &cbs,
this));
225 ESP_ERROR_CHECK(rmt_enable(_handle));
228 _queue = xQueueCreate(4,
sizeof(
size_t));
231 _rcv_cfg.signal_range_max_ns = idle_threshold_us * 1000ULL;
232 _rcv_cfg.signal_range_min_ns = filter_ns;
238 *
this = std::move(o);
241 std::swap(_handle, o._handle);
242 std::swap(_queue, o._queue);
243 std::swap(_rcv_cfg, o._rcv_cfg);
249 rmt_disable(_handle), rmt_del_channel(_handle);
251 vQueueDelete(_queue);
259 esp_err_t
receive(rmt_symbol_word_t* buffer,
size_t buffer_symbols,
size_t* out_symbols,
260 TickType_t timeout = portMAX_DELAY) {
261 if (!buffer || !out_symbols)
262 return ESP_ERR_INVALID_ARG;
265 rmt_receive(_handle, buffer, buffer_symbols *
sizeof(rmt_symbol_word_t), &_rcv_cfg);
270 if (xQueueReceive(_queue, out_symbols, timeout) != pdPASS) {
271 return ESP_ERR_TIMEOUT;
281 static bool rx_done_cb_static(rmt_channel_handle_t ,
282 const rmt_rx_done_event_data_t* edata,
void* user_ctx) {
283 auto* self =
static_cast<RmtRx*
>(user_ctx);
284 BaseType_t HPW = pdFALSE;
285 xQueueSendFromISR(self->_queue, &edata->num_symbols, &HPW);
286 return HPW == pdTRUE;
289 rmt_channel_handle_t _handle =
nullptr;
290 QueueHandle_t _queue =
nullptr;
291 rmt_receive_config_t _rcv_cfg = {};
Definition rmt_wrapper.hpp:203
RmtRx(RmtRx &&o) noexcept
Definition rmt_wrapper.hpp:237
RmtRx(const RmtRx &)=delete
RmtRx & operator=(const RmtRx &)=delete
esp_err_t receive(rmt_symbol_word_t *buffer, size_t buffer_symbols, size_t *out_symbols, TickType_t timeout=portMAX_DELAY)
Start a single receive transaction and block until it completes.
Definition rmt_wrapper.hpp:259
RmtRx & operator=(RmtRx &&o) noexcept
Definition rmt_wrapper.hpp:240
~RmtRx()
Definition rmt_wrapper.hpp:247
RmtRx(gpio_num_t gpio, uint32_t resolution_hz=10 '000 '000, size_t mem_symbols=64, uint32_t idle_threshold_us=1000, uint32_t filter_ns=200, rmt_clock_source_t clk_src=RMT_CLK_SRC_DEFAULT, int channel_id=-1)
Definition rmt_wrapper.hpp:205
rmt_channel_handle_t handle() const
Definition rmt_wrapper.hpp:276
Definition rmt_wrapper.hpp:50
RmtTx(RmtTx &&other) noexcept
Definition rmt_wrapper.hpp:93
~RmtTx()
Definition rmt_wrapper.hpp:103
RmtTx(gpio_num_t gpio, uint32_t resolution_hz=10 '000 '000, size_t mem_symbols=64, bool with_dma=false, uint32_t queue_depth=4, rmt_clock_source_t clk_src=RMT_CLK_SRC_DEFAULT, int channel_id=-1)
Create a Transmit channel.
Definition rmt_wrapper.hpp:63
RmtTx & operator=(const RmtTx &)=delete
esp_err_t TransmitBytes(const uint8_t *data, size_t length, const rmt_symbol_word_t &bit0, const rmt_symbol_word_t &bit1, TickType_t timeout=portMAX_DELAY)
Transmit an arbitrary byte stream with custom bit timing.
Definition rmt_wrapper.hpp:134
rmt_channel_handle_t Handle() const
Definition rmt_wrapper.hpp:174
RmtTx & operator=(RmtTx &&other) noexcept
Definition rmt_wrapper.hpp:96
RmtTx(const RmtTx &)=delete
esp_err_t TransmitWs2812(const uint8_t *grb, size_t length, TickType_t timeout=portMAX_DELAY)
Helper dedicated to WS2812/NeoPixel @ 800 kHz. Uses 10 MHz or faster resolution to achieve accurate t...
Definition rmt_wrapper.hpp:158
esp_err_t Transmit(const rmt_symbol_word_t *symbols, size_t count, TickType_t timeout=portMAX_DELAY) const
Transmit raw RMT symbols (blocking until done).
Definition rmt_wrapper.hpp:116
esp_err_t log_if_error(esp_err_t err, const char *tag, const char *msg)
Definition rmt_wrapper.hpp:38
Definition rmt_wrapper.hpp:35