#ifndef __INC_CONTROLLER_H #define __INC_CONTROLLER_H ///@file controller.h /// base definitions used by led controllers for writing out led data #include "FastLED.h" #include "led_sysdefs.h" #include "pixeltypes.h" #include "color.h" #include FASTLED_NAMESPACE_BEGIN #define RO(X) RGB_BYTE(RGB_ORDER, X) #define RGB_BYTE(RO,X) (((RO)>>(3*(2-(X)))) & 0x3) #define RGB_BYTE0(RO) ((RO>>6) & 0x3) #define RGB_BYTE1(RO) ((RO>>3) & 0x3) #define RGB_BYTE2(RO) ((RO) & 0x3) // operator byte *(struct CRGB[] arr) { return (byte*)arr; } #define DISABLE_DITHER 0x00 #define BINARY_DITHER 0x01 typedef uint8_t EDitherMode; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // LED Controller interface definition // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Base definition for an LED controller. Pretty much the methods that every LED controller object will make available. /// Note that the showARGB method is not impelemented for all controllers yet. Note also the methods for eventual checking /// of background writing of data (I'm looking at you, teensy 3.0 DMA controller!). If you want to pass LED controllers around /// to methods, make them references to this type, keeps your code saner. However, most people won't be seeing/using these objects /// directly at all class CLEDController { protected: friend class CFastLED; CRGB *m_Data; CLEDController *m_pNext; CRGB m_ColorCorrection; CRGB m_ColorTemperature; EDitherMode m_DitherMode; int m_nLeds; static CLEDController *m_pHead; static CLEDController *m_pTail; /// set all the leds on the controller to a given color ///@param data the crgb color to set the leds to ///@param nLeds the number 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; /// 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; 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; } if(m_pTail != NULL) { m_pTail->m_pNext = this; } m_pTail = this; } ///initialize the LED controller virtual void init() = 0; ///clear out/zero out the given number of leds. virtual void clearLeds(int nLeds) { showColor(CRGB::Black, nLeds, CRGB::Black); } /// 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 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 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)); } /// 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; } /// 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? virtual int size() { return m_nLeds; } /// Pointer to the CRGB array for this controller CRGB* leds() { return m_Data; } /// 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) { return computeAdjustment(scale, m_ColorCorrection, m_ColorTemperature); } 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. template struct PixelController { const uint8_t *mData; int mLen,mLenRemaining; uint8_t d[3]; uint8_t e[3]; CRGB mScale; int8_t mAdvance; int mOffsets[LANES]; PixelController(const PixelController & 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; mLenRemaining = 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<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 uint8_t R = 0; ++R; // R is wrapped around at 2^ditherBits, // so if ditherBits is 2, R will cycle through (0,1,2,3) uint8_t 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) uint8_t 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) { uint8_t s = mScale.raw[i]; e[i] = s ? (256/s) + 1 : 0; d[i] = scale8(Q, e[i]); #if (FASTLED_SCALE8_FIXED == 1) if(d[i]) (--d[i]); #endif if(e[i]) --e[i]; } #endif } // Do we have n pixels left to process? __attribute__((always_inline)) inline bool has(int n) { return mLenRemaining >= 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; } } __attribute__((always_inline)) inline int size() { return mLen; } // 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; --mLenRemaining;} // 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 __attribute__((always_inline)) inline static uint8_t loadByte(PixelController & pc) { return pc.mData[RO(SLOT)]; } template __attribute__((always_inline)) inline static uint8_t loadByte(PixelController & pc, int lane) { return pc.mData[pc.mOffsets[lane] + RO(SLOT)]; } template __attribute__((always_inline)) inline static uint8_t dither(PixelController & pc, uint8_t b) { return b ? qadd8(b, pc.d[RO(SLOT)]) : 0; } template __attribute__((always_inline)) inline static uint8_t dither(PixelController & , uint8_t b, uint8_t d) { return b ? qadd8(b,d) : 0; } template __attribute__((always_inline)) inline static uint8_t scale(PixelController & pc, uint8_t b) { return scale8(b, pc.mScale.raw[RO(SLOT)]); } template __attribute__((always_inline)) inline static uint8_t scale(PixelController & , uint8_t b, uint8_t scale) { return scale8(b, scale); } // composite shortcut functions for loading, dithering, and scaling template __attribute__((always_inline)) inline static uint8_t loadAndScale(PixelController & pc) { return scale(pc, pc.dither(pc, pc.loadByte(pc))); } template __attribute__((always_inline)) inline static uint8_t loadAndScale(PixelController & pc, int lane) { return scale(pc, pc.dither(pc, pc.loadByte(pc, lane))); } template __attribute__((always_inline)) inline static uint8_t loadAndScale(PixelController & pc, int lane, uint8_t d, uint8_t scale) { return scale8(pc.dither(pc, pc.loadByte(pc, lane), d), scale); } template __attribute__((always_inline)) inline static uint8_t loadAndScale(PixelController & pc, int lane, uint8_t scale) { return scale8(pc.loadByte(pc, lane), scale); } template __attribute__((always_inline)) inline static uint8_t advanceAndLoadAndScale(PixelController & pc) { pc.advanceData(); return pc.loadAndScale(pc); } template __attribute__((always_inline)) inline static uint8_t advanceAndLoadAndScale(PixelController & pc, int lane) { pc.advanceData(); return pc.loadAndScale(pc, lane); } template __attribute__((always_inline)) inline static uint8_t advanceAndLoadAndScale(PixelController & pc, int lane, uint8_t scale) { pc.advanceData(); return pc.loadAndScale(pc, lane, scale); } template __attribute__((always_inline)) inline static uint8_t getd(PixelController & pc) { return pc.d[RO(SLOT)]; } template __attribute__((always_inline)) inline static uint8_t getscale(PixelController & pc) { return pc.mScale.raw[RO(SLOT)]; } // Helper functions to get around gcc stupidities __attribute__((always_inline)) inline uint8_t loadAndScale0(int lane, uint8_t scale) { return loadAndScale<0>(*this, lane, scale); } __attribute__((always_inline)) inline uint8_t loadAndScale1(int lane, uint8_t scale) { return loadAndScale<1>(*this, lane, scale); } __attribute__((always_inline)) inline uint8_t loadAndScale2(int lane, uint8_t scale) { return loadAndScale<2>(*this, lane, scale); } __attribute__((always_inline)) inline uint8_t advanceAndLoadAndScale0(int lane, uint8_t scale) { return advanceAndLoadAndScale<0>(*this, lane, scale); } __attribute__((always_inline)) inline uint8_t stepAdvanceAndLoadAndScale0(int lane, uint8_t scale) { stepDithering(); return advanceAndLoadAndScale<0>(*this, lane, scale); } __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 advanceAndLoadAndScale0(int lane) { return advanceAndLoadAndScale<0>(*this, lane); } __attribute__((always_inline)) inline uint8_t stepAdvanceAndLoadAndScale0(int lane) { stepDithering(); return advanceAndLoadAndScale<0>(*this, lane); } __attribute__((always_inline)) inline uint8_t loadAndScale0() { return loadAndScale<0>(*this); } __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 getScale0() { return getscale<0>(*this); } __attribute__((always_inline)) inline uint8_t getScale1() { return getscale<1>(*this); } __attribute__((always_inline)) inline uint8_t getScale2() { return getscale<2>(*this); } }; template class CPixelLEDController : public CLEDController { protected: virtual void showPixels(PixelController & pixels) = 0; /// 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) { PixelController pixels(data, nLeds, scale, getDither()); showPixels(pixels); } /// 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) { PixelController pixels(data, nLeds < 0 ? -nLeds : nLeds, scale, getDither()); if(nLeds < 0) { // nLeds < 0 implies that we want to show them in reverse pixels.mAdvance = -pixels.mAdvance; } showPixels(pixels); } public: CPixelLEDController() : CLEDController() {} }; FASTLED_NAMESPACE_END #endif