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)."
28#include "esp_private/esp_clk.h"
30#include "driver/rmt_encoder.h"
31#include "driver/rmt_rx.h"
32#include "driver/rmt_tx.h"
33#include "freertos/FreeRTOS.h"
34#include "freertos/queue.h"
40[[nodiscard]]
inline esp_err_t
log_if_error(esp_err_t err,
const char* tag,
const char* msg) {
42 ESP_LOGE(tag,
"%s: %s", msg, esp_err_to_name(err));
67 uint32_t tick_rate_hz = configTICK_RATE_HZ) {
68 if (ticks == portMAX_DELAY) {
71 const uint64_t ms =
static_cast<uint64_t
>(ticks) * 1000U / tick_rate_hz;
72 return ms >
static_cast<uint64_t
>(INT32_MAX) ? INT32_MAX :
static_cast<int32_t
>(ms);
87 explicit RmtTx(gpio_num_t gpio, uint32_t resolution_hz = 10'000'000,
89 uint32_t queue_depth = 4, rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT,
90 int channel_id = -1) {
94 cfg_.clk_src = clk_src;
95 cfg_.mem_block_symbols = mem_symbols;
96 cfg_.resolution_hz = resolution_hz;
97 cfg_.trans_queue_depth = queue_depth;
98 cfg_.flags.invert_out =
false;
99 cfg_.flags.with_dma = with_dma;
107 *
this = std::move(other);
110 std::swap(cfg_, other.cfg_);
111 std::swap(init_err_, other.init_err_);
112 std::swap(handle_, other.handle_);
113 std::swap(copy_encoder_, other.copy_encoder_);
114 std::swap(ws_encoder_, other.ws_encoder_);
133 constexpr char TAG[] =
"RmtTx";
134 if (handle_ !=
nullptr) {
137 rmt_channel_handle_t channel =
nullptr;
139 "failed to create TX channel");
144 rmt_copy_encoder_config_t copy_cfg = {};
146 "failed to create copy encoder");
165 return handle_ !=
nullptr;
175 esp_err_t
Transmit(
const rmt_symbol_word_t* symbols,
size_t count,
177 if (handle_ ==
nullptr) {
178 return ESP_ERR_INVALID_STATE;
180 rmt_transmit_config_t tx_cfg = {};
181 tx_cfg.loop_count = 0;
183 rmt_transmit(handle_, copy_encoder_, symbols, count *
sizeof(rmt_symbol_word_t), &tx_cfg);
186 return rmt_tx_wait_all_done(handle_, timeout_ms);
190 template <
typename Ticks, std::enable_if_t<std::is_same_v<Ticks, TickType_t>,
int> = 0>
191 [[deprecated(
"RmtTx timeouts are int32_t milliseconds (-1 = forever)")]] esp_err_t
Transmit(
192 const rmt_symbol_word_t* symbols,
size_t count, Ticks timeout)
const {
204 esp_err_t
TransmitBytes(
const uint8_t* data,
size_t length,
const rmt_symbol_word_t& bit0,
205 const rmt_symbol_word_t& bit1, int32_t timeout_ms =
kWaitForever) {
206 if (handle_ ==
nullptr) {
207 return ESP_ERR_INVALID_STATE;
210 rmt_bytes_encoder_config_t be_cfg = {};
213 be_cfg.flags.msb_first =
true;
214 rmt_encoder_handle_t bytes_enc =
nullptr;
215 ESP_RETURN_ON_ERROR(rmt_new_bytes_encoder(&be_cfg, &bytes_enc),
"RmtTx",
"new bytes enc");
217 rmt_transmit_config_t tx_cfg = {};
218 tx_cfg.loop_count = 0;
219 esp_err_t err = rmt_transmit(handle_, bytes_enc, data, length, &tx_cfg);
221 err = rmt_tx_wait_all_done(handle_, timeout_ms);
222 rmt_del_encoder(bytes_enc);
227 template <
typename Ticks, std::enable_if_t<std::is_same_v<Ticks, TickType_t>,
int> = 0>
228 [[deprecated(
"RmtTx timeouts are int32_t milliseconds (-1 = forever)")]] esp_err_t
TransmitBytes(
229 const uint8_t* data,
size_t length,
const rmt_symbol_word_t& bit0,
230 const rmt_symbol_word_t& bit1, Ticks timeout) {
242 if (handle_ ==
nullptr) {
243 return ESP_ERR_INVALID_STATE;
246 rmt_bytes_encoder_config_t ws_cfg = {};
247 ws_cfg.bit0 = makeWs2812Bit0();
248 ws_cfg.bit1 = makeWs2812Bit1();
249 ws_cfg.flags.msb_first =
true;
250 ESP_RETURN_ON_ERROR(rmt_new_bytes_encoder(&ws_cfg, &ws_encoder_),
"RmtTx",
"new ws2812 enc");
252 rmt_transmit_config_t tx_cfg = {};
253 tx_cfg.loop_count = 0;
254 esp_err_t err = rmt_transmit(handle_, ws_encoder_, grb, length, &tx_cfg);
256 err = rmt_tx_wait_all_done(handle_, timeout_ms);
261 template <
typename Ticks, std::enable_if_t<std::is_same_v<Ticks, TickType_t>,
int> = 0>
262 [[deprecated(
"RmtTx timeouts are int32_t milliseconds (-1 = forever)")]] esp_err_t
TransmitWs2812(
263 const uint8_t* grb,
size_t length, Ticks timeout) {
274 rmt_del_encoder(ws_encoder_);
275 ws_encoder_ =
nullptr;
278 rmt_del_encoder(copy_encoder_);
279 copy_encoder_ =
nullptr;
282 rmt_disable(handle_);
283 rmt_del_channel(handle_);
288 static constexpr rmt_symbol_word_t makeWs2812Bit0(uint32_t resolution_hz = 10'000'000) {
290 uint32_t ticks_h = (400'000'000ULL / resolution_hz + 9) / 10;
291 uint32_t ticks_l = (850'000'000ULL / resolution_hz + 9) / 10;
293 .duration0 = (uint16_t)ticks_h, .level0 = 1, .duration1 = (uint16_t)ticks_l, .level1 = 0};
296 static constexpr rmt_symbol_word_t makeWs2812Bit1(uint32_t resolution_hz = 10'000'000) {
298 uint32_t ticks_h = (800'000'000ULL / resolution_hz + 9) / 10;
299 uint32_t ticks_l = (450'000'000ULL / resolution_hz + 9) / 10;
301 .duration0 = (uint16_t)ticks_h, .level0 = 1, .duration1 = (uint16_t)ticks_l, .level1 = 0};
304 rmt_tx_channel_config_t cfg_ = {};
305 esp_err_t init_err_ = ESP_ERR_INVALID_STATE;
306 rmt_channel_handle_t handle_ =
nullptr;
307 rmt_encoder_handle_t copy_encoder_ =
nullptr;
308 rmt_encoder_handle_t ws_encoder_ =
nullptr;
316 explicit RmtRx(gpio_num_t gpio, uint32_t resolution_hz = 10'000'000,
size_t mem_symbols = 64,
317 uint32_t idle_threshold_us = 1000, uint32_t filter_ns = 200,
318 rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT,
int channel_id = -1) {
319 constexpr char TAG[] =
"RmtRx";
321 if (channel_id < 0) {
325 rmt_rx_channel_config_t cfg = {};
327 cfg.clk_src = clk_src;
328 cfg.mem_block_symbols = mem_symbols;
329 cfg.resolution_hz = resolution_hz;
330 cfg.flags.invert_in =
false;
333 rmt_rx_event_callbacks_t cbs = {};
334 cbs.on_recv_done = &RmtRx::rx_done_cb_static;
335 ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(_handle, &cbs,
this));
336 ESP_ERROR_CHECK(rmt_enable(_handle));
339 _queue = xQueueCreate(4,
sizeof(
size_t));
342 _rcv_cfg.signal_range_max_ns = idle_threshold_us * 1000ULL;
343 _rcv_cfg.signal_range_min_ns = filter_ns;
349 *
this = std::move(o);
352 std::swap(_handle, o._handle);
353 std::swap(_queue, o._queue);
354 std::swap(_rcv_cfg, o._rcv_cfg);
360 rmt_disable(_handle), rmt_del_channel(_handle);
362 vQueueDelete(_queue);
370 esp_err_t
receive(rmt_symbol_word_t* buffer,
size_t buffer_symbols,
size_t* out_symbols,
371 TickType_t timeout = portMAX_DELAY) {
372 if (!buffer || !out_symbols)
373 return ESP_ERR_INVALID_ARG;
376 rmt_receive(_handle, buffer, buffer_symbols *
sizeof(rmt_symbol_word_t), &_rcv_cfg);
381 if (xQueueReceive(_queue, out_symbols, timeout) != pdPASS) {
382 return ESP_ERR_TIMEOUT;
392 static bool rx_done_cb_static(rmt_channel_handle_t ,
393 const rmt_rx_done_event_data_t* edata,
void* user_ctx) {
394 auto* self =
static_cast<RmtRx*
>(user_ctx);
395 BaseType_t HPW = pdFALSE;
396 xQueueSendFromISR(self->_queue, &edata->num_symbols, &HPW);
397 return HPW == pdTRUE;
400 rmt_channel_handle_t _handle =
nullptr;
401 QueueHandle_t _queue =
nullptr;
402 rmt_receive_config_t _rcv_cfg = {};
Definition rmt_wrapper.hpp:314
RmtRx(RmtRx &&o) noexcept
Definition rmt_wrapper.hpp:348
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:370
RmtRx & operator=(RmtRx &&o) noexcept
Definition rmt_wrapper.hpp:351
~RmtRx()
Definition rmt_wrapper.hpp:358
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:316
rmt_channel_handle_t handle() const
Definition rmt_wrapper.hpp:387
Definition rmt_wrapper.hpp:52
esp_err_t Transmit(const rmt_symbol_word_t *symbols, size_t count, int32_t timeout_ms=kWaitForever) const
Transmit raw RMT symbols (blocking until done).
Definition rmt_wrapper.hpp:175
esp_err_t Transmit(const rmt_symbol_word_t *symbols, size_t count, Ticks timeout) const
Definition rmt_wrapper.hpp:191
esp_err_t Init()
Create and enable the channel if that has not succeeded yet.
Definition rmt_wrapper.hpp:132
esp_err_t InitStatus() const
Result of the last Init(): ESP_OK when the channel is ready.
Definition rmt_wrapper.hpp:159
RmtTx(RmtTx &&other) noexcept
Definition rmt_wrapper.hpp:106
esp_err_t TransmitWs2812(const uint8_t *grb, size_t length, int32_t timeout_ms=kWaitForever)
Helper dedicated to WS2812/NeoPixel @ 800 kHz. Uses 10 MHz or faster resolution to achieve accurate t...
Definition rmt_wrapper.hpp:241
~RmtTx()
Definition rmt_wrapper.hpp:118
static constexpr int32_t kWaitForever
Definition rmt_wrapper.hpp:58
RmtTx & operator=(const RmtTx &)=delete
bool IsReady() const
True when the channel was created and enabled.
Definition rmt_wrapper.hpp:164
RmtTx(gpio_num_t gpio, uint32_t resolution_hz=10 '000 '000, size_t mem_symbols=kDefaultMemSymbols, 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:87
rmt_channel_handle_t Handle() const
Definition rmt_wrapper.hpp:267
RmtTx & operator=(RmtTx &&other) noexcept
Definition rmt_wrapper.hpp:109
RmtTx(const RmtTx &)=delete
static constexpr size_t kDefaultMemSymbols
Definition rmt_wrapper.hpp:55
esp_err_t TransmitBytes(const uint8_t *data, size_t length, const rmt_symbol_word_t &bit0, const rmt_symbol_word_t &bit1, Ticks timeout)
Definition rmt_wrapper.hpp:228
esp_err_t TransmitWs2812(const uint8_t *grb, size_t length, Ticks timeout)
Definition rmt_wrapper.hpp:262
esp_err_t TransmitBytes(const uint8_t *data, size_t length, const rmt_symbol_word_t &bit0, const rmt_symbol_word_t &bit1, int32_t timeout_ms=kWaitForever)
Transmit an arbitrary byte stream with custom bit timing.
Definition rmt_wrapper.hpp:204
static constexpr int32_t TicksToTimeoutMs(TickType_t ticks, uint32_t tick_rate_hz=configTICK_RATE_HZ)
Convert a FreeRTOS tick count to the millisecond timeout the transmit calls take.
Definition rmt_wrapper.hpp:66
esp_err_t log_if_error(esp_err_t err, const char *tag, const char *msg)
Definition rmt_wrapper.hpp:40
Definition rmt_wrapper.hpp:37
RMT resources the driver requests per strip (shared by the C and C++ APIs).
#define WS2812_RMT_MEM_BLOCK_SYMBOLS
Definition ws2812_rmt_caps.h:18