├── .gitattributes ├── examples ├── esp-idf │ ├── demo1 │ │ ├── components │ │ │ └── esp32_digital_led_lib │ │ │ │ └── component.mk │ │ ├── Makefile │ │ ├── populate.sh │ │ ├── main │ │ │ ├── component.mk │ │ │ ├── arduinoish.hpp │ │ │ └── main.cpp │ │ └── sdkconfig.defaults │ └── demo2 │ │ ├── components │ │ └── esp32_digital_led_lib │ │ │ └── component.mk │ │ ├── Makefile │ │ ├── main │ │ ├── component.mk │ │ └── main.c │ │ └── sdkconfig.defaults └── arduino-esp32 │ ├── demo2 │ └── demo2.ino │ └── demo1 │ └── demo1.ino ├── library.properties ├── LICENSE ├── .gitignore ├── library.json ├── src ├── esp32_digital_led_funcs.h ├── fireworks_effects.h ├── esp32_digital_led_lib.h ├── fireworks_effects.cpp ├── esp32_digital_led_funcs.cpp └── esp32_digital_led_lib.cpp └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.png binary 3 | *.jpg binary 4 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/components/esp32_digital_led_lib/component.mk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/esp-idf/demo2/components/esp32_digital_led_lib/component.mk: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := demo1 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /examples/esp-idf/demo2/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # This is a project Makefile. It is assumed the directory this Makefile resides in is a 3 | # project subdirectory. 4 | # 5 | 6 | PROJECT_NAME := app-template 7 | 8 | include $(IDF_PATH)/make/project.mk 9 | 10 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP32 Digital RGB LED Drivers 2 | version=1.5.4 3 | author=Martin F. Falatic 4 | maintainer=Martin Falatic 5 | sentence=A library for driving self-timed digital RGB/RGBW LEDs (WS2812, SK6812, NeoPixel, WS2813, etc.) using the Espressif ESP32 microcontroller's RMT output peripheral. 6 | paragraph=Please see the README for more details. 7 | category=Display 8 | url=https://github.com/MartyMacGyver/ESP32-Digital-RGB-LED-Drivers 9 | architectures=esp32 10 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/populate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | cp ../../arduino-esp32/demo1/demo1.ino main/main.cpp 4 | cp ../../../src/*.cpp components/esp32_digital_led_lib 5 | mkdir components/esp32_digital_led_lib/include 6 | cp ../../../src/*.h components/esp32_digital_led_lib/include 7 | 8 | make menuconfig 9 | # make sdkconfig # no menu 10 | 11 | make clean 12 | 13 | make -j 6 ;: # often fails the first time on some "file already exists" issue 14 | 15 | # make flash 16 | # make erase_flash flash 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main Makefile. This is basically the same as a component makefile. 3 | # 4 | # This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default, 5 | # this will take the sources in the src/ directory, compile them and link them into 6 | # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, 7 | # please read the ESP-IDF documents if you need to do this. 8 | # 9 | 10 | #include $(IDF_PATH)/make/component_common.mk 11 | -------------------------------------------------------------------------------- /examples/esp-idf/demo2/main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main Makefile. This is basically the same as a component makefile. 3 | # 4 | # This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default, 5 | # this will take the sources in the src/ directory, compile them and link them into 6 | # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, 7 | # please read the ESP-IDF documents if you need to do this. 8 | # 9 | 10 | #include $(IDF_PATH)/make/component_common.mk 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Martin Falatic 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 | -------------------------------------------------------------------------------- /examples/esp-idf/demo2/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Default overrides used when running `make menuconfig` or `make sdkconfig` 3 | # for the first time (this has no effect if `sdkconfig` already exists) 4 | # 5 | # If you run `make defconfig` settings here WILL change an existing `sdkconfig` 6 | # 7 | # Example inits are commented out 8 | #----------------------------------------------------------------------------- 9 | 10 | #----------------------------------------------------------------------------- 11 | # Menu: Serial flasher config --> Default serial port 12 | # CONFIG_ESPTOOLPY_PORT="/dev/ttyUSB0" 13 | CONFIG_ESPTOOLPY_PORT="COM17" 14 | 15 | #----------------------------------------------------------------------------- 16 | # Menu: Component config --> ESP32-specific --> CPU frequency 17 | CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y 18 | CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 19 | 20 | #----------------------------------------------------------------------------- 21 | # Menu: Component config --> FreeRTOS --> Tick rate (Hz) 22 | CONFIG_FREERTOS_HZ=1000 23 | 24 | #----------------------------------------------------------------------------- 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build output 2 | build 3 | sdkconfig 4 | sdkconfig.old 5 | 6 | # Prerequisites 7 | *.d 8 | 9 | # Object files 10 | *.slo 11 | *.lo 12 | *.o 13 | *.ko 14 | *.obj 15 | *.elf 16 | 17 | # Linker output 18 | *.ilk 19 | *.map 20 | *.exp 21 | 22 | # Precompiled Headers 23 | *.gch 24 | *.pch 25 | 26 | # Libraries 27 | *.lib 28 | *.a 29 | *.la 30 | *.lai 31 | *.lo 32 | 33 | # Shared objects (inc. Windows DLLs) 34 | *.dll 35 | *.so 36 | *.so.* 37 | *.dylib 38 | 39 | # Executables 40 | *.exe 41 | *.out 42 | *.app 43 | *.i*86 44 | *.x86_64 45 | *.hex 46 | 47 | # Debug files 48 | *.dSYM/ 49 | *.su 50 | *.idb 51 | *.pdb 52 | 53 | # Kernel Module Compile Results 54 | *.mod* 55 | *.cmd 56 | modules.order 57 | Module.symvers 58 | Mkfile.old 59 | dkms.conf 60 | 61 | # OSX rules 62 | # General 63 | .DS_Store 64 | .AppleDouble 65 | .LSOverride 66 | 67 | # Files that might appear in the root of a volume 68 | .DocumentRevisions-V100 69 | .fseventsd 70 | .Spotlight-V100 71 | .TemporaryItems 72 | .Trashes 73 | .VolumeIcon.icns 74 | .com.apple.timemachine.donotpresent 75 | 76 | # Directories potentially created on remote AFP share 77 | .AppleDB 78 | .AppleDesktop 79 | Network Trash Folder 80 | Temporary Items 81 | .apdisk 82 | 83 | /.vscode 84 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ESP32 Digital RGB LED Drivers", 3 | "description": "A library for driving self-timed digital RGB/RGBW LEDs (WS2812, SK6812, NeoPixel, WS2813, etc.) using the Espressif ESP32 microcontroller's RMT output peripheral.", 4 | "keywords": "rgb,rgbw,led,rmt,esp32,espressif,arduino,WS2812,SK6812,NeoPixel,WS2813", 5 | "authors": [ 6 | { 7 | "name": "Martin F. Falatic", 8 | "url": "https://www.falatic.com/", 9 | "email": "martin@falatic.com", 10 | "maintainer": true 11 | } 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/MartyMacGyver/ESP32-Digital-RGB-LED-Drivers" 16 | }, 17 | "version": "1.5.4", 18 | "license": "MIT", 19 | "frameworks": "arduino", 20 | "platforms": "espressif32", 21 | "export": { 22 | "exclude": [ 23 | "docs", 24 | "extras" 25 | ] 26 | }, 27 | "build": { 28 | "srcFilter": [ 29 | "+<*.c>", 30 | "+<*.cpp>", 31 | "+<*.h>", 32 | "+<*.hpp>" 33 | ], 34 | "libArchive": false 35 | }, 36 | "examples": "arduino-esp32/*/*.ino" 37 | } 38 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Default overrides used when running `make menuconfig` or `make sdkconfig` 3 | # for the first time (this has no effect if `sdkconfig` already exists) 4 | # 5 | # If you run `make defconfig` settings here WILL change an existing `sdkconfig` 6 | # 7 | # Example inits are commented out 8 | #----------------------------------------------------------------------------- 9 | 10 | #----------------------------------------------------------------------------- 11 | # Menu: Serial flasher config --> Default serial port 12 | # CONFIG_ESPTOOLPY_PORT="/dev/ttyUSB0" 13 | CONFIG_ESPTOOLPY_PORT="COM16" 14 | CONFIG_ESPTOOLPY_FLASHFREQ_80M=y 15 | CONFIG_ESPTOOLPY_FLASHFREQ="80m" 16 | 17 | #----------------------------------------------------------------------------- 18 | # Menu: Component config --> ESP32-specific --> CPU frequency 19 | CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y 20 | CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240 21 | 22 | #----------------------------------------------------------------------------- 23 | # Menu: Component config --> FreeRTOS --> Tick rate (Hz) 24 | CONFIG_FREERTOS_HZ=1000 25 | 26 | #----------------------------------------------------------------------------- 27 | -------------------------------------------------------------------------------- /src/esp32_digital_led_funcs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Supplemental function for the ESP32 Digital LED Library 3 | * 4 | * Copyright (c) 2019 Martin F. Falatic 5 | * 6 | * Rainbow animation is based on public domain code created 19 Nov 2016 7 | * by Chris Osborn http://insentricity.com 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | #ifndef ESP32_DIGITAL_LED_FUNCTIONS_H 32 | #define ESP32_DIGITAL_LED_FUNCTIONS_H 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | void simpleStepper(strand_t * strands [], int numStrands, unsigned long delay_ms, unsigned long timeout_ms); 39 | void randomStrands(strand_t * strands[], int numStrands, unsigned long delay_ms, unsigned long timeout_ms); 40 | void scanners(strand_t * strands[], int numStrands, unsigned long delay_ms, unsigned long timeout_ms); 41 | void scanner(strand_t * pStrand, unsigned long delay_ms, unsigned long timeout_ms); 42 | void rainbows(strand_t * strands[], int numStrands, unsigned long delay_ms, unsigned long timeout_ms); 43 | void rainbow(strand_t * pStrand, unsigned long delay_ms, unsigned long timeout_ms); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | #endif /* ESP32_DIGITAL_LED_FUNCTIONS_H */ 50 | 51 | 52 | //**************************************************************************// 53 | -------------------------------------------------------------------------------- /examples/arduino-esp32/demo2/demo2.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Demo code for driving a single digital RGB(W) strand using esp32_digital_led_lib 3 | * 4 | * Modifications Copyright (c) 2017-2019 Martin F. Falatic 5 | * 6 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 7 | * http://insentricity.com 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | #include "esp32_digital_led_lib.h" 32 | #include "esp32_digital_led_funcs.h" 33 | #include "fireworks_effects.h" 34 | 35 | 36 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 37 | 38 | #pragma GCC diagnostic push 39 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" // It's noisy here with `-Wall` 40 | 41 | //strand_t strand = {.rmtChannel = 0, .gpioNum = 26, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 64}; 42 | strand_t strand = {.rmtChannel = 0, .gpioNum = 27, .ledType = LED_SK6812W_V1, .brightLimit = 64, .numPixels = 144}; 43 | 44 | strand_t * STRANDS [] = { &strand }; 45 | 46 | int STRANDCNT = COUNT_OF(STRANDS); 47 | 48 | #pragma GCC diagnostic pop 49 | 50 | 51 | FireworksEffects * fweffects; 52 | 53 | //**************************************************************************// 54 | void setup() 55 | { 56 | Serial.begin(115200); 57 | Serial.println("Initializing..."); 58 | 59 | digitalLeds_initDriver(); 60 | 61 | // Init unused outputs low to reduce noise 62 | gpioSetup(14, OUTPUT, LOW); 63 | gpioSetup(15, OUTPUT, LOW); 64 | gpioSetup(26, OUTPUT, LOW); 65 | gpioSetup(27, OUTPUT, LOW); 66 | 67 | gpioSetup(strand.gpioNum, OUTPUT, LOW); 68 | int rc = digitalLeds_addStrands(STRANDS, STRANDCNT); 69 | if (rc) { 70 | Serial.print("Init rc = "); 71 | Serial.println(rc); 72 | } 73 | 74 | if (digitalLeds_initDriver()) { 75 | Serial.println("Init FAILURE: halting"); 76 | while (true) {}; 77 | } 78 | digitalLeds_resetPixels(STRANDS, STRANDCNT); 79 | 80 | fweffects = new FireworksEffects(&strand); 81 | } 82 | 83 | 84 | //**************************************************************************// 85 | void loop() 86 | { 87 | // simpleStepper(STRANDS, STRANDCNT, 0, 0); 88 | // randomStrands(STRANDS, STRANDCNT, 200, 10000); 89 | // rainbows(STRANDS, STRANDCNT, 1, 0); 90 | Serial.println("Rendering"); 91 | fweffects->Render(); 92 | } 93 | -------------------------------------------------------------------------------- /src/fireworks_effects.h: -------------------------------------------------------------------------------- 1 | /* 2 | * FireworksEffect Fireworks Effect for Fourth of July 3 | * 4 | * Modifications Copyright (c) 2019 Martin F. Falatic 5 | * 6 | * Based on code licensed "Free for all use" 22 Jun 2019 by Davepl www.reddit.com/u/davepl 7 | * https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/ 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | #ifndef FIREWORKS_EFFECTS_H 32 | #define FIREWORKS_EFFECTS_H 33 | 34 | #include 35 | 36 | class FireworksEffects { 37 | 38 | public: 39 | FireworksEffects(strand_t * pStrand); 40 | 41 | //protected: 42 | class Particle { 43 | public: 44 | pixelColor_t _starColor; 45 | unsigned long _birthTime; 46 | unsigned long _lastUpdate; 47 | double _velocity; 48 | double _position; 49 | 50 | Particle(pixelColor_t * starColor, double pos, double maxSpeed); 51 | double Age(); 52 | void Update(); 53 | }; 54 | 55 | class GraphicsStub { 56 | public: 57 | strand_t * StrandPtr; 58 | int DotCount; 59 | 60 | GraphicsStub(strand_t * pStrand); 61 | void FillSolid(uint32_t cval); 62 | void SetPixels(double pos, int width, pixelColor_t c); 63 | void RefreshPixels(); 64 | }; 65 | 66 | uint32_t colorChoices [24] = { // RGB with 0x00 W values 67 | 0xFFFF00, 0xFF7F00, 0xFF0000, 68 | 0x7FFF00, 0x7F7F00, 0x7F0000, 69 | 0x00FF00, 0x007F00, //0x000000, 70 | 0x00FFFF, 0x00FF7F, 0x00FF00, 71 | 0x007FFF, 0x007F7F, 0x007F00, 72 | 0x0000FF, 0x00007F, //0x000000, 73 | 0xFF00FF, 0x7F00FF, 0x0000FF, 74 | 0xFF007F, 0x7F007F, 0x00007F, 75 | 0xFF0000, 0x7F0000, //0x000000, 76 | }; 77 | 78 | GraphicsStub * graphics; 79 | std::deque _Particles; // FIFO particles 80 | double MaxSpeed = 375.0; // Max velocity 81 | double NewParticleProbability = 0.1; // Odds of new particle 82 | double ParticlePreignitonTime = 0.0; // How long to "wink" 83 | double ParticleIgnition = 0.2; // How long to "flash" 84 | double ParticleHoldTime = 0.0; // Main lifecycle time 85 | double ParticleFadeTime = 2.0; // Fade out time 86 | double ParticleSize = 0.0; // Size of the particle 87 | void Render(); 88 | }; 89 | 90 | #endif /* FIREWORKS_EFFECTS_H */ 91 | 92 | 93 | //**************************************************************************// 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Digital RGB(W) LED Drivers 2 | 3 | A library for driving self-timed digital RGB/RGBW LEDs (WS2812, SK6812, NeoPixel, WS2813, etc.) using the Espressif ESP32 microcontroller's RMT output peripheral 4 | 5 | Based upon the [ESP32 WS2812 driver work by Chris Osborn](https://github.com/FozzTexx/ws2812-demo) 6 | 7 |
8 | 9 | ### Notes 10 | 11 | The RMT peripheral of the ESP32 is used for controlling up to 8 LED "strands" (in whatever form factor the serially-chained LEDs are placed). These strands are independently controlled and buffered. 12 | 13 | There are working demos for Espressif's IoT Development Framework (esp-idf) and Arduino-ESP32 core. Some demos are ONLY for the ESP IDF (demonstrating C-only techniques). Otherwise, a given demo should be exactly the same on either framework. 14 | 15 | This library currently works well with WS2812-type and NeoPixel RGB LEDs (3 bytes of data per LED) - SK6812 RGB LEDs should work equally well. This also works fine with WS2813 (the backup channel is not used currently - tie `Backup In` to `Data In` for now). 16 | 17 | This also works well with SK6812 RGBW LEDs (4 bytes of data per LED). These are similar to the WS2812 LEDs, but with a white LED present as well - keep in mind that RGBW LEDs draw a fair amount of extra power versus the usual RGB LEDs due to the added white LED element present. 18 | 19 | Timings for a given LED type can vary even within that line: use the version variant that works best for you (see `ledParamsAll` for details). 20 | 21 |
22 | 23 | ### ESP-IDF build notes - Important! 24 | 25 | ## The ESP-IDF demos are WIP right now (not currently buildable) and may be that way indefinitely 26 | 27 | The ESP-IDF varies so regularly and so significantly it's more difficult to keep up with it, versus the abstracted Arduino-ESP32 interfaces. As time permits, I'm working on making it buildable with v3.2.2 and later sub-versions but it's slow going. 28 | 29 | While the Arduino-ESP32 side is my main focus, the ESP-IDF build ought to be very similar (demo*.ino --> main.cpp, and the header and source libs aren't in their canonical locations by default). Beyond convenience, there was no real value in maintaining duplicate copies of the code and libraries as I did previously so those duplicates were removed. Also, symlinks in the repo are specifically disallowed in Arduino so that's not happening. 30 | 31 | Please see the `sdkconfig.defaults` file for details of the defaults I use. If you run `make menuconfig` or `make sdkconfig` this file will be parsed for initial settings ONLY if `sdkconfig` doesn't exist. However, this file will be processed every time you run `make defconfig`. 32 | 33 |
34 | 35 | ### TODO 36 | 37 | - API (current): 38 | - init --> initStrands (all strands) 39 | - setColors --> update --> updatePixels (per strand) 40 | - (n/a) --> reset --> resetPixels (per strand) 41 | 42 | - Future additional (class-driven): 43 | - (n/a) --> updateTimings (for fine-tuning, debugging, or marginal/odd devices) 44 | - (n/a) --> updateType (for fine-tuning, debugging, or marginal/odd devices) 45 | - (n/a) --> addStrand/deleteStrand? (maybe not...) 46 | 47 | - Other: 48 | - Make the whole library a proper class - more robust and extensible! 49 | - ledParams - refine and clarify 50 | - ledParams - lower bounds? Currently bit timing constants are pre-padded +50ns 51 | - ledParams - add a small constant string as a mapping to the name? 52 | - Nope! C99 designator 'name' outside aggregate initializer 53 | - ledParams - instead of bytesPerPixel, specify color format (GRBW, RGB, etc.)? 54 | - Would have the same problem as naming... 55 | - WS2813 - handle backup channel 56 | - Resolve open TODOs in code 57 | - Add more interleaved demos, and more demos in general 58 | - Make Arduino side a true Arduino library? May not be practical. 59 | - APA102/DotStar support? 60 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/main/arduinoish.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Arduino-style shim code for ESP32 native builds 3 | * 4 | * Allows Arduino-ESP32 code to build via ESP-IDF with minimal changes 5 | * 6 | * Copyright (c) 2017 Martin F. Falatic 7 | * 8 | */ 9 | /* 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | 29 | #ifndef ARDUINOISH_HPP 30 | #define ARDUINOISH_HPP 31 | 32 | #if defined(ARDUINO) && ARDUINO >= 100 33 | // No extras 34 | #elif defined(ARDUINO) // pre-1.0 35 | // No extras 36 | #elif defined(ESP_PLATFORM) 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #define HIGH 1 47 | #define LOW 0 48 | //#define OUTPUT GPIO_MODE_OUTPUT 49 | //#define INPUT GPIO_MODE_INPUT 50 | #define INPUT 0x01 51 | #define OUTPUT 0x02 52 | 53 | #define DEC 10 54 | #define HEX 16 55 | #define OCT 8 56 | #define BIN 2 57 | 58 | #define min(a, b) ((a) < (b) ? (a) : (b)) 59 | #define max(a, b) ((a) > (b) ? (a) : (b)) 60 | #define floor(a) ((int)(a)) 61 | #define ceil(a) ((int)((int)(a) < (a) ? (a+1) : (a))) 62 | 63 | uint32_t IRAM_ATTR millis() 64 | { 65 | return xTaskGetTickCount() * portTICK_PERIOD_MS; 66 | } 67 | 68 | void delay(uint32_t ms) 69 | { 70 | if (ms > 0) { 71 | vTaskDelay(ms / portTICK_PERIOD_MS); 72 | } 73 | } 74 | 75 | class SerialStub { 76 | public: 77 | inline char * _intToBin(int arg, char *retBuf, int bufSize) 78 | { 79 | int NumBits = bufSize - 1; 80 | int mask = 1; 81 | int lastOne = NumBits - 1; // Removes zero-padding, but always get at least '0' 82 | for (int i = NumBits; i > 0; i--) 83 | { 84 | retBuf[i-1] = '0'; 85 | if ((arg & mask) == mask) 86 | { 87 | retBuf[i-1] = '1'; 88 | lastOne = i - 1; 89 | } 90 | mask <<= 1; 91 | } 92 | retBuf[NumBits] = 0; 93 | return &retBuf[lastOne]; 94 | } 95 | 96 | inline void begin(uint32_t baud_rate) 97 | { 98 | delay(500); 99 | uart_set_baudrate(UART_NUM_0, baud_rate); 100 | } 101 | 102 | inline void print() 103 | { 104 | ets_printf(""); 105 | } 106 | inline void println() 107 | { 108 | ets_printf("\n"); 109 | } 110 | 111 | inline void print(const char * arg) 112 | { 113 | ets_printf("%s", arg); 114 | } 115 | inline void println(const char * arg) 116 | { 117 | print(arg); 118 | ets_printf("\n"); 119 | } 120 | 121 | inline void print(const int arg, int argType = DEC) 122 | { 123 | switch (argType) { 124 | case DEC: 125 | ets_printf("%ld", arg); 126 | break; 127 | case HEX: 128 | ets_printf("%X", arg); 129 | break; 130 | case OCT: 131 | ets_printf("%o", arg); 132 | break; 133 | case BIN: 134 | { 135 | const int NumBits = 32; 136 | char buf[sizeof(char) * NumBits + 1]; 137 | ets_printf("%s", _intToBin(arg, buf, NumBits+1)); 138 | } 139 | break; 140 | default: 141 | ets_printf("%d", arg); 142 | } 143 | } 144 | inline void println(const int arg, int argType = DEC) 145 | { 146 | print(arg, argType); 147 | ets_printf("\n"); 148 | } 149 | 150 | } Serial; 151 | 152 | void setup(void); 153 | 154 | void loop(void); 155 | 156 | void task_main(void *pvParameters) 157 | { 158 | setup(); 159 | for (;;) { 160 | loop(); 161 | } 162 | return; 163 | } 164 | 165 | extern "C" int app_main(void) 166 | { 167 | nvs_flash_init(); 168 | xTaskCreate(task_main, "task_main", 4096, NULL, 10, NULL); 169 | return 0; 170 | } 171 | #endif 172 | 173 | #endif /* ARDUINOISH_HPP */ 174 | -------------------------------------------------------------------------------- /src/esp32_digital_led_lib.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Library for driving digital RGB(W) LEDs using the ESP32's RMT peripheral 3 | * 4 | * Modifications Copyright (c) 2017-2019 Martin F. Falatic 5 | * 6 | * Portions modified using FastLED's ClocklessController as a reference 7 | * Copyright (c) 2018 Samuel Z. Guyer 8 | * Copyright (c) 2017 Thomas Basler 9 | * 10 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 11 | * http://insentricity.com 12 | * 13 | */ 14 | 15 | /* 16 | * Permission is hereby granted, free of charge, to any person obtaining a copy 17 | * of this software and associated documentation files (the "Software"), to deal 18 | * in the Software without restriction, including without limitation the rights 19 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | * copies of the Software, and to permit persons to whom the Software is 21 | * furnished to do so, subject to the following conditions: 22 | * 23 | * The above copyright notice and this permission notice shall be included in 24 | * all copies or substantial portions of the Software. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | */ 34 | 35 | #ifndef ESP32_DIGITAL_LED_LIB_H 36 | #define ESP32_DIGITAL_LED_LIB_H 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | #include 43 | 44 | #define DEBUG_ESP32_DIGITAL_LED_LIB 0 45 | 46 | typedef union { 47 | struct __attribute__ ((packed)) { 48 | uint8_t b, g, r, w; // Little-endian ordered 49 | }; 50 | uint32_t raw32; 51 | } pixelColor_t; 52 | 53 | inline pixelColor_t pixelFromRGB(uint8_t r, uint8_t g, uint8_t b) 54 | { 55 | pixelColor_t v; 56 | v.r = r; 57 | v.g = g; 58 | v.b = b; 59 | v.w = 0; 60 | return v; 61 | } 62 | 63 | inline pixelColor_t pixelFromRGBhex(uint8_t r, uint8_t g, uint8_t b) 64 | { 65 | pixelColor_t v; 66 | v.r = r; 67 | v.g = g; 68 | v.b = b; 69 | v.w = 0; 70 | return v; 71 | } 72 | 73 | inline pixelColor_t pixelFromRGBW(uint8_t r, uint8_t g, uint8_t b, uint8_t w) 74 | { 75 | pixelColor_t v; 76 | v.r = r; 77 | v.g = g; 78 | v.b = b; 79 | v.w = w; 80 | return v; 81 | } 82 | 83 | inline pixelColor_t pixelFromRGBWhex(uint8_t r, uint8_t g, uint8_t b, uint8_t w) 84 | { 85 | // The value is of the form 0xWWRRGGBB 86 | pixelColor_t v; 87 | v.r = r; 88 | v.g = g; 89 | v.b = b; 90 | v.w = w; 91 | return v; 92 | } 93 | 94 | typedef struct { 95 | int rmtChannel; 96 | int gpioNum; 97 | int ledType; 98 | int brightLimit; 99 | int numPixels; 100 | pixelColor_t * pixels; 101 | void * _stateVars; 102 | } strand_t; 103 | 104 | typedef struct { 105 | int bytesPerPixel; 106 | uint32_t T0H; 107 | uint32_t T1H; 108 | uint32_t T0L; 109 | uint32_t T1L; 110 | uint32_t TRS; 111 | } ledParams_t; 112 | 113 | enum led_types { 114 | LED_WS2812_V1, 115 | LED_WS2812B_V1, 116 | LED_WS2812B_V2, 117 | LED_WS2812B_V3, 118 | LED_WS2813_V1, 119 | LED_WS2813_V2, 120 | LED_WS2813_V3, 121 | LED_SK6812_V1, 122 | LED_SK6812W_V1, 123 | }; 124 | 125 | const ledParams_t ledParamsAll[] = { // Still must match order of `led_types` 126 | [LED_WS2812_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 700, .T0L = 800, .T1L = 600, .TRS = 50000}, // Various 127 | [LED_WS2812B_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 900, .T0L = 900, .T1L = 350, .TRS = 50000}, // Older datasheet 128 | [LED_WS2812B_V2] = { .bytesPerPixel = 3, .T0H = 400, .T1H = 850, .T0L = 850, .T1L = 400, .TRS = 50000}, // 2016 datasheet 129 | [LED_WS2812B_V3] = { .bytesPerPixel = 3, .T0H = 450, .T1H = 850, .T0L = 850, .T1L = 450, .TRS = 50000}, // cplcpu test 130 | [LED_WS2813_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 800, .T0L = 350, .T1L = 350, .TRS = 300000}, // Older datasheet 131 | [LED_WS2813_V2] = { .bytesPerPixel = 3, .T0H = 270, .T1H = 800, .T0L = 800, .T1L = 270, .TRS = 300000}, // 2016 datasheet 132 | [LED_WS2813_V3] = { .bytesPerPixel = 3, .T0H = 270, .T1H = 630, .T0L = 630, .T1L = 270, .TRS = 300000}, // 2017-05 WS datasheet 133 | [LED_SK6812_V1] = { .bytesPerPixel = 3, .T0H = 300, .T1H = 600, .T0L = 900, .T1L = 600, .TRS = 80000}, // Various, all consistent 134 | [LED_SK6812W_V1] = { .bytesPerPixel = 4, .T0H = 300, .T1H = 600, .T0L = 900, .T1L = 600, .TRS = 80000}, // Various, all consistent 135 | }; 136 | 137 | extern void espPinMode(int pinNum, int pinDir); 138 | extern void gpioSetup(int gpioNum, int gpioMode, int gpioVal); 139 | extern double randDouble(); 140 | extern pixelColor_t adjustByUniformFactor(pixelColor_t * color, double adjFactor); 141 | 142 | extern int digitalLeds_initDriver(); 143 | extern int digitalLeds_addStrands(strand_t * strands [], int numStrands); 144 | extern int digitalLeds_removeStrands(strand_t * strands [], int numStrands); 145 | extern int digitalLeds_drawPixels(strand_t * strands [], int numStrands); 146 | extern int digitalLeds_resetPixels(strand_t * strands [], int numStrands); 147 | 148 | #ifdef __cplusplus 149 | } 150 | #endif 151 | 152 | #endif /* ESP32_DIGITAL_LED_LIB_H */ 153 | 154 | 155 | //**************************************************************************// 156 | -------------------------------------------------------------------------------- /examples/esp-idf/demo2/main/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Demo code for driving digital RGB(W) LEDs using the ESP32's RMT peripheral 3 | * 4 | * Modifications Copyright (c) 2017 Martin F. Falatic 5 | * 6 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 7 | * http://insentricity.com 8 | * 9 | */ 10 | /* 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | * THE SOFTWARE. 28 | */ 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "esp32_digital_led_lib.h" 36 | 37 | #ifndef __cplusplus 38 | #define nullptr NULL 39 | #endif 40 | 41 | #define HIGH 1 42 | #define LOW 0 43 | #define OUTPUT GPIO_MODE_OUTPUT 44 | #define INPUT GPIO_MODE_INPUT 45 | 46 | #define DEC 10 47 | #define HEX 16 48 | #define OCT 8 49 | #define BIN 2 50 | 51 | #define min(a, b) ((a) < (b) ? (a) : (b)) 52 | #define max(a, b) ((a) > (b) ? (a) : (b)) 53 | #define floor(a) ((int)(a)) 54 | #define ceil(a) ((int)((int)(a) < (a) ? (a+1) : (a))) 55 | 56 | strand_t STRANDS[] = { // Avoid using any of the strapping pins on the ESP32 57 | {.rmtChannel = 1, .gpioNum = 17, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 93, 58 | .pixels = nullptr, ._stateVars = nullptr}, 59 | {.rmtChannel = 2, .gpioNum = 18, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 93, 60 | .pixels = nullptr, ._stateVars = nullptr}, 61 | {.rmtChannel = 3, .gpioNum = 19, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 64, 62 | .pixels = nullptr, ._stateVars = nullptr}, 63 | //{.rmtChannel = 0, .gpioNum = 16, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 256, 64 | // .pixels = nullptr, ._stateVars = nullptr}, 65 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_SK6812W_V1, .brightLimit = 32, .numPixels = 300, 66 | // .pixels = nullptr, ._stateVars = nullptr}, 67 | {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_WS2813_V2, .brightLimit = 32, .numPixels = 300, 68 | .pixels = nullptr, ._stateVars = nullptr}, 69 | }; 70 | 71 | int STRANDCNT = sizeof(STRANDS)/sizeof(STRANDS[0]); 72 | 73 | void gpioSetup(int gpioNum, int gpioMode, int gpioVal) { 74 | #if defined(ARDUINO) && ARDUINO >= 100 75 | pinMode (gpioNum, gpioMode); 76 | digitalWrite (gpioNum, gpioVal); 77 | #elif defined(ESP_PLATFORM) 78 | gpio_num_t gpioNumNative = (gpio_num_t)(gpioNum); 79 | gpio_mode_t gpioModeNative = (gpio_mode_t)(gpioMode); 80 | gpio_pad_select_gpio(gpioNumNative); 81 | gpio_set_direction(gpioNumNative, gpioModeNative); 82 | gpio_set_level(gpioNumNative, gpioVal); 83 | #endif 84 | } 85 | 86 | uint32_t IRAM_ATTR millis() 87 | { 88 | return xTaskGetTickCount() * portTICK_PERIOD_MS; 89 | } 90 | 91 | void delay(uint32_t ms) 92 | { 93 | if (ms > 0) { 94 | vTaskDelay(ms / portTICK_PERIOD_MS); 95 | } 96 | } 97 | 98 | void rainbow(strand_t * pStrand, unsigned long delay_ms, unsigned long timeout_ms) 99 | { 100 | const uint8_t color_div = 4; 101 | const uint8_t anim_step = 1; 102 | const uint8_t anim_max = pStrand->brightLimit - anim_step; 103 | pixelColor_t color1 = pixelFromRGB(anim_max, 0, 0); 104 | pixelColor_t color2 = pixelFromRGB(anim_max, 0, 0); 105 | uint8_t stepVal1 = 0; 106 | uint8_t stepVal2 = 0; 107 | bool runForever = (timeout_ms == 0 ? true : false); 108 | unsigned long start_ms = millis(); 109 | while (runForever || (millis() - start_ms < timeout_ms)) { 110 | color1 = color2; 111 | stepVal1 = stepVal2; 112 | for (uint16_t i = 0; i < pStrand->numPixels; i++) { 113 | pStrand->pixels[i] = pixelFromRGB(color1.r/color_div, color1.g/color_div, color1.b/color_div); 114 | if (i == 1) { 115 | color2 = color1; 116 | stepVal2 = stepVal1; 117 | } 118 | switch (stepVal1) { 119 | case 0: 120 | color1.g += anim_step; 121 | if (color1.g >= anim_max) 122 | stepVal1++; 123 | break; 124 | case 1: 125 | color1.r -= anim_step; 126 | if (color1.r == 0) 127 | stepVal1++; 128 | break; 129 | case 2: 130 | color1.b += anim_step; 131 | if (color1.b >= anim_max) 132 | stepVal1++; 133 | break; 134 | case 3: 135 | color1.g -= anim_step; 136 | if (color1.g == 0) 137 | stepVal1++; 138 | break; 139 | case 4: 140 | color1.r += anim_step; 141 | if (color1.r >= anim_max) 142 | stepVal1++; 143 | break; 144 | case 5: 145 | color1.b -= anim_step; 146 | if (color1.b == 0) 147 | stepVal1 = 0; 148 | break; 149 | } 150 | } 151 | digitalLeds_updatePixels(pStrand); 152 | delay(delay_ms); 153 | } 154 | digitalLeds_resetPixels(pStrand); 155 | } 156 | 157 | void app_main() { 158 | gpioSetup(16, OUTPUT, LOW); 159 | gpioSetup(17, OUTPUT, LOW); 160 | gpioSetup(18, OUTPUT, LOW); 161 | gpioSetup(19, OUTPUT, LOW); 162 | if (digitalLeds_initStrands(STRANDS, STRANDCNT)) { 163 | ets_printf("Init FAILURE: halting\n"); 164 | while (true) {}; 165 | } 166 | while (true) { 167 | for (int i = 0; i < STRANDCNT; i++) { 168 | strand_t * pStrand = &STRANDS[i]; 169 | rainbow(pStrand, 0, 2000); 170 | digitalLeds_resetPixels(pStrand); 171 | } 172 | } 173 | vTaskDelete(NULL); 174 | } 175 | -------------------------------------------------------------------------------- /src/fireworks_effects.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * FireworksEffects - Fireworks effects for Fourth of July 3 | * 4 | * Modifications Copyright (c) 2019 Martin F. Falatic 5 | * 6 | * Based on code licensed "Free for all use" 22 Jun 2019 by Davepl www.reddit.com/u/davepl 7 | * https://www.reddit.com/r/arduino/comments/c3sd46/i_made_this_fireworks_effect_for_my_led_strips/ 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | 32 | #if defined(ARDUINO) && ARDUINO >= 100 33 | #include 34 | #elif defined(ARDUINO) // pre-1.0 35 | // No extras 36 | #elif defined(ESP_PLATFORM) 37 | #include "../../main/arduinoish.hpp" 38 | #endif 39 | 40 | #include "esp32_digital_led_lib.h" 41 | #include "fireworks_effects.h" 42 | #include 43 | 44 | 45 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 46 | 47 | 48 | FireworksEffects::GraphicsStub::GraphicsStub(strand_t * pStrand) { 49 | StrandPtr = pStrand; 50 | DotCount = StrandPtr->numPixels; 51 | } 52 | 53 | 54 | void FireworksEffects::GraphicsStub::FillSolid(uint32_t cval) { 55 | strand_t * strands [] = { StrandPtr }; 56 | digitalLeds_resetPixels(strands, 1); 57 | } 58 | 59 | 60 | void FireworksEffects::GraphicsStub::SetPixels(double pos, int width, pixelColor_t c) { 61 | for (int i = -width/2; i <= width/2; i++) { 62 | if (pos+i >= 0 && pos+i < DotCount) { 63 | StrandPtr->pixels[int(pos+i)] = c; 64 | } 65 | } 66 | } 67 | 68 | 69 | void FireworksEffects::GraphicsStub::RefreshPixels() { 70 | strand_t * strands [] = { StrandPtr }; 71 | digitalLeds_drawPixels(strands, 1); 72 | } 73 | 74 | 75 | // Each particle in the particle system remembers its color, birth time, postion, velocity, etc. 76 | FireworksEffects::Particle::Particle(pixelColor_t * starColor, double pos, double maxSpeed) 77 | { 78 | _position = pos; 79 | _velocity = randDouble() * maxSpeed * 2 - maxSpeed; 80 | _starColor.raw32 = starColor->raw32; 81 | _birthTime = millis(); 82 | _lastUpdate = _birthTime; 83 | } 84 | 85 | 86 | double FireworksEffects::Particle::Age() 87 | { 88 | return (millis() - _birthTime) / 1000.0; 89 | } 90 | 91 | 92 | void FireworksEffects::Particle::Update() 93 | { 94 | // As the particle ages we actively fade its color and slow its speed 95 | double deltaTime = (millis() - _lastUpdate) / 1000.0; 96 | _position += _velocity * deltaTime; 97 | _lastUpdate = millis() * 1.0; 98 | _velocity -= (2 * _velocity * deltaTime); 99 | _starColor = adjustByUniformFactor(&_starColor, randDouble() * 0.1); 100 | } 101 | 102 | 103 | FireworksEffects::FireworksEffects(strand_t * pStrand) 104 | { 105 | graphics = new GraphicsStub(pStrand); 106 | } 107 | 108 | 109 | void FireworksEffects::Render() 110 | { 111 | // Randomly create some new stars this frame; the number we create is tied 112 | // to the size of the display so that the display size can change and 113 | // the "effect density" will stay the same 114 | 115 | const int min_width = 10; 116 | const int max_width = 50; 117 | const int max_speed_multiplier = 3.0; 118 | 119 | for (int iPass = 0; iPass < graphics->DotCount / max_width; iPass++) 120 | { 121 | if (randDouble() < NewParticleProbability && _Particles.size() == 0) 122 | { 123 | // Pick a random color and location. 124 | // If you don't have FastLED palettes, all you need to do 125 | // here is generate a random color. 126 | 127 | int iStartPos = (int)(randDouble() * graphics->DotCount); 128 | pixelColor_t color; 129 | color.raw32 = colorChoices[int(randDouble() * COUNT_OF(colorChoices))]; 130 | int c = int(randDouble() * (max_width - min_width) + min_width); 131 | double speed_multiplier = randDouble() * MaxSpeed * max_speed_multiplier; 132 | 133 | for (int i = 1; i < c; i++) 134 | { 135 | Particle particle(&color, iStartPos, randDouble() * speed_multiplier); 136 | _Particles.push_back(particle); 137 | } 138 | } 139 | } 140 | 141 | // In the degenerate case of particles not aging out for some reason, 142 | // we need to set a pseudo-realistic upper bound, and the very number of 143 | // possible pixels seems like a reasonable one 144 | 145 | while (_Particles.size() > graphics->DotCount) { 146 | _Particles.pop_front(); 147 | } 148 | graphics->FillSolid(0x00000000); // This is hacky 149 | 150 | for (int i = 0; i < _Particles.size(); i++) 151 | { 152 | Particle * star = &(_Particles[i]); 153 | star->Update(); 154 | 155 | pixelColor_t c; 156 | c.raw32 = (star->_starColor).raw32; 157 | double fade = 0.0; 158 | 159 | // If the star is brand new, it flashes white briefly. 160 | // Otherwise it just fades over time. 161 | if (star->Age() > ParticlePreignitonTime && star->Age() < (ParticleIgnition + ParticlePreignitonTime)) 162 | { 163 | c = pixelFromRGBW(0x3F, 0x3F, 0x3F, 0x3F); 164 | } 165 | else 166 | { 167 | // Figure out how much to fade and shrink the star based on 168 | // its age relative to its lifetime 169 | double age = star->Age(); 170 | if (age < ParticlePreignitonTime) { 171 | fade = 1.0 - (age / ParticlePreignitonTime); 172 | } 173 | else 174 | { 175 | age -= ParticlePreignitonTime; 176 | 177 | if (age < ParticleHoldTime + ParticleIgnition) 178 | fade = 0.0; // Just born 179 | else if (age > ParticleHoldTime + ParticleIgnition + ParticleFadeTime) 180 | fade = 1.0; // Black hole, all faded out 181 | else 182 | { 183 | age -= (ParticleHoldTime + ParticleIgnition); 184 | fade = (age / ParticleFadeTime); // Fading star 185 | } 186 | } 187 | if (c.raw32 > 0) { 188 | c = adjustByUniformFactor(&c, fade); 189 | } 190 | } 191 | ParticleSize = (1 - fade) * 5.0; 192 | 193 | // Because (the original) supports antialiasing and partial pixels, this takes a 194 | // non-integer number of pixels to draw. But if you just made it 195 | // plot 'ParticleSize' pixels in int form, you'd be 99% of the way there 196 | 197 | graphics->SetPixels(star->_position, (int)ParticleSize, c); 198 | } 199 | 200 | // Remove any particles who have completed their lifespan 201 | while (!_Particles.empty() && _Particles.front().Age() > ParticleHoldTime + ParticleIgnition + ParticleFadeTime) 202 | { 203 | _Particles.pop_front(); 204 | } 205 | 206 | graphics->RefreshPixels(); 207 | } 208 | 209 | 210 | //**************************************************************************// 211 | -------------------------------------------------------------------------------- /src/esp32_digital_led_funcs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Supplemental function for the ESP32 Digital LED Library 3 | * 4 | * Copyright (c) 2019 Martin F. Falatic 5 | * 6 | * Rainbow animation is based on public domain code created 19 Nov 2016 7 | * by Chris Osborn http://insentricity.com 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | 32 | #if defined(ARDUINO) && ARDUINO >= 100 33 | #include 34 | #elif defined(ARDUINO) // pre-1.0 35 | // No extras 36 | #elif defined(ESP_PLATFORM) 37 | #include "../../main/arduinoish.hpp" 38 | #endif 39 | 40 | #include "esp32_digital_led_lib.h" 41 | #include "esp32_digital_led_funcs.h" 42 | 43 | 44 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 45 | 46 | //**************************************************************************// 47 | void simpleStepper(strand_t * strands [], int numStrands, unsigned long delay_ms, unsigned long timeout_ms) 48 | { 49 | int stepper = 0; 50 | int colord = 0; 51 | int highLimit = 32; 52 | unsigned long start_ms = millis(); 53 | while (timeout_ms == 0 || (millis() - start_ms < timeout_ms)) { 54 | strand_t * strand = strands[0]; 55 | strand->pixels[stepper] = pixelFromRGBW(colord, colord, colord, 0); 56 | 57 | stepper++; 58 | if(stepper > strand->numPixels) { 59 | stepper = 0; 60 | colord += 2; 61 | } 62 | 63 | if(colord > highLimit) 64 | colord = 0; 65 | 66 | digitalLeds_drawPixels(strands, numStrands); 67 | delay(delay_ms); 68 | } 69 | digitalLeds_resetPixels(strands, numStrands); 70 | } 71 | 72 | 73 | //**************************************************************************// 74 | void randomStrands(strand_t * strands[], int numStrands, unsigned long delay_ms, unsigned long timeout_ms) 75 | { 76 | Serial.print("DEMO: random colors, delay = "); 77 | Serial.println(delay_ms); 78 | uint32_t dimmer = 0x0F0F0F0F; 79 | unsigned long start_ms = millis(); 80 | while (timeout_ms == 0 || (millis() - start_ms < timeout_ms)) { 81 | for (int n = 0; n < numStrands; n++) { 82 | strand_t * pStrand = strands[n]; 83 | for (uint16_t i = 0; i < pStrand->numPixels; i++) { 84 | pStrand->pixels[i].raw32 = (esp_random() & dimmer); 85 | } 86 | } 87 | digitalLeds_drawPixels(strands, numStrands); 88 | delay(delay_ms); 89 | } 90 | } 91 | 92 | 93 | //**************************************************************************// 94 | class Scannerer { 95 | private: 96 | strand_t * pStrand; 97 | pixelColor_t minColor; 98 | pixelColor_t maxColor; 99 | int prevIdx; 100 | int currIdx; 101 | public: 102 | Scannerer(strand_t *, pixelColor_t); 103 | void prepareNext(); 104 | }; 105 | 106 | 107 | Scannerer::Scannerer(strand_t * pStrandIn, pixelColor_t maxColorIn) 108 | { 109 | pStrand = pStrandIn; 110 | minColor = pixelFromRGBW(0, 0, 0, 0); 111 | maxColor = maxColorIn; 112 | prevIdx = 0; 113 | currIdx = 0; 114 | } 115 | 116 | 117 | void Scannerer::prepareNext() 118 | { 119 | pStrand->pixels[prevIdx] = minColor; 120 | pStrand->pixels[currIdx] = maxColor; 121 | prevIdx = currIdx; 122 | currIdx++; 123 | if (currIdx >= pStrand->numPixels) { 124 | currIdx = 0; 125 | } 126 | } 127 | 128 | 129 | void scanners(strand_t * strands[], int numStrands, unsigned long delay_ms, unsigned long timeout_ms) 130 | { 131 | //Scannerer scan(pStrand); Scannerer * pScanner = &scan; 132 | Scannerer * pScanner[numStrands]; 133 | int i; 134 | uint8_t c = strands[0]->brightLimit; // TODO: improve 135 | pixelColor_t scanColors [] = { 136 | pixelFromRGBW(c, 0, 0, 0), 137 | pixelFromRGBW(0, c, 0, 0), 138 | pixelFromRGBW(c, c, 0, 0), 139 | pixelFromRGBW(0, 0, c, 0), 140 | pixelFromRGBW(c, 0, c, 0), 141 | pixelFromRGBW(0, c, c, 0), 142 | pixelFromRGBW(c, c, c, 0), 143 | pixelFromRGBW(0, 0, 0, c), 144 | }; 145 | Serial.print("DEMO: scanners("); 146 | for (i = 0; i < numStrands; i++) { 147 | pScanner[i] = new Scannerer(strands[i], scanColors[i]); 148 | if (i > 0) { 149 | Serial.print(", "); 150 | } 151 | Serial.print("ch"); 152 | Serial.print(strands[i]->rmtChannel); 153 | Serial.print(" (0x"); 154 | Serial.print((uint32_t)pScanner[i], HEX); 155 | Serial.print(")"); 156 | Serial.print(" #"); 157 | Serial.print((uint32_t)scanColors[i].raw32, HEX); 158 | } 159 | Serial.print(")"); 160 | Serial.println(); 161 | unsigned long start_ms = millis(); 162 | while (timeout_ms == 0 || (millis() - start_ms < timeout_ms)) { 163 | for (i = 0; i < numStrands; i++) { 164 | pScanner[i]->prepareNext(); 165 | } 166 | digitalLeds_drawPixels(strands, numStrands); 167 | delay(delay_ms); 168 | } 169 | for (i = 0; i < numStrands; i++) { 170 | delete pScanner[i]; 171 | } 172 | digitalLeds_resetPixels(strands, numStrands); 173 | } 174 | 175 | 176 | void scanner(strand_t * pStrand, unsigned long delay_ms, unsigned long timeout_ms) 177 | { 178 | strand_t * strands [] = { pStrand }; 179 | scanners(strands, 1, delay_ms, timeout_ms); 180 | } 181 | 182 | 183 | //**************************************************************************// 184 | class Rainbower { 185 | private: 186 | strand_t * pStrand; 187 | const uint8_t color_div = 4; 188 | const uint8_t anim_step = 1; 189 | uint8_t anim_max; 190 | uint8_t stepVal1; 191 | uint8_t stepVal2; 192 | pixelColor_t color1; 193 | pixelColor_t color2; 194 | public: 195 | Rainbower(strand_t *); 196 | void prepareNext(); 197 | }; 198 | 199 | 200 | Rainbower::Rainbower(strand_t * pStrandIn) 201 | { 202 | pStrand = pStrandIn; 203 | anim_max = pStrand->brightLimit - anim_step; 204 | stepVal1 = 0; 205 | stepVal2 = 0; 206 | color1 = pixelFromRGBW(anim_max, 0, 0, 0); 207 | color2 = pixelFromRGBW(anim_max, 0, 0, 0); 208 | } 209 | 210 | 211 | void Rainbower::prepareNext() 212 | { 213 | color1 = color2; 214 | stepVal1 = stepVal2; 215 | for (uint16_t i = 0; i < pStrand->numPixels; i++) { 216 | pStrand->pixels[i] = pixelFromRGBW(color1.r/color_div, color1.g/color_div, color1.b/color_div, 0); 217 | if (i == 1) { 218 | color2 = color1; 219 | stepVal2 = stepVal1; 220 | } 221 | switch (stepVal1) { 222 | case 0: 223 | color1.g += anim_step; 224 | if (color1.g >= anim_max) 225 | stepVal1++; 226 | break; 227 | case 1: 228 | color1.r -= anim_step; 229 | if (color1.r == 0) 230 | stepVal1++; 231 | break; 232 | case 2: 233 | color1.b += anim_step; 234 | if (color1.b >= anim_max) 235 | stepVal1++; 236 | break; 237 | case 3: 238 | color1.g -= anim_step; 239 | if (color1.g == 0) 240 | stepVal1++; 241 | break; 242 | case 4: 243 | color1.r += anim_step; 244 | if (color1.r >= anim_max) 245 | stepVal1++; 246 | break; 247 | case 5: 248 | color1.b -= anim_step; 249 | if (color1.b == 0) 250 | stepVal1 = 0; 251 | break; 252 | } 253 | } 254 | } 255 | 256 | 257 | void rainbows(strand_t * strands[], int numStrands, unsigned long delay_ms, unsigned long timeout_ms) 258 | { 259 | //Rainbower rbow(pStrand); Rainbower * pRbow = &rbow; 260 | Rainbower * pRbow[numStrands]; 261 | int i; 262 | Serial.print("DEMO: rainbows("); 263 | for (i = 0; i < numStrands; i++) { 264 | pRbow[i] = new Rainbower(strands[i]); 265 | if (i > 0) { 266 | Serial.print(", "); 267 | } 268 | Serial.print("ch"); 269 | Serial.print(strands[i]->rmtChannel); 270 | Serial.print(" (0x"); 271 | Serial.print((uint32_t)pRbow[i], HEX); 272 | Serial.print(")"); 273 | } 274 | Serial.print(")"); 275 | Serial.println(); 276 | unsigned long start_ms = millis(); 277 | while (timeout_ms == 0 || (millis() - start_ms < timeout_ms)) { 278 | for (i = 0; i < numStrands; i++) { 279 | pRbow[i]->prepareNext(); 280 | } 281 | digitalLeds_drawPixels(strands, numStrands); 282 | delay(delay_ms); 283 | } 284 | for (i = 0; i < numStrands; i++) { 285 | delete pRbow[i]; 286 | } 287 | digitalLeds_resetPixels(strands, numStrands); 288 | } 289 | 290 | 291 | void rainbow(strand_t * pStrand, unsigned long delay_ms, unsigned long timeout_ms) 292 | { 293 | strand_t * strands [] = { pStrand }; 294 | rainbows(strands, 1, delay_ms, timeout_ms); 295 | } 296 | 297 | 298 | //**************************************************************************// 299 | -------------------------------------------------------------------------------- /examples/arduino-esp32/demo1/demo1.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Demo code for driving multiple digital RGB(W) strands using esp32_digital_led_lib 3 | * 4 | * Modifications Copyright (c) 2017-2019 Martin F. Falatic 5 | * 6 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 7 | * http://insentricity.com 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | 32 | #if defined(ARDUINO) && ARDUINO >= 100 33 | // No extras 34 | #elif defined(ARDUINO) // pre-1.0 35 | // No extras 36 | #elif defined(ESP_PLATFORM) 37 | #include "arduinoish.hpp" 38 | #endif 39 | 40 | #include "esp32_digital_led_lib.h" 41 | #include "esp32_digital_led_funcs.h" 42 | #include "fireworks_effects.h" 43 | 44 | 45 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 46 | 47 | // **Required** if debugging is enabled in library header 48 | // TODO: Is there any way to put this in digitalLeds_addStrands() and avoid undefined refs? 49 | #if DEBUG_ESP32_DIGITAL_LED_LIB 50 | int digitalLeds_debugBufferSz = 1024; 51 | char * digitalLeds_debugBuffer = static_cast(calloc(digitalLeds_debugBufferSz, sizeof(char))); 52 | #endif 53 | 54 | 55 | #pragma GCC diagnostic push 56 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" // It's noisy here with `-Wall` 57 | 58 | strand_t STRANDS[] = { // Avoid using any of the strapping pins on the ESP32, anything >=32, 16, 17... not much left. 59 | // {.rmtChannel = 0, .gpioNum = 14, .ledType = LED_SK6812W_V1, .brightLimit = 24, .numPixels = 144}, 60 | 61 | // {.rmtChannel = 0, .gpioNum = 14, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 93}, 62 | // {.rmtChannel = 1, .gpioNum = 15, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 93}, 63 | // {.rmtChannel = 2, .gpioNum = 26, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 93}, 64 | // {.rmtChannel = 3, .gpioNum = 27, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 93}, 65 | 66 | {.rmtChannel = 0, .gpioNum = 13, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 67 | {.rmtChannel = 1, .gpioNum = 14, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 68 | {.rmtChannel = 2, .gpioNum = 15, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 69 | {.rmtChannel = 3, .gpioNum = 16, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 70 | {.rmtChannel = 4, .gpioNum = 17, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 71 | {.rmtChannel = 5, .gpioNum = 18, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 72 | {.rmtChannel = 6, .gpioNum = 21, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 73 | {.rmtChannel = 7, .gpioNum = 22, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 74 | 75 | // {.rmtChannel = 3, .gpioNum = 19, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 64}, 76 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 256}, 77 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_SK6812W_V1, .brightLimit = 32, .numPixels = 300}, 78 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_WS2813_V2, .brightLimit = 32, .numPixels = 300}, 79 | }; 80 | 81 | //strand_t STRAND0 = {.rmtChannel = 1, .gpioNum = 14, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 93, 82 | // .pixels = nullptr, ._stateVars = nullptr}; 83 | 84 | #pragma GCC diagnostic pop 85 | 86 | int STRANDCNT = COUNT_OF(STRANDS); 87 | 88 | 89 | //**************************************************************************// 90 | int getMaxMalloc(int min_mem, int max_mem) { 91 | int prev_size = min_mem; 92 | int curr_size = min_mem; 93 | int max_free = 0; 94 | // Serial.print("checkmem: testing alloc from "); 95 | // Serial.print(min_mem); 96 | // Serial.print(" : "); 97 | // Serial.print(max_mem); 98 | // Serial.println(" bytes"); 99 | while (1) { 100 | void * foo1 = malloc(curr_size); 101 | // Serial.print("checkmem: attempt alloc of "); 102 | // Serial.print(curr_size); 103 | // Serial.print(" bytes --> pointer 0x"); 104 | // Serial.println((uintptr_t)foo1, HEX); 105 | if (foo1 == nullptr) { // Back off 106 | max_mem = std::min(curr_size, max_mem); 107 | // Serial.print("checkmem: backoff 2 prev = "); 108 | // Serial.print(prev_size); 109 | // Serial.print(", curr = "); 110 | // Serial.print(curr_size); 111 | // Serial.print(", max_mem = "); 112 | // Serial.print(max_mem); 113 | // Serial.println(); 114 | curr_size = (int)(curr_size - (curr_size - prev_size) / 2.0); 115 | // Serial.print("checkmem: backoff 2 prev = "); 116 | // Serial.print(prev_size); 117 | // Serial.print(", curr = "); 118 | // Serial.print(curr_size); 119 | // Serial.println(); 120 | } 121 | else { // Advance 122 | free(foo1); 123 | max_free = curr_size; 124 | prev_size = curr_size; 125 | curr_size = std::min(curr_size * 2, max_mem); 126 | // Serial.print("checkmem: advance 2 prev = "); 127 | // Serial.print(prev_size); 128 | // Serial.print(", curr = "); 129 | // Serial.print(curr_size); 130 | // Serial.println(); 131 | } 132 | if (abs(curr_size - prev_size) == 0) { 133 | break; 134 | } 135 | } 136 | Serial.print("checkmem: max free heap = "); 137 | Serial.print(esp_get_free_heap_size()); 138 | Serial.print(" bytes, max allocable = "); 139 | Serial.print(max_free); 140 | Serial.println(" bytes"); 141 | return max_free; 142 | } 143 | 144 | 145 | void dumpSysInfo() { 146 | esp_chip_info_t sysinfo; 147 | esp_chip_info(&sysinfo); 148 | Serial.print("Model: "); 149 | Serial.print((int)sysinfo.model); 150 | Serial.print("; Features: 0x"); 151 | Serial.print((int)sysinfo.features, HEX); 152 | Serial.print("; Cores: "); 153 | Serial.print((int)sysinfo.cores); 154 | Serial.print("; Revision: r"); 155 | Serial.println((int)sysinfo.revision); 156 | } 157 | 158 | 159 | void dumpDebugBuffer(int id, char * debugBuffer) 160 | { 161 | Serial.print("DEBUG: ("); 162 | Serial.print(id); 163 | Serial.print(") "); 164 | Serial.println(debugBuffer); 165 | debugBuffer[0] = 0; 166 | } 167 | 168 | 169 | 170 | //**************************************************************************// 171 | bool initStrands() 172 | { 173 | /**************************************************************************** 174 | If you have multiple strands connected, but not all are in use, the 175 | GPIO power-on defaults for the unused strand data lines will typically be 176 | high-impedance. Unless you are pulling the data lines high or low via a 177 | resistor, this will lead to noise on those unused but connected channels 178 | and unwanted driving of those unallocated strands. 179 | This optional gpioSetup() code helps avoid that problem programmatically. 180 | ****************************************************************************/ 181 | 182 | digitalLeds_initDriver(); 183 | 184 | for (int i = 0; i < STRANDCNT; i++) { 185 | gpioSetup(STRANDS[i].gpioNum, OUTPUT, LOW); 186 | } 187 | 188 | strand_t * strands[8]; 189 | for (int i = 0; i < STRANDCNT; i++) { 190 | strands[i] = &STRANDS[i]; 191 | } 192 | int rc = digitalLeds_addStrands(strands, STRANDCNT); 193 | if (rc) { 194 | Serial.print("Init rc = "); 195 | Serial.println(rc); 196 | return false; 197 | } 198 | 199 | for (int i = 0; i < STRANDCNT; i++) { 200 | strand_t * pStrand = strands[i]; 201 | Serial.print("Strand "); 202 | Serial.print(i); 203 | Serial.print(" = "); 204 | Serial.print((uint32_t)(pStrand->pixels), HEX); 205 | Serial.println(); 206 | #if DEBUG_ESP32_DIGITAL_LED_LIB 207 | dumpDebugBuffer(-2, digitalLeds_debugBuffer); 208 | #endif 209 | } 210 | 211 | return true; 212 | } 213 | 214 | 215 | // Hacky debugging method 216 | // espPinMode((gpio_num_t)5, OUTPUT); 217 | // gpio_set_level((gpio_num_t)5, 0); 218 | // gpio_set_level((gpio_num_t)5, 1); gpio_set_level((gpio_num_t)5, 0); 219 | 220 | //**************************************************************************// 221 | void setup() 222 | { 223 | Serial.begin(115200); 224 | Serial.println("Initializing..."); 225 | dumpSysInfo(); 226 | getMaxMalloc(1*1024, 16*1024*1024); 227 | 228 | if (!initStrands()) { 229 | Serial.println("Init FAILURE: halting"); 230 | while (true) { 231 | delay(100); 232 | } 233 | } 234 | delay(100); 235 | Serial.println("Init complete"); 236 | } 237 | 238 | 239 | //**************************************************************************// 240 | void loop() 241 | { 242 | strand_t * strands [STRANDCNT]; 243 | for (int i = 0; i < STRANDCNT; i++) { 244 | strands[i] = &STRANDS[i]; 245 | } 246 | 247 | digitalLeds_resetPixels(strands, STRANDCNT); 248 | 249 | int m1 = getMaxMalloc(1*1024, 16*1024*1024); 250 | 251 | for (int i = STRANDCNT; i > 0; i--) { 252 | randomStrands(strands, i, 0, 1000); 253 | } 254 | 255 | for (int i = STRANDCNT; i > 0; i--) { 256 | randomStrands(strands, i, 100, 3000); 257 | } 258 | 259 | for (int i = STRANDCNT; i > 0; i--) { 260 | scanners(strands, i, 0, 2000); 261 | } 262 | 263 | for (int i = STRANDCNT; i >= 0; i--) { 264 | rainbows(strands, i, 0, 4000); 265 | } 266 | 267 | int m2 = getMaxMalloc(1*1024, 16*1024*1024); 268 | assert(m2 >= m1); // Sanity check 269 | 270 | for (int i = 0; i < STRANDCNT; i++) { 271 | strand_t * pStrand = &STRANDS[i]; 272 | rainbow(pStrand, 0, 2000); 273 | scanner(pStrand, 0, 2000); 274 | } 275 | digitalLeds_resetPixels(strands, STRANDCNT); 276 | 277 | #if DEBUG_ESP32_DIGITAL_LED_LIB 278 | dumpDebugBuffer(0, digitalLeds_debugBuffer); 279 | #endif 280 | } 281 | 282 | //**************************************************************************// 283 | -------------------------------------------------------------------------------- /examples/esp-idf/demo1/main/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Demo code for driving multiple digital RGB(W) strands using esp32_digital_led_lib 3 | * 4 | * Modifications Copyright (c) 2017-2019 Martin F. Falatic 5 | * 6 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 7 | * http://insentricity.com 8 | * 9 | */ 10 | 11 | /* 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | * THE SOFTWARE. 29 | */ 30 | 31 | 32 | #if defined(ARDUINO) && ARDUINO >= 100 33 | // No extras 34 | #elif defined(ARDUINO) // pre-1.0 35 | // No extras 36 | #elif defined(ESP_PLATFORM) 37 | #include "arduinoish.hpp" 38 | #endif 39 | 40 | #include "esp32_digital_led_lib.h" 41 | #include "esp32_digital_led_funcs.h" 42 | #include "fireworks_effects.h" 43 | 44 | 45 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 46 | 47 | // **Required** if debugging is enabled in library header 48 | // TODO: Is there any way to put this in digitalLeds_addStrands() and avoid undefined refs? 49 | #if DEBUG_ESP32_DIGITAL_LED_LIB 50 | int digitalLeds_debugBufferSz = 1024; 51 | char * digitalLeds_debugBuffer = static_cast(calloc(digitalLeds_debugBufferSz, sizeof(char))); 52 | #endif 53 | 54 | 55 | #pragma GCC diagnostic push 56 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" // It's noisy here with `-Wall` 57 | 58 | strand_t STRANDS[] = { // Avoid using any of the strapping pins on the ESP32, anything >=32, 16, 17... not much left. 59 | // {.rmtChannel = 0, .gpioNum = 14, .ledType = LED_SK6812W_V1, .brightLimit = 24, .numPixels = 144}, 60 | 61 | // {.rmtChannel = 0, .gpioNum = 14, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 93}, 62 | // {.rmtChannel = 1, .gpioNum = 15, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 93}, 63 | // {.rmtChannel = 2, .gpioNum = 26, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 93}, 64 | // {.rmtChannel = 3, .gpioNum = 27, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 93}, 65 | 66 | {.rmtChannel = 0, .gpioNum = 13, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 67 | {.rmtChannel = 1, .gpioNum = 14, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 68 | {.rmtChannel = 2, .gpioNum = 15, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 69 | {.rmtChannel = 3, .gpioNum = 16, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 70 | {.rmtChannel = 4, .gpioNum = 17, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 71 | {.rmtChannel = 5, .gpioNum = 18, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 72 | {.rmtChannel = 6, .gpioNum = 21, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 73 | {.rmtChannel = 7, .gpioNum = 22, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 320}, 74 | 75 | // {.rmtChannel = 3, .gpioNum = 19, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 64}, 76 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_WS2812B_V3, .brightLimit = 32, .numPixels = 256}, 77 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_SK6812W_V1, .brightLimit = 32, .numPixels = 300}, 78 | // {.rmtChannel = 0, .gpioNum = 16, .ledType = LED_WS2813_V2, .brightLimit = 32, .numPixels = 300}, 79 | }; 80 | 81 | //strand_t STRAND0 = {.rmtChannel = 1, .gpioNum = 14, .ledType = LED_WS2812B_V3, .brightLimit = 24, .numPixels = 93, 82 | // .pixels = nullptr, ._stateVars = nullptr}; 83 | 84 | #pragma GCC diagnostic pop 85 | 86 | int STRANDCNT = COUNT_OF(STRANDS); 87 | 88 | 89 | //**************************************************************************// 90 | int getMaxMalloc(int min_mem, int max_mem) { 91 | int prev_size = min_mem; 92 | int curr_size = min_mem; 93 | int max_free = 0; 94 | // Serial.print("checkmem: testing alloc from "); 95 | // Serial.print(min_mem); 96 | // Serial.print(" : "); 97 | // Serial.print(max_mem); 98 | // Serial.println(" bytes"); 99 | while (1) { 100 | void * foo1 = malloc(curr_size); 101 | // Serial.print("checkmem: attempt alloc of "); 102 | // Serial.print(curr_size); 103 | // Serial.print(" bytes --> pointer 0x"); 104 | // Serial.println((uintptr_t)foo1, HEX); 105 | if (foo1 == nullptr) { // Back off 106 | max_mem = std::min(curr_size, max_mem); 107 | // Serial.print("checkmem: backoff 2 prev = "); 108 | // Serial.print(prev_size); 109 | // Serial.print(", curr = "); 110 | // Serial.print(curr_size); 111 | // Serial.print(", max_mem = "); 112 | // Serial.print(max_mem); 113 | // Serial.println(); 114 | curr_size = (int)(curr_size - (curr_size - prev_size) / 2.0); 115 | // Serial.print("checkmem: backoff 2 prev = "); 116 | // Serial.print(prev_size); 117 | // Serial.print(", curr = "); 118 | // Serial.print(curr_size); 119 | // Serial.println(); 120 | } 121 | else { // Advance 122 | free(foo1); 123 | max_free = curr_size; 124 | prev_size = curr_size; 125 | curr_size = std::min(curr_size * 2, max_mem); 126 | // Serial.print("checkmem: advance 2 prev = "); 127 | // Serial.print(prev_size); 128 | // Serial.print(", curr = "); 129 | // Serial.print(curr_size); 130 | // Serial.println(); 131 | } 132 | if (abs(curr_size - prev_size) == 0) { 133 | break; 134 | } 135 | } 136 | Serial.print("checkmem: max free heap = "); 137 | Serial.print(esp_get_free_heap_size()); 138 | Serial.print(" bytes, max allocable = "); 139 | Serial.print(max_free); 140 | Serial.println(" bytes"); 141 | return max_free; 142 | } 143 | 144 | 145 | void dumpSysInfo() { 146 | esp_chip_info_t sysinfo; 147 | esp_chip_info(&sysinfo); 148 | Serial.print("Model: "); 149 | Serial.print((int)sysinfo.model); 150 | Serial.print("; Features: 0x"); 151 | Serial.print((int)sysinfo.features, HEX); 152 | Serial.print("; Cores: "); 153 | Serial.print((int)sysinfo.cores); 154 | Serial.print("; Revision: r"); 155 | Serial.println((int)sysinfo.revision); 156 | } 157 | 158 | 159 | void dumpDebugBuffer(int id, char * debugBuffer) 160 | { 161 | Serial.print("DEBUG: ("); 162 | Serial.print(id); 163 | Serial.print(") "); 164 | Serial.println(debugBuffer); 165 | debugBuffer[0] = 0; 166 | } 167 | 168 | 169 | 170 | //**************************************************************************// 171 | bool initStrands() 172 | { 173 | /**************************************************************************** 174 | If you have multiple strands connected, but not all are in use, the 175 | GPIO power-on defaults for the unused strand data lines will typically be 176 | high-impedance. Unless you are pulling the data lines high or low via a 177 | resistor, this will lead to noise on those unused but connected channels 178 | and unwanted driving of those unallocated strands. 179 | This optional gpioSetup() code helps avoid that problem programmatically. 180 | ****************************************************************************/ 181 | 182 | digitalLeds_initDriver(); 183 | 184 | for (int i = 0; i < STRANDCNT; i++) { 185 | gpioSetup(STRANDS[i].gpioNum, OUTPUT, LOW); 186 | } 187 | 188 | strand_t * strands[8]; 189 | for (int i = 0; i < STRANDCNT; i++) { 190 | strands[i] = &STRANDS[i]; 191 | } 192 | int rc = digitalLeds_addStrands(strands, STRANDCNT); 193 | if (rc) { 194 | Serial.print("Init rc = "); 195 | Serial.println(rc); 196 | return false; 197 | } 198 | 199 | for (int i = 0; i < STRANDCNT; i++) { 200 | strand_t * pStrand = strands[i]; 201 | Serial.print("Strand "); 202 | Serial.print(i); 203 | Serial.print(" = "); 204 | Serial.print((uint32_t)(pStrand->pixels), HEX); 205 | Serial.println(); 206 | #if DEBUG_ESP32_DIGITAL_LED_LIB 207 | dumpDebugBuffer(-2, digitalLeds_debugBuffer); 208 | #endif 209 | } 210 | 211 | return true; 212 | } 213 | 214 | 215 | // Hacky debugging method 216 | // espPinMode((gpio_num_t)5, OUTPUT); 217 | // gpio_set_level((gpio_num_t)5, 0); 218 | // gpio_set_level((gpio_num_t)5, 1); gpio_set_level((gpio_num_t)5, 0); 219 | 220 | //**************************************************************************// 221 | void setup() 222 | { 223 | Serial.begin(115200); 224 | Serial.println("Initializing..."); 225 | dumpSysInfo(); 226 | getMaxMalloc(1*1024, 16*1024*1024); 227 | 228 | if (!initStrands()) { 229 | Serial.println("Init FAILURE: halting"); 230 | while (true) { 231 | delay(100); 232 | } 233 | } 234 | delay(100); 235 | Serial.println("Init complete"); 236 | } 237 | 238 | 239 | //**************************************************************************// 240 | void loop() 241 | { 242 | strand_t * strands [STRANDCNT]; 243 | for (int i = 0; i < STRANDCNT; i++) { 244 | strands[i] = &STRANDS[i]; 245 | } 246 | 247 | digitalLeds_resetPixels(strands, STRANDCNT); 248 | 249 | int m1 = getMaxMalloc(1*1024, 16*1024*1024); 250 | 251 | for (int i = STRANDCNT; i > 0; i--) { 252 | randomStrands(strands, i, 0, 1000); 253 | } 254 | 255 | for (int i = STRANDCNT; i > 0; i--) { 256 | randomStrands(strands, i, 100, 3000); 257 | } 258 | 259 | for (int i = STRANDCNT; i > 0; i--) { 260 | scanners(strands, i, 0, 2000); 261 | } 262 | 263 | for (int i = STRANDCNT; i >= 0; i--) { 264 | rainbows(strands, i, 0, 4000); 265 | } 266 | 267 | int m2 = getMaxMalloc(1*1024, 16*1024*1024); 268 | assert(m2 >= m1); // Sanity check 269 | 270 | for (int i = 0; i < STRANDCNT; i++) { 271 | strand_t * pStrand = &STRANDS[i]; 272 | rainbow(pStrand, 0, 2000); 273 | scanner(pStrand, 0, 2000); 274 | } 275 | digitalLeds_resetPixels(strands, STRANDCNT); 276 | 277 | #if DEBUG_ESP32_DIGITAL_LED_LIB 278 | dumpDebugBuffer(0, digitalLeds_debugBuffer); 279 | #endif 280 | } 281 | 282 | //**************************************************************************// 283 | -------------------------------------------------------------------------------- /src/esp32_digital_led_lib.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Library for driving digital RGB(W) LEDs using the ESP32's RMT peripheral 3 | * 4 | * Modifications Copyright (c) 2017-2019 Martin F. Falatic 5 | * 6 | * Portions modified using FastLED's ClocklessController as a reference 7 | * Copyright (c) 2018 Samuel Z. Guyer 8 | * Copyright (c) 2017 Thomas Basler 9 | * 10 | * Based on public domain code created 19 Nov 2016 by Chris Osborn 11 | * http://insentricity.com 12 | * 13 | */ 14 | 15 | /* 16 | * Permission is hereby granted, free of charge, to any person obtaining a copy 17 | * of this software and associated documentation files (the "Software"), to deal 18 | * in the Software without restriction, including without limitation the rights 19 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | * copies of the Software, and to permit persons to whom the Software is 21 | * furnished to do so, subject to the following conditions: 22 | * 23 | * The above copyright notice and this permission notice shall be included in 24 | * all copies or substantial portions of the Software. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 32 | * THE SOFTWARE. 33 | */ 34 | 35 | #include "esp32_digital_led_lib.h" 36 | 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | #if defined(ARDUINO) 43 | #include "esp32-hal.h" 44 | #include "esp_intr.h" 45 | #include "driver/gpio.h" 46 | #include "driver/rmt.h" 47 | #include "driver/periph_ctrl.h" 48 | #include "freertos/semphr.h" 49 | #include "soc/rmt_struct.h" 50 | #elif defined(ESP_PLATFORM) 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include // memset, memcpy, etc. live here! 61 | #endif 62 | 63 | #ifdef __cplusplus 64 | } 65 | #endif 66 | 67 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 68 | 69 | #if DEBUG_ESP32_DIGITAL_LED_LIB 70 | extern char * digitalLeds_debugBuffer; 71 | extern int digitalLeds_debugBufferSz; 72 | #endif 73 | 74 | static DRAM_ATTR const uint16_t MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass 75 | static DRAM_ATTR const uint16_t DIVIDER = 4; // 8 still seems to work, but timings become marginal 76 | static DRAM_ATTR const double RMT_DURATION_NS = 12.5; // Minimum time of a single RMT duration based on clock ns 77 | 78 | 79 | // Considering the RMT_INT_RAW_REG (raw int status) and RMT_INT_ST_REG (masked int status) registers (each 32-bit): 80 | // Where op = {raw, st, ena, clr} and n = {0..7} 81 | // Every three bits = RMT.int_.ch_tx_end, RMT.int_.ch_rx_end, RMT.int_.ch_err 82 | // The final 8 bits are RMT.int_.ch_tx_thr_event 83 | 84 | // LUT for mapping bits in RMT.int_.ch_tx_thr_event 85 | static DRAM_ATTR const uint32_t tx_thr_event_offsets [] = { 86 | static_cast(1) << (24 + 0), 87 | static_cast(1) << (24 + 1), 88 | static_cast(1) << (24 + 2), 89 | static_cast(1) << (24 + 3), 90 | static_cast(1) << (24 + 4), 91 | static_cast(1) << (24 + 5), 92 | static_cast(1) << (24 + 6), 93 | static_cast(1) << (24 + 7), 94 | }; 95 | 96 | // LUT for mapping bits in RMT.int_.ch_tx_end 97 | static DRAM_ATTR const uint32_t tx_end_offsets [] = { 98 | static_cast(1) << (0 + 0) * 3, 99 | static_cast(1) << (0 + 1) * 3, 100 | static_cast(1) << (0 + 2) * 3, 101 | static_cast(1) << (0 + 3) * 3, 102 | static_cast(1) << (0 + 4) * 3, 103 | static_cast(1) << (0 + 5) * 3, 104 | static_cast(1) << (0 + 6) * 3, 105 | static_cast(1) << (0 + 7) * 3, 106 | }; 107 | 108 | typedef union { 109 | struct { 110 | uint32_t duration0:15; 111 | uint32_t level0:1; 112 | uint32_t duration1:15; 113 | uint32_t level1:1; 114 | }; 115 | uint32_t val; 116 | } rmtPulsePair; 117 | 118 | typedef struct { 119 | uint8_t * buf_data; 120 | uint16_t buf_pos, buf_len, buf_half, buf_isDirty; 121 | rmtPulsePair pulsePairMap[2]; 122 | bool isProcessing; 123 | } digitalLeds_stateData; 124 | 125 | double randDouble() 126 | { 127 | return double(esp_random()>>16) / (UINT16_MAX + 1); 128 | } 129 | 130 | pixelColor_t adjustByUniformFactor(pixelColor_t * color, double adjFactor) { 131 | color->r = uint8_t(color->r * (1.0 - adjFactor)); 132 | color->g = uint8_t(color->g * (1.0 - adjFactor)); 133 | color->b = uint8_t(color->b * (1.0 - adjFactor)); 134 | color->w = uint8_t(color->w * (1.0 - adjFactor)); 135 | return *color; 136 | } 137 | 138 | 139 | const static int MAX_RMT_CHANNELS = 8; 140 | static strand_t * strandDataPtrs[MAX_RMT_CHANNELS] = {nullptr}; // Indexed by RMT channel 141 | 142 | // Forward declarations of local functions 143 | static void copyHalfBlockToRmt(strand_t * pStrand); 144 | static void rmtInterruptHandler(void *arg); 145 | 146 | 147 | static xSemaphoreHandle gRmtSem = nullptr; 148 | static intr_handle_t gRmtIntrHandle = nullptr; 149 | 150 | static int gToProcess = 0; 151 | 152 | 153 | #if defined(ARDUINO) && ARDUINO >= 100 154 | void espPinMode(int pinNum, int pinDir) { 155 | // Enable GPIO32 or 33 as output. Device-dependent 156 | // (only works if these aren't used for external XTAL). 157 | // https://esp32.com/viewtopic.php?t=9151#p38282 158 | if (pinNum == 32 || pinNum == 33) { 159 | uint64_t gpioBitMask = (pinNum == 32) ? 1ULL<= 100 178 | espPinMode(gpioNum, gpioMode); 179 | digitalWrite (gpioNum, gpioVal); 180 | #elif defined(ESP_PLATFORM) 181 | gpio_num_t gpioNumNative = static_cast(gpioNum); 182 | gpio_mode_t gpioModeNative = static_cast(gpioMode); 183 | gpio_pad_select_gpio(gpioNumNative); 184 | gpio_set_direction(gpioNumNative, gpioModeNative); 185 | gpio_set_level(gpioNumNative, gpioVal); 186 | #endif 187 | } 188 | 189 | 190 | int digitalLeds_initDriver() 191 | { 192 | #if DEBUG_ESP32_DIGITAL_LED_LIB 193 | snprintf(digitalLeds_debugBuffer, digitalLeds_debugBufferSz, "digitalLeds_initDriver\n"); 194 | #endif 195 | 196 | esp_err_t rc = ESP_OK; 197 | 198 | if (gRmtIntrHandle == nullptr) { // Only on first run 199 | // Sem is created here 200 | gRmtSem = xSemaphoreCreateBinary(); 201 | xSemaphoreGive(gRmtSem); 202 | rc = esp_intr_alloc(ETS_RMT_INTR_SOURCE, 0, rmtInterruptHandler, nullptr, &gRmtIntrHandle); 203 | } 204 | 205 | return rc; 206 | } 207 | 208 | 209 | int digitalLeds_addStrands(strand_t * strands [], int numStrands) 210 | { 211 | for (int i = 0; i < numStrands; i++) { 212 | int rmtChannel = strands[i]->rmtChannel; 213 | strand_t * pStrand = strands[i]; 214 | strandDataPtrs[rmtChannel] = pStrand; 215 | 216 | ledParams_t ledParams = ledParamsAll[pStrand->ledType]; 217 | 218 | pStrand->pixels = static_cast(malloc(pStrand->numPixels * sizeof(pixelColor_t))); 219 | if (pStrand->pixels == nullptr) { 220 | return -1; 221 | } 222 | 223 | pStrand->_stateVars = static_cast(malloc(sizeof(digitalLeds_stateData))); 224 | if (pStrand->_stateVars == nullptr) { 225 | return -2; 226 | } 227 | digitalLeds_stateData * pState = static_cast(pStrand->_stateVars); 228 | 229 | pState->buf_len = (pStrand->numPixels * ledParams.bytesPerPixel); 230 | pState->buf_data = static_cast(malloc(pState->buf_len)); 231 | if (pState->buf_data == nullptr) { 232 | return -3; 233 | } 234 | 235 | // RMT configuration for transmission 236 | rmt_config_t rmt_tx; 237 | rmt_tx.channel = static_cast(rmtChannel); 238 | rmt_tx.gpio_num = static_cast(pStrand->gpioNum); 239 | rmt_tx.rmt_mode = RMT_MODE_TX; 240 | rmt_tx.mem_block_num = 1; 241 | rmt_tx.clk_div = DIVIDER; 242 | rmt_tx.tx_config.loop_en = false; 243 | rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; 244 | rmt_tx.tx_config.carrier_en = false; 245 | rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; 246 | rmt_tx.tx_config.idle_output_en = true; 247 | rmt_config(&rmt_tx); 248 | 249 | // RMT config for transmitting a '0' bit val to this LED strand 250 | pState->pulsePairMap[0].level0 = 1; 251 | pState->pulsePairMap[0].level1 = 0; 252 | pState->pulsePairMap[0].duration0 = ledParams.T0H / (RMT_DURATION_NS * DIVIDER); 253 | pState->pulsePairMap[0].duration1 = ledParams.T0L / (RMT_DURATION_NS * DIVIDER); 254 | 255 | // RMT config for transmitting a '0' bit val to this LED strand 256 | pState->pulsePairMap[1].level0 = 1; 257 | pState->pulsePairMap[1].level1 = 0; 258 | pState->pulsePairMap[1].duration0 = ledParams.T1H / (RMT_DURATION_NS * DIVIDER); 259 | pState->pulsePairMap[1].duration1 = ledParams.T1L / (RMT_DURATION_NS * DIVIDER); 260 | 261 | pState->isProcessing = false; 262 | 263 | // Set interrupts 264 | rmt_set_tx_thr_intr_en(static_cast(rmtChannel), true, MAX_PULSES); // sets rmt_set_tx_wrap_en and RMT.tx_lim_ch.limit 265 | } 266 | 267 | digitalLeds_resetPixels(strands, numStrands); 268 | 269 | return 0; 270 | } 271 | 272 | 273 | int digitalLeds_removeStrands(strand_t * strands [], int numStrands) 274 | { 275 | digitalLeds_resetPixels(strands, numStrands); 276 | 277 | for (int i = 0; i < numStrands; i++) { 278 | int rmtChannel = strands[i]->rmtChannel; 279 | strand_t * pStrand = strandDataPtrs[rmtChannel]; 280 | if (pStrand) { 281 | strandDataPtrs[rmtChannel] = nullptr; 282 | } 283 | } 284 | 285 | return 0; 286 | } 287 | 288 | 289 | int digitalLeds_resetPixels(strand_t * strands [], int numStrands) 290 | { 291 | // TODO: The input is strands for convenience - the point is to get indicies of strands to draw 292 | // Could just pass the channel numbers, but would it be slower to construct that list? 293 | 294 | for (int i = 0; i < numStrands; i++) { 295 | int rmtChannel = strands[i]->rmtChannel; 296 | strand_t * pStrand = strandDataPtrs[rmtChannel]; 297 | memset(pStrand->pixels, 0, pStrand->numPixels * sizeof(pixelColor_t)); 298 | } 299 | 300 | digitalLeds_drawPixels(strands, numStrands); 301 | 302 | return 0; 303 | } 304 | 305 | 306 | int IRAM_ATTR digitalLeds_drawPixels(strand_t * strands [], int numStrands) 307 | { 308 | // TODO: The input is strands for convenience - the point is to get indicies of strands to draw 309 | // Could just pass the channel numbers, but would it be slower to construct that list? 310 | 311 | if (numStrands == 0) { 312 | return 0; 313 | } 314 | 315 | gToProcess = numStrands; 316 | 317 | xSemaphoreTake(gRmtSem, portMAX_DELAY); 318 | 319 | for (int i = 0; i < numStrands; i++) { 320 | int rmtChannel = strands[i]->rmtChannel; 321 | strand_t * pStrand = strandDataPtrs[rmtChannel]; 322 | 323 | digitalLeds_stateData * pState = static_cast(pStrand->_stateVars); 324 | ledParams_t ledParams = ledParamsAll[pStrand->ledType]; 325 | 326 | pState->isProcessing = true; 327 | 328 | // Pack pixels into transmission buffer 329 | if (ledParams.bytesPerPixel == 3) { 330 | for (uint16_t i = 0; i < pStrand->numPixels; i++) { 331 | // Color order is translated from RGB to GRB 332 | pState->buf_data[0 + i * 3] = pStrand->pixels[i].g; 333 | pState->buf_data[1 + i * 3] = pStrand->pixels[i].r; 334 | pState->buf_data[2 + i * 3] = pStrand->pixels[i].b; 335 | } 336 | } 337 | else if (ledParams.bytesPerPixel == 4) { 338 | for (uint16_t i = 0; i < pStrand->numPixels; i++) { 339 | // Color order is translated from RGBW to GRBW 340 | pState->buf_data[0 + i * 4] = pStrand->pixels[i].g; 341 | pState->buf_data[1 + i * 4] = pStrand->pixels[i].r; 342 | pState->buf_data[2 + i * 4] = pStrand->pixels[i].b; 343 | pState->buf_data[3 + i * 4] = pStrand->pixels[i].w; 344 | } 345 | } 346 | else { 347 | return -1; 348 | } 349 | 350 | pState->buf_pos = 0; 351 | pState->buf_half = 0; 352 | 353 | rmt_set_tx_intr_en(static_cast(rmtChannel), true); 354 | 355 | copyHalfBlockToRmt(pStrand); 356 | if (pState->buf_pos < pState->buf_len) { // Fill the other half of the buffer block 357 | copyHalfBlockToRmt(pStrand); 358 | } 359 | 360 | // Starts RMT, which will end up giving us the semaphore back 361 | // Immediately starts transmitting 362 | rmt_set_tx_intr_en(static_cast(rmtChannel), true); 363 | rmt_tx_start(static_cast(rmtChannel), true); 364 | } 365 | 366 | // Give back semaphore after drawing is done 367 | xSemaphoreTake(gRmtSem, portMAX_DELAY); 368 | xSemaphoreGive(gRmtSem); 369 | 370 | return 0; 371 | } 372 | 373 | 374 | static IRAM_ATTR void copyHalfBlockToRmt(strand_t * pStrand) 375 | { 376 | // This fills half an RMT block 377 | // When wraparound is happening, we want to keep the inactive half of the RMT block filled 378 | digitalLeds_stateData * pState = static_cast(pStrand->_stateVars); 379 | ledParams_t ledParams = ledParamsAll[pStrand->ledType]; 380 | 381 | uint16_t i, j, offset, len, byteval; 382 | 383 | offset = pState->buf_half * MAX_PULSES; 384 | pState->buf_half = !pState->buf_half; 385 | 386 | len = pState->buf_len - pState->buf_pos; 387 | if (len > (MAX_PULSES / 8)) 388 | len = (MAX_PULSES / 8); 389 | 390 | if (!len) { 391 | if (!pState->buf_isDirty) { 392 | return; 393 | } 394 | // Clear the channel's data block and return 395 | for (i = 0; i < MAX_PULSES; i++) { 396 | RMTMEM.chan[pStrand->rmtChannel].data32[i + offset].val = 0; 397 | } 398 | pState->buf_isDirty = 0; 399 | return; 400 | } 401 | pState->buf_isDirty = 1; 402 | 403 | for (i = 0; i < len; i++) { 404 | byteval = pState->buf_data[i + pState->buf_pos]; 405 | 406 | // Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to 407 | // the rmtPulsePair value corresponding to the buffered bit value 408 | for (j = 0; j < 8; j++, byteval <<= 1) { 409 | int bitval = (byteval >> 7) & 0x01; 410 | int data32_idx = i * 8 + offset + j; 411 | RMTMEM.chan[pStrand->rmtChannel].data32[data32_idx].val = pState->pulsePairMap[bitval].val; 412 | } 413 | 414 | // Handle the reset bit by stretching duration1 for the final bit in the stream 415 | if (i + pState->buf_pos == pState->buf_len - 1) { 416 | RMTMEM.chan[pStrand->rmtChannel].data32[i * 8 + offset + 7].duration1 = 417 | ledParams.TRS / (RMT_DURATION_NS * DIVIDER); 418 | } 419 | } 420 | 421 | // Clear the remainder of the channel's data not set above 422 | for (i *= 8; i < MAX_PULSES; i++) { 423 | RMTMEM.chan[pStrand->rmtChannel].data32[i + offset].val = 0; 424 | } 425 | 426 | pState->buf_pos += len; 427 | 428 | return; 429 | } 430 | 431 | 432 | static IRAM_ATTR void rmtInterruptHandler(void *arg) 433 | { 434 | portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE; 435 | 436 | for (int rmtChannel = 0; rmtChannel < MAX_RMT_CHANNELS; rmtChannel++) { 437 | strand_t * pStrand = strandDataPtrs[rmtChannel]; 438 | if (pStrand == nullptr) { 439 | continue; 440 | } 441 | 442 | digitalLeds_stateData * pState = static_cast(pStrand->_stateVars); 443 | if (!pState->isProcessing) { 444 | continue; 445 | } 446 | 447 | if (RMT.int_st.val & tx_thr_event_offsets[rmtChannel]) { 448 | // We got an RMT.int_st.ch_tx_thr_event interrupt because RMT.tx_lim_ch.limit was crossed 449 | RMT.int_clr.val |= tx_thr_event_offsets[rmtChannel]; // set RMT.int_clr.ch_tx_thr_event (reset interrupt bit) 450 | copyHalfBlockToRmt(pStrand); 451 | } 452 | else if (RMT.int_st.val & tx_end_offsets[rmtChannel]) { 453 | // We got an RMT.int_st.ch_tx_end interrupt with a zero-length entry which means we're done 454 | RMT.int_clr.val |= tx_end_offsets[rmtChannel]; // set RMT.int_clr.ch_tx_end (reset interrupt bit) 455 | //gpio_matrix_out(static_cast(pStrand->gpioNum), 0x100, 0, 0); // only useful if rmt_config keeps getting called 456 | pState->isProcessing = false; 457 | gToProcess--; 458 | if (gToProcess == 0) { 459 | xSemaphoreGiveFromISR(gRmtSem, &xHigherPriorityTaskWoken); 460 | if (xHigherPriorityTaskWoken == pdTRUE) { // Perform cleanup if we're all done 461 | portYIELD_FROM_ISR(); 462 | } 463 | } 464 | } 465 | 466 | } 467 | 468 | return; 469 | } 470 | 471 | 472 | //**************************************************************************// 473 | --------------------------------------------------------------------------------