├── .gitignore ├── library.properties ├── README.md ├── src ├── fastled_compat.h ├── Ws2812Adapter.h ├── Ws2812Adapter.cpp ├── clockless_rmt_esp32.h └── clockless_rmt_esp32.cpp └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Ws2812Adapter 2 | version=1.0 3 | author=Ben Hencke 4 | maintainer=Ben Hencke 5 | sentence=This is a lightweight buffer-less driver for ws2812 (aka NeoPixel) LED strips. 6 | paragraph=Instead of setting pixels in a buffer then sending that data out, this calls out to a function to generate each pixel. The pixel data could come from some other buffer, or be created on the fly. 7 | category=Display 8 | url=https://github.com/simap/Ws2812Adapter 9 | architectures=esp8266,esp32 10 | includes=Ws2812Adapter.h 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ws2812 Adapter 2 | ========= 3 | 4 | This is a lightweight buffer-less driver to send pixel data out via UART1 on the ESP8266 to drive ws2812/sk6812 aka NeoPixel LED strips. 5 | 6 | Instead of setting pixels in a buffer then sending that data out, this calls out to a function to generate each pixel. The pixel data could come from some other buffer, or be created on the fly. 7 | 8 | This uses less memory and can theoretically drive a very long chain of LEDs. 9 | 10 | A buffered option is also supported. 11 | 12 | 13 | ESP32 14 | ========== 15 | 16 | NOTE: this does not use the rmt peripheral. It seems that between the interrupt necessary to fill the buffer and things like async web server (even pinned to another core), buffer underflows are happening and causing glitches. 17 | With async pinned to core0 and this running in core1, no glitches happen. 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/fastled_compat.h: -------------------------------------------------------------------------------- 1 | 2 | #include "stdint.h" 3 | #include "Arduino.h" 4 | 5 | /// Class to ensure that a minimum amount of time has kicked since the last time run - and delay if not enough time has passed yet 6 | /// this should make sure that chipsets that have 7 | template class CMinWait { 8 | uint16_t mLastMicros; 9 | public: 10 | CMinWait() { mLastMicros = 0; } 11 | 12 | void wait() { 13 | uint16_t diff; 14 | do { 15 | diff = (micros() & 0xFFFF) - mLastMicros; 16 | } while(diff < WAIT); 17 | } 18 | 19 | void mark() { mLastMicros = micros() & 0xFFFF; } 20 | }; 21 | 22 | 23 | // Allow clock that clockless controller is based on to have different 24 | // frequency than the CPU. 25 | #if !defined(CLOCKLESS_FREQUENCY) 26 | #define CLOCKLESS_FREQUENCY F_CPU 27 | #endif 28 | 29 | #define C_NS(_NS) (((_NS * ((CLOCKLESS_FREQUENCY / 1000000L)) + 999)) / 1000) 30 | 31 | 32 | 33 | #include "clockless_rmt_esp32.h" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ben Hencke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Ws2812Adapter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Ben Hencke on 2/10/17. 3 | // 4 | 5 | #ifndef WS2812ADAPTER_HPP 6 | #define WS2812ADAPTER_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef ESP32 13 | #include "fastled_compat.h" 14 | #endif 15 | 16 | typedef std::function Ws2812PixelFunction; 17 | 18 | class Ws2812Adapter { 19 | public: 20 | #ifdef ESP32 21 | ESP32RMTController mRMTController; 22 | Ws2812Adapter() : mRMTController(23, C_NS(250), C_NS(500), C_NS(500)) { 23 | setColorOrder(2, 1, 0); 24 | }; 25 | #else 26 | Ws2812Adapter(uint8_t o = WS2812_BGR) { 27 | setColorOrder(o); 28 | }; 29 | #endif 30 | 31 | ~Ws2812Adapter(); 32 | 33 | void begin(); 34 | 35 | void end(); 36 | 37 | void setColorOrder(uint8_t ri, uint8_t gi, uint8_t bi) { 38 | rOffset = ri; 39 | gOffset = gi; 40 | bOffset = bi; 41 | elements = 3; 42 | } 43 | void setColorOrder(uint8_t ri, uint8_t gi, uint8_t bi, uint8_t wi) { 44 | rOffset = ri; 45 | gOffset = gi; 46 | bOffset = bi; 47 | wOffset = wi; 48 | elements = 4; 49 | } 50 | 51 | void show(uint16_t numPixels, Ws2812PixelFunction cb); 52 | 53 | private: 54 | 55 | bool setBuffer(size_t size); 56 | void clearBuffer(); 57 | 58 | unsigned long timer; 59 | uint8_t 60 | rOffset, // Index of red in 3-byte pixel 61 | gOffset, // Index of green byte 62 | bOffset, // Index of blue byte 63 | wOffset; // Index of white byte 64 | bool useBuffer; 65 | uint8_t elements = 3; //3 = RGB or 4 = RGBW 66 | std::unique_ptr buffer; 67 | size_t bufferSize; 68 | 69 | }; 70 | 71 | 72 | #endif //WS2812ADAPTER_HPP 73 | -------------------------------------------------------------------------------- /src/Ws2812Adapter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Ben Hencke on 11/28/17. 3 | // 4 | 5 | /* 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | #include "Ws2812Adapter.h" 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #ifdef ESP8266 33 | extern "C" 34 | { 35 | #include "eagle_soc.h" 36 | #include "uart_register.h" 37 | } 38 | #endif 39 | 40 | #ifdef ESP32 41 | #include "esp32-hal-uart.h" 42 | #include "soc/uart_reg.h" 43 | #include "soc/uart_struct.h" 44 | 45 | #endif 46 | 47 | #ifdef ESP8266 48 | static const uint8_t bits[4] = { 49 | 0b11101111, 50 | 0b11001111, 51 | 0b11101110, 52 | 0b11001110, 53 | }; 54 | 55 | 56 | static inline void write(uint8_t c) { 57 | #ifdef ESP32 58 | UART1.fifo.rw_byte = c; 59 | #endif 60 | #ifdef ESP8266 61 | Serial1.write(c); 62 | #endif 63 | } 64 | #endif 65 | 66 | void Ws2812Adapter::show(uint16_t numPixels, Ws2812PixelFunction cb) { 67 | int curPixel; 68 | uint8_t rgb[elements], buf[elements]; 69 | 70 | memset(rgb, 0, elements); 71 | 72 | if (numPixels > 2500) 73 | numPixels = 2500; 74 | 75 | size_t bufferSize = numPixels * elements; 76 | setBuffer(bufferSize); 77 | //render and buffer pixels 78 | for (curPixel = 0; curPixel < numPixels; curPixel++) { 79 | cb(curPixel, rgb); 80 | int pixelOffset = curPixel * elements; 81 | //swap around rgb values based on mapping 82 | buffer[pixelOffset + rOffset] = rgb[0]; 83 | buffer[pixelOffset + gOffset] = rgb[1]; 84 | buffer[pixelOffset + bOffset] = rgb[2]; 85 | if (elements == 4) 86 | buffer[pixelOffset + wOffset] = rgb[3]; 87 | } 88 | 89 | //wait for any previous latch 90 | while (micros() - timer < 300) //use ws2813 timing 91 | yield(); 92 | 93 | #ifdef ESP8266 94 | //we need 12-16 bytes (elements * 4) free in the uart tx fifo for a whole pixel 95 | register int uartHighWatermark = 0x7f - (elements << 2); 96 | 97 | //stream out a pixel at a time to the uart fifo 98 | for (curPixel = 0; curPixel < numPixels; curPixel++) { 99 | int pixelOffset = curPixel * elements; 100 | buf[0] = buffer[pixelOffset]; 101 | buf[1] = buffer[pixelOffset + 1]; 102 | buf[2] = buffer[pixelOffset + 2]; 103 | if (elements == 4) 104 | buf[3] = buffer[pixelOffset + 3]; 105 | //wait for 12-16 bytes (elements * 4) free in the uart tx fifo before locking interrupts 106 | while((USS(UART1) >> USTXC) >= uartHighWatermark) { 107 | //busy loop, or should we yield? 108 | } 109 | os_intr_lock(); 110 | 111 | for (uint8_t i = 0; i < elements; i++) { 112 | uint8_t c = buf[i]; 113 | write(bits[c >> 6]); 114 | write(bits[(c >> 4) & 0x03]); 115 | write(bits[(c >> 2) & 0x03]); 116 | write(bits[c & 0x03]); 117 | } 118 | 119 | os_intr_unlock(); 120 | 121 | } 122 | 123 | //wait for the last bits to send before starting latch timer 124 | Serial1.flush(); 125 | 126 | #endif 127 | 128 | #ifdef ESP32 129 | 130 | //TODO when showPixels is async, wait for last showPixels to finish, + 300 micros 131 | uint8_t * pData = mRMTController.getPixelData(bufferSize); 132 | std::memcpy(pData, buffer.get(), bufferSize); 133 | 134 | //TODO rewrite showPixels to be async, not block, and record time of completion 135 | mRMTController.showPixels(); 136 | #endif 137 | 138 | timer = micros(); 139 | } 140 | 141 | 142 | void Ws2812Adapter::end() { 143 | // Serial1.end(); 144 | } 145 | 146 | Ws2812Adapter::~Ws2812Adapter() { 147 | end(); 148 | } 149 | 150 | void Ws2812Adapter::begin() { 151 | 152 | #ifdef ESP8266 153 | Serial1.begin(3500000, SERIAL_8N1, SERIAL_TX_ONLY); 154 | SET_PERI_REG_MASK(UART_CONF0(UART1), BIT22); 155 | #endif 156 | #ifdef ESP32 157 | // Serial1.begin(3500000, SERIAL_8N1, -1, 23); 158 | // UART1.conf0.txd_inv = 1; //inverted 159 | #endif 160 | 161 | timer = micros(); 162 | } 163 | 164 | bool Ws2812Adapter::setBuffer(size_t size) { 165 | if (bufferSize == size) { 166 | return true; 167 | } 168 | 169 | bufferSize = 0; 170 | buffer.reset(nullptr); 171 | if (size) 172 | buffer.reset(new uint8_t[size]); 173 | if (buffer) { 174 | bufferSize = size; 175 | return true; 176 | } 177 | return false; 178 | } 179 | 180 | void Ws2812Adapter::clearBuffer() { 181 | setBuffer(0); 182 | } 183 | -------------------------------------------------------------------------------- /src/clockless_rmt_esp32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Integration into FastLED ClocklessController 3 | * Copyright (c) 2018,2019,2020 Samuel Z. Guyer 4 | * Copyright (c) 2017 Thomas Basler 5 | * Copyright (c) 2017 Martin F. Falatic 6 | * 7 | * ESP32 support is provided using the RMT peripheral device -- a unit 8 | * on the chip designed specifically for generating (and receiving) 9 | * precisely-timed digital signals. Nominally for use in infrared 10 | * remote controls, we use it to generate the signals for clockless 11 | * LED strips. The main advantage of using the RMT device is that, 12 | * once programmed, it generates the signal asynchronously, allowing 13 | * the CPU to continue executing other code. It is also not vulnerable 14 | * to interrupts or other timing problems that could disrupt the signal. 15 | * 16 | * The implementation strategy is borrowed from previous work and from 17 | * the RMT support built into the ESP32 IDF. The RMT device has 8 18 | * channels, which can be programmed independently to send sequences 19 | * of high/low bits. Memory for each channel is limited, however, so 20 | * in order to send a long sequence of bits, we need to continuously 21 | * refill the buffer until all the data is sent. To do this, we fill 22 | * half the buffer and then set an interrupt to go off when that half 23 | * is sent. Then we refill that half while the second half is being 24 | * sent. This strategy effectively overlaps computation (by the CPU) 25 | * and communication (by the RMT). 26 | * 27 | * Since the RMT device only has 8 channels, we need a strategy to 28 | * allow more than 8 LED controllers. Our driver assigns controllers 29 | * to channels on the fly, queuing up controllers as necessary until a 30 | * channel is free. The main showPixels routine just fires off the 31 | * first 8 controllers; the interrupt handler starts new controllers 32 | * asynchronously as previous ones finish. So, for example, it can 33 | * send the data for 8 controllers simultaneously, but 16 controllers 34 | * would take approximately twice as much time. 35 | * 36 | * There is a #define that allows a program to control the total 37 | * number of channels that the driver is allowed to use. It defaults 38 | * to 8 -- use all the channels. Setting it to 1, for example, results 39 | * in fully serial output: 40 | * 41 | * #define FASTLED_RMT_MAX_CHANNELS 1 42 | * 43 | * OTHER RMT APPLICATIONS 44 | * 45 | * The default FastLED driver takes over control of the RMT interrupt 46 | * handler, making it hard to use the RMT device for other 47 | * (non-FastLED) purposes. You can change it's behavior to use the ESP 48 | * core driver instead, allowing other RMT applications to 49 | * co-exist. To switch to this mode, add the following directive 50 | * before you include FastLED.h: 51 | * 52 | * #define FASTLED_RMT_BUILTIN_DRIVER 1 53 | * 54 | * There may be a performance penalty for using this mode. We need to 55 | * compute the RMT signal for the entire LED strip ahead of time, 56 | * rather than overlapping it with communication. We also need a large 57 | * buffer to hold the signal specification. Each bit of pixel data is 58 | * represented by a 32-bit pulse specification, so it is a 32X blow-up 59 | * in memory use. 60 | * 61 | * NEW: Use of Flash memory on the ESP32 can interfere with the timing 62 | * of pixel output. The ESP-IDF system code disables all other 63 | * code running on *either* core during these operation. To prevent 64 | * this from happening, define this flag. It will force flash 65 | * operations to wait until the show() is done. 66 | * 67 | * #define FASTLED_ESP32_FLASH_LOCK 1 68 | * 69 | * NEW (June 2020): The RMT controller has been split into two 70 | * classes: ClocklessController, which is an instantiation of the 71 | * FastLED CPixelLEDController template, and ESP32RMTController, 72 | * which just handles driving the RMT peripheral. One benefit of 73 | * this design is that ESP32RMTContoller is not a template, so 74 | * its methods can be marked with the IRAM_ATTR and end up in 75 | * IRAM memory. Another benefit is that all of the color channel 76 | * processing is done up-front, in the templated class, so we 77 | * can fill the RMT buffers more quickly. 78 | * 79 | * IN THEORY, this design would also allow FastLED.show() to 80 | * send the data while the program continues to prepare the next 81 | * frame of data. 82 | * 83 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 84 | * http://insentricity.com * 85 | * 86 | */ 87 | /* 88 | * Permission is hereby granted, free of charge, to any person obtaining a copy 89 | * of this software and associated documentation files (the "Software"), to deal 90 | * in the Software without restriction, including without limitation the rights 91 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 92 | * copies of the Software, and to permit persons to whom the Software is 93 | * furnished to do so, subject to the following conditions: 94 | * 95 | * The above copyright notice and this permission notice shall be included in 96 | * all copies or substantial portions of the Software. 97 | * 98 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 99 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 100 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 101 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 102 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 103 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 104 | * THE SOFTWARE. 105 | */ 106 | 107 | #pragma once 108 | 109 | 110 | #ifdef __cplusplus 111 | extern "C" { 112 | #endif 113 | 114 | #include "esp32-hal.h" 115 | #include "esp_intr.h" 116 | #include "driver/gpio.h" 117 | #include "driver/rmt.h" 118 | #include "driver/periph_ctrl.h" 119 | #include "freertos/semphr.h" 120 | #include "soc/rmt_struct.h" 121 | 122 | #include "esp_log.h" 123 | 124 | extern void spi_flash_op_lock(void); 125 | extern void spi_flash_op_unlock(void); 126 | 127 | #ifdef __cplusplus 128 | } 129 | #endif 130 | 131 | __attribute__ ((always_inline)) inline static uint32_t __clock_cycles() { 132 | uint32_t cyc; 133 | __asm__ __volatile__ ("rsr %0,ccount":"=a" (cyc)); 134 | return cyc; 135 | } 136 | 137 | #define FASTLED_HAS_CLOCKLESS 1 138 | #define NUM_COLOR_CHANNELS 3 139 | 140 | // NOT CURRENTLY IMPLEMENTED: 141 | // -- Set to true to print debugging information about timing 142 | // Useful for finding out if timing is being messed up by other things 143 | // on the processor (WiFi, for example) 144 | //#ifndef FASTLED_RMT_SHOW_TIMER 145 | //#define FASTLED_RMT_SHOW_TIMER false 146 | //#endif 147 | 148 | // -- Configuration constants 149 | #define DIVIDER 2 /* 4, 8 still seem to work, but timings become marginal */ 150 | #define MAX_PULSES 64 /* A channel has a 64 "pulse" buffer */ 151 | #define PULSES_PER_FILL 32 /* Half of the channel buffer */ 152 | 153 | // -- Convert ESP32 CPU cycles to RMT device cycles, taking into account the divider 154 | #define F_CPU_RMT ( 80000000L) 155 | #define RMT_CYCLES_PER_SEC (F_CPU_RMT/DIVIDER) 156 | #define RMT_CYCLES_PER_ESP_CYCLE (F_CPU / RMT_CYCLES_PER_SEC) 157 | #define ESP_TO_RMT_CYCLES(n) ((n) / (RMT_CYCLES_PER_ESP_CYCLE)) 158 | 159 | // -- Number of cycles to signal the strip to latch 160 | #define NS_PER_CYCLE ( 1000000000L / RMT_CYCLES_PER_SEC ) 161 | #define NS_TO_CYCLES(n) ( (n) / NS_PER_CYCLE ) 162 | #define RMT_RESET_DURATION NS_TO_CYCLES(50000) 163 | 164 | // -- Core or custom driver 165 | #ifndef FASTLED_RMT_BUILTIN_DRIVER 166 | #define FASTLED_RMT_BUILTIN_DRIVER false 167 | #endif 168 | 169 | // -- Max number of controllers we can support 170 | #ifndef FASTLED_RMT_MAX_CONTROLLERS 171 | #define FASTLED_RMT_MAX_CONTROLLERS 32 172 | #endif 173 | 174 | // -- Number of RMT channels to use (up to 8) 175 | // Redefine this value to 1 to force serial output 176 | #ifndef FASTLED_RMT_MAX_CHANNELS 177 | #define FASTLED_RMT_MAX_CHANNELS 8 178 | #endif 179 | 180 | class ESP32RMTController 181 | { 182 | private: 183 | 184 | // -- RMT has 8 channels, numbered 0 to 7 185 | rmt_channel_t mRMT_channel; 186 | 187 | // -- Store the GPIO pin 188 | gpio_num_t mPin; 189 | 190 | // -- Timing values for zero and one bits, derived from T1, T2, and T3 191 | rmt_item32_t mZero; 192 | rmt_item32_t mOne; 193 | 194 | // -- Pixel data 195 | uint8_t * mPixelData; 196 | int mSize; 197 | int mCur; 198 | 199 | // -- RMT memory 200 | volatile uint32_t * mRMT_mem_ptr; 201 | int mWhichHalf; 202 | 203 | // -- Buffer to hold all of the pulses. For the version that uses 204 | // the RMT driver built into the ESP core. 205 | rmt_item32_t * mBuffer; 206 | uint16_t mBufferSize; 207 | int mCurPulse; 208 | 209 | // -- Make sure we can't call show() too quickly 210 | CMinWait<50> mWait; 211 | 212 | public: 213 | 214 | // -- Constructor 215 | // Mainly just stores the template parameters from the LEDController as 216 | // member variables. 217 | ESP32RMTController(int DATA_PIN, int T1, int T2, int T3); 218 | 219 | // -- Getters and setters for use in ClocklessController 220 | uint8_t * getPixelData(int size_in_bytes); 221 | 222 | // -- Initialize RMT subsystem 223 | // This only needs to be done once 224 | static void init(); 225 | 226 | // -- Show this string of pixels 227 | // This is the main entry point for the pixel controller 228 | void IRAM_ATTR showPixels(); 229 | 230 | // -- Start up the next controller 231 | // This method is static so that it can dispatch to the 232 | // appropriate startOnChannel method of the given controller. 233 | static void IRAM_ATTR startNext(int channel); 234 | 235 | // -- Start this controller on the given channel 236 | // This function just initiates the RMT write; it does not wait 237 | // for it to finish. 238 | void IRAM_ATTR startOnChannel(int channel); 239 | 240 | // -- Start RMT transmission 241 | // Setting this RMT flag is what actually kicks off the peripheral 242 | void IRAM_ATTR tx_start(); 243 | 244 | // -- A controller is done 245 | // This function is called when a controller finishes writing 246 | // its data. It is called either by the custom interrupt 247 | // handler (below), or as a callback from the built-in 248 | // interrupt handler. It is static because we don't know which 249 | // controller is done until we look it up. 250 | static void IRAM_ATTR doneOnChannel(rmt_channel_t channel, void * arg); 251 | 252 | // -- Custom interrupt handler 253 | // This interrupt handler handles two cases: a controller is 254 | // done writing its data, or a controller needs to fill the 255 | // next half of the RMT buffer with data. 256 | static void IRAM_ATTR interruptHandler(void *arg); 257 | 258 | // -- Fill RMT buffer 259 | // Puts 32 bits of pixel data into the next 32 slots in the RMT memory 260 | // Each data bit is represented by a 32-bit RMT item that specifies how 261 | // long to hold the signal high, followed by how long to hold it low. 262 | void IRAM_ATTR fillNext(); 263 | 264 | // -- Init pulse buffer 265 | // Set up the buffer that will hold all of the pulse items for this 266 | // controller. 267 | // This function is only used when the built-in RMT driver is chosen 268 | void initPulseBuffer(int size_in_bytes); 269 | 270 | // -- Convert a byte into RMT pulses 271 | // This function is only used when the built-in RMT driver is chosen 272 | void convertByte(uint32_t byteval); 273 | }; 274 | /* 275 | template 276 | class ClocklessController : public CPixelLEDController 277 | { 278 | private: 279 | 280 | // -- The actual controller object for ESP32 281 | ESP32RMTController mRMTController; 282 | 283 | // -- This instantiation forces a check on the pin choice 284 | FastPin mFastPin; 285 | 286 | public: 287 | 288 | ClocklessController() 289 | : mRMTController(DATA_PIN, T1, T2, T3) 290 | {} 291 | 292 | void init() 293 | { 294 | // mRMTController = new ESP32RMTController(DATA_PIN, T1, T2, T3); 295 | } 296 | 297 | virtual uint16_t getMaxRefreshRate() const { return 400; } 298 | 299 | protected: 300 | 301 | // -- Load pixel data 302 | // This method loads all of the pixel data into a separate buffer for use by 303 | // by the RMT driver. Copying does two important jobs: it fixes the color 304 | // order for the pixels, and it performs the scaling/adjusting ahead of time. 305 | void loadPixelData(PixelController & pixels) 306 | { 307 | // -- Make sure the buffer is allocated 308 | int size = pixels.size() * 3; 309 | uint8_t * pData = mRMTController.getPixelData(size); 310 | 311 | // -- Read out the pixel data using the pixel controller methods that 312 | // perform the scaling and adjustments 313 | int count = 0; 314 | while (pixels.has(1)) { 315 | *pData++ = pixels.loadAndScale0(); 316 | *pData++ = pixels.loadAndScale1(); 317 | *pData++ = pixels.loadAndScale2(); 318 | pixels.advanceData(); 319 | pixels.stepDithering(); 320 | count += 3; 321 | } 322 | 323 | assert(count == size); 324 | } 325 | 326 | // -- Show pixels 327 | // This is the main entry point for the controller. 328 | virtual void showPixels(PixelController & pixels) 329 | { 330 | if (FASTLED_RMT_BUILTIN_DRIVER) { 331 | convertAllPixelData(pixels); 332 | } else { 333 | loadPixelData(pixels); 334 | } 335 | 336 | mRMTController.showPixels(); 337 | } 338 | 339 | // -- Convert all pixels to RMT pulses 340 | // This function is only used when the user chooses to use the 341 | // built-in RMT driver, which needs all of the RMT pulses 342 | // up-front. 343 | void convertAllPixelData(PixelController & pixels) 344 | { 345 | // -- Make sure the data buffer is allocated 346 | mRMTController.initPulseBuffer(pixels.size() * 3); 347 | 348 | // -- Cycle through the R,G, and B values in the right order, 349 | // storing the pulses in the big buffer 350 | 351 | uint32_t byteval; 352 | while (pixels.has(1)) { 353 | byteval = pixels.loadAndScale0(); 354 | mRMTController.convertByte(byteval); 355 | byteval = pixels.loadAndScale1(); 356 | mRMTController.convertByte(byteval); 357 | byteval = pixels.loadAndScale2(); 358 | mRMTController.convertByte(byteval); 359 | pixels.advanceData(); 360 | pixels.stepDithering(); 361 | } 362 | } 363 | }; 364 | */ 365 | -------------------------------------------------------------------------------- /src/clockless_rmt_esp32.cpp: -------------------------------------------------------------------------------- 1 | 2 | #ifdef ESP32 3 | 4 | #define FASTLED_INTERNAL 5 | #include "fastled_compat.h" 6 | 7 | #define FASTLED_ESP32_FLASH_LOCK 0 8 | #define FASTLED_ESP32_SHOWTIMING 0 9 | 10 | // -- Forward reference 11 | class ESP32RMTController; 12 | 13 | // -- Array of all controllers 14 | // This array is filled at the time controllers are registered 15 | // (Usually when the sketch calls addLeds) 16 | static ESP32RMTController * gControllers[FASTLED_RMT_MAX_CONTROLLERS]; 17 | 18 | // -- Current set of active controllers, indexed by the RMT 19 | // channel assigned to them. 20 | static ESP32RMTController * gOnChannel[FASTLED_RMT_MAX_CHANNELS]; 21 | 22 | // -- Channels that need a buffer refill 23 | static volatile bool gRefillChannel[FASTLED_RMT_MAX_CHANNELS]; 24 | 25 | // -- Channels that are done 26 | static volatile bool gDoneChannel[FASTLED_RMT_MAX_CHANNELS]; 27 | 28 | static int gNumControllers = 0; 29 | static int gNumStarted = 0; 30 | static int gNumDone = 0; 31 | static int gNext = 0; 32 | 33 | static intr_handle_t gRMT_intr_handle = NULL; 34 | 35 | // -- Global semaphore for the whole show process 36 | // Semaphore is not given until all data has been sent 37 | static xSemaphoreHandle gTX_sem = NULL; 38 | 39 | static bool gInitialized = false; 40 | 41 | // -- Timing stuff 42 | static uint32_t gTiming[500]; 43 | static int gTimeIndex; 44 | static uint32_t gLastTime; 45 | 46 | 47 | ESP32RMTController::ESP32RMTController(int DATA_PIN, int T1, int T2, int T3) 48 | : mPixelData(0), 49 | mSize(0), 50 | mCur(0), 51 | mWhichHalf(0), 52 | mBuffer(0), 53 | mBufferSize(0), 54 | mCurPulse(0) 55 | { 56 | // -- Precompute rmt items corresponding to a zero bit and a one bit 57 | // according to the timing values given in the template instantiation 58 | // T1H 59 | mOne.level0 = 1; 60 | mOne.duration0 = ESP_TO_RMT_CYCLES(T1+T2); // TO_RMT_CYCLES(T1+T2); 61 | // T1L 62 | mOne.level1 = 0; 63 | mOne.duration1 = ESP_TO_RMT_CYCLES(T3); // TO_RMT_CYCLES(T3); 64 | 65 | // T0H 66 | mZero.level0 = 1; 67 | mZero.duration0 = ESP_TO_RMT_CYCLES(T1); // TO_RMT_CYCLES(T1); 68 | // T0L 69 | mZero.level1 = 0; 70 | mZero.duration1 = ESP_TO_RMT_CYCLES(T2+T3); // TO_RMT_CYCLES(T2 + T3); 71 | 72 | gControllers[gNumControllers] = this; 73 | gNumControllers++; 74 | 75 | mPin = gpio_num_t(DATA_PIN); 76 | } 77 | 78 | // -- Getters and setters for use in ClocklessController 79 | uint8_t * ESP32RMTController::getPixelData(int size_in_bytes) 80 | { 81 | if (mPixelData == 0 || mSize != size_in_bytes) { 82 | mSize = size_in_bytes; 83 | if (mPixelData) { 84 | mPixelData = (uint8_t *) realloc(mPixelData, mSize); 85 | if (mPixelData) { 86 | memset(mPixelData, mSize, sizeof(uint8_t)); 87 | } 88 | } else { 89 | mPixelData = (uint8_t *) calloc( mSize, sizeof(uint8_t)); 90 | } 91 | } 92 | return mPixelData; 93 | } 94 | 95 | // -- Initialize RMT subsystem 96 | // This only needs to be done once 97 | void ESP32RMTController::init() 98 | { 99 | if (gInitialized) return; 100 | 101 | for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { 102 | gOnChannel[i] = NULL; 103 | 104 | // -- RMT configuration for transmission 105 | rmt_config_t rmt_tx; 106 | rmt_tx.channel = rmt_channel_t(i); 107 | rmt_tx.rmt_mode = RMT_MODE_TX; 108 | rmt_tx.gpio_num = gpio_num_t(0); // The particular pin will be assigned later 109 | rmt_tx.mem_block_num = 1; 110 | rmt_tx.clk_div = DIVIDER; 111 | rmt_tx.tx_config.loop_en = false; 112 | rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; 113 | rmt_tx.tx_config.carrier_en = false; 114 | rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; 115 | rmt_tx.tx_config.idle_output_en = true; 116 | 117 | // -- Apply the configuration 118 | rmt_config(&rmt_tx); 119 | 120 | if (FASTLED_RMT_BUILTIN_DRIVER) { 121 | rmt_driver_install(rmt_channel_t(i), 0, 0); 122 | } else { 123 | // -- Set up the RMT to send 1 pixel of the pulse buffer and then 124 | // generate an interrupt. When we get this interrupt we 125 | // fill the other part in preparation (kind of like double-buffering) 126 | rmt_set_tx_thr_intr_en(rmt_channel_t(i), true, PULSES_PER_FILL); 127 | } 128 | } 129 | 130 | // -- Create a semaphore to block execution until all the controllers are done 131 | if (gTX_sem == NULL) { 132 | gTX_sem = xSemaphoreCreateBinary(); 133 | xSemaphoreGive(gTX_sem); 134 | } 135 | 136 | if ( ! FASTLED_RMT_BUILTIN_DRIVER) { 137 | // -- Allocate the interrupt if we have not done so yet. This 138 | // interrupt handler must work for all different kinds of 139 | // strips, so it delegates to the refill function for each 140 | // specific instantiation of ClocklessController. 141 | if (gRMT_intr_handle == NULL) 142 | esp_intr_alloc(ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, interruptHandler, 0, &gRMT_intr_handle); 143 | } 144 | 145 | gInitialized = true; 146 | } 147 | 148 | // -- Show this string of pixels 149 | // This is the main entry point for the pixel controller 150 | void ESP32RMTController::showPixels() 151 | { 152 | if (gNumStarted == 0) { 153 | // -- First controller: make sure everything is set up 154 | ESP32RMTController::init(); 155 | xSemaphoreTake(gTX_sem, portMAX_DELAY); 156 | 157 | for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { 158 | gRefillChannel[i] = false; 159 | gDoneChannel[i] = false; 160 | } 161 | 162 | #if FASTLED_ESP32_FLASH_LOCK == 1 163 | // -- Make sure no flash operations happen right now 164 | spi_flash_op_lock(); 165 | #endif 166 | } 167 | 168 | // -- Keep track of the number of strips we've seen 169 | gNumStarted++; 170 | 171 | // -- The last call to showPixels is the one responsible for doing 172 | // all of the actual worl 173 | if (gNumStarted == gNumControllers) { 174 | gNext = 0; 175 | 176 | #if FASTLED_ESP32_SHOWTIMING == 1 177 | gTimeIndex = 0; 178 | #endif 179 | 180 | // -- First, fill all the available channels 181 | int channel = 0; 182 | while (channel < FASTLED_RMT_MAX_CHANNELS && gNext < gNumControllers) { 183 | ESP32RMTController::startNext(channel); 184 | channel++; 185 | } 186 | 187 | // -- Make sure it's been at least 50us since last show 188 | // mWait.wait(); 189 | 190 | // -- Start them all 191 | for (int i = 0; i < channel; i++) { 192 | ESP32RMTController * pController = gControllers[i]; 193 | pController->tx_start(); 194 | #if FASTLED_ESP32_SHOWTIMING == 1 195 | gLastTime = __clock_cycles(); 196 | #endif 197 | } 198 | 199 | bool all_done = false; 200 | do { 201 | xSemaphoreTake(gTX_sem, portMAX_DELAY); 202 | 203 | for (int i = 0; i < FASTLED_RMT_MAX_CHANNELS; i++) { 204 | // if (gRefillChannel[i]) { 205 | // gOnChannel[i]->fillNext(); 206 | // gRefillChannel[i] = false; 207 | // } 208 | 209 | if (gDoneChannel[i]) { 210 | doneOnChannel(rmt_channel_t(i), 0); 211 | if (gNumDone == gNumControllers) { 212 | all_done = true; 213 | } 214 | gDoneChannel[i] = false; 215 | } 216 | } 217 | 218 | xSemaphoreGive(gTX_sem); 219 | } while ( ! all_done); 220 | 221 | // mWait.mark(); 222 | 223 | // -- Reset the counters 224 | gNumStarted = 0; 225 | gNumDone = 0; 226 | gNext = 0; 227 | 228 | #if FASTLED_ESP32_FLASH_LOCK == 1 229 | // -- Release the lock on flash operations 230 | spi_flash_op_unlock(); 231 | #endif 232 | } 233 | 234 | #if FASTLED_ESP32_SHOWTIMING == 1 235 | for (int i = 0; i < gTimeIndex; i++) { 236 | if (gTiming[i] > 10000) { 237 | Serial.print(i); 238 | Serial.print(" "); 239 | Serial.println(gTiming[i]); 240 | } 241 | } 242 | #endif 243 | } 244 | 245 | // -- Start up the next controller 246 | // This method is static so that it can dispatch to the 247 | // appropriate startOnChannel method of the given controller. 248 | void ESP32RMTController::startNext(int channel) 249 | { 250 | if (gNext < gNumControllers) { 251 | ESP32RMTController * pController = gControllers[gNext]; 252 | pController->startOnChannel(channel); 253 | gNext++; 254 | } 255 | } 256 | 257 | // -- Start this controller on the given channel 258 | // This function just initiates the RMT write; it does not wait 259 | // for it to finish. 260 | void ESP32RMTController::startOnChannel(int channel) 261 | { 262 | // -- Assign this channel and configure the RMT 263 | mRMT_channel = rmt_channel_t(channel); 264 | 265 | // -- Store a reference to this controller, so we can get it 266 | // inside the interrupt handler 267 | gOnChannel[channel] = this; 268 | 269 | // -- Assign the pin to this channel 270 | rmt_set_pin(mRMT_channel, RMT_MODE_TX, mPin); 271 | 272 | if (FASTLED_RMT_BUILTIN_DRIVER) { 273 | // -- Use the built-in RMT driver to send all the data in one shot 274 | rmt_register_tx_end_callback(doneOnChannel, 0); 275 | rmt_write_items(mRMT_channel, mBuffer, mBufferSize, false); 276 | } else { 277 | // -- Use our custom driver to send the data incrementally 278 | 279 | // -- Initialize the counters that keep track of where we are in 280 | // the pixel data. 281 | mRMT_mem_ptr = & (RMTMEM.chan[mRMT_channel].data32[0].val); 282 | mCur = 0; 283 | mWhichHalf = 0; 284 | 285 | // -- Store 2 pixels worth of data (two "buffers" full) 286 | fillNext(); 287 | fillNext(); 288 | 289 | // -- Turn on the interrupts 290 | rmt_set_tx_intr_en(mRMT_channel, true); 291 | } 292 | } 293 | 294 | // -- Start RMT transmission 295 | // Setting this RMT flag is what actually kicks off the peripheral 296 | void ESP32RMTController::tx_start() 297 | { 298 | // dev->conf_ch[channel].conf1.tx_start = 1; 299 | rmt_tx_start(mRMT_channel, true); 300 | } 301 | 302 | // -- A controller is done 303 | // This function is called when a controller finishes writing 304 | // its data. It is called either by the custom interrupt 305 | // handler (below), or as a callback from the built-in 306 | // interrupt handler. It is static because we don't know which 307 | // controller is done until we look it up. 308 | void ESP32RMTController::doneOnChannel(rmt_channel_t channel, void * arg) 309 | { 310 | ESP32RMTController * pController = gOnChannel[channel]; 311 | 312 | // -- Turn off output on the pin 313 | // SZG: Do I really need to do this? 314 | // gpio_matrix_out(pController->mPin, 0x100, 0, 0); 315 | 316 | gOnChannel[channel] = NULL; 317 | gNumDone++; 318 | 319 | if (gNumDone == gNumControllers) { 320 | // -- If this is the last controller, signal that we are all done 321 | if (FASTLED_RMT_BUILTIN_DRIVER) { 322 | xSemaphoreGive(gTX_sem); 323 | } 324 | } else { 325 | // -- Otherwise, if there are still controllers waiting, then 326 | // start the next one on this channel 327 | if (gNext < gNumControllers) { 328 | startNext(channel); 329 | pController->tx_start(); 330 | } 331 | } 332 | } 333 | 334 | // -- Custom interrupt handler 335 | // This interrupt handler handles two cases: a controller is 336 | // done writing its data, or a controller needs to fill the 337 | // next half of the RMT buffer with data. 338 | void IRAM_ATTR ESP32RMTController::interruptHandler(void *arg) 339 | { 340 | // -- The basic structure of this code is borrowed from the 341 | // interrupt handler in esp-idf/components/driver/rmt.c 342 | uint32_t intr_st = RMT.int_st.val; 343 | uint8_t channel; 344 | 345 | #if FASTLED_ESP32_SHOWTIMING == 1 346 | uint32_t curt = __clock_cycles(); 347 | gTiming[gTimeIndex++] = curt - gLastTime; 348 | gLastTime = curt; 349 | #endif 350 | 351 | bool stuff_to_do = false; 352 | for (channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; channel++) { 353 | int tx_done_bit = channel * 3; 354 | int tx_next_bit = channel + 24; 355 | 356 | ESP32RMTController * pController = gOnChannel[channel]; 357 | if (pController != NULL) { 358 | 359 | // -- More to send on this channel 360 | if (intr_st & BIT(tx_next_bit)) { 361 | RMT.int_clr.val |= BIT(tx_next_bit); 362 | gRefillChannel[channel] = true; 363 | stuff_to_do = true; 364 | } else { 365 | // -- Transmission is complete on this channel 366 | if (intr_st & BIT(tx_done_bit)) { 367 | RMT.int_clr.val |= BIT(tx_done_bit); 368 | gDoneChannel[channel] = true; 369 | stuff_to_do = true; 370 | } 371 | } 372 | } 373 | } 374 | 375 | if (stuff_to_do) { 376 | for (channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; channel++) { 377 | if (gRefillChannel[channel]) { 378 | gOnChannel[channel]->fillNext(); 379 | gRefillChannel[channel] = false; 380 | } 381 | } 382 | 383 | portBASE_TYPE HPTaskAwoken = 0; 384 | xSemaphoreGiveFromISR(gTX_sem, &HPTaskAwoken); 385 | if (HPTaskAwoken == pdTRUE) portYIELD_FROM_ISR(); 386 | } 387 | } 388 | 389 | // -- Fill RMT buffer 390 | // Puts 32 bits of pixel data into the next 32 slots in the RMT memory 391 | // Each data bit is represented by a 32-bit RMT item that specifies how 392 | // long to hold the signal high, followed by how long to hold it low. 393 | void IRAM_ATTR ESP32RMTController::fillNext() 394 | { 395 | if (mCur < mSize) { 396 | // -- Get the zero and one values into local variables 397 | uint32_t one_val = mOne.val; 398 | uint32_t zero_val = mZero.val; 399 | 400 | // -- Fill 32 slots in the RMT memory 401 | uint8_t a = mPixelData[mCur++]; 402 | uint8_t b = mPixelData[mCur++]; 403 | uint8_t c = mPixelData[mCur++]; 404 | uint8_t d = mPixelData[mCur++]; 405 | register uint32_t pixeldata = a << 24 | b << 16 | c << 8 | d; 406 | 407 | // -- Use locals for speed 408 | volatile register uint32_t * pItem = mRMT_mem_ptr; 409 | 410 | // Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to the 411 | // rmt_item32_t value corresponding to the buffered bit value 412 | for (register uint32_t j = 0; j < PULSES_PER_FILL; j++) { 413 | *pItem++ = (pixeldata & 0x80000000L) ? one_val : zero_val; 414 | // Replaces: RMTMEM.chan[mRMT_channel].data32[mCurPulse].val = val; 415 | 416 | pixeldata <<= 1; 417 | } 418 | 419 | // -- Flip to the other half, resetting the pointer if necessary 420 | mWhichHalf++; 421 | if (mWhichHalf == 2) { 422 | pItem = & (RMTMEM.chan[mRMT_channel].data32[0].val); 423 | mWhichHalf = 0; 424 | } 425 | 426 | // -- Store the new pointer back into the object 427 | mRMT_mem_ptr = pItem; 428 | } else { 429 | // -- No more data; signal to the RMT we are done 430 | for (uint32_t j = 0; j < PULSES_PER_FILL; j++) { 431 | * mRMT_mem_ptr++ = 0; 432 | } 433 | } 434 | } 435 | 436 | // -- Init pulse buffer 437 | // Set up the buffer that will hold all of the pulse items for this 438 | // controller. 439 | // This function is only used when the built-in RMT driver is chosen 440 | void ESP32RMTController::initPulseBuffer(int size_in_bytes) 441 | { 442 | if (mBuffer == 0) { 443 | // -- Each byte has 8 bits, each bit needs a 32-bit RMT item 444 | int size = size_in_bytes * 8 * 4; 445 | 446 | mBuffer = (rmt_item32_t *) calloc( mBufferSize, sizeof(rmt_item32_t)); 447 | } 448 | mCurPulse = 0; 449 | } 450 | 451 | // -- Convert a byte into RMT pulses 452 | // This function is only used when the built-in RMT driver is chosen 453 | void ESP32RMTController::convertByte(uint32_t byteval) 454 | { 455 | // -- Write one byte's worth of RMT pulses to the big buffer 456 | byteval <<= 24; 457 | for (register uint32_t j = 0; j < 8; j++) { 458 | mBuffer[mCurPulse] = (byteval & 0x80000000L) ? mOne : mZero; 459 | byteval <<= 1; 460 | mCurPulse++; 461 | } 462 | } 463 | 464 | #endif 465 | --------------------------------------------------------------------------------