HF-WS2812 Driver 0.1.0-dev
HF-WS2812 ESP32 RMT Driver
Loading...
Searching...
No Matches
rmt_wrapper.hpp
Go to the documentation of this file.
1// rmt_wrapper.hpp — High‑level C++ RAII wrapper for the ESP‑IDF v5.3 RMT driver
2// on ESP32‑C6 Copyright (c) 2025 Nebiyu Tadesse SPDX‑License‑Identifier:
3// GPL‑3.0-or-later
9#pragma once
10
11#include "esp_idf_version.h"
12
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)."
17#endif
18
19#include <array>
20#include <atomic>
21#include <cstddef>
22#include <cstdint>
23#include <utility>
24
25#include "esp_check.h"
26#include "esp_log.h"
27#include "esp_private/esp_clk.h"
28
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"
34
35namespace ws2812 {
36
37namespace detail {
38[[nodiscard]] inline esp_err_t log_if_error(esp_err_t err, const char* tag, const char* msg) {
39 if (err != ESP_OK) {
40 ESP_LOGE(tag, "%s: %s", msg, esp_err_to_name(err));
41 }
42 return err;
43}
44
45} // namespace detail
46
47//=============================================================================
48// RmtTx — Transmit‑only wrapper
49//=============================================================================
50class RmtTx {
51public:
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";
67
68 if (channel_id < 0) {
69 channel_id = 0;
70 }
71
72 rmt_tx_channel_config_t cfg = {};
73 cfg.gpio_num = gpio;
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;
80
81 ESP_ERROR_CHECK(detail::log_if_error(rmt_new_tx_channel(&cfg, &handle_), TAG,
82 "failed to create TX channel"));
83 ESP_ERROR_CHECK(detail::log_if_error(rmt_enable(handle_), TAG, "enable TX"));
84
85 // Prepare a generic copy encoder (raw symbol streaming)
86 rmt_copy_encoder_config_t copy_cfg = {};
87 ESP_ERROR_CHECK(rmt_new_copy_encoder(&copy_cfg, &copy_encoder_));
88 }
89
90 RmtTx(const RmtTx&) = delete;
91 RmtTx& operator=(const RmtTx&) = delete;
92
93 RmtTx(RmtTx&& other) noexcept {
94 *this = std::move(other);
95 }
96 RmtTx& operator=(RmtTx&& other) noexcept {
97 std::swap(handle_, other.handle_);
98 std::swap(copy_encoder_, other.copy_encoder_);
99 std::swap(ws_encoder_, other.ws_encoder_);
100 return *this;
101 }
102
104 if (ws_encoder_)
105 rmt_del_encoder(ws_encoder_);
106 if (copy_encoder_)
107 rmt_del_encoder(copy_encoder_);
108 if (handle_)
109 rmt_disable(handle_), rmt_del_channel(handle_);
110 }
111
112 //-------------------------------------------------------------------------
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;
120 esp_err_t err =
121 rmt_transmit(handle_, copy_encoder_, symbols, count * sizeof(rmt_symbol_word_t), &tx_cfg);
122 if (err != ESP_OK)
123 return err;
124 return rmt_tx_wait_all_done(handle_, timeout);
125 }
126
127 //-------------------------------------------------------------------------
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) {
136 // Create a throw‑away bytes encoder (kept only for this call)
137 rmt_bytes_encoder_config_t be_cfg = {};
138 be_cfg.bit0 = bit0;
139 be_cfg.bit1 = bit1;
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");
143
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);
147 if (err == ESP_OK)
148 err = rmt_tx_wait_all_done(handle_, timeout);
149 rmt_del_encoder(bytes_enc);
150 return err;
151 }
152
153 //-------------------------------------------------------------------------
158 esp_err_t TransmitWs2812(const uint8_t* grb, size_t length, TickType_t timeout = portMAX_DELAY) {
159 if (!ws_encoder_) {
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");
165 }
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);
169 if (err == ESP_OK)
170 err = rmt_tx_wait_all_done(handle_, timeout);
171 return err;
172 }
173
174 rmt_channel_handle_t Handle() const {
175 return handle_;
176 }
177
178private:
179 static constexpr rmt_symbol_word_t makeWs2812Bit0(uint32_t resolution_hz = 10'000'000) {
180 // T0H=0.4 μs, T0L≈0.85 μs
181 uint32_t ticks_h = (400'000'000ULL / resolution_hz + 9) / 10; // round‑nearest
182 uint32_t ticks_l = (850'000'000ULL / resolution_hz + 9) / 10;
183 return {
184 .duration0 = (uint16_t)ticks_h, .level0 = 1, .duration1 = (uint16_t)ticks_l, .level1 = 0};
185 }
186
187 static constexpr rmt_symbol_word_t makeWs2812Bit1(uint32_t resolution_hz = 10'000'000) {
188 // T1H=0.8 μs, T1L≈0.45 μs
189 uint32_t ticks_h = (800'000'000ULL / resolution_hz + 9) / 10;
190 uint32_t ticks_l = (450'000'000ULL / resolution_hz + 9) / 10;
191 return {
192 .duration0 = (uint16_t)ticks_h, .level0 = 1, .duration1 = (uint16_t)ticks_l, .level1 = 0};
193 }
194
195 rmt_channel_handle_t handle_ = nullptr;
196 rmt_encoder_handle_t copy_encoder_ = nullptr;
197 rmt_encoder_handle_t ws_encoder_ = nullptr;
198};
199
200//=============================================================================
201// RmtRx — Receive‑only wrapper
202//=============================================================================
203class RmtRx {
204public:
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";
209
210 if (channel_id < 0) {
211 channel_id = 0;
212 }
213
214 rmt_rx_channel_config_t cfg = {};
215 cfg.gpio_num = gpio;
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;
220 ESP_ERROR_CHECK(detail::log_if_error(rmt_new_rx_channel(&cfg, &_handle), TAG, "create RX"));
221
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));
226
227 // Create queue for ISR -> task communication
228 _queue = xQueueCreate(4, sizeof(size_t));
229
230 // Configure RX receive behavior (can be changed later)
231 _rcv_cfg.signal_range_max_ns = idle_threshold_us * 1000ULL;
232 _rcv_cfg.signal_range_min_ns = filter_ns;
233 }
234
235 RmtRx(const RmtRx&) = delete;
236 RmtRx& operator=(const RmtRx&) = delete;
237 RmtRx(RmtRx&& o) noexcept {
238 *this = std::move(o);
239 }
240 RmtRx& operator=(RmtRx&& o) noexcept {
241 std::swap(_handle, o._handle);
242 std::swap(_queue, o._queue);
243 std::swap(_rcv_cfg, o._rcv_cfg);
244 return *this;
245 }
246
248 if (_handle)
249 rmt_disable(_handle), rmt_del_channel(_handle);
250 if (_queue)
251 vQueueDelete(_queue);
252 }
253
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;
263
264 esp_err_t err =
265 rmt_receive(_handle, buffer, buffer_symbols * sizeof(rmt_symbol_word_t), &_rcv_cfg);
266 if (err != ESP_OK)
267 return err;
268
269 // Wait until ISR posts the symbol count
270 if (xQueueReceive(_queue, out_symbols, timeout) != pdPASS) {
271 return ESP_ERR_TIMEOUT;
272 }
273 return ESP_OK;
274 }
275
276 rmt_channel_handle_t handle() const {
277 return _handle;
278 }
279
280private:
281 static bool rx_done_cb_static(rmt_channel_handle_t /*chan*/,
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; // context switch request
287 }
288
289 rmt_channel_handle_t _handle = nullptr;
290 QueueHandle_t _queue = nullptr;
291 rmt_receive_config_t _rcv_cfg = {};
292};
293
294} // namespace ws2812
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