diff --git a/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino new file mode 100644 index 00000000000..e0575d3a910 --- /dev/null +++ b/examples/PixelRangeKnightRider/PixelRangeKnightRider.ino @@ -0,0 +1,80 @@ +#define FRAMES_PER_SECOND 30 + +#include + +#define PIXEL_COUNT 15 +#define STRIP_LENGTH (PIXEL_COUNT * 4) +#define NEOPIXEL_PIN 6 + +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[0].setRangeList(ranges); + + // 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.show(); + FastLED.delay(1000/FRAMES_PER_SECOND); +} diff --git a/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino b/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino new file mode 100644 index 00000000000..c81947bc385 --- /dev/null +++ b/examples/PixelRangeRandomiser/PixelRangeRandomiser.ino @@ -0,0 +1,74 @@ +#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[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(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)+RANGE_LENGTH-1]; + ranges[i].skip = -3; + } +} + +void setup() { + + pinMode(NEOPIXEL_PIN, OUTPUT); + + FastLED.addLeds(leds, STRIP_LENGTH).setCorrection(TypicalLEDStrip); + FastLED.setBrightness(255); + + // link the linked list + for(uint8_t i = 0; i 2500000) { + start = end; + randomise(); + } + +} diff --git a/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino new file mode 100644 index 00000000000..9735b49499c --- /dev/null +++ b/examples/PixelRangeScrollingComet/PixelRangeScrollingComet.ino @@ -0,0 +1,115 @@ +#include + +#define FRAMES_PER_SECOND 50 + +#define STRIP_LENGTH 60 +#define COMET_LENGTH 16 +#define NEOPIXEL_PIN 6 + +CRGB cometPixel[COMET_LENGTH]; +CRGB spacePixel[1] = { CRGB::Black }; + +PixelRange left; +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); + FastLED.addLeds(cometPixel, STRIP_LENGTH).setCorrection(TypicalLEDStrip); + FastLED.setBrightness(192); + + FastLED[0].setRangeList(&left); + + // draw the comet. + for (uint8_t i = 0; i < COMET_LENGTH; i++) { + cometPixel[i] = CRGB::White; + cometPixel[i].fadeToBlackBy((i*256)/COMET_LENGTH); + } + + // link the linked list + left.next = &middle; + middle.next = &right; + right.next = NULL; + +} + +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.length = COMET_LENGTH; + middle.skip = -3; + + right.leds = &spacePixel[0]; + right.length = STRIP_LENGTH - COMET_LENGTH; + right.skip = 0; +} + +void configureSplitComet() { + left.leds = &cometPixel[COMET_LENGTH-1]; + left.length = 0; + left.skip = -3; + + middle.leds = &spacePixel[0]; + middle.length = STRIP_LENGTH - COMET_LENGTH; + middle.skip = 0; + + right.leds = &cometPixel[COMET_LENGTH-1]; + right.length = COMET_LENGTH; + right.skip = -3; +} + + +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 = comet_head + COMET_LENGTH; + + 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) { + // 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) { + // in split comet + left.leds = &cometPixel[comet_tail - STRIP_LENGTH]; + left.length++; + right.length--; + + } else { + // in split space + left.length++; + right.length--; + } + + FastLED.show(); + + current_position++; + if(current_position == STRIP_LENGTH) { + current_position = 0; + } + + FastLED.delay(1000/FRAMES_PER_SECOND); + +} diff --git a/src/controller.h b/src/controller.h index 7b7a7cf9408..53f9c520d21 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; @@ -59,6 +60,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 +82,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)); @@ -87,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 @@ -107,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(const struct PixelRange *data) { + m_RangeList = data; + return *this; + } + /// zero out the led data managed by this controller void clearLedData() { if(m_Data) { @@ -390,6 +407,18 @@ 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 *)range->leds; + pixels.mLen = pixels.mLenRemaining = range->length; + pixels.mAdvance = range->skip; + showPixels(pixels); + range = range->next; + } while(range); + } + /// 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 +441,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() {} }; diff --git a/src/pixeltypes.h b/src/pixeltypes.h index f28c2c670a7..f83cb6278dd 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 { + CRGB *leds; + PixelRange *next; + uint8_t length; + int8_t skip; +}; + + FASTLED_NAMESPACE_END ///@} diff --git a/src/platforms/avr/clockless_trinket.h b/src/platforms/avr/clockless_trinket.h index 57cb6c60a7e..ad73860003d 100644 --- a/src/platforms/avr/clockless_trinket.h +++ b/src/platforms/avr/clockless_trinket.h @@ -147,6 +147,57 @@ class ClocklessController : public CPixelLEDController { CMinWait mWait; + 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; + } + +#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. + uint16_t microsTaken = (uint32_t)nLeds * (uint32_t)CLKS_TO_MICROS((24) * (T1 + T2 + T3)); + MS_COUNTER += (microsTaken >> 10); +#endif + +#endif + } + public: virtual void init() { FastPin::setOutput(); @@ -155,6 +206,42 @@ class ClocklessController : public CPixelLEDController { 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(); @@ -164,62 +251,17 @@ class ClocklessController : public CPixelLEDController { if(pixels.mLen > 0) { showRGBInternal(pixels); + juggleClock(pixels.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)); - - // 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); - - // 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. - uint16_t microsTaken = (uint32_t)nLeds * (uint32_t)CLKS_TO_MICROS((24) * (T1 + T2 + T3)); - MS_COUNTER += (microsTaken >> 10); -#endif - -#endif #if (!defined(FASTLED_ALLOW_INTERRUPTS) || FASTLED_ALLOW_INTERRUPTS == 0) sei(); #endif mWait.mark(); } + + #define USE_ASM_MACROS #if defined(__AVR_ATmega4809__)