| 1 | // Implements the RMT peripheral on Espressif SoCs |
| 2 | // Copyright (c) 2020 Lucian Copeland for Adafruit Industries |
| 3 | |
| 4 | /* Uses code from Espressif RGB LED Strip demo and drivers |
| 5 | * Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD |
| 6 | * |
| 7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | * you may not use this file except in compliance with the License. |
| 9 | * You may obtain a copy of the License at |
| 10 | * |
| 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | * |
| 13 | * Unless required by applicable law or agreed to in writing, software |
| 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | * See the License for the specific language governing permissions and |
| 17 | * limitations under the License. |
| 18 | */ |
| 19 | |
| 20 | #if defined(ESP32) |
| 21 | |
| 22 | #include <Arduino.h> |
| 23 | |
| 24 | #if defined(ESP_IDF_VERSION) |
| 25 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) |
| 26 | #define HAS_ESP_IDF_4 |
| 27 | #endif |
| 28 | #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) |
| 29 | #define HAS_ESP_IDF_5 |
| 30 | #endif |
| 31 | #endif |
| 32 | |
| 33 | #ifdef HAS_ESP_IDF_5 |
| 34 | |
| 35 | static SemaphoreHandle_t show_mutex = NULL; |
| 36 | |
| 37 | void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, |
| 38 | boolean is800KHz) { |
| 39 | // Note: Because rmtPin is shared between all instances, we will |
| 40 | // end up releasing/initializing the RMT channels each time we |
| 41 | // invoke on different pins. This is probably ok, just not |
| 42 | // efficient. led_data is shared between all instances but will |
| 43 | // be allocated with enough space for the largest instance; data |
| 44 | // is not used beyond the mutex lock so this should be fine. |
| 45 | |
| 46 | #define SEMAPHORE_TIMEOUT_MS 50 |
| 47 | |
| 48 | static rmt_data_t *led_data = NULL; |
| 49 | static uint32_t led_data_size = 0; |
| 50 | static int rmtPin = -1; |
| 51 | |
| 52 | if (show_mutex && |
| 53 | xSemaphoreTake(show_mutex, SEMAPHORE_TIMEOUT_MS / portTICK_PERIOD_MS) == |
| 54 | pdTRUE) { |
| 55 | uint32_t requiredSize = numBytes * 8; |
| 56 | if (requiredSize > led_data_size) { |
| 57 | free(led_data); |
| 58 | led_data = (rmt_data_t *)malloc(requiredSize * sizeof(rmt_data_t)); |
| 59 | if (led_data != NULL) { |
| 60 | led_data_size = requiredSize; |
| 61 | } else { |
| 62 | led_data_size = 0; |
| 63 | } |
| 64 | } else if (requiredSize == 0) { |
| 65 | // To release RMT resources (RMT channels and led_data), call |
| 66 | // .updateLength(0) to set number of pixels/bytes to zero, |
| 67 | // then call .show() to invoke this code and free resources. |
| 68 | free(led_data); |
| 69 | led_data = NULL; |
| 70 | if (rmtPin >= 0) { |
| 71 | rmtDeinit(rmtPin); |
| 72 | rmtPin = -1; |
| 73 | } |
| 74 | led_data_size = 0; |
| 75 | } |
| 76 | |
| 77 | if (led_data_size > 0 && requiredSize <= led_data_size) { |
| 78 | if (pin != rmtPin) { |
| 79 | if (rmtPin >= 0) { |
| 80 | rmtDeinit(rmtPin); |
| 81 | rmtPin = -1; |
| 82 | } |
| 83 | if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, |
| 84 | 10000000)) { |
| 85 | log_e("Failed to init RMT TX mode on pin %d", pin); |
| 86 | return; |
| 87 | } |
| 88 | rmtPin = pin; |
| 89 | } |
| 90 | |
| 91 | if (rmtPin >= 0) { |
| 92 | int i = 0; |
| 93 | for (int b = 0; b < numBytes; b++) { |
| 94 | for (int bit = 0; bit < 8; bit++) { |
| 95 | if (pixels[b] & (1 << (7 - bit))) { |
| 96 | led_data[i].level0 = 1; |
| 97 | led_data[i].duration0 = 8; |
| 98 | led_data[i].level1 = 0; |
| 99 | led_data[i].duration1 = 4; |
| 100 | } else { |
| 101 | led_data[i].level0 = 1; |
| 102 | led_data[i].duration0 = 4; |
| 103 | led_data[i].level1 = 0; |
| 104 | led_data[i].duration1 = 8; |
| 105 | } |
| 106 | i++; |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | rmtWrite(pin, led_data, numBytes * 8, RMT_WAIT_FOR_EVER); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | xSemaphoreGive(show_mutex); |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // To avoid race condition initializing the mutex, all instances of |
| 119 | // Adafruit_NeoPixel must be constructed before launching and child threads |
| 120 | void espInit() { |
| 121 | if (!show_mutex) { |
| 122 | show_mutex = xSemaphoreCreateMutex(); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | #else |
| 127 | |
| 128 | #include "driver/rmt.h" |
| 129 | |
| 130 | // This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered |
| 131 | // to work with the Arduino version of the ESP-IDF (3.2) |
| 132 | |
| 133 | #define WS2812_T0H_NS (400) |
| 134 | #define WS2812_T0L_NS (850) |
| 135 | #define WS2812_T1H_NS (800) |
| 136 | #define WS2812_T1L_NS (450) |
| 137 | |
| 138 | #define WS2811_T0H_NS (500) |
| 139 | #define WS2811_T0L_NS (2000) |
| 140 | #define WS2811_T1H_NS (1200) |
| 141 | #define WS2811_T1L_NS (1300) |
| 142 | |
| 143 | static uint32_t t0h_ticks = 0; |
| 144 | static uint32_t t1h_ticks = 0; |
| 145 | static uint32_t t0l_ticks = 0; |
| 146 | static uint32_t t1l_ticks = 0; |
| 147 | |
| 148 | // Limit the number of RMT channels available for the Neopixels. Defaults to all |
| 149 | // channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free |
| 150 | // any channels with a higher number for other uses, such as IR send-and-recieve |
| 151 | // libraries. Redefine as 1 to restrict Neopixels to only a single channel. |
| 152 | #define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX |
| 153 | |
| 154 | #define RMT_LL_HW_BASE (&RMT) |
| 155 | |
| 156 | bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX]; |
| 157 | |
| 158 | static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, |
| 159 | size_t src_size, size_t wanted_num, |
| 160 | size_t *translated_size, |
| 161 | size_t *item_num) { |
| 162 | if (src == NULL || dest == NULL) { |
| 163 | *translated_size = 0; |
| 164 | *item_num = 0; |
| 165 | return; |
| 166 | } |
| 167 | const rmt_item32_t bit0 = {{{t0h_ticks, 1, t0l_ticks, 0}}}; // Logical 0 |
| 168 | const rmt_item32_t bit1 = {{{t1h_ticks, 1, t1l_ticks, 0}}}; // Logical 1 |
| 169 | size_t size = 0; |
| 170 | size_t num = 0; |
| 171 | uint8_t *psrc = (uint8_t *)src; |
| 172 | rmt_item32_t *pdest = dest; |
| 173 | while (size < src_size && num < wanted_num) { |
| 174 | for (int i = 0; i < 8; i++) { |
| 175 | // MSB first |
| 176 | if (*psrc & (1 << (7 - i))) { |
| 177 | pdest->val = bit1.val; |
| 178 | } else { |
| 179 | pdest->val = bit0.val; |
| 180 | } |
| 181 | num++; |
| 182 | pdest++; |
| 183 | } |
| 184 | size++; |
| 185 | psrc++; |
| 186 | } |
| 187 | *translated_size = size; |
| 188 | *item_num = num; |
| 189 | } |
| 190 | |
| 191 | void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, |
| 192 | boolean is800KHz) { |
| 193 | // Reserve channel |
| 194 | rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX; |
| 195 | for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) { |
| 196 | if (!rmt_reserved_channels[i]) { |
| 197 | rmt_reserved_channels[i] = true; |
| 198 | channel = i; |
| 199 | break; |
| 200 | } |
| 201 | } |
| 202 | if (channel == ADAFRUIT_RMT_CHANNEL_MAX) { |
| 203 | // Ran out of channels! |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | #if defined(HAS_ESP_IDF_4) |
| 208 | rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel); |
| 209 | config.clk_div = 2; |
| 210 | #else |
| 211 | // Match default TX config from ESP-IDF version 3.4 |
| 212 | rmt_config_t config = {.rmt_mode = RMT_MODE_TX, |
| 213 | .channel = channel, |
| 214 | .gpio_num = pin, |
| 215 | .clk_div = 2, |
| 216 | .mem_block_num = 1, |
| 217 | .tx_config = { |
| 218 | .carrier_freq_hz = 38000, |
| 219 | .carrier_level = RMT_CARRIER_LEVEL_HIGH, |
| 220 | .idle_level = RMT_IDLE_LEVEL_LOW, |
| 221 | .carrier_duty_percent = 33, |
| 222 | .carrier_en = false, |
| 223 | .loop_en = false, |
| 224 | .idle_output_en = true, |
| 225 | }}; |
| 226 | #endif |
| 227 | rmt_config(&config); |
| 228 | rmt_driver_install(config.channel, 0, 0); |
| 229 | |
| 230 | // Convert NS timings to ticks |
| 231 | uint32_t counter_clk_hz = 0; |
| 232 | |
| 233 | #if defined(HAS_ESP_IDF_4) |
| 234 | rmt_get_counter_clock(channel, &counter_clk_hz); |
| 235 | #else |
| 236 | // this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4 |
| 237 | if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == |
| 238 | RMT_BASECLK_REF) { |
| 239 | uint32_t div_cnt = |
| 240 | RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; |
| 241 | uint32_t div = div_cnt == 0 ? 256 : div_cnt; |
| 242 | counter_clk_hz = REF_CLK_FREQ / (div); |
| 243 | } else { |
| 244 | uint32_t div_cnt = |
| 245 | RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; |
| 246 | uint32_t div = div_cnt == 0 ? 256 : div_cnt; |
| 247 | counter_clk_hz = APB_CLK_FREQ / (div); |
| 248 | } |
| 249 | #endif |
| 250 | |
| 251 | // NS to tick converter |
| 252 | float ratio = (float)counter_clk_hz / 1e9; |
| 253 | |
| 254 | if (is800KHz) { |
| 255 | t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); |
| 256 | t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); |
| 257 | t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); |
| 258 | t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); |
| 259 | } else { |
| 260 | t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS); |
| 261 | t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS); |
| 262 | t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS); |
| 263 | t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS); |
| 264 | } |
| 265 | |
| 266 | // Initialize automatic timing translator |
| 267 | rmt_translator_init(config.channel, ws2812_rmt_adapter); |
| 268 | |
| 269 | // Write and wait to finish |
| 270 | rmt_write_sample(config.channel, pixels, (size_t)numBytes, true); |
| 271 | rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100)); |
| 272 | |
| 273 | // Free channel again |
| 274 | rmt_driver_uninstall(config.channel); |
| 275 | rmt_reserved_channels[channel] = false; |
| 276 | |
| 277 | gpio_set_direction(pin, GPIO_MODE_OUTPUT); |
| 278 | } |
| 279 | |
| 280 | #endif // ifndef IDF5 |
| 281 | |
| 282 | #endif // ifdef(ESP32) |