diff options
Diffstat (limited to 'controller.h')
-rw-r--r-- | controller.h | 332 |
1 files changed, 292 insertions, 40 deletions
diff --git a/controller.h b/controller.h index a2519e1f..b9929a4f 100644 --- a/controller.h +++ b/controller.h @@ -1,10 +1,15 @@ #ifndef __INC_CONTROLLER_H #define __INC_CONTROLLER_H +///@file controller.h +/// base definitions used by led controllers for writing out led data + #include "led_sysdefs.h" #include "pixeltypes.h" #include "color.h" +FASTLED_NAMESPACE_BEGIN + #define RO(X) RGB_BYTE(RGB_ORDER, X) #define RGB_BYTE(RO,X) (((RO)>>(3*(2-(X)))) & 0x3) @@ -41,11 +46,16 @@ protected: static CLEDController *m_pHead; static CLEDController *m_pTail; - // set all the leds on the controller to a given color + /// 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 + ///@param scale the rgb scaling value for outputting color virtual void showColor(const struct CRGB & data, int nLeds, CRGB scale) = 0; - // note that the uint8_ts will be in the order that you want them sent out to the device. - // nLeds is the number of RGB leds being written to + /// write the passed in rgb data out to the leds managed by this controller + ///@param data the rgb data to write out to the strip + ///@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; #ifdef SUPPORT_ARGB @@ -53,6 +63,7 @@ protected: virtual void show(const struct CARGB *data, int nLeds, CRGB scale) = 0; #endif public: + /// create an led controller object, add it to the chain of controllers CLEDController() : m_Data(NULL), m_ColorCorrection(UncorrectedColor), m_ColorTemperature(UncorrectedTemperature), m_DitherMode(BINARY_DITHER), m_nLeds(0) { m_pNext = NULL; if(m_pHead==NULL) { m_pHead = this; } @@ -60,33 +71,35 @@ public: m_pTail = this; } - // initialize the LED controller + ///initialize the LED controller virtual void init() = 0; - // clear out/zero out the given number of leds. + ///clear out/zero out the given number of leds. virtual void clearLeds(int nLeds) = 0; - // show function w/integer brightness, will scale for color correction and temperature + /// show function w/integer brightness, will scale for color correction and temperature void show(const struct CRGB *data, int nLeds, uint8_t brightness) { show(data, nLeds, getAdjustment(brightness)); } - // show function w/integer brightness, will scale for color correction and temperature + /// 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)); } - // show function using the "attached to this controller" led data + /// show function using the "attached to this controller" led data void showLeds(uint8_t brightness=255) { show(m_Data, m_nLeds, getAdjustment(brightness)); } + /// show the given color on the led strip void showColor(const struct CRGB & data, uint8_t brightness=255) { showColor(data, m_nLeds, getAdjustment(brightness)); } - // navigating the list of controllers + /// get the first led controller in the chain of controllers static CLEDController *head() { return m_pHead; } + /// get the next controller in the chain after this one. will return NULL at the end of the chain CLEDController *next() { return m_pNext; } #ifdef SUPPORT_ARGB @@ -96,64 +109,80 @@ public: } #endif + /// set the default array of leds to be used by this controller CLEDController & setLeds(CRGB *data, int nLeds) { m_Data = data; m_nLeds = nLeds; return *this; } + /// zero out the led data managed by this controller void clearLedData() { if(m_Data) { memset8((void*)m_Data, 0, sizeof(struct CRGB) * m_nLeds); } } - // How many leds does this controller manage? + /// How many leds does this controller manage? int size() { return m_nLeds; } - // Pointer to the CRGB array for this controller + /// Pointer to the CRGB array for this controller CRGB* leds() { return m_Data; } - // Reference to the n'th item in the controller + /// Reference to the n'th item in the controller CRGB &operator[](int x) { return m_Data[x]; } + /// set the dithering mode for this controller to use inline CLEDController & setDither(uint8_t ditherMode = BINARY_DITHER) { m_DitherMode = ditherMode; return *this; } + /// get the dithering option currently set for this controller inline uint8_t getDither() { return m_DitherMode; } + /// the the color corrction to use for this controller, expressed as an rgb object CLEDController & setCorrection(CRGB correction) { m_ColorCorrection = correction; return *this; } + /// set the color correction to use for this controller CLEDController & setCorrection(LEDColorCorrection correction) { m_ColorCorrection = correction; return *this; } + /// get the correction value used by this controller CRGB getCorrection() { return m_ColorCorrection; } + /// set the color temperature, aka white point, for this controller CLEDController & setTemperature(CRGB temperature) { m_ColorTemperature = temperature; return *this; } + /// set the color temperature, aka white point, for this controller CLEDController & setTemperature(ColorTemperature temperature) { m_ColorTemperature = temperature; return *this; } + /// get the color temperature, aka whipe point, for this controller CRGB getTemperature() { return m_ColorTemperature; } + /// Get the combined brightness/color adjustment for this controller CRGB getAdjustment(uint8_t scale) { -#if defined(NO_CORRECTION) && (NO_CORRECTION==1) - return CRGB(scale,scale,scale); -#else - CRGB adj(0,0,0); - - if(scale > 0) { - for(uint8_t i = 0; i < 3; i++) { - uint8_t cc = m_ColorCorrection.raw[i]; - uint8_t ct = m_ColorTemperature.raw[i]; - if(cc > 0 && ct > 0) { - uint32_t work = (((uint32_t)cc)+1) * (((uint32_t)ct)+1) * scale; - work /= 0x10000L; - adj.raw[i] = work & 0xFF; - } - } - } + return computeAdjustment(scale, m_ColorCorrection, m_ColorTemperature); + } - return adj; -#endif + static CRGB computeAdjustment(uint8_t scale, const CRGB & colorCorrection, const CRGB & colorTemperature) { + #if defined(NO_CORRECTION) && (NO_CORRECTION==1) + return CRGB(scale,scale,scale); + #else + CRGB adj(0,0,0); + + if(scale > 0) { + for(uint8_t i = 0; i < 3; i++) { + uint8_t cc = colorCorrection.raw[i]; + uint8_t ct = colorTemperature.raw[i]; + if(cc > 0 && ct > 0) { + uint32_t work = (((uint32_t)cc)+1) * (((uint32_t)ct)+1) * scale; + work /= 0x10000L; + adj.raw[i] = work & 0xFF; + } + } + } + + return adj; + #endif } + virtual uint16_t getMaxRefreshRate() const { return 0; } }; -// Pixel controller class. This is the class that we use to centralize pixel access in a block of data, including -// support for things like RGB reordering, scaling, dithering, skipping (for ARGB data), and eventually, we will -// centralize 8/12/16 conversions here as well. +/// Pixel controller class. This is the class that we use to centralize pixel access in a block of data, including +/// support for things like RGB reordering, scaling, dithering, skipping (for ARGB data), and eventually, we will +/// centralize 8/12/16 conversions here as well. template<EOrder RGB_ORDER> struct PixelController { const uint8_t *mData; @@ -163,6 +192,7 @@ struct PixelController { CRGB mScale; uint8_t mAdvance; + ///copy constructor for the pixel controller object PixelController(const PixelController & other) { d[0] = other.d[0]; d[1] = other.d[1]; @@ -176,6 +206,15 @@ struct PixelController { mLen = other.mLen; } + + /// create a pixel controller for managing led data as it is being written out + ///@{ + ///@param d the led data this controller is managing + ///@param len the number of leds this controller is managing + ///@param s the combined rgb scaling adjustment for the leds + ///@param dither the dither mode for these pixels + ///@param advance whether or not to walk through the array of data for each pixel, or just write out the first pixel len times + ///@param skip whether or not there is extra data to skip when writing out led data, e.g. if passed in argb data PixelController(const uint8_t *d, int len, CRGB & s, EDitherMode dither = BINARY_DITHER, bool advance=true, uint8_t skip=0) : mData(d), mLen(len), mScale(s) { enable_dithering(dither); mData += skip; @@ -207,7 +246,9 @@ struct PixelController { mAdvance = 4; } #endif + ///@} + /// initialize the binary dithering for this controller void init_binary_dithering() { #if !defined(NO_DITHERING) || (NO_DITHERING != 1) @@ -235,6 +276,7 @@ struct PixelController { (UPDATES_PER_FULL_DITHER_CYCLE>64) + \ (UPDATES_PER_FULL_DITHER_CYCLE>128) ) #define VIRTUAL_BITS RECOMMENDED_VIRTUAL_BITS + // R is the digther signal 'counter'. static byte R = 0; R++; @@ -283,12 +325,12 @@ struct PixelController { #endif } - // Do we have n pixels left to process? + /// Do we have n pixels left to process? __attribute__((always_inline)) inline bool has(int n) { return mLen >= n; } - // toggle dithering enable + /// toggle dithering enable void enable_dithering(EDitherMode dither) { switch(dither) { case BINARY_DITHER: init_binary_dithering(); break; @@ -296,13 +338,13 @@ struct PixelController { } } - // get the amount to advance the pointer by + /// get the amount to advance the pointer by __attribute__((always_inline)) inline int advanceBy() { return mAdvance; } - // advance the data pointer forward, adjust position counter + /// advance the data pointer forward, adjust position counter __attribute__((always_inline)) inline void advanceData() { mData += mAdvance; mLen--;} - // step the dithering forward + /// step the dithering forward __attribute__((always_inline)) inline void stepDithering() { // IF UPDATING HERE, BE SURE TO UPDATE THE ASM VERSION IN // clockless_trinket.h! @@ -311,7 +353,7 @@ struct PixelController { d[2] = e[2] - d[2]; } - // Some chipsets pre-cycle the first byte, which means we want to cycle byte 0's dithering separately + /// Some chipsets pre-cycle the first byte, which means we want to cycle byte 0's dithering separately __attribute__((always_inline)) inline void preStepFirstByteDithering() { d[RO(0)] = e[RO(0)] - d[RO(0)]; } @@ -329,7 +371,217 @@ struct PixelController { __attribute__((always_inline)) inline uint8_t loadAndScale1() { return loadAndScale<1>(*this); } __attribute__((always_inline)) inline uint8_t loadAndScale2() { return loadAndScale<2>(*this); } __attribute__((always_inline)) inline uint8_t advanceAndLoadAndScale0() { return advanceAndLoadAndScale<0>(*this); } - __attribute__((always_inline)) inline uint8_t stepAdvanceAndLoadAndScale0() { stepDithering(); return advanceAndLoadAndScale<0>(*this); } + __attribute__((always_inline)) inline uint8_t stepAdvanceAndLoadAndScale0() { stepDithering(); return advanceAndLoadAndScale<0>(*this); } +}; + +// Pixel controller class. This is the class that we use to centralize pixel access in a block of data, including +// support for things like RGB reordering, scaling, dithering, skipping (for ARGB data), and eventually, we will +// centralize 8/12/16 conversions here as well. +template<int LANES,int MASK, EOrder RGB_ORDER> +struct MultiPixelController { + const uint8_t *mData; + int mLen; + uint8_t d[3]; + uint8_t e[3]; + CRGB mScale; + int8_t mAdvance; + int mOffsets[LANES]; + + MultiPixelController(const MultiPixelController & other) { + d[0] = other.d[0]; + d[1] = other.d[1]; + d[2] = other.d[2]; + e[0] = other.e[0]; + e[1] = other.e[1]; + e[2] = other.e[2]; + mData = other.mData; + mScale = other.mScale; + mAdvance = other.mAdvance; + mLen = other.mLen; + for(int i = 0; i < LANES; i++) { mOffsets[i] = other.mOffsets[i]; } + + } + + void initOffsets(int len) { + int nOffset = 0; + for(int i = 0; i < LANES; i++) { + mOffsets[i] = nOffset; + if((1<<i) & MASK) { nOffset += (len * mAdvance); } + } + } + + MultiPixelController(const uint8_t *d, int len, CRGB & s, EDitherMode dither = BINARY_DITHER, bool advance=true, uint8_t skip=0) : mData(d), mLen(len), mScale(s) { + enable_dithering(dither); + mData += skip; + mAdvance = (advance) ? 3+skip : 0; + initOffsets(len); + } + + MultiPixelController(const CRGB *d, int len, CRGB & s, EDitherMode dither = BINARY_DITHER) : mData((const uint8_t*)d), mLen(len), mScale(s) { + enable_dithering(dither); + mAdvance = 3; + initOffsets(len); + } + + MultiPixelController(const CRGB &d, int len, CRGB & s, EDitherMode dither = BINARY_DITHER) : mData((const uint8_t*)&d), mLen(len), mScale(s) { + enable_dithering(dither); + mAdvance = 0; + initOffsets(len); + } + +#ifdef SUPPORT_ARGB + MultiPixelController(const CARGB &d, int len, CRGB & s, EDitherMode dither = BINARY_DITHER) : mData((const uint8_t*)&d), mLen(len), mScale(s) { + enable_dithering(dither); + // skip the A in CARGB + mData += 1; + mAdvance = 0; + initOffsets(len); + } + + MultiPixelController(const CARGB *d, int len, CRGB & s, EDitherMode dither = BINARY_DITHER) : mData((const uint8_t*)d), mLen(len), mScale(s) { + enable_dithering(dither); + // skip the A in CARGB + mData += 1; + mAdvance = 4; + initOffsets(len); + } +#endif + + void init_binary_dithering() { +#if !defined(NO_DITHERING) || (NO_DITHERING != 1) + + // Set 'virtual bits' of dithering to the highest level + // that is not likely to cause excessive flickering at + // low brightness levels + low update rates. + // These pre-set values are a little ambitious, since + // a 400Hz update rate for WS2811-family LEDs is only + // possible with 85 pixels or fewer. + // Once we have a 'number of milliseconds since last update' + // value available here, we can quickly calculate the correct + // number of 'virtual bits' on the fly with a couple of 'if' + // statements -- no division required. At this point, + // the division is done at compile time, so there's no runtime + // cost, but the values are still hard-coded. +#define MAX_LIKELY_UPDATE_RATE_HZ 400 +#define MIN_ACCEPTABLE_DITHER_RATE_HZ 50 +#define UPDATES_PER_FULL_DITHER_CYCLE (MAX_LIKELY_UPDATE_RATE_HZ / MIN_ACCEPTABLE_DITHER_RATE_HZ) +#define RECOMMENDED_VIRTUAL_BITS ((UPDATES_PER_FULL_DITHER_CYCLE>1) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>2) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>4) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>8) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>16) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>32) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>64) + \ + (UPDATES_PER_FULL_DITHER_CYCLE>128) ) +#define VIRTUAL_BITS RECOMMENDED_VIRTUAL_BITS + + // R is the digther signal 'counter'. + static byte R = 0; + R++; + + // R is wrapped around at 2^ditherBits, + // so if ditherBits is 2, R will cycle through (0,1,2,3) + byte ditherBits = VIRTUAL_BITS; + R &= (0x01 << ditherBits) - 1; + + // Q is the "unscaled dither signal" itself. + // It's initialized to the reversed bits of R. + // If 'ditherBits' is 2, Q here will cycle through (0,128,64,192) + byte Q = 0; + + // Reverse bits in a byte + { + if(R & 0x01) { Q |= 0x80; } + if(R & 0x02) { Q |= 0x40; } + if(R & 0x04) { Q |= 0x20; } + if(R & 0x08) { Q |= 0x10; } + if(R & 0x10) { Q |= 0x08; } + if(R & 0x20) { Q |= 0x04; } + if(R & 0x40) { Q |= 0x02; } + if(R & 0x80) { Q |= 0x01; } + } + + // Now we adjust Q to fall in the center of each range, + // instead of at the start of the range. + // If ditherBits is 2, Q will be (0, 128, 64, 192) at first, + // and this adjustment makes it (31, 159, 95, 223). + if( ditherBits < 8) { + Q += 0x01 << (7 - ditherBits); + } + + // D and E form the "scaled dither signal" + // which is added to pixel values to affect the + // actual dithering. + + // Setup the initial D and E values + for(int i = 0; i < 3; i++) { + byte s = mScale.raw[i]; + e[i] = s ? (256/s) + 1 : 0; + d[i] = scale8(Q, e[i]); + if(e[i]) e[i]--; + } +#endif + } + + // Do we have n pixels left to process? + __attribute__((always_inline)) inline bool has(int n) { + return mLen >= n; + } + + // toggle dithering enable + void enable_dithering(EDitherMode dither) { + switch(dither) { + case BINARY_DITHER: init_binary_dithering(); break; + default: d[0]=d[1]=d[2]=e[0]=e[1]=e[2]=0; break; + } + } + + // get the amount to advance the pointer by + __attribute__((always_inline)) inline int advanceBy() { return mAdvance; } + + // advance the data pointer forward, adjust position counter + __attribute__((always_inline)) inline void advanceData() { mData += mAdvance; mLen--;} + + // step the dithering forward + __attribute__((always_inline)) inline void stepDithering() { + // IF UPDATING HERE, BE SURE TO UPDATE THE ASM VERSION IN + // clockless_trinket.h! + d[0] = e[0] - d[0]; + d[1] = e[1] - d[1]; + d[2] = e[2] - d[2]; + } + + // Some chipsets pre-cycle the first byte, which means we want to cycle byte 0's dithering separately + __attribute__((always_inline)) inline void preStepFirstByteDithering() { + d[RO(0)] = e[RO(0)] - d[RO(0)]; + } + + template<int SLOT> __attribute__((always_inline)) inline static uint8_t loadByte(MultiPixelController & pc, int lane) { return pc.mData[pc.mOffsets[lane] + RO(SLOT)]; } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t dither(MultiPixelController & pc, uint8_t b) { return b ? qadd8(b, pc.d[RO(SLOT)]) : 0; } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t dither(MultiPixelController & pc, uint8_t b, uint8_t d) { return b ? qadd8(b,d) : 0; } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t scale(MultiPixelController & pc, uint8_t b) { return scale8(b, pc.mScale.raw[RO(SLOT)]); } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t scale(MultiPixelController & pc, uint8_t b, uint8_t scale) { return scale8(b, scale); } + + // composite shortcut functions for loading, dithering, and scaling + template<int SLOT> __attribute__((always_inline)) inline static uint8_t loadAndScale(MultiPixelController & pc, int lane) { return scale<SLOT>(pc, pc.dither<SLOT>(pc, pc.loadByte<SLOT>(pc, lane))); } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t loadAndScale(MultiPixelController & pc, int lane, uint8_t d, uint8_t scale) { return scale8(pc.dither<SLOT>(pc, pc.loadByte<SLOT>(pc, lane), d), scale); } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t loadAndScale(MultiPixelController & pc, int lane, uint8_t scale) { return scale8(pc.loadByte<SLOT>(pc, lane), scale); } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t advanceAndLoadAndScale(MultiPixelController & pc, int lane) { pc.advanceData(); return pc.loadAndScale<SLOT>(pc, lane); } + + template<int SLOT> __attribute__((always_inline)) inline static uint8_t getd(MultiPixelController & pc) { return pc.d[RO(SLOT)]; } + template<int SLOT> __attribute__((always_inline)) inline static uint8_t getscale(MultiPixelController & pc) { return pc.mScale.raw[RO(SLOT)]; } + + // Helper functions to get around gcc stupidities + __attribute__((always_inline)) inline uint8_t loadAndScale0(int lane) { return loadAndScale<0>(*this, lane); } + __attribute__((always_inline)) inline uint8_t loadAndScale1(int lane) { return loadAndScale<1>(*this, lane); } + __attribute__((always_inline)) inline uint8_t loadAndScale2(int lane) { return loadAndScale<2>(*this, lane); } + __attribute__((always_inline)) inline uint8_t loadAndScale0(int lane, uint8_t d, uint8_t scale) { return loadAndScale<0>(*this, lane, d, scale); } + __attribute__((always_inline)) inline uint8_t loadAndScale1(int lane, uint8_t d, uint8_t scale) { return loadAndScale<1>(*this, lane, d, scale); } + __attribute__((always_inline)) inline uint8_t loadAndScale2(int lane, uint8_t d, uint8_t scale) { return loadAndScale<2>(*this, lane, d, scale); } + __attribute__((always_inline)) inline uint8_t advanceAndLoadAndScale0(int lane) { return advanceAndLoadAndScale<0>(*this, lane); } + __attribute__((always_inline)) inline uint8_t stepAdvanceAndLoadAndScale0(int lane) { stepDithering(); return advanceAndLoadAndScale<0>(*this, lane); } }; +FASTLED_NAMESPACE_END + #endif |