| 
 | 1 | +/*  | 
 | 2 | + * Espressif Modified MIT License  | 
 | 3 | + *  | 
 | 4 | + * Copyright (c) 2025 Espressif Systems (Shanghai) CO., LTD  | 
 | 5 | + *  | 
 | 6 | + * Permission is hereby granted for use EXCLUSIVELY with Espressif Systems products.  | 
 | 7 | + * This includes the right to use, copy, modify, merge, publish, distribute, and sublicense  | 
 | 8 | + * the Software, subject to the following conditions:  | 
 | 9 | + *  | 
 | 10 | + * 1. This Software MUST BE USED IN CONJUNCTION WITH ESPRESSIF SYSTEMS PRODUCTS.  | 
 | 11 | + * 2. The above copyright notice and this permission notice shall be included in all copies  | 
 | 12 | + * or substantial portions of the Software.  | 
 | 13 | + * 3. Redistribution of the Software in source or binary form FOR USE WITH NON-ESPRESSIF PRODUCTS  | 
 | 14 | + * is strictly prohibited.  | 
 | 15 | + *  | 
 | 16 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,  | 
 | 17 | + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR  | 
 | 18 | + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE  | 
 | 19 | + * FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR  | 
 | 20 | + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER  | 
 | 21 | + * DEALINGS IN THE SOFTWARE.  | 
 | 22 | + *  | 
 | 23 | + * SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT  | 
 | 24 | + */  | 
 | 25 | + | 
 | 26 | +#include <string.h>  | 
 | 27 | +#include "esp_log.h"  | 
 | 28 | +#include "freertos/FreeRTOS.h"  | 
 | 29 | +#include "driver/rmt_tx.h"  | 
 | 30 | +#include "driver/rmt_encoder.h"  | 
 | 31 | +#include "ws2812_rmt.h"  | 
 | 32 | +#include "esp_idf_version.h"  | 
 | 33 | + | 
 | 34 | +#define WS2812_RMT_RESOLUTION_HZ (10 * 1000 * 1000) // 10MHz resolution, 1 tick = 0.1us  | 
 | 35 | +#define WS2812_RMT_T0H_NS (350)  | 
 | 36 | +#define WS2812_RMT_T0L_NS (1000)  | 
 | 37 | +#define WS2812_RMT_T1H_NS (1000)  | 
 | 38 | +#define WS2812_RMT_T1L_NS (350)  | 
 | 39 | +#define WS2812_RMT_RESET_US (280)  | 
 | 40 | + | 
 | 41 | +static const char *TAG = "WS2812_RMT";  | 
 | 42 | + | 
 | 43 | +/**  | 
 | 44 | + * @brief Default configuration for LED strip  | 
 | 45 | + *  | 
 | 46 | + */  | 
 | 47 | +#define WS2812_RMT_DEFAULT_CONFIG(number, channel_handle) { \  | 
 | 48 | + .max_leds = number, \  | 
 | 49 | + .channel = channel_handle, \  | 
 | 50 | +}  | 
 | 51 | + | 
 | 52 | +#define WS2812_RMT_CHECK(a, str, goto_tag, ret_value, ...) do { \  | 
 | 53 | + if (!(a)) { \  | 
 | 54 | + ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \  | 
 | 55 | + ret = ret_value; \  | 
 | 56 | + goto goto_tag; \  | 
 | 57 | + } \  | 
 | 58 | +} while (0)  | 
 | 59 | + | 
 | 60 | +typedef struct {  | 
 | 61 | + ws2812_t parent;  | 
 | 62 | + rmt_channel_handle_t rmt_channel;  | 
 | 63 | + rmt_encoder_handle_t bytes_encoder;  | 
 | 64 | + uint32_t strip_len;  | 
 | 65 | + uint8_t buffer[0];  | 
 | 66 | +} ws2812_rmt_dev_t;  | 
 | 67 | + | 
 | 68 | +/**  | 
 | 69 | + * @brief LED Strip Configuration Type  | 
 | 70 | + *  | 
 | 71 | + */  | 
 | 72 | +typedef struct {  | 
 | 73 | + uint32_t max_leds; /*!< Maximum LEDs in a single strip */  | 
 | 74 | + rmt_channel_handle_t channel; /*!< RMT channel handle */  | 
 | 75 | +} ws2812_rmt_config_t;  | 
 | 76 | + | 
 | 77 | +static esp_err_t _ws2812_rmt_set_pixel(ws2812_t *strip, uint32_t index, uint8_t red, uint8_t green, uint8_t blue)  | 
 | 78 | +{  | 
 | 79 | + esp_err_t ret = ESP_OK;  | 
 | 80 | + ws2812_rmt_dev_t *ws2812 = __containerof(strip, ws2812_rmt_dev_t, parent);  | 
 | 81 | + WS2812_RMT_CHECK(index < ws2812->strip_len, "index out of the maximum number of leds", err, ESP_ERR_INVALID_ARG);  | 
 | 82 | + uint32_t start = index * 3;  | 
 | 83 | + // In the order of GRB  | 
 | 84 | + ws2812->buffer[start + 0] = green;  | 
 | 85 | + ws2812->buffer[start + 1] = red;  | 
 | 86 | + ws2812->buffer[start + 2] = blue;  | 
 | 87 | + return ESP_OK;  | 
 | 88 | +err:  | 
 | 89 | + return ret;  | 
 | 90 | +}  | 
 | 91 | + | 
 | 92 | +static esp_err_t _ws2812_rmt_refresh(ws2812_t *strip, uint32_t timeout_ms)  | 
 | 93 | +{  | 
 | 94 | + esp_err_t ret = ESP_OK;  | 
 | 95 | + ws2812_rmt_dev_t *ws2812 = __containerof(strip, ws2812_rmt_dev_t, parent);  | 
 | 96 | + | 
 | 97 | + rmt_transmit_config_t transmit_config = {  | 
 | 98 | + .loop_count = 0, // No loop  | 
 | 99 | + };  | 
 | 100 | + | 
 | 101 | + WS2812_RMT_CHECK(rmt_transmit(ws2812->rmt_channel, ws2812->bytes_encoder, ws2812->buffer,  | 
 | 102 | + ws2812->strip_len * 3, &transmit_config)  | 
 | 103 | + == ESP_OK,  | 
 | 104 | + "transmit RMT samples failed", err, ESP_FAIL);  | 
 | 105 | + | 
 | 106 | + return rmt_tx_wait_all_done(ws2812->rmt_channel, pdMS_TO_TICKS(timeout_ms));  | 
 | 107 | +err:  | 
 | 108 | + return ret;  | 
 | 109 | +}  | 
 | 110 | + | 
 | 111 | +static esp_err_t _ws2812_rmt_clear(ws2812_t *strip, uint32_t timeout_ms)  | 
 | 112 | +{  | 
 | 113 | + ws2812_rmt_dev_t *ws2812 = __containerof(strip, ws2812_rmt_dev_t, parent);  | 
 | 114 | + // Write zero to turn off all leds  | 
 | 115 | + memset(ws2812->buffer, 0, ws2812->strip_len * 3);  | 
 | 116 | + return _ws2812_rmt_refresh(strip, timeout_ms);  | 
 | 117 | +}  | 
 | 118 | + | 
 | 119 | +static esp_err_t _ws2812_rmt_del(ws2812_t *strip)  | 
 | 120 | +{  | 
 | 121 | + ws2812_rmt_dev_t *ws2812 = __containerof(strip, ws2812_rmt_dev_t, parent);  | 
 | 122 | + | 
 | 123 | + // Disable the channel first  | 
 | 124 | + ESP_ERROR_CHECK(rmt_disable(ws2812->rmt_channel));  | 
 | 125 | + | 
 | 126 | + // Delete encoder and channel  | 
 | 127 | + ESP_ERROR_CHECK(rmt_del_encoder(ws2812->bytes_encoder));  | 
 | 128 | + ESP_ERROR_CHECK(rmt_del_channel(ws2812->rmt_channel));  | 
 | 129 | + | 
 | 130 | + free(ws2812);  | 
 | 131 | + return ESP_OK;  | 
 | 132 | +}  | 
 | 133 | + | 
 | 134 | +static ws2812_t *ws2812_rmt_new_ws2812(const ws2812_rmt_config_t *config)  | 
 | 135 | +{  | 
 | 136 | + ws2812_t *ret = NULL;  | 
 | 137 | + ws2812_rmt_dev_t *ws2812 = NULL;  | 
 | 138 | + WS2812_RMT_CHECK(config, "configuration can't be null", err, NULL);  | 
 | 139 | + | 
 | 140 | + // 24 bits per led  | 
 | 141 | + uint32_t ws2812_size = sizeof(ws2812_rmt_dev_t) + config->max_leds * 3;  | 
 | 142 | + | 
 | 143 | + ws2812 = calloc(1, ws2812_size);  | 
 | 144 | + WS2812_RMT_CHECK(ws2812, "request memory for ws2812 failed", err, NULL);  | 
 | 145 | + | 
 | 146 | + ws2812->rmt_channel = config->channel;  | 
 | 147 | + ws2812->strip_len = config->max_leds;  | 
 | 148 | + | 
 | 149 | + // Create bytes encoder for WS2812  | 
 | 150 | + rmt_bytes_encoder_config_t bytes_encoder_config = {  | 
 | 151 | + .bit0 = {  | 
 | 152 | + .level0 = 1,  | 
 | 153 | + .duration0 = WS2812_RMT_T0H_NS / 100,  | 
 | 154 | + .level1 = 0,  | 
 | 155 | + .duration1 = WS2812_RMT_T0L_NS / 100,  | 
 | 156 | + },  | 
 | 157 | + .bit1 = {  | 
 | 158 | + .level0 = 1,  | 
 | 159 | + .duration0 = WS2812_RMT_T1H_NS / 100,  | 
 | 160 | + .level1 = 0,  | 
 | 161 | + .duration1 = WS2812_RMT_T1L_NS / 100,  | 
 | 162 | + },  | 
 | 163 | + .flags.msb_first = 1, // WS2812 uses MSB first  | 
 | 164 | + };  | 
 | 165 | + | 
 | 166 | + WS2812_RMT_CHECK(rmt_new_bytes_encoder(&bytes_encoder_config, &ws2812->bytes_encoder) == ESP_OK,  | 
 | 167 | + "create bytes encoder failed", err, NULL);  | 
 | 168 | + | 
 | 169 | + ws2812->parent.set_pixel = _ws2812_rmt_set_pixel;  | 
 | 170 | + ws2812->parent.refresh = _ws2812_rmt_refresh;  | 
 | 171 | + ws2812->parent.clear = _ws2812_rmt_clear;  | 
 | 172 | + ws2812->parent.del = _ws2812_rmt_del;  | 
 | 173 | + | 
 | 174 | + return &ws2812->parent;  | 
 | 175 | +err:  | 
 | 176 | + if (ws2812) {  | 
 | 177 | + free(ws2812);  | 
 | 178 | + }  | 
 | 179 | + return ret;  | 
 | 180 | +}  | 
 | 181 | + | 
 | 182 | +ws2812_t *ws2812_rmt_init(uint8_t channel, uint8_t gpio, uint16_t led_num)  | 
 | 183 | +{  | 
 | 184 | + // New driver does not need to specify channel  | 
 | 185 | + (void)channel;  | 
 | 186 | + // Create RMT TX channel  | 
 | 187 | + rmt_tx_channel_config_t tx_chan_config = {  | 
 | 188 | + .clk_src = RMT_CLK_SRC_DEFAULT,  | 
 | 189 | + .gpio_num = gpio,  | 
 | 190 | + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,  | 
 | 191 | + .resolution_hz = WS2812_RMT_RESOLUTION_HZ,  | 
 | 192 | + .trans_queue_depth = 1,  | 
 | 193 | + };  | 
 | 194 | + | 
 | 195 | + rmt_channel_handle_t rmt_channel = NULL;  | 
 | 196 | + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &rmt_channel));  | 
 | 197 | + | 
 | 198 | + // Enable the channel  | 
 | 199 | + ESP_ERROR_CHECK(rmt_enable(rmt_channel));  | 
 | 200 | + | 
 | 201 | + // install ws2812 driver  | 
 | 202 | + ws2812_rmt_config_t strip_config = WS2812_RMT_DEFAULT_CONFIG(led_num, rmt_channel);  | 
 | 203 | + | 
 | 204 | + ws2812_t *strip = ws2812_rmt_new_ws2812(&strip_config);  | 
 | 205 | + | 
 | 206 | + if (!strip) {  | 
 | 207 | + ESP_LOGE(TAG, "install WS2812 driver failed");  | 
 | 208 | + return NULL;  | 
 | 209 | + }  | 
 | 210 | + | 
 | 211 | + // Clear LED strip (turn off all LEDs)  | 
 | 212 | + ESP_ERROR_CHECK(strip->clear(strip, 100));  | 
 | 213 | + | 
 | 214 | + return strip;  | 
 | 215 | +}  | 
 | 216 | + | 
 | 217 | +esp_err_t ws2812_rmt_deinit(ws2812_t *strip)  | 
 | 218 | +{  | 
 | 219 | + return strip->del(strip);  | 
 | 220 | +}  | 
 | 221 | + | 
0 commit comments