From ec0fc2d3e8a6508aef293d8e33877186215ad1b1 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Thu, 29 Jul 2021 22:15:30 +0100 Subject: [PATCH 01/25] Allow interrupts on WS2811 etc on AVR without glitches For many WS2811 strips, we can relax the clearing of interrupts down to protecting the timing of the shortest pulse. Your interrupts must still be very very fast in order for the strip not to latch part-way through. --- src/platforms/avr/clockless_trinket.h | 53 ++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/platforms/avr/clockless_trinket.h b/src/platforms/avr/clockless_trinket.h index 176221d9008..e25b0c0999c 100644 --- a/src/platforms/avr/clockless_trinket.h +++ b/src/platforms/avr/clockless_trinket.h @@ -24,6 +24,27 @@ FASTLED_NAMESPACE_BEGIN #define US_PER_TICK (64 / (F_CPU/1000000)) +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Due to the tight timing specifications of WS2811 and friends on AVR, interrupts are disabled by default to keep timings exact. +// However, many WS2811 or WS2812 strips are surprisingly tolerant of jittery timings (such as those caused by interrupts), on the +// condition that the shortest pulse in the specification (representing a 0 bit) is kept under a certain length. After exceeding +// that length it would be interpreted as a 1, causing a glitch. +// +// If you set FASTLED_ALLOW_INTERRUPTS to 1, interrupts will only be enabled when necessary to keep this short pulse short. +// +// Beware: even with FASTLED_ALLOW_INTERRUPTS enabled, you must ensure that your interrupt handlers are *very* fast. If they take +// longer than about ~6000ns, which is roughly 90 clock cycles on a 16MHz AVR, the strip will latch partway through rendering, and +// you will see big glitches. +// +// See https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ for more information on the +// tolerances. +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//#define FASTLED_ALLOW_INTERRUPTS 1 + + // Variations on the functions in delay.h - w/a loop var passed in to preserve registers across calls by the optimizer/compiler template inline void _dc(register uint8_t & loopvar); @@ -73,11 +94,20 @@ template<> __attribute__((always_inline)) inline void _dc<18>(register uint8_t & template<> __attribute__((always_inline)) inline void _dc<19>(register uint8_t & loopvar) { _dc<10>(loopvar); _dc<9>(loopvar); } template<> __attribute__((always_inline)) inline void _dc<20>(register uint8_t & loopvar) { _dc<10>(loopvar); _dc<10>(loopvar); } +#if (FASTLED_ALLOW_INTERRUPTS == 1) +// If interrupts are enabled, HI1 actually takes 2 clocks due to cli(). +// To keep the timings exact, D3 (which precedes it) must be 1 clock less. +// The same adjustment must be made for D2, due the corresponding sei(). +#define D_INT_ADJ 1 +#else +#define D_INT_ADJ 0 +#endif + #define DINTPIN(T,ADJ,PINADJ) (T-(PINADJ+ADJ)>0) ? _dc(loopvar) : _dc<0>(loopvar); #define DINT(T,ADJ) if(AVR_PIN_CYCLES(DATA_PIN)==1) { DINTPIN(T,ADJ,1) } else { DINTPIN(T,ADJ,2); } #define _D1(ADJ) DINT(T1,ADJ) -#define _D2(ADJ) DINT(T2,ADJ) -#define _D3(ADJ) DINT(T3,ADJ) +#define _D2(ADJ) DINT(T2,ADJ+D_INT_ADJ) +#define _D3(ADJ) DINT(T3,ADJ+D_INT_ADJ) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // @@ -120,7 +150,9 @@ class ClocklessController : public CPixelLEDController { virtual void showPixels(PixelController & pixels) { mWait.wait(); +#if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) cli(); +#endif if(pixels.mLen > 0) { showRGBInternal(pixels); @@ -175,7 +207,9 @@ class ClocklessController : public CPixelLEDController { #endif +#if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) sei(); +#endif mWait.mark(); } #define USE_ASM_MACROS @@ -215,10 +249,19 @@ class ClocklessController : public CPixelLEDController { [O2] "M" (RGB_BYTE2(RGB_ORDER)) \ : "cc" /* clobber registers */ + +#if (FASTLED_ALLOW_INTERRUPTS == 1) +#define HI1CLI cli() +#define QLO2SEI sei() +#else +#define HI1CLI +#define QLO2SEI +#endif + #if defined(__AVR_ATmega4809__) // 1 cycle, write hi to the port -#define HI1 do {*FastPin::port()=hi;} while(0); +#define HI1 HI1CLI; do {*FastPin::port()=hi;} while(0); // 1 cycle, write lo to the port #define LO1 do {*FastPin::port()=lo;} while(0); @@ -226,14 +269,14 @@ class ClocklessController : public CPixelLEDController { // Note: the code in the else in HI1/LO1 will be turned into an sts (2 cycle, 2 word) // 1 cycle, write hi to the port -#define HI1 FASTLED_SLOW_CLOCK_ADJUST if((int)(FastPin::port())-0x20 < 64) { asm __volatile__("out %[PORT], %[hi]" ASM_VARS ); } else { *FastPin::port()=hi; } +#define HI1 FASTLED_SLOW_CLOCK_ADJUST HI1CLI; if((int)(FastPin::port())-0x20 < 64) { asm __volatile__("out %[PORT], %[hi]" ASM_VARS ); } else { *FastPin::port()=hi; } // 1 cycle, write lo to the port #define LO1 if((int)(FastPin::port())-0x20 < 64) { asm __volatile__("out %[PORT], %[lo]" ASM_VARS ); } else { *FastPin::port()=lo; } #endif // 2 cycles, sbrs on flipping the line to lo if we're pushing out a 0 -#define QLO2(B, N) asm __volatile__("sbrs %[" #B "], " #N ASM_VARS ); LO1; +#define QLO2(B, N) asm __volatile__("sbrs %[" #B "], " #N ASM_VARS ); LO1; QLO2SEI; // load a byte from ram into the given var with the given offset #define LD2(B,O) asm __volatile__("ldd %[" #B "], Z + %[" #O "]\n\t" ASM_VARS ); // 4 cycles - load a byte from ram into the scaling scratch space with the given offset, clear the target var, clear carry From 28b48ddf6ec77a9475e6c29f69fd81acee5eaa51 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Fri, 30 Jul 2021 10:48:30 +0100 Subject: [PATCH 02/25] Update to comments. --- src/platforms/avr/clockless_trinket.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platforms/avr/clockless_trinket.h b/src/platforms/avr/clockless_trinket.h index e25b0c0999c..f323ac60aae 100644 --- a/src/platforms/avr/clockless_trinket.h +++ b/src/platforms/avr/clockless_trinket.h @@ -31,11 +31,13 @@ FASTLED_NAMESPACE_BEGIN // condition that the shortest pulse in the specification (representing a 0 bit) is kept under a certain length. After exceeding // that length it would be interpreted as a 1, causing a glitch. // -// If you set FASTLED_ALLOW_INTERRUPTS to 1, interrupts will only be enabled when necessary to keep this short pulse short. +// If you set FASTLED_ALLOW_INTERRUPTS to 1, interrupts will only be disabled for a few cycles at a time, when necessary to keep +// this signal pulse short. // // Beware: even with FASTLED_ALLOW_INTERRUPTS enabled, you must ensure that your interrupt handlers are *very* fast. If they take // longer than about ~6000ns, which is roughly 90 clock cycles on a 16MHz AVR, the strip will latch partway through rendering, and -// you will see big glitches. +// you will see big glitches. If you are using multiple timers with interrupts, you can set them out of phase so they only fire +// one at a time. // // See https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ for more information on the // tolerances. From aca96fcaaa003d95e3272941f0c35268c666ae66 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Fri, 30 Jul 2021 20:09:48 +0100 Subject: [PATCH 03/25] Improve detail in comments about interrupts. --- src/platforms/avr/clockless_trinket.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/platforms/avr/clockless_trinket.h b/src/platforms/avr/clockless_trinket.h index f323ac60aae..57cb6c60a7e 100644 --- a/src/platforms/avr/clockless_trinket.h +++ b/src/platforms/avr/clockless_trinket.h @@ -35,9 +35,15 @@ FASTLED_NAMESPACE_BEGIN // this signal pulse short. // // Beware: even with FASTLED_ALLOW_INTERRUPTS enabled, you must ensure that your interrupt handlers are *very* fast. If they take -// longer than about ~6000ns, which is roughly 90 clock cycles on a 16MHz AVR, the strip will latch partway through rendering, and -// you will see big glitches. If you are using multiple timers with interrupts, you can set them out of phase so they only fire -// one at a time. +// longer than 5µs, which is 80 clock cycles on a 16MHz AVR, the strip might latch partway through rendering, and you will see big +// glitches. +// +// Remember to account for the interrupt overhead when writing your ISR. This accounts for at least 10 cycles, often 20+. +// +// If you are using multiple timers with interrupts, you can set them out of phase so they only fire one at a time. +// +// TODO: it would be possible to support longer interrupts providing that they only fire during the "on" pulse - holding the +// signal high indefinitely will never latch, although it would affect the framerate. Maybe the subject of a future PR… // // See https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ for more information on the // tolerances. From 5c2118175e58243bb59fcf1a26194ef2b2eac8cc Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Mon, 2 Aug 2021 13:38:09 +0100 Subject: [PATCH 04/25] Add PixelRange struct type. --- src/pixeltypes.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pixeltypes.h b/src/pixeltypes.h index f28c2c670a7..0af005c49f6 100644 --- a/src/pixeltypes.h +++ b/src/pixeltypes.h @@ -858,6 +858,20 @@ enum EOrder { BGR=0210 }; + +// A linked list of pointers to ranges of LEDs. Use this to display pixels in a different order from how they are arranged in RAM. +// For example: +// * using two of these, you can display the last half of the list first, then the first half of the list, to achieve a scrolling offset +// * using two of these, you can display a reflection that is twice as long as the amount of actual data by show the data and then showing the same data in reverse +// * with 10 of these, on a 10x10 grid, if you set the skip value to 10*sizeof(CRGB), you can rotate the display 90 degrees +struct PixelRange { + const CRGB *leds; + const PixelRange *next; + uint8_t length; + int8_t skip; +}; + + FASTLED_NAMESPACE_END ///@} From 90f6af7408042cbfdce945a2774b7e1aaa9b0be3 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Mon, 2 Aug 2021 13:38:45 +0100 Subject: [PATCH 05/25] Add show(pixelRange, scale) method signature and default implementation. --- src/controller.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/controller.h b/src/controller.h index 7b7a7cf9408..9a8a8a740cd 100644 --- a/src/controller.h +++ b/src/controller.h @@ -59,6 +59,7 @@ class CLEDController { ///@param nLeds the number of leds being written out ///@param scale the rgb scaling to apply to each led before writing it out virtual void show(const struct CRGB *data, int nLeds, CRGB scale) = 0; + virtual void show(const struct PixelRange *rangeList, CRGB scale) = 0; public: /// create an led controller object, add it to the chain of controllers @@ -80,6 +81,11 @@ class CLEDController { show(data, nLeds, getAdjustment(brightness)); } + // show a linked list of pixel ranges on the same strip + void show(const struct PixelRange *rangeList, uint8_t brightness) { + show(rangeList, getAdjustment(brightness)); + } + /// show function w/integer brightness, will scale for color correction and temperature void showColor(const struct CRGB &data, int nLeds, uint8_t brightness) { showColor(data, nLeds, getAdjustment(brightness)); @@ -390,6 +396,16 @@ template class CPixelLE protected: virtual void showPixels(PixelController & pixels) = 0; + virtual void showPixels(PixelController & pixels, const struct PixelRange *rangeList) { + do { + pixels.mData = (uint8_t *)rangeList->leds; + pixels.mLen = rangeList->length; + pixels.mLenRemaining = rangeList->length; + pixels.mAdvance = rangeList->skip; + showPixels(pixels); + } while(rangeList = rangeList->next); + } + /// set all the leds on the controller to a given color ///@param data the crgb color to set the leds to ///@param nLeds the numner of leds to set to this color @@ -412,6 +428,17 @@ template class CPixelLE showPixels(pixels); } + /// Using a linked list of pixel ranges, render all of them in sequence on the same controller. + /// See notes on PixelRange in pixeltypes.h for examples of times you might want to use this (offsetting, scrolling, rotation). + /// NOTE: By default, FastLED enforces a minimum wait period for sketches that don't have their own code to wait from frame to frame. + /// Until all controllers override showPixels(&pixels, *rangeList), you may also need to define NO_MINIMUM_WAIT 1. + ///@param rangeList linked list of pixel ranges to render in sequence on the strip. + ///@param scale the rgb scaling to apply to each led before writing it out + virtual void show(const struct PixelRange *rangeList, CRGB scale) { + PixelController pixels((CRGB *)NULL, 0, scale, getDither()); + showPixels(pixels, rangeList); + } + public: CPixelLEDController() : CLEDController() {} }; From cc41d741e2c4f835ec1b3e9ece5404900a9aff0b Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Mon, 2 Aug 2021 21:13:00 +0100 Subject: [PATCH 06/25] Update showPixels(PixelRange) to match the clockless imiplementation --- src/controller.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/controller.h b/src/controller.h index 9a8a8a740cd..a2ae60b5b73 100644 --- a/src/controller.h +++ b/src/controller.h @@ -396,14 +396,16 @@ template class CPixelLE protected: virtual void showPixels(PixelController & pixels) = 0; + //// default implementation. For performance this is overriden by some implementations virtual void showPixels(PixelController & pixels, const struct PixelRange *rangeList) { + PixelRange const *range = rangeList; do { - pixels.mData = (uint8_t *)rangeList->leds; - pixels.mLen = rangeList->length; - pixels.mLenRemaining = rangeList->length; - pixels.mAdvance = rangeList->skip; + pixels.mData = (uint8_t *)range->leds; + pixels.mLen = pixels.mLenRemaining = range->length; + pixels.mAdvance = range->skip; showPixels(pixels); - } while(rangeList = rangeList->next); + range = range->next; + } while(range); } /// set all the leds on the controller to a given color @@ -430,7 +432,7 @@ template class CPixelLE /// Using a linked list of pixel ranges, render all of them in sequence on the same controller. /// See notes on PixelRange in pixeltypes.h for examples of times you might want to use this (offsetting, scrolling, rotation). - /// NOTE: By default, FastLED enforces a minimum wait period for sketches that don't have their own code to wait from frame to frame. + /// *NOTE*: By default, FastLED enforces a minimum wait period for sketches that don't have their own code to wait from frame to frame. /// Until all controllers override showPixels(&pixels, *rangeList), you may also need to define NO_MINIMUM_WAIT 1. ///@param rangeList linked list of pixel ranges to render in sequence on the strip. ///@param scale the rgb scaling to apply to each led before writing it out From bb5b7a66575f40f58f7aa9bf2ab339ed5123ff05 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Mon, 2 Aug 2021 21:14:04 +0100 Subject: [PATCH 07/25] specialised implementation of showPixels(PixelRange) * factor out the clock juggler * wrap showRGBInternal() in order to be fast enough --- src/platforms/avr/clockless_trinket.h | 90 ++++++++++++++++++++------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/src/platforms/avr/clockless_trinket.h b/src/platforms/avr/clockless_trinket.h index 57cb6c60a7e..b1fed34cf73 100644 --- a/src/platforms/avr/clockless_trinket.h +++ b/src/platforms/avr/clockless_trinket.h @@ -147,33 +147,15 @@ class ClocklessController : public CPixelLEDController { CMinWait mWait; -public: - virtual void init() { - FastPin::setOutput(); - } - - virtual uint16_t getMaxRefreshRate() const { return 400; } - -protected: - virtual void showPixels(PixelController & pixels) { - - mWait.wait(); -#if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) - cli(); -#endif - - if(pixels.mLen > 0) { - showRGBInternal(pixels); - } - + void juggleClock(int size) { // Adjust the timer #if (!defined(NO_CLOCK_CORRECTION) || (NO_CLOCK_CORRECTION == 0)) && (FASTLED_ALLOW_INTERRUPTS == 0) - uint32_t microsTaken = (uint32_t)pixels.size() * (uint32_t)CLKS_TO_MICROS(24 * (T1 + T2 + T3)); + uint32_t microsTaken = (uint32_t)size * (uint32_t)CLKS_TO_MICROS(24 * (T1 + T2 + T3)); // adust for approximate observed actal runtime (as of January 2015) // roughly 9.6 cycles per pixel, which is 0.6us/pixel at 16MHz // microsTaken += nLeds * 0.6 * CLKS_TO_MICROS(16); - microsTaken += scale16by8(pixels.size(),(0.6 * 256) + 1) * CLKS_TO_MICROS(16); + microsTaken += scale16by8(size,(0.6 * 256) + 1) * CLKS_TO_MICROS(16); // if less than 1000us, there is NO timer impact, // this is because the ONE interrupt that might come in while interrupts @@ -214,12 +196,72 @@ class ClocklessController : public CPixelLEDController { #endif #endif + } -#if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) - sei(); -#endif +public: + virtual void init() { + FastPin::setOutput(); + } + + virtual uint16_t getMaxRefreshRate() const { return 400; } + +protected: + + // The default implementation in PixelController is just about fast enough with all three of + // NO_MINIMUM_WAIT, NO_CLOCK_CORRECTION and FASTLED_ALLOW_INTERRUPTS all defined, but that won't do. + // This specialisation does not require any of those to be defined to be fast enough, because it wraps + // showRGBInternal() rather than showPixels(). + virtual void showPixels(PixelController & pixels, const struct PixelRange *rangeList) { + + mWait.wait(); +# if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) + cli(); +# endif + + uint16_t size = 0; + PixelRange const *range = rangeList; + do { + pixels.mData = (uint8_t *)range->leds; + pixels.mLen = pixels.mLenRemaining = range->length; + pixels.mAdvance = range->skip; + if(pixels.size() > 0) { + size += pixels.size(); + showRGBInternal(pixels); + } + range = range->next; + } while(range); + + if(size > 0) { + juggleClock(size); + } + + +# if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) + sei(); +# endif + mWait.mark(); + } + + virtual void showPixels(PixelController & pixels) { + + mWait.wait(); +# if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) + cli(); +# endif + + if(pixels.mLen > 0) { + showRGBInternal(pixels); + juggleClock(pixels.size()); + } + + +# if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) + sei(); +# endif mWait.mark(); } + + #define USE_ASM_MACROS #if defined(__AVR_ATmega4809__) From edc0b414c4cd68cb494f6fbc33edceb18ee3a942 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Mon, 2 Aug 2021 22:51:38 +0100 Subject: [PATCH 08/25] Add PixelRange example. The example rotates a white dot around the LED strip, whilst remixing segments of the strip randomly. --- examples/PixelRange/PixelRange.ino | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 examples/PixelRange/PixelRange.ino diff --git a/examples/PixelRange/PixelRange.ino b/examples/PixelRange/PixelRange.ino new file mode 100644 index 00000000000..c1a43a3b220 --- /dev/null +++ b/examples/PixelRange/PixelRange.ino @@ -0,0 +1,65 @@ +#include + +#define FASTLED_ALLOW_INTERRUPTS 1 + +#define NEOPIXEL_PIN 6 +#define STRIP_LENGTH 60 +#define RANGE_LENGTH 6 + +CRGB leds[STRIP_LENGTH]; + +PixelRange ranges[10] = { 0 }; + +void randomise() { + uint8_t i = random8(10); + uint8_t j = random8(10); + if(random8(2)) { + ranges[i].leds = &leds[RANGE_LENGTH*j]; + ranges[i].skip = 3; + } else { + ranges[i].leds = &leds[RANGE_LENGTH*(j+1)-1]; + ranges[i].skip = -3; + } +} + +void setup() { + + pinMode(NEOPIXEL_PIN, OUTPUT); + + FastLED.addLeds(leds, STRIP_LENGTH).setCorrection(TypicalLEDStrip); + FastLED.setDither( 0 ); + + // link the linked list + for(uint8_t i = 0; i<10; i++) { + ranges[i].next = &ranges[i+1]; + } + + // set them up to follow each other + for(uint8_t i = 0; i<=10; i++) { + ranges[i].leds = &leds[RANGE_LENGTH*i]; + ranges[i].length = RANGE_LENGTH; + ranges[i].skip = 3; + } +} + +void loop() { + static uint32_t start = 0; + static uint8_t dot_idx = 0; + + leds[dot_idx] = CRGB::Black; + dot_idx++; + if(dot_idx == STRIP_LENGTH) { + dot_idx = 0; + } + leds[dot_idx] = CRGB::White; + + FastLED[0].show(&ranges[0], 255); + + uint32_t end = micros(); + if(end - start > 2500000) { + start = end; + randomise(); + } + // FastLED.delay(50); + delay(10); +} From 48e026c720efbe39c048c7030c0677752c6b8383 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Mon, 2 Aug 2021 22:53:47 +0100 Subject: [PATCH 09/25] Fix spaces/tabs faux pas --- src/platforms/avr/clockless_trinket.h | 98 +++++++++++++-------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/platforms/avr/clockless_trinket.h b/src/platforms/avr/clockless_trinket.h index b1fed34cf73..d17db037a09 100644 --- a/src/platforms/avr/clockless_trinket.h +++ b/src/platforms/avr/clockless_trinket.h @@ -150,49 +150,49 @@ class ClocklessController : public CPixelLEDController { void juggleClock(int size) { // Adjust the timer #if (!defined(NO_CLOCK_CORRECTION) || (NO_CLOCK_CORRECTION == 0)) && (FASTLED_ALLOW_INTERRUPTS == 0) - uint32_t microsTaken = (uint32_t)size * (uint32_t)CLKS_TO_MICROS(24 * (T1 + T2 + T3)); - - // adust for approximate observed actal runtime (as of January 2015) - // roughly 9.6 cycles per pixel, which is 0.6us/pixel at 16MHz - // microsTaken += nLeds * 0.6 * CLKS_TO_MICROS(16); - microsTaken += scale16by8(size,(0.6 * 256) + 1) * CLKS_TO_MICROS(16); - - // if less than 1000us, there is NO timer impact, - // this is because the ONE interrupt that might come in while interrupts - // are disabled is queued up, and it will be serviced as soon as - // interrupts are re-enabled. - // This actually should technically also account for the runtime of the - // interrupt handler itself, but we're just not going to worry about that. - if( microsTaken > 1000) { - - // Since up to one timer tick will be queued, we don't need - // to adjust the MS_COUNTER for that one. - microsTaken -= 1000; - - // Now convert microseconds to 256ths of a second, approximately like this: - // 250ths = (us/4) - // 256ths = 250ths * (263/256); - uint16_t x256ths = microsTaken >> 2; - x256ths += scale16by8(x256ths,7); - - x256ths += gTimeErrorAccum256ths; - MS_COUNTER += (x256ths >> 8); - gTimeErrorAccum256ths = x256ths & 0xFF; - } + uint32_t microsTaken = (uint32_t)size * (uint32_t)CLKS_TO_MICROS(24 * (T1 + T2 + T3)); + + // adust for approximate observed actal runtime (as of January 2015) + // roughly 9.6 cycles per pixel, which is 0.6us/pixel at 16MHz + // microsTaken += nLeds * 0.6 * CLKS_TO_MICROS(16); + microsTaken += scale16by8(size,(0.6 * 256) + 1) * CLKS_TO_MICROS(16); + + // if less than 1000us, there is NO timer impact, + // this is because the ONE interrupt that might come in while interrupts + // are disabled is queued up, and it will be serviced as soon as + // interrupts are re-enabled. + // This actually should technically also account for the runtime of the + // interrupt handler itself, but we're just not going to worry about that. + if( microsTaken > 1000) { + + // Since up to one timer tick will be queued, we don't need + // to adjust the MS_COUNTER for that one. + microsTaken -= 1000; + + // Now convert microseconds to 256ths of a second, approximately like this: + // 250ths = (us/4) + // 256ths = 250ths * (263/256); + uint16_t x256ths = microsTaken >> 2; + x256ths += scale16by8(x256ths,7); + + x256ths += gTimeErrorAccum256ths; + MS_COUNTER += (x256ths >> 8); + gTimeErrorAccum256ths = x256ths & 0xFF; + } #if 0 - // For pixel counts of 30 and under at 16Mhz, no correction is necessary. - // For pixel counts of 15 and under at 8Mhz, no correction is necessary. - // - // This code, below, is smaller, and quicker clock correction, which drifts much - // more significantly, but is a few bytes smaller. Presented here for consideration - // as an alternate on the ATtiny, which can't have more than about 150 pixels MAX - // anyway, meaning that microsTaken will never be more than about 4,500, which fits in - // a 16-bit variable. The difference between /1000 and /1024 only starts showing - // up in the range of about 100 pixels, so many ATtiny projects won't even - // see a clock difference due to the approximation there. + // For pixel counts of 30 and under at 16Mhz, no correction is necessary. + // For pixel counts of 15 and under at 8Mhz, no correction is necessary. + // + // This code, below, is smaller, and quicker clock correction, which drifts much + // more significantly, but is a few bytes smaller. Presented here for consideration + // as an alternate on the ATtiny, which can't have more than about 150 pixels MAX + // anyway, meaning that microsTaken will never be more than about 4,500, which fits in + // a 16-bit variable. The difference between /1000 and /1024 only starts showing + // up in the range of about 100 pixels, so many ATtiny projects won't even + // see a clock difference due to the approximation there. uint16_t microsTaken = (uint32_t)nLeds * (uint32_t)CLKS_TO_MICROS((24) * (T1 + T2 + T3)); - MS_COUNTER += (microsTaken >> 10); + MS_COUNTER += (microsTaken >> 10); #endif #endif @@ -211,7 +211,7 @@ class ClocklessController : public CPixelLEDController { // NO_MINIMUM_WAIT, NO_CLOCK_CORRECTION and FASTLED_ALLOW_INTERRUPTS all defined, but that won't do. // This specialisation does not require any of those to be defined to be fast enough, because it wraps // showRGBInternal() rather than showPixels(). - virtual void showPixels(PixelController & pixels, const struct PixelRange *rangeList) { + virtual void showPixels(PixelController & pixels, const struct PixelRange *rangeList) { mWait.wait(); # if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) @@ -219,17 +219,17 @@ class ClocklessController : public CPixelLEDController { # endif uint16_t size = 0; - PixelRange const *range = rangeList; - do { - pixels.mData = (uint8_t *)range->leds; - pixels.mLen = pixels.mLenRemaining = range->length; - pixels.mAdvance = range->skip; + PixelRange const *range = rangeList; + do { + pixels.mData = (uint8_t *)range->leds; + pixels.mLen = pixels.mLenRemaining = range->length; + pixels.mAdvance = range->skip; if(pixels.size() > 0) { size += pixels.size(); showRGBInternal(pixels); } - range = range->next; - } while(range); + range = range->next; + } while(range); if(size > 0) { juggleClock(size); @@ -240,7 +240,7 @@ class ClocklessController : public CPixelLEDController { sei(); # endif mWait.mark(); - } + } virtual void showPixels(PixelController & pixels) { From 7dc15743ac3b192ea9b7bb8a64258034aaeec875 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sat, 8 Jan 2022 14:46:42 +0000 Subject: [PATCH 10/25] A new example which shows 80 >>> >>> <<< <<< lights from 20 CRGB LEDs --- .../PixelRangeKnightRider.ino | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 examples/PixelRangeKnightRider/PixelRangeKnightRider.ino diff --git a/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino new file mode 100644 index 00000000000..003c0ee3aa5 --- /dev/null +++ b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino @@ -0,0 +1,85 @@ +#define FRAMES_PER_SECOND 30 +// #define FASTLED_ALLOW_INTERRUPTS 1 +// #define NO_MINIMUM_WAIT 1 +// #define NO_CLOCK_CORRECTION 1 + +#include + +#define PIXEL_COUNT 20 +#define STRIP_LENGTH (PIXEL_COUNT * 4) +#define NEOPIXEL_PIN 6 + +CRGB leds[PIXEL_COUNT]; + +PixelRange ranges[4] = { 0 }; + + +void setup() { + // put your setup code here, to run once: + pinMode(NEOPIXEL_PIN, OUTPUT); + FastLED.addLeds(leds, STRIP_LENGTH).setCorrection(TypicalLEDStrip); + FastLED.setDither( 0 ); + + // set up 4 ranges over the same LEDs. two forward (repeated), two reverse (repeated) + + // first, link the linked list + ranges[0].next = &ranges[1]; + ranges[1].next = &ranges[2]; + ranges[2].next = &ranges[3]; + ranges[3].next = NULL; + + // all ranges are the same length + ranges[0].length = PIXEL_COUNT; + ranges[1].length = PIXEL_COUNT; + ranges[2].length = PIXEL_COUNT; + ranges[3].length = PIXEL_COUNT; + + // now, two forward + ranges[0].leds = &leds[0]; + ranges[0].skip = 3; + + ranges[1].leds = &leds[0]; + ranges[1].skip = 3; + + // finally, two reverse + ranges[2].leds = &leds[PIXEL_COUNT-1]; + ranges[2].skip = -3; + + ranges[3].leds = &leds[PIXEL_COUNT-1]; + ranges[3].skip = -3; +} + + +void loop() { + static uint8_t current_position = 0; + static bool forward = true; + + // fade every pixel + for (uint8_t i = 0; i < PIXEL_COUNT; i++) { + // fade + leds[i] >>= 1; + } + leds[current_position] = CRGB::White; + + // bounce a dot from end to end of each range + if(forward) { + current_position++; + if(current_position == PIXEL_COUNT-1) { + // bounce + forward = false; + } + } else { + current_position--; + if(current_position == 0) { + // bounce + forward = true; + } + } + + FastLED[0].show(ranges, 255); + + // note: dither relies on show() which relies on multiple CLEDControllers, which we haven't got here (they're all FastLED[0]) + // so this will not dither unless we get that fixed somehow + delay(1000/FRAMES_PER_SECOND); + +} From d48244f3ed64057207089cfd2062deb32e4b2118 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sat, 8 Jan 2022 16:44:23 +0000 Subject: [PATCH 11/25] Range scrolling example --- .../PixelRangeScrollingComet.ino | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino new file mode 100644 index 00000000000..479ba8356e2 --- /dev/null +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -0,0 +1,111 @@ +#include + +#define FRAMES_PER_SECOND 30 + +#define STRIP_LENGTH 100 +#define COMET_LENGTH 8 +#define NEOPIXEL_PIN 6 + +CRGB cometPixel[COMET_LENGTH]; +CRGB spacePixel[1]; + +PixelRange left; +PixelRange middle; +PixelRange right; + + +void setup() { + // put your setup code here, to run once: + pinMode(NEOPIXEL_PIN, OUTPUT); + FastLED.addLeds(cometPixel, STRIP_LENGTH).setCorrection(TypicalLEDStrip); + FastLED.setDither( 0 ); + + // draw the comet. + for (uint8_t i = 0; i < COMET_LENGTH; i++) { + cometPixel[i] = CRGB::White; + cometPixel[i] >>= i; + } + + // draw space. + spacePixel[0] = CRGB::Black; + + configureSplitSpace(); + +} + +void configureSplitSpace() { + left.leds = &spacePixel[0]; + left.length = 0; + left.skip = 0; + left.next = &middle; + + middle.leds = &cometPixel[0]; + middle.length = COMET_LENGTH; + middle.skip = 3; + middle.next = &right; + + right.leds = &spacePixel[0]; + right.length = STRIP_LENGTH - COMET_LENGTH; + right.skip = 0; + right.next = NULL; +} + +void configureSplitComet() { + left.leds = &cometPixel[0]; + left.length = 0; + left.skip = 3; + left.next = &middle; + + middle.leds = &spacePixel[0]; + middle.length = STRIP_LENGTH - COMET_LENGTH; + middle.skip = 0; + middle.next = &right; + + right.leds = &cometPixel[0]; + right.length = COMET_LENGTH; + right.skip = 3; + right.next = NULL; +} + + +void loop() { + + // where on the strip the comet starts. + static uint16_t current_position = 0; + + uint16_t comet_head = current_position; + uint16_t comet_tail = current_position + COMET_LENGTH; + + if(comet_tail == COMET_LENGTH) { + // reconfigure linked ranges for split space + configureSplitSpace(); + + } else if(comet_tail == STRIP_LENGTH) { + // reconfigure linked ranges for split comet + configureSplitComet(); + + } else if(comet_tail > STRIP_LENGTH) { + // in split comet + left.leds = &cometPixel[comet_tail - STRIP_LENGTH]; + left.length++; + right.length--; + + } else { + // in split space + left.length++; + right.length--; + } + + + FastLED[0].show(&left, 255); + + current_position++; + if(current_position == STRIP_LENGTH) { + current_position = 0; + } + + // note: dither relies on show() which relies on multiple CLEDControllers, which we haven't got here (they're all FastLED[0]) + // so this will not dither unless we get that fixed somehow + delay(1000/FRAMES_PER_SECOND); + +} From aa018f768c217cf3cbc3c45c65ff874c8b4bb28a Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sat, 8 Jan 2022 17:06:26 +0000 Subject: [PATCH 12/25] Make the comet go in the right direction --- .../PixelRangeScrollingComet.ino | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index 479ba8356e2..179b3398ba0 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -3,7 +3,7 @@ #define FRAMES_PER_SECOND 30 #define STRIP_LENGTH 100 -#define COMET_LENGTH 8 +#define COMET_LENGTH 16 #define NEOPIXEL_PIN 6 CRGB cometPixel[COMET_LENGTH]; @@ -23,7 +23,7 @@ void setup() { // draw the comet. for (uint8_t i = 0; i < COMET_LENGTH; i++) { cometPixel[i] = CRGB::White; - cometPixel[i] >>= i; + cometPixel[i] >>= i/2; } // draw space. @@ -39,9 +39,9 @@ void configureSplitSpace() { left.skip = 0; left.next = &middle; - middle.leds = &cometPixel[0]; + middle.leds = &cometPixel[COMET_LENGTH-1]; middle.length = COMET_LENGTH; - middle.skip = 3; + middle.skip = -3; middle.next = &right; right.leds = &spacePixel[0]; @@ -51,9 +51,9 @@ void configureSplitSpace() { } void configureSplitComet() { - left.leds = &cometPixel[0]; + left.leds = &cometPixel[COMET_LENGTH-1]; left.length = 0; - left.skip = 3; + left.skip = -3; left.next = &middle; middle.leds = &spacePixel[0]; @@ -76,15 +76,15 @@ void loop() { uint16_t comet_head = current_position; uint16_t comet_tail = current_position + COMET_LENGTH; - if(comet_tail == COMET_LENGTH) { + if(current_position == 0) { // reconfigure linked ranges for split space configureSplitSpace(); - } else if(comet_tail == STRIP_LENGTH) { + } else if(comet_tail == STRIP_LENGTH - 1) { // reconfigure linked ranges for split comet configureSplitComet(); - } else if(comet_tail > STRIP_LENGTH) { + } else if(comet_tail >= STRIP_LENGTH) { // in split comet left.leds = &cometPixel[comet_tail - STRIP_LENGTH]; left.length++; From fd084c6c7cfd25fcd7e8962fd9bfe13909aa5ad8 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sat, 8 Jan 2022 17:16:50 +0000 Subject: [PATCH 13/25] remove redundant call in example --- .../PixelRangeScrollingComet.ino | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index 179b3398ba0..d290176f803 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -29,7 +29,10 @@ void setup() { // draw space. spacePixel[0] = CRGB::Black; - configureSplitSpace(); + // link the linked list + left.next = &middle; + middle.next = &right; + right.next = NULL; } @@ -37,34 +40,28 @@ void configureSplitSpace() { left.leds = &spacePixel[0]; left.length = 0; left.skip = 0; - left.next = &middle; middle.leds = &cometPixel[COMET_LENGTH-1]; middle.length = COMET_LENGTH; middle.skip = -3; - middle.next = &right; right.leds = &spacePixel[0]; right.length = STRIP_LENGTH - COMET_LENGTH; right.skip = 0; - right.next = NULL; } void configureSplitComet() { left.leds = &cometPixel[COMET_LENGTH-1]; left.length = 0; left.skip = -3; - left.next = &middle; middle.leds = &spacePixel[0]; middle.length = STRIP_LENGTH - COMET_LENGTH; middle.skip = 0; - middle.next = &right; - right.leds = &cometPixel[0]; + right.leds = &cometPixel[COMET_LENGTH-1]; right.length = COMET_LENGTH; - right.skip = 3; - right.next = NULL; + right.skip = -3; } @@ -96,7 +93,6 @@ void loop() { right.length--; } - FastLED[0].show(&left, 255); current_position++; From 55e259eb7eb558de443bff78db4edd80e7beef93 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 00:01:00 +0000 Subject: [PATCH 14/25] Change to 60 pixels to match the video on the PR --- examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index d290176f803..a6e2b690fa1 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -2,7 +2,7 @@ #define FRAMES_PER_SECOND 30 -#define STRIP_LENGTH 100 +#define STRIP_LENGTH 60 #define COMET_LENGTH 16 #define NEOPIXEL_PIN 6 From 23b5fa18fd0e2283c32c5d6edeaa72ded950e06c Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:01:22 +0000 Subject: [PATCH 15/25] Better comet trail math --- .../PixelRangeScrollingComet/PixelRangeScrollingComet.ino | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index a6e2b690fa1..47c1fc4296b 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -18,12 +18,13 @@ void setup() { // put your setup code here, to run once: pinMode(NEOPIXEL_PIN, OUTPUT); FastLED.addLeds(cometPixel, STRIP_LENGTH).setCorrection(TypicalLEDStrip); - FastLED.setDither( 0 ); + // FastLED.setDither( 0 ); + FastLED.setBrightness(128); // draw the comet. for (uint8_t i = 0; i < COMET_LENGTH; i++) { cometPixel[i] = CRGB::White; - cometPixel[i] >>= i/2; + cometPixel[i].fadeToBlackBy((i*256)/COMET_LENGTH); } // draw space. From 18861596c39a3bb5ae079008bf0f2e1a01537b4c Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:19:20 +0000 Subject: [PATCH 16/25] Fix show() and delay() for dithering. --- .../PixelRangeScrollingComet.ino | 8 ++++---- src/controller.h | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index 47c1fc4296b..fd091c50642 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -21,6 +21,8 @@ void setup() { // FastLED.setDither( 0 ); FastLED.setBrightness(128); + FastLED[0].setRangeList(&left); + // draw the comet. for (uint8_t i = 0; i < COMET_LENGTH; i++) { cometPixel[i] = CRGB::White; @@ -94,15 +96,13 @@ void loop() { right.length--; } - FastLED[0].show(&left, 255); + FastLED.show(); current_position++; if(current_position == STRIP_LENGTH) { current_position = 0; } - // note: dither relies on show() which relies on multiple CLEDControllers, which we haven't got here (they're all FastLED[0]) - // so this will not dither unless we get that fixed somehow - delay(1000/FRAMES_PER_SECOND); + FastLED.delay(1000/FRAMES_PER_SECOND); } diff --git a/src/controller.h b/src/controller.h index a2ae60b5b73..6a90b510dd1 100644 --- a/src/controller.h +++ b/src/controller.h @@ -40,6 +40,7 @@ class CLEDController { protected: friend class CFastLED; CRGB *m_Data; + PixelRange *m_RangeList; CLEDController *m_pNext; CRGB m_ColorCorrection; CRGB m_ColorTemperature; @@ -93,7 +94,11 @@ class CLEDController { /// show function using the "attached to this controller" led data void showLeds(uint8_t brightness=255) { - show(m_Data, m_nLeds, getAdjustment(brightness)); + if(m_RangeList) { + show(m_RangeList, getAdjustment(brightness)); + } else { + show(m_Data, m_nLeds, getAdjustment(brightness)); + } } /// show the given color on the led strip @@ -113,6 +118,12 @@ class CLEDController { return *this; } + /// set the default RangeList to be used by this controller. Note that showLeds() will prefer this to the LED array if it is present at all + CLEDController & setRangeList(PixelRange *data) { + m_RangeList = data; + return *this; + } + /// zero out the led data managed by this controller void clearLedData() { if(m_Data) { From 3c8927ec797890d6972bd8e4dd358173bc068aa6 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:37:32 +0000 Subject: [PATCH 17/25] Make example neater and add more explanatory comments. --- .../PixelRangeScrollingComet.ino | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index fd091c50642..06845edf0c8 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -7,7 +7,7 @@ #define NEOPIXEL_PIN 6 CRGB cometPixel[COMET_LENGTH]; -CRGB spacePixel[1]; +CRGB spacePixel[1] = { CRGB::Black }; PixelRange left; PixelRange middle; @@ -18,8 +18,7 @@ void setup() { // put your setup code here, to run once: pinMode(NEOPIXEL_PIN, OUTPUT); FastLED.addLeds(cometPixel, STRIP_LENGTH).setCorrection(TypicalLEDStrip); - // FastLED.setDither( 0 ); - FastLED.setBrightness(128); + FastLED.setBrightness(32); FastLED[0].setRangeList(&left); @@ -29,9 +28,6 @@ void setup() { cometPixel[i].fadeToBlackBy((i*256)/COMET_LENGTH); } - // draw space. - spacePixel[0] = CRGB::Black; - // link the linked list left.next = &middle; middle.next = &right; @@ -74,14 +70,17 @@ void loop() { static uint16_t current_position = 0; uint16_t comet_head = current_position; - uint16_t comet_tail = current_position + COMET_LENGTH; + uint16_t comet_tail = comet_head + COMET_LENGTH; - if(current_position == 0) { - // reconfigure linked ranges for split space + if(comet_head == 0) { + // The entire comet has emerged as one contiguous block of pixels, now that it's head is at 0. + // So, reconfigure linked ranges for split space (i.e. draw "space -> comet -> space") configureSplitSpace(); } else if(comet_tail == STRIP_LENGTH - 1) { - // reconfigure linked ranges for split comet + // The tail of the comet has reached the end of the strip. Next frame, the comet will start to + // wrap to the other end of the strip. So, reconfigure linked ranges for split comet + // (i.e. draw "comet -> space -> comet") configureSplitComet(); } else if(comet_tail >= STRIP_LENGTH) { From 765c9c401cbb5de98e7d1515cab8ffc6a06bd324 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:39:24 +0000 Subject: [PATCH 18/25] Add another explaner comment --- .../PixelRangeScrollingComet/PixelRangeScrollingComet.ino | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index 06845edf0c8..f24bb5ba63b 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -36,11 +36,14 @@ void setup() { } void configureSplitSpace() { + // the comet is back to front, so we start at the end (middle.leds = the last pixel) + // and we draw it backwards (middle.skip = -3) + left.leds = &spacePixel[0]; left.length = 0; left.skip = 0; - middle.leds = &cometPixel[COMET_LENGTH-1]; + middle.leds = &cometPixel[COMET_LENGTH-1]; middle.length = COMET_LENGTH; middle.skip = -3; From 9b8edf73633b3a46a1636227b25a79722c235754 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:45:44 +0000 Subject: [PATCH 19/25] Tinker with FPS and brightness again --- .../PixelRangeScrollingComet/PixelRangeScrollingComet.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index f24bb5ba63b..a67b7b49e52 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -1,6 +1,6 @@ #include -#define FRAMES_PER_SECOND 30 +#define FRAMES_PER_SECOND 50 #define STRIP_LENGTH 60 #define COMET_LENGTH 16 @@ -18,7 +18,7 @@ void setup() { // put your setup code here, to run once: pinMode(NEOPIXEL_PIN, OUTPUT); FastLED.addLeds(cometPixel, STRIP_LENGTH).setCorrection(TypicalLEDStrip); - FastLED.setBrightness(32); + FastLED.setBrightness(192); FastLED[0].setRangeList(&left); From 3ee793dfed70a3603243f86e09c0e1db8334393e Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:46:39 +0000 Subject: [PATCH 20/25] Rename PixelRange.ino to PixelRangeRandomiser.ino as it's more descriptive and not the best example (so i don't want to imply it's "best" by not giving it a more precise name) --- .../PixelRangeRandomiser.ino} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{PixelRange/PixelRange.ino => PixelRangeRandomiser/PixelRangeRandomiser.ino} (100%) diff --git a/examples/PixelRange/PixelRange.ino b/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino similarity index 100% rename from examples/PixelRange/PixelRange.ino rename to examples/PixelRangeRandomiser/PixelRangeRandomiser.ino From f00f9f6e8873f5f2fa064f0ca7937d9718ea862e Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 09:53:11 +0000 Subject: [PATCH 21/25] Update KnightRider example now i've fixed dither --- .../PixelRangeKnightRider.ino | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino index 003c0ee3aa5..0e20917ab0d 100644 --- a/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino +++ b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino @@ -1,11 +1,8 @@ #define FRAMES_PER_SECOND 30 -// #define FASTLED_ALLOW_INTERRUPTS 1 -// #define NO_MINIMUM_WAIT 1 -// #define NO_CLOCK_CORRECTION 1 #include -#define PIXEL_COUNT 20 +#define PIXEL_COUNT 15 #define STRIP_LENGTH (PIXEL_COUNT * 4) #define NEOPIXEL_PIN 6 @@ -13,12 +10,15 @@ CRGB leds[PIXEL_COUNT]; PixelRange ranges[4] = { 0 }; +// In this example, the ranges are static. They are used to display the same set of +// leds 4 times (twice forward, twice backward) on the same strip. void setup() { // put your setup code here, to run once: pinMode(NEOPIXEL_PIN, OUTPUT); FastLED.addLeds(leds, STRIP_LENGTH).setCorrection(TypicalLEDStrip); FastLED.setDither( 0 ); + FastLED[0].setRangeList(ranges); // set up 4 ranges over the same LEDs. two forward (repeated), two reverse (repeated) @@ -76,10 +76,6 @@ void loop() { } } - FastLED[0].show(ranges, 255); - - // note: dither relies on show() which relies on multiple CLEDControllers, which we haven't got here (they're all FastLED[0]) - // so this will not dither unless we get that fixed somehow - delay(1000/FRAMES_PER_SECOND); - + FastLED.show(); + FastLED.delay(1000/FRAMES_PER_SECOND); } From d5bd25c12777a73a6b18e146130f769927f81776 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 15:46:51 +0000 Subject: [PATCH 22/25] KnightRider can have dither now --- examples/PixelRangeKnightRider/PixelRangeKnightRider.ino | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino index 0e20917ab0d..e0575d3a910 100644 --- a/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino +++ b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino @@ -17,7 +17,6 @@ void setup() { // put your setup code here, to run once: pinMode(NEOPIXEL_PIN, OUTPUT); FastLED.addLeds(leds, STRIP_LENGTH).setCorrection(TypicalLEDStrip); - FastLED.setDither( 0 ); FastLED[0].setRangeList(ranges); // set up 4 ranges over the same LEDs. two forward (repeated), two reverse (repeated) From 74f7b7dfc5a015deee307f7509fec8b7cea2ad51 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 15:56:19 +0000 Subject: [PATCH 23/25] Fix some off by one bugs with this demo, use better constants, and FastLED.show() / FastLED.delay() --- .../PixelRangeRandomiser.ino | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino b/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino index c1a43a3b220..c81947bc385 100644 --- a/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino +++ b/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino @@ -1,23 +1,29 @@ -#include - +#define FRAMES_PER_SECOND 30 #define FASTLED_ALLOW_INTERRUPTS 1 +#include + #define NEOPIXEL_PIN 6 #define STRIP_LENGTH 60 #define RANGE_LENGTH 6 +#define RANGE_COUNT 10 CRGB leds[STRIP_LENGTH]; -PixelRange ranges[10] = { 0 }; +PixelRange ranges[RANGE_COUNT] = { 0 }; + +// In this example, the strip is divided up into an equal number of equal sized ranges. +// Every few seconds, we then randomise which section of the strip the range actually +// points to (some parts may show multiple times, or not at all), and whether forward/reverse. void randomise() { - uint8_t i = random8(10); - uint8_t j = random8(10); + uint8_t i = random8(RANGE_COUNT); + uint8_t j = random8(RANGE_COUNT); if(random8(2)) { ranges[i].leds = &leds[RANGE_LENGTH*j]; ranges[i].skip = 3; } else { - ranges[i].leds = &leds[RANGE_LENGTH*(j+1)-1]; + ranges[i].leds = &leds[(RANGE_LENGTH*j)+RANGE_LENGTH-1]; ranges[i].skip = -3; } } @@ -27,19 +33,22 @@ void setup() { pinMode(NEOPIXEL_PIN, OUTPUT); FastLED.addLeds(leds, STRIP_LENGTH).setCorrection(TypicalLEDStrip); - FastLED.setDither( 0 ); + FastLED.setBrightness(255); // link the linked list - for(uint8_t i = 0; i<10; i++) { + for(uint8_t i = 0; i 2500000) { start = end; randomise(); } - // FastLED.delay(50); - delay(10); + } From a278d92e0de6fd93ccb8227b6fac3bf1ac4bd131 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 15:59:37 +0000 Subject: [PATCH 24/25] Fix some const stuff --- src/controller.h | 2 +- src/pixeltypes.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controller.h b/src/controller.h index 6a90b510dd1..53f9c520d21 100644 --- a/src/controller.h +++ b/src/controller.h @@ -119,7 +119,7 @@ class CLEDController { } /// set the default RangeList to be used by this controller. Note that showLeds() will prefer this to the LED array if it is present at all - CLEDController & setRangeList(PixelRange *data) { + CLEDController & setRangeList(const struct PixelRange *data) { m_RangeList = data; return *this; } diff --git a/src/pixeltypes.h b/src/pixeltypes.h index 0af005c49f6..f83cb6278dd 100644 --- a/src/pixeltypes.h +++ b/src/pixeltypes.h @@ -865,8 +865,8 @@ enum EOrder { // * using two of these, you can display a reflection that is twice as long as the amount of actual data by show the data and then showing the same data in reverse // * with 10 of these, on a 10x10 grid, if you set the skip value to 10*sizeof(CRGB), you can rotate the display 90 degrees struct PixelRange { - const CRGB *leds; - const PixelRange *next; + CRGB *leds; + PixelRange *next; uint8_t length; int8_t skip; }; From 3e2a983a0f275d42ca6d82cc6c336168d6f58c49 Mon Sep 17 00:00:00 2001 From: Ben Isaacs <75862+ben-xo@users.noreply.github.com> Date: Sun, 9 Jan 2022 16:08:23 +0000 Subject: [PATCH 25/25] Add explanation to the scrolling comet --- .../PixelRangeScrollingComet/PixelRangeScrollingComet.ino | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino index a67b7b49e52..9735b49499c 100644 --- a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -14,6 +14,11 @@ PixelRange middle; PixelRange right; +// This demo shows a "comet" whizzing round and round the strip. The interesting this about this demo is how +// little RAM it uses: even though the strip is 60 pixels long (which would usually use 180 bytes just for the pixels), +// this sketch only requires around 150 bytes of ram in total: 16 * 3 for the comet's pixels, 3 for the black pixel (reused a lot) +// and then 6 bytes each for left, middle and right PixelRanges, and then of course all the other stuff such as the clock etc. + void setup() { // put your setup code here, to run once: pinMode(NEOPIXEL_PIN, OUTPUT);