commit eb23dd1cffec5f8b722c331c5bbd65e031f9c579 Author: KurtMF Date: Sun Nov 24 14:08:32 2024 -0500 Add files via upload diff --git a/ObjectFLED.h b/ObjectFLED.h new file mode 100644 index 0000000..b3c427d --- /dev/null +++ b/ObjectFLED.h @@ -0,0 +1,175 @@ +/* ObjectFLED - Teensy 4.x DMA to all pins for independent control of large and + multiple LED display objects + + Copyright (c) 2024 Kurt Funderburg + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + OctoWS2811 library code was well-studied and substantial portions of it used + to implement high-speed, non-blocking DMA for LED signal output in this library. + See ObjectFLED.cpp for a summary of changes made to the original OctoWS2811. + + OctoWS2811 - High Performance WS2811 LED Display Library + Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC + http://www.pjrc.com/teensy/td_libs_OctoWS2811.html + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef __IMXRT1062__ +#error "Sorry, ObjectFLED only works on Teensy 4.x boards." +#endif +#if TEENSYDUINO < 121 +#error "Teensyduino version 1.21 or later is required to compile this library." +#endif +#ifndef ObjectFLED_h +#define ObjectFLED_h +#include +#include "DMAChannel.h" + +// Ordinary RGB data is converted to GPIO bitmasks on-the-fly using +// a transmit buffer sized for 2 DMA transfers. The larger this setting, +// the more interrupt latency OctoWS2811 can tolerate, but the transmit +// buffer grows in size. For good performance, the buffer should be kept +// smaller than the half the Cortex-M7 data cache. +//bitdata[B_P_D * 64] buffer holds data (10KB) for 80 LED bytes: 4DW * 8b = 32DW/LEDB = 96DW/LED +//framebuffer_index = B_P_D * 2 = pointer to next block for transfer (80 LEDB / bitdata buffer) +#define BYTES_PER_DMA 20 //= number of pairs of LEDB (40=80B) bitmasks in bitdata. + +#define CORDER_RGB 0 //* WS2811, YF923 +#define CORDER_RBG 1 +#define CORDER_GRB 2 //* WS2811B, Most LED strips are wired this way +#define CORDER_GBR 3 +#define CORDER_BRG 4 //* Adafruit Product ID: 5984 As of November 5, 2024 - this strand has different 'internal' color ordering. It's now BRG not RGB, +#define CORDER_BGR 5 // Adafruit Dotstar LEDs SK9822 uses this CO but they use inverted start/stop bits +#define CORDER_RGBW 6 //* Popular +#define CORDER_RBGW 7 +#define CORDER_GRBW 8 +#define CORDER_GBRW 9 +#define CORDER_BRGW 10 +#define CORDER_BGRW 11 // Adafruit Dotstar LEDs SK9822 uses this CO but they use inverted start/stop bits +#define CORDER_WRGB 12 +#define CORDER_WRBG 13 +#define CORDER_WGRB 14 +#define CORDER_WGBR 15 +#define CORDER_WBRG 16 +#define CORDER_WBGR 17 +#define CORDER_RWGB 18 +#define CORDER_RWBG 19 +#define CORDER_GWRB 20 +#define CORDER_GWBR 21 +#define CORDER_BWRG 22 +#define CORDER_BWGR 23 +#define CORDER_RGWB 24 +#define CORDER_RBWG 25 +#define CORDER_GRWB 26 +#define CORDER_GBWR 27 +#define CORDER_BRWG 28 +#define CORDER_BGWR 29 + + +//Usage: ObjectFLED myCube ( Num_LEDs, *drawBuffer, LED_type, numPins, *pinList, serpentineNumber ) +class ObjectFLED { +public: + //Usage: ObjectFLED myCube ( Num_LEDs, *drawBuffer, LED_type, numPins, *pinList, serpentineNumber ) + ObjectFLED(uint16_t numLEDs, void* drawBuf, uint8_t config, uint8_t numPins, const uint8_t* pinList, \ + uint8_t serpentine = 0); + + ~ObjectFLED() { delete frameBuffer; } + + //begin() - Use defalut LED timing: 1.0 OC Factor, 1250 nS CLK (=800 KHz), 417 nS T0H, 834 nS T1H, 70 uS LED Latch Delay. + void begin(void); + + //begin(LED_Overclock_Factor) - divides default 1250 nS LED CLK (=800 KHz), 417 nS T0H, 834 nS T1H. + void begin(float); + + //begin(LED_Overclock_Factor, LED_Latch_Delay_uS) - divides default 1250 nS LED CLK (=800 KHz), + // 417 nS T0H, 834 nS T1H; and sets the LED Latch Delay. + void begin(float, uint16_t); + + //begin(LED_Overclock_Factor, LED_CLK_nS, LED_T0H_nS, LED_T1H_nS, LED_Latch_Delay_uS) - + //specifies full LED timing. Values given for CLK, T0H, T1H are divided by OC Factor. + void begin(float, uint16_t, uint16_t, uint16_t, uint16_t); + + void show(void); + + int busy(void); + + //Brightness values 0-255 + void setBrightness(uint8_t); + + //Color Balance is 3-byte number in RGB order. Each byte is a brightness value for that color. + void setBalance(uint32_t); + + uint8_t getBrightness() { return brightness; } + + uint32_t getBalance() { return colorBalance; } + +private: + static void isr(void); + + void genFrameBuffer(uint); + + static uint8_t* frameBuffer; //isr() + static uint32_t numbytes; //isr() + static uint8_t numpins; //isr() + static uint8_t pin_bitnum[NUM_DIGITAL_PINS]; //isr() + static uint8_t pin_offset[NUM_DIGITAL_PINS]; //isr() + DMAMEM static uint32_t bitdata[BYTES_PER_DMA * 64] __attribute__((used, aligned(32))); //isr() + DMAMEM static uint32_t bitmask[4] __attribute__((used, aligned(32))); + static DMAChannel dma1, dma2, dma3; + static DMASetting dma2next; + + uint32_t update_begin_micros = 0; + uint8_t brightness = 255; + uint32_t colorBalance = 0xFFFFFF; + uint32_t rLevel = 65025; + uint32_t gLevel = 65025; + uint32_t bLevel = 65025; + void* drawBuffer; + uint16_t stripLen; + uint8_t params; + uint8_t pinlist[NUM_DIGITAL_PINS]; + uint16_t comp1load[3]; + uint8_t serpNumber; + float OC_FACTOR = 1.0; //used to reduce period of LED output + uint16_t TH_TL = 1250; //nS- period of LED output + uint16_t T0H = TH_TL / 3.0; //nS- duration of T0H + uint16_t T1H = TH_TL * 2.0 / 3.0; //nS- duration of T1H + uint16_t LATCH_DELAY = 75; //uS time to hold output low for LED latch. + + //for show context switch + uint32_t bitmaskLocal[4]; + uint8_t numpinsLocal; + uint8_t* frameBufferLocal; + uint32_t numbytesLocal; + uint8_t pin_bitnumLocal[NUM_DIGITAL_PINS]; + uint8_t pin_offsetLocal[NUM_DIGITAL_PINS]; +}; // class ObjectFLED + + +//fadeToColorBy(RGB_array, LED_count, Color, FadeAmount) +//Fades an RGB array towards the background color by amount. +void fadeToColorBy(void*, uint16_t, uint32_t, uint8_t); + + +//drawSquare(RGB_Array, LED_Rows, LED_Cols, Y_Corner, X_Corner, square_Size) +//Draws square in a 2D RGB array with lower left corner at (Y_Corner, X_Corner). +//Safe to specify -Y, -X corner, safe to draw a box which partially fits on LED plane. +void drawSquare(void*, uint16_t, uint16_t, int, int, uint, uint32_t); + +#endif diff --git a/OjectFLED.cpp b/OjectFLED.cpp new file mode 100644 index 0000000..154e3fa --- /dev/null +++ b/OjectFLED.cpp @@ -0,0 +1,596 @@ +/* ObjectFLED - Teensy 4.x DMA to all pins for independent control of large and + multiple LED display objects + + Copyright (c) 2024 Kurt Funderburg + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + OctoWS2811 library code was well-studied and substantial portions of it used + to implement high-speed, non-blocking DMA for LED signal output in this library. + See below for a summary of changes made to the original OctoWS2811. + + OctoWS2811 - High Performance WS2811 LED Display Library + Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC + http://www.pjrc.com/teensy/td_libs_OctoWS2811.html + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +/* +Teensy 4.0 pin - port assignments +GPIO6List = { 0, 1, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 } //12 top, 4 bottom +GPIO7List = { 6, 7, 8, 9, 10, 11, 12, 13, 32 } //8 top, 1 bottom +GPIO8List = { 28, 30, 31, 34, 35, 36, 37, 38, 39 } //0 top, 9 bottom +GOIO9List = { 2, 3, 4, 5, 29, 33 } //4 top, 2 bottom +Teensy 4.1 pin - port assignments +GPIO6List = { 0, 1, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 38, 39, 40, 41 } //20 top, 0 bottom +GPIO7List = { 6, 7, 8, 9, 10, 11, 12, 13, 32, 34, 35, 36, 37 } //13 top, 0 bottom +GPIO8List = { 28, 30, 31, 42, 43, 44, 45, 46, 47 } //3 top, 6 bottom +GOIO9List = { 2, 3, 4, 5, 29, 33, 48, 49, 50, 51, 52, 53, 54 } //6 top, 7 bottom +* 4 pin groups, 4 timer groups, !4 dma channel groups: only 1 DMA group (4 ch) maps to GPIO (DMAMUX +* mapping) Also, only DMA ch0-3 have periodic mode for timer trigger (p77 manual). Separate objects +* cannot DMA at once. +* +* CHANGES: +* Moved some variables so class supports multiple instances with separate LED config params +* Implemented context switching so multiple instances can show() independently +* Re-parameterized TH_TL, T0H, T1H, OC_FACTOR; recalc time for latch at end of show() +* Added genFrameBuffer() to implement RGB order, brightness, color balance, and serpentine +* Added setBrightness(), setBalance() +* FrameBuffer no longer passed in, constructor now creates buffer; destructor added +* Added support for per-object setting of OC factor, TH+TL, T0H, T1H, and LATCH_DELAY in begin function +*/ +#ifndef __IMXRT1062__ +#error Only Teensy 4.x supported. +#else +#include "ObjectFLED.h" + +volatile uint32_t framebuffer_index = 0; //isr() +uint8_t* ObjectFLED::frameBuffer; //isr() +uint32_t ObjectFLED::numbytes; //isr() +uint8_t ObjectFLED::numpins; //isr() +uint8_t ObjectFLED::pin_bitnum[NUM_DIGITAL_PINS]; //isr() +uint8_t ObjectFLED::pin_offset[NUM_DIGITAL_PINS]; //isr() +uint32_t ObjectFLED::bitdata[BYTES_PER_DMA * 64] __attribute__((used, aligned(32))); //isr() +uint32_t ObjectFLED::bitmask[4] __attribute__((used, aligned(32))); + +DMASetting ObjectFLED::dma2next; +DMAChannel ObjectFLED::dma1; +DMAChannel ObjectFLED::dma2; +DMAChannel ObjectFLED::dma3; +volatile bool dma_first; + + +ObjectFLED::ObjectFLED(uint16_t numLEDs, void *drawBuf, uint8_t config, uint8_t numPins, \ + const uint8_t *pinList, uint8_t serpentine) { + serpNumber = serpentine; + drawBuffer = drawBuf; + params = config; + if (numPins > NUM_DIGITAL_PINS) numPins = NUM_DIGITAL_PINS; + numpins = numPins; //static/isr + stripLen = numLEDs / numpins; + memcpy(pinlist, pinList, numpins); + if ((params & 0x3F) < 6) { + frameBuffer = new uint8_t[numLEDs * 3]; //static/isr + numbytes = stripLen * 3; // RGB formats //static/isr + } + else { + frameBuffer = new uint8_t[numLEDs * 4]; //static/isr + numbytes = stripLen * 4; // RGBW formats //static/isr + } + + numpinsLocal = numPins; + frameBufferLocal = frameBuffer; + numbytesLocal = numbytes; +} // ObjectFLED constructor + + +extern "C" void xbar_connect(unsigned int input, unsigned int output); // in pwm.c +static volatile uint32_t *standard_gpio_addr(volatile uint32_t *fastgpio) { + return (volatile uint32_t *)((uint32_t)fastgpio - 0x01E48000); +} + + +void ObjectFLED::begin(float OCF) { + OC_FACTOR = OCF; + begin(); +} + + +void ObjectFLED::begin(float OCF, uint16_t latchDelay) { + OC_FACTOR = OCF; + LATCH_DELAY = latchDelay; + begin(); +} + + +void ObjectFLED::begin(float OCF, uint16_t period, uint16_t t0h, uint16_t t1h, uint16_t latchDelay) { + OC_FACTOR = OCF; + TH_TL = period; + T0H = t0h; + T1H = t1h; + LATCH_DELAY = latchDelay; + begin(); +} + + +// INPUT stripLen, frameBuffer, params, numPins, pinList +// GPIOR bits set for pins[i] -> bitmask, pin_bitnum[i], pin_offset[i] +// init timers, xbar to DMA, DMA bitdata -> GPIOR; clears frameBuffer (total LEDs * 3 bytes) +void ObjectFLED::begin(void) { + numpins = numpinsLocal; //needed to compute pin mask/offset & bitmask since static for isr + // Set each pin's bitmask bit, store offset & bit# for pin + memset(bitmask, 0, sizeof(bitmask)); + for (uint32_t i=0; i < numpins; i++) { + uint8_t pin = pinlist[i]; + if (pin >= NUM_DIGITAL_PINS) continue; // ignore illegal pins + uint8_t bit = digitalPinToBit(pin); // pin's bit index in word port DR + // which GPIO R controls this pin: 0-3 map to GPIO6-9 then map to DMA compat GPIO1-4 + uint8_t offset = ((uint32_t)portOutputRegister(pin) - (uint32_t)&GPIO6_DR) >> 14; + if (offset > 3) continue; //ignore unknown pins + pin_bitnum[i] = bit; //static/isr + pin_offset[i] = offset; //static/isr + uint32_t mask = 1 << bit; //mask32 = bit set @position in GPIO DR + bitmask[offset] |= mask; //bitmask32[0..3] = collective pin bit masks for each GPIO DR + //clear pin bit in IOMUX_GPR26 to map GPIO6-9 to GPIO1-4 for DMA + *(&IOMUXC_GPR_GPR26 + offset) &= ~mask; + *standard_gpio_addr(portModeRegister(pin)) |= mask; //GDIR? bit flag set output mode + } + //stash context for multi-show + memcpy(bitmaskLocal, bitmask, 16); + memcpy(pin_bitnumLocal, pin_bitnum, numpins); + memcpy(pin_offsetLocal, pin_offset, numpins); + + arm_dcache_flush_delete(bitmask, sizeof(bitmask)); //can't DMA from cached memory + + // Set up 3 timers to create waveform timing events + comp1load[0] = (uint16_t)((float)F_BUS_ACTUAL / 1000000000.0 * (float)TH_TL / OC_FACTOR ); + comp1load[1] = (uint16_t)((float)F_BUS_ACTUAL / 1000000000.0 * (float)T0H / OC_FACTOR ); + comp1load[2] = (uint16_t)((float)F_BUS_ACTUAL / 1000000000.0 * (float)T1H / OC_FACTOR ); + TMR4_ENBL &= ~7; + TMR4_SCTRL0 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE | TMR_SCTRL_MSTR; + TMR4_CSCTRL0 = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_TCF1EN; + TMR4_CNTR0 = 0; + TMR4_LOAD0 = 0; + TMR4_COMP10 = comp1load[0]; + TMR4_CMPLD10 = comp1load[0]; + TMR4_CTRL0 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(3); + TMR4_SCTRL1 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; + TMR4_CNTR1 = 0; + TMR4_LOAD1 = 0; + TMR4_COMP11 = comp1load[1]; // T0H + TMR4_CMPLD11 = comp1load[1]; + TMR4_CTRL1 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_COINIT | TMR_CTRL_OUTMODE(3); + TMR4_SCTRL2 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; + TMR4_CNTR2 = 0; + TMR4_LOAD2 = 0; + TMR4_COMP12 = comp1load[2]; // T1H + TMR4_CMPLD12 = comp1load[2]; + TMR4_CTRL2 = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | TMR_CTRL_COINIT | TMR_CTRL_OUTMODE(3); + + // route the timer outputs through XBAR to edge trigger DMA request: only 4 mappings avail. + CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); + xbar_connect(XBARA1_IN_QTIMER4_TIMER0, XBARA1_OUT_DMA_CH_MUX_REQ30); + xbar_connect(XBARA1_IN_QTIMER4_TIMER1, XBARA1_OUT_DMA_CH_MUX_REQ31); + xbar_connect(XBARA1_IN_QTIMER4_TIMER2, XBARA1_OUT_DMA_CH_MUX_REQ94); + XBARA1_CTRL0 = XBARA_CTRL_STS1 | XBARA_CTRL_EDGE1(3) | XBARA_CTRL_DEN1 | + XBARA_CTRL_STS0 | XBARA_CTRL_EDGE0(3) | XBARA_CTRL_DEN0; + XBARA1_CTRL1 = XBARA_CTRL_STS0 | XBARA_CTRL_EDGE0(3) | XBARA_CTRL_DEN0; + + // configure DMA channels + dma1.begin(); + dma1.TCD->SADDR = bitmask; // source 4*32b GPIO pin mask + dma1.TCD->SOFF = 8; // bytes offset added to SADDR after each transfer + // SMOD(4) low bits of SADDR to update with adds of SOFF + // SSIZE(3) code for 64 bit transfer size DSIZE(2) code for 32 bit transfer size + dma1.TCD->ATTR = DMA_TCD_ATTR_SSIZE(3) | DMA_TCD_ATTR_SMOD(4) | DMA_TCD_ATTR_DSIZE(2); + dma1.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE | // Dest minor loop offsetting enable + DMA_TCD_NBYTES_MLOFFYES_MLOFF(-65536) | + DMA_TCD_NBYTES_MLOFFYES_NBYTES(16); // #bytes to tansfer, offsetting enabled + dma1.TCD->SLAST = 0; // add to SADDR after xfer + dma1.TCD->DADDR = &GPIO1_DR_SET; + dma1.TCD->DOFF = 16384; //&GPIO1_DR_SET + DOFF = next &GPIO2_DR_SET + dma1.TCD->CITER_ELINKNO = numbytes * 8; // CITER outer loop count (linking disabled) = # LED bits to write + dma1.TCD->DLASTSGA = -65536; // add to DADDR after xfer + dma1.TCD->BITER_ELINKNO = numbytes * 8; // Beginning CITER (not decremented by transfer) + dma1.TCD->CSR = DMA_TCD_CSR_DREQ; // channel ERQ field cleared when minor loop completed + dma1.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_0); // only 4 XBAR1 triggers (DMA MUX mapping) + + dma2next.TCD->SADDR = bitdata; //uint32_t bitdata[BYTES_PER_DMA*64] + dma2next.TCD->SOFF = 8; + dma2next.TCD->ATTR = DMA_TCD_ATTR_SSIZE(3) | DMA_TCD_ATTR_DSIZE(2); + dma2next.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE | + DMA_TCD_NBYTES_MLOFFYES_MLOFF(-65536) | + DMA_TCD_NBYTES_MLOFFYES_NBYTES(16); + dma2next.TCD->SLAST = 0; + dma2next.TCD->DADDR = &GPIO1_DR_CLEAR; + dma2next.TCD->DOFF = 16384; + dma2next.TCD->CITER_ELINKNO = BYTES_PER_DMA * 8; + dma2next.TCD->DLASTSGA = (int32_t)(dma2next.TCD); + dma2next.TCD->BITER_ELINKNO = BYTES_PER_DMA * 8; + dma2next.TCD->CSR = 0; + + dma2.begin(); + dma2 = dma2next; // copies TCD + dma2.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_1); + dma2.attachInterrupt(isr); + + dma3.begin(); + dma3.TCD->SADDR = bitmask; + dma3.TCD->SOFF = 8; + dma3.TCD->ATTR = DMA_TCD_ATTR_SSIZE(3) | DMA_TCD_ATTR_SMOD(4) | DMA_TCD_ATTR_DSIZE(2); + dma3.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_DMLOE | + DMA_TCD_NBYTES_MLOFFYES_MLOFF(-65536) | + DMA_TCD_NBYTES_MLOFFYES_NBYTES(16); + dma3.TCD->SLAST = 0; + dma3.TCD->DADDR = &GPIO1_DR_CLEAR; + dma3.TCD->DOFF = 16384; + dma3.TCD->CITER_ELINKNO = numbytes * 8; + dma3.TCD->DLASTSGA = -65536; + dma3.TCD->BITER_ELINKNO = numbytes * 8; + dma3.TCD->CSR = DMA_TCD_CSR_DREQ | DMA_TCD_CSR_DONE; + dma3.triggerAtHardwareEvent(DMAMUX_SOURCE_XBAR1_2); +} // begin() + + +//*dest = *bitdata + pin offset +//*pixels = pin's block in frameBuffer +//mask = pin's bit position in GPIOR +//set a pin's mask32 for each color bit=0 at every 4*words32 in bitdata+offset +void fillbits(uint32_t *dest, const uint8_t *pixels, int n, uint32_t mask) { + do { + uint8_t pix = *pixels++; + if (!(pix & 0x80)) *dest |= mask; + dest += 4; + if (!(pix & 0x40)) *dest |= mask; + dest += 4; + if (!(pix & 0x20)) *dest |= mask; + dest += 4; + if (!(pix & 0x10)) *dest |= mask; + dest += 4; + if (!(pix & 0x08)) *dest |= mask; + dest += 4; + if (!(pix & 0x04)) *dest |= mask; + dest += 4; + if (!(pix & 0x02)) *dest |= mask; + dest += 4; + if (!(pix & 0x01)) *dest |= mask; + dest += 4; + } while (--n > 0); +} + + +void ObjectFLED::genFrameBuffer(uint serp) { + uint j = 0; + int jChange = -3; + if (serp == 0) { // use faster loops if no serp + switch (params & 0x3F) { + case CORDER_RGBW: // R,G,B = R,G,B - min(R,G,B); W = min(R,G,B) + for (uint16_t i = 0; i < (numbytes * numpins); i += 4) { + uint8_t minRGB = min(*((uint8_t*)drawBuffer + j) * rLevel / 65025, \ + *((uint8_t*)drawBuffer + j + 1) * rLevel / 65025); + minRGB = min(minRGB, *((uint8_t*)drawBuffer + j + 2) * rLevel / 65025); + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025 - minRGB; + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025 - minRGB; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025 - minRGB; + *(frameBuffer + i + 3) = minRGB; + j += 3; + } //for(leds in drawbuffer) + break; + case CORDER_BRG: + for (uint16_t i = 0; i < (numbytes * numpins); i += 3) { + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025; + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025; + j += 3; + } //for(leds in drawbuffer) + break; + case CORDER_GRB: + for (uint16_t i = 0; i < (numbytes * numpins); i += 3) { + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025; + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025; + j += 3; + } //for(leds in drawbuffer) + break; + case CORDER_RGB: + default: + for (uint16_t i = 0; i < (numbytes * numpins); i += 3) { + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025; + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025; + j += 3; + } //for(leds in drawbuffer) + } // switch() + } else { //serpentine + switch (params & 0x3F) { + case CORDER_RGBW: // R,G,B = R,G,B - min(R,G,B); W = min(R,G,B) + for (uint16_t i = 0; i < (numbytes * numpins); i += 4) { + uint8_t minRGB = min(*((uint8_t*)drawBuffer + j) * rLevel / 65025, \ + * ((uint8_t*)drawBuffer + j + 1) * rLevel / 65025); + minRGB = min(minRGB, *((uint8_t*)drawBuffer + j + 2) * rLevel / 65025); + if (i % (serp * 4) == 0) { + if (jChange < 0) { j = i / 4 * 3; jChange = 3; } + else { j = (i / 4 + serp - 1) * 3; jChange = -3; } + } + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025 - minRGB; + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025 - minRGB; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025 - minRGB; + *(frameBuffer + i + 3) = minRGB; + j += jChange; + } //for(leds in drawbuffer) + break; + case CORDER_BRG: + for (uint16_t i = 0; i < (numbytes * numpins); i += 3) { + if (i % (serp * 3) == 0) { + if (jChange < 0) { j = i; jChange = 3; } + else { j = i + (serp - 1) * 3; jChange = -3; } + } + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025; + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025; + j += jChange; + } //for(leds in drawbuffer) + break; + case CORDER_GRB: + for (uint16_t i = 0; i < (numbytes * numpins); i += 3) { + if (i % (serp * 3) == 0) { + if (jChange < 0) { j = i; jChange = 3; } + else { j = i + (serp - 1) * 3; jChange = -3; } + } + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j) * rLevel / 65025; + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025; + j += jChange; + } //for(leds in drawbuffer) + break; + case CORDER_RGB: + default: + for (uint16_t i = 0; i < (numbytes * numpins); i += 3) { + if (i % (serp * 3) == 0) { + if (jChange < 0) { j = i; jChange = 3; } + else { j = i + (serp - 1) * 3; jChange = -3; } + } + *(frameBuffer + i) = *((uint8_t*)drawBuffer + j) * rLevel / 65025; + *(frameBuffer + i + 1) = *((uint8_t*)drawBuffer + j + 1) * gLevel / 65025; + *(frameBuffer + i + 2) = *((uint8_t*)drawBuffer + j + 2) * bLevel / 65025; + j += jChange; + } //for(leds in drawbuffer) + } // switch() + } // else serpentine +} //genFrameBuffer() + + +// pre-show prior transfer wait, copies drawBuffer -> frameBuffer +// resets timers, clears pending DMA reqs +// fills bitdata[BYTES_PER_DMA * 64 * 4 bytes] from frameBuffer with 4-block bitmasks for 0's in led data +// 4 word32s for each bit in (led data)/pin = 16 * 8 = 96 bitdata bytes for each LED byte: 288 bytes / LED +// launches DMA with IRQ activation to reload bitdata from frameBuffer +void ObjectFLED::show(void) { + while (!dma3.complete()); // wait for dma to complete before reset/re-use + + //Restore context if needed + if (frameBuffer != frameBufferLocal) { + numpins = numpinsLocal; + frameBuffer = frameBufferLocal; + numbytes = numbytesLocal; + memcpy(bitmask, bitmaskLocal, 16); + memcpy(pin_bitnum, pin_bitnumLocal, numpins); + memcpy(pin_offset, pin_offsetLocal, numpins); + arm_dcache_flush_delete(bitmask, sizeof(bitmask)); //can't DMA from cached memory + // Restore 3 timers to create waveform timing events + TMR4_COMP10 = comp1load[0]; + TMR4_CMPLD10 = comp1load[0]; + TMR4_COMP11 = comp1load[1]; // T0H + TMR4_CMPLD11 = comp1load[1]; + TMR4_COMP12 = comp1load[2]; // T1H + TMR4_CMPLD12 = comp1load[2]; + //restore DMA loop control + dma1.TCD->CITER_ELINKNO = numbytes * 8; // CITER outer loop count (linking disabled) = # LED bits to write + dma1.TCD->BITER_ELINKNO = numbytes * 8; // Beginning CITER (not decremented by transfer) + dma3.TCD->CITER_ELINKNO = numbytes * 8; + dma3.TCD->BITER_ELINKNO = numbytes * 8; + } //done restoring context + + genFrameBuffer(serpNumber); + + // disable timers + uint16_t enable = TMR4_ENBL; + TMR4_ENBL = enable & ~7; + + // force all timer outputs to logic low + TMR4_SCTRL0 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE | TMR_SCTRL_MSTR; + TMR4_SCTRL1 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; + TMR4_SCTRL2 = TMR_SCTRL_OEN | TMR_SCTRL_FORCE; + + // clear any prior pending DMA requests + XBARA1_CTRL0 |= XBARA_CTRL_STS1 | XBARA_CTRL_STS0; + XBARA1_CTRL1 |= XBARA_CTRL_STS0; + + // fill the DMA transmit buffer + memset(bitdata, 0, sizeof(bitdata)); //BYTES_PER_DMA * 64 words32 + uint32_t count = numbytes; //bytes per strip + if (count > BYTES_PER_DMA*2) count = BYTES_PER_DMA*2; + framebuffer_index = count; //ptr to framebuffer last byte output + + //Sets each pin mask in bitdata32[BYTES_PER_DMA*64] for every 0 bit of pin's frameBuffer block bytes + for (uint32_t i=0; i < numpins; i++) { //for each pin + fillbits(bitdata + pin_offset[i], (uint8_t *)frameBuffer + i*numbytes, + count, 1<SADDR = bitdata; + dma2.TCD->DADDR = &GPIO1_DR_CLEAR; + dma2.TCD->CITER_ELINKNO = count * 8; + dma2.TCD->CSR = DMA_TCD_CSR_DREQ; + } else { + dma2.TCD->SADDR = bitdata; + dma2.TCD->DADDR = &GPIO1_DR_CLEAR; + dma2.TCD->CITER_ELINKNO = BYTES_PER_DMA * 8; + dma2.TCD->CSR = 0; + dma2.TCD->CSR = DMA_TCD_CSR_INTMAJOR | DMA_TCD_CSR_ESG; + dma2next.TCD->SADDR = bitdata + BYTES_PER_DMA*32; + dma2next.TCD->CITER_ELINKNO = BYTES_PER_DMA * 8; + if (numbytes <= BYTES_PER_DMA*3) { + dma2next.TCD->CSR = DMA_TCD_CSR_ESG; + } else { + dma2next.TCD->CSR = DMA_TCD_CSR_ESG | DMA_TCD_CSR_INTMAJOR; + } + dma_first = true; + } + dma3.clearComplete(); + dma1.enable(); + dma2.enable(); + dma3.enable(); + + // initialize timers + TMR4_CNTR0 = 0; + TMR4_CNTR1 = comp1load[0] + 1; + TMR4_CNTR2 = comp1load[0] + 1; + + // wait for last LED reset to finish + while (micros() - update_begin_micros < numbytes * 8 * TH_TL / OC_FACTOR / 1000 + LATCH_DELAY); + + // start everything running! + TMR4_ENBL = enable | 7; + update_begin_micros = micros(); +} // show() + + +//INPUT: dma2, dma2next, bitdata, framebuffer_inedex, numpins, numbytes, pin_offset[], pin_bitnum[] +//Reads next block of framebuffer -> fillbits() -> bitdata +//Checks for last block to transfer, next to last, or not to update dma2next major loop +void ObjectFLED::isr(void) +{ + // first ack the interrupt + dma2.clearInterrupt(); + + // fill (up to) half the transmit buffer with new fillbits(frameBuffer data) + //digitalWriteFast(12, HIGH); + uint32_t *dest; + if (dma_first) { + dma_first = false; + dest = bitdata; + } else { + dma_first = true; + dest = bitdata + BYTES_PER_DMA*32; + } + memset(dest, 0, sizeof(bitdata)/2); + uint32_t index = framebuffer_index; + uint32_t count = numbytes - framebuffer_index; + if (count > BYTES_PER_DMA) count = BYTES_PER_DMA; + framebuffer_index = index + count; + for (int i=0; i < numpins; i++) { + fillbits(dest + pin_offset[i], (uint8_t *)frameBuffer + index + i*numbytes, + count, 1<SADDR = dest; + dma2next.TCD->CITER_ELINKNO = count * 8; + uint32_t remain = numbytes - (index + count); + if (remain == 0) { + dma2next.TCD->CSR = DMA_TCD_CSR_DREQ; + } else if (remain <= BYTES_PER_DMA) { + dma2next.TCD->CSR = DMA_TCD_CSR_ESG; + } else { + dma2next.TCD->CSR = DMA_TCD_CSR_ESG | DMA_TCD_CSR_INTMAJOR; + } +} // isr() + + +int ObjectFLED::busy(void) +{ + if (micros() - update_begin_micros < numbytes * TH_TL / OC_FACTOR / 1000 * 8 + LATCH_DELAY) { + return 1; + } + return 0; +} + + +void ObjectFLED::setBrightness(uint8_t brightLevel) { + brightness = brightLevel; + rLevel = (brightness + 1) * (colorBalance >> 16); + gLevel = (brightness + 1) * ((colorBalance >> 8) & 0xFF); + bLevel = (brightness + 1) * (colorBalance & 0xFF); + } + + +void ObjectFLED::setBalance(uint32_t balMask) { + colorBalance = balMask & 0xFFFFFF; + rLevel = (brightness + 1) * (colorBalance >> 16); + gLevel = (brightness + 1) * ((colorBalance >> 8) & 0xFF); + bLevel = (brightness + 1) * (colorBalance & 0xFF); +} + + +//Fades CRGB array towards the background color by amount. +void fadeToColorBy(void* leds, uint16_t count, uint32_t color, uint8_t fadeAmt) { + for (uint x = 0; x < count * 3; x += 3) { + //fade red + *((uint8_t*)leds + x) = (( *((uint8_t*)leds + x) * (1 + (255 - fadeAmt))) >> 8) + \ + (( ((color >> 16) & 0xFF) * (1 + fadeAmt)) >> 8); + //fade green + *((uint8_t*)leds + x + 1) = (( *((uint8_t*)leds + x + 1) * (1 + (255 - fadeAmt))) >> 8) + \ + (( ((color >> 8) & 0xFF) * (1 + fadeAmt)) >> 8); + //fade blue + *((uint8_t*)leds + x + 2) = (( *((uint8_t*)leds + x + 2) * (1 + (255 - fadeAmt))) >> 8) + \ + (( (color & 0xFF) * (1 + fadeAmt)) >> 8); + } +} //fadeToColorBy() + + +// Safely draws box even if partially offscreen on 2D CRGB array +void drawSquare(void* leds, uint16_t planeY, uint16_t planeX, int yCorner, int xCorner, uint size, uint32_t color) { + if (size != 0) { size--; } + else { return; } + for (int x = xCorner; x <= xCorner + (int)size; x++) { + // if validX { if validY+S {draw Y+S,X}; if validY {draw Y, X} } + if ((x >= 0) && (x < planeX)) { //valid X + if ((yCorner >= 0) && (yCorner < planeY)) { + *((uint8_t*)leds + (yCorner * planeX + x) * 3) = ((color >> 16) & 0xFF); + *((uint8_t*)leds + (yCorner * planeX + x) * 3 + 1) = ((color >> 8) & 0xFF); + *((uint8_t*)leds + (yCorner * planeX + x) * 3 + 2) = (color & 0xFF); + } + if ((yCorner + size >= 0) && (yCorner + size < planeY)) { + *((uint8_t*)leds + ((yCorner + size) * planeX + x) * 3) = ((color >> 16) & 0xFF); + *((uint8_t*)leds + ((yCorner + size) * planeX + x) * 3 + 1) = ((color >> 8) & 0xFF); + *((uint8_t*)leds + ((yCorner + size) * planeX + x) * 3 + 2) = (color & 0xFF); + } + } //if valid x + } //for x + for (int y = yCorner; y <= yCorner + (int)size; y++) { + if ((y >= 0) && (y < planeY)) { //valid y + if ((xCorner >= 0) && (xCorner < planeX)) { + *((uint8_t*)leds + (xCorner + y * planeX) * 3) = ((color >> 16) & 0xFF); + *((uint8_t*)leds + (xCorner + y * planeX) * 3 + 1) = ((color >> 8) & 0xFF); + *((uint8_t*)leds + (xCorner + y * planeX) * 3 + 2) = (color & 0xFF); + } + if ((xCorner + size >= 0) && (xCorner + size < planeX)) { + *((uint8_t*)leds + (xCorner + size + y * planeX) * 3) = ((color >> 16) & 0xFF); + *((uint8_t*)leds + (xCorner + size + y * planeX) * 3 + 1) = ((color >> 8) & 0xFF); + *((uint8_t*)leds + (xCorner + size + y * planeX) * 3 + 2) = (color & 0xFF); + } + } //if valid y + } //for y +} // drawSquare() + +#endif // __IMXRT1062__ diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9edc94 --- /dev/null +++ b/README.md @@ -0,0 +1,212 @@ +# ObjectFLED.h + +Allows for configuration and control of multiple (physical) digital LED display objects +independently in code. Display objects use DMA transfer (from [OctoWS2811 library](http://www.pjrc.com/teensy/td_libs_OctoWS2811.html)) +on any or all 40 or 55 pins of Teensy 4.0 or 4.1 to drive digital LEDs with massive parallelism, +if needed. + +The show() function is non-blocking, returning control to the graphics drawing code in just 6% of +the time it takes to complete buffer transmission to an LED string (tested with WS2812B, 1.6 +overclock factor, 256 LED per pin x 16 pins = 4096 LEDs total at 204 back-to-back fps). + +ObjectFLED works with FastLED arrays of CRGB, or other drawing buffer in RGB 3-byte format. + +### Glossary: +* "LED object" or "display object" refers to the ObjectFLED display object(s) in your code. +* "LED device" refers to physical LEDs: strings, planes, cubes, etc. +* "Segment" refers to physical LEDs connected to a single pin. This can be a group of LED devices +(physically daisy-chained) or a subset of LED devices in a larger LED device (rows of a plane, +planes of a cube, a single string of house lights, an orbit of an electron, etc.) + +## SUPPORTED + + - For Teensy 4.x only. It may be possible to add other boards if they support DMA driven by + configurable timers, and ability to map digital pins to a data register which DMA can target. + - LED data color formats: RGB, GRB, BRG, and RGBW + + +## MAIN BENEFITS + +* You can independently configure, control and display multiple LED devices connected to your +Teensy 4.x, even if the devices use different LED types with different specs. Combine LED devices +into a single display object, or define separate objects for segments of each device, or just plain +one display object for each device (segmentation is automatic). It is also possible to define 2 +display objects to display the same drawing buffer on 2 different LED devices. + +* Large LED devices can be driven with parallel output to separate segments of the device. Physical +parallelism allows for multi-fold increase in refresh rate. ObjectFLED automatically breaks your +drawing buffer into segments and writes to the pins simultaneously in the specified order. Simply +define an ordered pin list for your object, in the same order they are connected physically to the +segments in your LED device. ObjectFLED performs segmentation "under the covers". + +* ObjectFLED works with arrays of CRGB for full compatibility with the rich suite of LED graphics +functions of FastLED. But you can use any memory object for drawing buffer, so long as it is 3- +bytes per LED in RGB order. + +* You can tweak the shape of the LED data waveform generated by this driver. By calling begin() +with full pulse timing specs, you can tweak the waveform to achieve the highest possible overclock +(and highest back-to-back refresh rate). Each display object has it's own begin() function, and +it's own LED timing. + +* The show() function is non-blocking, and returns in just 6% of the time required to write a single +segment. However when calling show() back-to-back, each must wait for the prior to complete it's +latch pulse at the end of writing. This is also true when calling show() for one object right after +calling show() for another object. All display objects share the same DMA-Timer pipeline in hardware. +Therefore, for code which controls a large LED device and a small one, with sequential calls to each +show(): show the small one first, then show the large one, then update your drawing while the large +display is still DMA-transmitting 94% of it's LED data. + +* Show() function has built-in handling for serpentine, color order, brightness, and color balance +for each object. Like in FastLED, these are applied to the frame buffer, not your drawing buffer. +Get and set functions for brightness and colormask are included (the ones in FastLED won't work on +ObjectFLED display objects). + +* Accessory functions included to fade a drawing array to a color other than black, and to draw a +square on an LED plane. + + +## LED DEVICES + +Physical LED devices are connected to Teensy's digital pins via a buffer circuit to step the 3.3v +Teensy squarewave up to 5v for the LEDs. For buffering, use [OctoWS2811 boards](https://www.pjrc.com/store/octo28_adaptor.html) or SN74AHCT125 or equivalent. + +LED planes can have their data paths physically wired for serpentine (reverse direction on every +other row). ObjectFLED will automatically perform serpentine output if you specify an LED count +(row size) for your display object. However, with planes in a cube, ObjectFLED expects the last LED +of a plane to connect to the first LED of the next plane (no plane-level serpentine, only row- +level). + +When taking advantage of parallel inputs into an LED device, each pin is assumed to drive the same +number of LEDs (or rows or planes). When you define your ObjectFLED object, provide an ordered pin +list which matches the order in which the pins connect to the device. ObjectFLED will output segments +from your display buffer to those pins in the order you specify. + +Individual LEDs come with various formats for RGB color order, data signal clock frequency, and +pulse timing specs. ObjectFLED defaults will work for popular RGB-order LEDs with 800KHz clock and +75uS latch delay (Note that for tested WS2812's, which specify 50uS latch, I had to use 72, perhaps +because of added CPU overhead). Use the begin( params ) function on an ObjectFLED object in order +to use timing specs from your LED's datasheet. Specify the color order when you instantiate +ObjectFLED. + + +## USAGE + +### ObjectFLED CLASS + +ObjectFLED(uint16_t numLEDs, void* drawBuf, uint8_t config, uint8_t numPins, const uint8_t* pinList, + uint8_t serpentine = 0); + +- numLEDs = total number of LEDs in the device(s) represented by your drawing buffer +- drawBuf = drawing array of CRGB or similar format which contains your graphics NOTE: multi-dimensional +drawing arrays must be in row-major order. That means myCube[ Z ][ Y ][ X ], not myCube[ X ][ Y ][ Z ], +where Z is plane, Y is row, X is pixel in row. +- config = predefined value for color order (ie- CORDER_RGB, see above for supported list) +- numPins = number of pins connected to the LED device(s) +- pinList = array of pin numbers in the order which they are connected to successive segments +- serpentine (optional) = number of LEDs in each row for serpentine + +**Example:** + + CRGB myCube[16][16][16]; + uint8_t pinList[8] = {1, 8, 14, 17, 24, 3, 4, 5}; //8 pins for 16 planes: 2 planes per pin + //Pins are attached to each successive pair of planes in the given list order + ObjectFLED dispCube( 16*16*16, myCube, CORDER_GRB, 8, pinList, 16 ); //serpentine 16 LEDs/row + +**Best Practice:** +Use variable names which partially match for creating a drawing array and it's matching display +object. Ex.- + + CRGB grid4[Y][X]; ObjectFLED dspGrid4( Y*X, grid4, etc. ); + + +### begin() FUNCTION + +Called once in setup for the display object + +// Default timing 800KHz clock, 75uS latch delay +void ObjectFLED::begin(void); + +// Overclock default timing by the given factor +void ObjectFLED::begin(float OCF); + +// Overclock default timing and overrde default latch delay +void ObjectFLED::begin(float OCF, uint16_t latchDelay); + +// Fully specify output waveform timing. NOTE: Given period, t0h, t1h are divided by given OCF. +void ObjectFLED::begin(float OCF, uint16_t period, uint16_t t0h, uint16_t t1h, uint16_t latchDelay); + +- OCF = Overclocking factor multiples the clock rate (by dividing the pulse width values below) +- period = nS time for duration of a full LED data pulse (from LED datasheet) NOTE: For 800KHz clock, +period = 1250nS. This is the default setting. +- t0h = nS time for duration of high portion of pulse for LED data 0 (from LED datasheet) +- t1h = nS time for duration of high portion of pulse for LED data 1 (from LED datasheet) +- latchDelay = uS time to hold data low for LEDs to latch color data to DACs (from LED datasheet) +NOTE: In test I had to use 72uS latch delay for LEDs with a spec of 50uS, in order to stop + a severe display glitch. I've read at least one LED model specifies 300uS latch. If you + get display glitches on a new LED type, latch time should be the first thing to check. + +**Example:** + + dispCube.begin(1.5, 72); //overclocks by factor 1.5 (1200KHz) and sets 72uS latch delay + dispVid.begin(1.68, 1250, 420, 840, 72); //420nS, 840nS are 1/3, 2/3 of period + + +### show() FUNCTION + +See MAIN BENEFITS above for details. + +**Example:** + + dispCube.show(); //dispCube has far fewer LEDs than dispVid (it is a small cube) + dispVid.show(); //by calling show on the large object last, code can re-draw while + //~94% of dispVid's LED data is being written by DMA under the covers. + //if you call dispCube.show() last, it would wait for all that 94% of + //dispVid data to finish, then wait for latch cycle, before it ran. + + +### setBrightness(), getBrightness() FUNCTIONS + +Brightness values 0-255. Brightness is applied by show() to frame buffer, not your +drawing buffer. + +* void setBrightness(uint8_t) + +* uint8_t getBrightness() { return brightness; } + + +### setBalance(), getBalance() FUNCTIONS + +Color Balance is 3-byte number in RGB order. Each byte is a brightness value for that color. +Like brightness, this is applied by show() to frame buffer, not to your drawing buffer. + +* void setBalance(uint32_t); + +* uint32_t getBalance() { return colorBalance; } + + +## ACCESSORY FUNCTIONS + +These are not part of display objects, call them without object specifier. Unlike brightness and +balance, these functions operate on your drawing buffer. + +* void fadeToColorBy(void* leds, uint16_t count, uint32_t color, uint8_t fadeAmt); + +Fades drawing array towards the background color by amount. It is used just like FastLED's +fadeToBlackBy(). + +**Example:** + + fadeToColorBy( myCube, 16*16*16, 0xFF8000, 20 ); //fades towards orange by 20/255ths + +* void drawSquare(void* leds, uint16_t planeY, uint16_t planeX, int yCorner, int xCorner, uint size, + uint32_t color); + +Safely draws box in given RGB color on LED plane. cornerY and cornerX specify the lower left +corner of the box. It is safe to specify -cornerY, -cornerX, and safe to draw a box which only +partially fits on LED plane. + +**Example:** + + //draws a red 10x10 square on the 3rd 16x16 plane of myCube, lower left corner anchored at + //col = row = 4. + drawSquare( &myCube[2][0][0], 16, 16, 4, 4, 10, 0xFF0000 ); \ No newline at end of file diff --git a/examples/Cylon/Cylon.ino b/examples/Cylon/Cylon.ino new file mode 100644 index 0000000..5291d79 --- /dev/null +++ b/examples/Cylon/Cylon.ino @@ -0,0 +1,38 @@ +//Cylon demo for OctoFLED +#include +#include + +// How many leds in your strip? +#define NUM_LEDS 32 + +// For led chips like Neopixels, which have a data line, ground, and power, you just +// need to define DATA_PIN. Or list of pins for parallel connections to your LED device. +uint8_t DATA_PIN[1] = { 17 }; +// Define the array of leds +CRGB leds[NUM_LEDS]; +// Define the display object with 8-pixel serpentine on 8x8 plane +ObjectFLED cylon( NUM_LEDS, leds, CORDER_GRB, sizeof(DATA_PIN), DATA_PIN, 8); +void setup() { + cylon.begin(); //make display object ready at default LED clock rate 800 KHz + cylon.setBrightness(84); +} + +CRGB dotColor = 0xff0000; +const int delayT = 25; +const int fadeAmt = 40; +void loop() { + for( int i=0; i=0; i--) { + leds[i] = dotColor; + cylon.show(); + fadeToBlackBy(leds, NUM_LEDS, fadeAmt); + delay(delayT); + } + delay(delayT); +} //loop() diff --git a/examples/DemoReel2D/DemoReel2D.ino b/examples/DemoReel2D/DemoReel2D.ino new file mode 100644 index 0000000..b22f891 --- /dev/null +++ b/examples/DemoReel2D/DemoReel2D.ino @@ -0,0 +1,165 @@ +/// @file DemoReel100.ino +/// @brief FastLED "100 lines of code" demo reel, showing off some effects +/// @example DemoReel100.ino +// FastLED "100-lines-of-code" demo reel, showing just a few +// of the kinds of animation patterns you can quickly and easily +// compose using FastLED. +// +// This example also shows one easy way to define multiple +// animations patterns and have them automatically rotate. +// +// -Mark Kriegsman, December 2014 +// +// Ported to use ObjectFLED, input brightness via Serial Nov 2024 by Kurt Funderburg +#include +#include + +#define NUM_LEDS 8 //per row +#define NUM_STRIPS 8 //should be even multiple of NUM_PINS; 24 = 4 rows per pin +uint BRIGHTNESS = 6; +const uint NUM_PINS = 2; +//uint8_t ports[NUM_PINS] = { 1, 8, 14, 17, 24, 29 }; +uint8_t ports[NUM_PINS] = { 17, 24 }; +uint8_t ports2[4] = { 1, 8, 14, 29 }; + +//Create 2 display objects using the same drawing array to display image on 2 devices +CRGB plane[NUM_STRIPS][NUM_LEDS]; +//8x8 grid with data connection broken between 4th & 5th rows to drive with 2 pins parallel +ObjectFLED dispPlane(NUM_LEDS*NUM_STRIPS, plane, CORDER_GRB, NUM_PINS, ports, 8); +//4 4x4 grids of LEDs on my breadboard (for better results, replace this with device of same +// or even multiples of the first device dimensions) +ObjectFLED dispPlane2(NUM_LEDS*NUM_STRIPS, plane, CORDER_RGB, 4, ports2, 4); + +void setup() { + Serial.begin(1000000); + Serial.print("*********************************************\n"); + Serial.print("* DemoReel1002D.ino *\n"); + Serial.print("*********************************************\n"); + Serial.printf("CPU Speed: %d MHz Temp: %.1f C %.1f F Brightness %d\n", F_CPU_ACTUAL / 1000000, \ + tempmonGetTemp(), tempmonGetTemp() * 9.0 / 5.0 + 32, BRIGHTNESS); + Serial.println("Enter brightness level at any time via serial monitor."); + + //start both display objects + dispPlane.begin(); + dispPlane2.begin(); + dispPlane.setBrightness(BRIGHTNESS); + dispPlane2.setBrightness(BRIGHTNESS); +} // setup() + + +// List of patterns to cycle through. Each is defined as a separate function below. +typedef void (*SimplePatternList[])(); +SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm }; + +uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current +static uint8_t gHue = 0; // rotating "base color" used by many of the patterns +uint strIdx = 0, strLen = 8; //for reading brightness int from serial +char serInput[8], x; + +void loop() { + // Call the current pattern function once, updating the 'plane' array + gPatterns[gCurrentPatternNumber](); + + + // send the 'plane' array out to both displays + dispPlane.show(); + dispPlane2.show(); + delay(40); // too see the glitter + + // do some periodic updates + EVERY_N_MILLISECONDS(30) { + gHue++; + } // slowly cycle the "base color" through the rainbow + + EVERY_N_SECONDS(1) { // check/read serial for new brightness# + strIdx = 0; + while(Serial.available() && strIdx < strLen) { + if ((x = Serial.read()) != -1) { + if(x >= '0' && x <= '9') { serInput[strIdx++] = x; } //store it if a digit + } + } //while R bytes + Serial.clear(); // got my 8B, anything else must be cat on keybaord + + if(strIdx > 0) { //str > int, then set brightness + BRIGHTNESS = 0; + for(uint i=0; i 32864) || (phase < 0)) phaseInc = -phaseInc; + phase += phaseInc; +} + +void bpm() { + // colored stripes pulsing at a defined Beats-Per-Minute (BPM) + uint8_t BeatsPerMinute = 30; + CRGBPalette16 palette = PartyColors_p; + uint8_t beat = beatsin8(BeatsPerMinute, 64, 255); + for (int j = 0; j < NUM_STRIPS; j++) { + for (int i = 0; i < NUM_LEDS; i++) { + plane[j][i] = ColorFromPalette(palette, i*32+j*32, beat); + } + } +} + +void juggle() { + // eight colored dots, weaving in and out of sync with each other + fadeToBlackBy(plane[0], NUM_LEDS * NUM_STRIPS, 15); + uint8_t dothue = 0; + for (int k = 0; k < NUM_STRIPS; k++) { + for (int i = 0; i < NUM_LEDS; i++) { + plane[k][beatsin16(40, 0, NUM_LEDS - 1)] = CHSV(dothue, 255, 255); + dothue += 2; + } + } +} diff --git a/examples/ObjectFLEDExperiment/ObjectFLEDExperiment.ino b/examples/ObjectFLEDExperiment/ObjectFLEDExperiment.ino new file mode 100644 index 0000000..b7a687c --- /dev/null +++ b/examples/ObjectFLEDExperiment/ObjectFLEDExperiment.ino @@ -0,0 +1,112 @@ +/* OctoExperiment.ino - Test program to show revolving electron around edges of + 3 4x4 planes laid out horizontally (on a breadboard) while fading the nucleus + (an 8x8 plane) between blue and orange in sync with electron. + Kurt Funderburg - Nov 2024 + + The 3 planes are each connected to a Teensy pin, driven in parallel, while the + 8x8 plane is separated into 2 groups of 4 rows, each group connected to a pin. + You can alter the variables to reflect the geometry and signal path for your LED + devices. + + In code, the 3 planes are stored in CRGB electronLED[Z][Y][X]. The nucleus plane + is CRGB nucleusLED[Y][X]. This allows for intuitive loop control, and ObjectFLED + requires drawing buffer arrays to be in row-major order. +*/ +#include +#include + +//3x4x4 YF923 RGB "cube" actually 3 planes laid out horizontally in 4x12. +#define PIX_PER_ROW 4 +#define ROWS_PER_PLANE 4 +#define NUM_PLANES 3 +#define NUM_PINS 3 +#define NUM_LEDS (PIX_PER_ROW * ROWS_PER_PLANE * NUM_PLANES) +const byte pinList[NUM_PINS] = {1, 8, 14}; +const int config = CORDER_RGB; +CRGB electronLED[NUM_PLANES][ROWS_PER_PLANE][PIX_PER_ROW]; +ObjectFLED electron(NUM_LEDS, electronLED, config, NUM_PINS, pinList, PIX_PER_ROW); + +//8x8 WS2812B GRB plane from Amazon +#define PIX_PER_ROW2 8 +#define NUM_ROWS2 8 +#define NUM_PINS2 2 +#define NUM_LEDS2 (PIX_PER_ROW2 * NUM_ROWS2) +const byte pinList2[NUM_PINS2] = {17, 24}; +const int config2 = CORDER_GRB; +CRGB nucleusLED[NUM_ROWS2][PIX_PER_ROW2]; //Octo assumes this buffer size/type +ObjectFLED nucleus(NUM_LEDS2, nucleusLED, config2, NUM_PINS2, pinList2, 8); + +const uint32_t background = 0xFFFF00; +const uint32_t drawColor = 0x0000FF; +const uint8_t brightness = 4; +const int delayT = 60; // mS loop delay +const int fade = 50; // fade electron +int fade2 = 20; // fade nucleus +uint planeColor1 = 0x0000FF; +uint planeColor2 = 0xff7000; +uint planeColor = planeColor1; +void setup() { + Serial.begin(1000000); + Serial.println("OctoExperiment"); + + //"nucleus" plane 8x8 fades back and forth from orange to blue driven by 2 pins + nucleus.begin(); //use default LED timing: 800 KHz (1250 nS) with 75 uS latch delay + nucleus.setBrightness(brightness); + nucleus.setBalance(0xdae0ff); + fill_solid(nucleusLED[0], NUM_LEDS2, planeColor2); + nucleus.show(); + + //"electron orbit" around 3 4x4 planes driven by 3 pins + //1.68 OC factor with these timing values are max OC for YF923's I found posing as WS2812B's + electron.begin(1.68, 1250, 420, 840, 72); + electron.setBrightness(brightness); + electron.setBalance(0xdae0ff); + fill_solid(electronLED[0][0], NUM_LEDS, background); + electron.show(); +//while(Serial.read()==-1); +} + +void loop() { + for (int i=0; i=0; i--) { //electron down right edge of right plane + electronLED[NUM_PLANES-1][i][PIX_PER_ROW-1] = drawColor; + electron.show(); + fadeToColorBy(electronLED[0][0], NUM_LEDS, background, fade); + drawSquare(nucleusLED, NUM_ROWS2, PIX_PER_ROW2, 4 - i - 1, 4 - i - 1, i * 2 + 2, 0x800000); + nucleus.show(); + fadeToColorBy(nucleusLED[0], NUM_LEDS2, planeColor, fade2); + delay(delayT); + } + for (int j=NUM_PLANES-1; j>=0; j--) { //electron left across bottom of planes + for (int i=PIX_PER_ROW-1; i>=0; i--) { + electronLED[j][0][i] = drawColor; + electron.show(); + fadeToColorBy(electronLED[0][0], NUM_LEDS, background, fade); + nucleus.show(); + fadeToColorBy(nucleusLED[0], NUM_LEDS2, planeColor, fade2); + delay(delayT); + } + } + + if(planeColor == planeColor2) { planeColor = planeColor1; fade2 = fade2 / 2; } + else { planeColor = planeColor2; fade2 = fade2 * 2; } +} // loop() diff --git a/examples/ObjectFLEDTest/ObjectFLEDTest.ino b/examples/ObjectFLEDTest/ObjectFLEDTest.ino new file mode 100644 index 0000000..f22b22a --- /dev/null +++ b/examples/ObjectFLEDTest/ObjectFLEDTest.ino @@ -0,0 +1,138 @@ +/* ObjectFLEDTest.ino - Test program to compute back-to-back and one-time refresh + rates for various LED configurations + Kurt Funderburg - Nov 2024 +*/ +#include +#include + +//configure your test cube with display object = leds, draw buffer = testCube[Z][Y][X]. +//configure a separate cube to pre-clear any physical LEDs you actually have connected +// with display object = blanks, draw buffer = blankLeds[Z][Y][X]. +//notice that with ObjectFLED you can define 2 display objects to drive the same physical +// object on the same physical pins. +const int PIX_PER_ROW = 16, + NUM_ROWS = 16, + NUM_PLANES = 16, + NUM_CHANNELS = 16, + COLOR_ORDER = RGB, + //LED baud * 3 bits/LED byte = Serial baud; 2.4 MHz serial, 3.6 passed testing + LED_SERIAL_BAUD = 800 * 1.8, //1200, //SerialFLED in kHz + STD_OUT_BAUD = 100000; +const CRGB background = 0x505000; +byte pinList[NUM_CHANNELS] = {1, 8, 14, 17, 24, 29, 20, 0, 15, 16, 18, 19, 21, 22, 23, 25}; +byte pinListBlank[7] = {1, 8, 14, 17, 24, 29, 20}; +const int config = CORDER_RGB; + +CRGB testCube[NUM_PLANES][NUM_ROWS][PIX_PER_ROW]; +CRGB blankLeds[7][8][8]; +ObjectFLED leds(PIX_PER_ROW * NUM_ROWS * NUM_PLANES, testCube, config, NUM_CHANNELS, pinList); +ObjectFLED blanks(7*8*8, blankLeds, config, 7, pinListBlank); + +CRGB leds2[PIX_PER_ROW * NUM_ROWS * NUM_PLANES]; // FastLED draw buffer +uint startT=0, stopT=0; +void setup() { + Serial.begin(STD_OUT_BAUD); + Serial.print("*********************************************\n"); + Serial.print("* ObjectFLEDTest.ino *\n"); + Serial.print("*********************************************\n"); + Serial.printf("CPU speed: %d MHz Temp: %.1f C %.1f F Serial baud: %.1f MHz\n", \ + F_CPU_ACTUAL / 1000000, \ + tempmonGetTemp(), tempmonGetTemp() * 9.0 / 5.0 + 32, \ + 800000 * 1.6 / 1000000.0); + + //blank all leds/ all channels + + //Second parallel pin group Teensy 4.0: 10,12,11,13,6,9,32,8,7 + //Third parallel pin group Teensy 4.0: 37, 36, 35, 34, 39, 38, 28, 31, 30 from FastLED docs +// FastLED.addLeds(leds2, PIX_PER_ROW * NUM_ROWS * NUM_PLANES / NUM_CHANNELS); //4-strip parallel +// FastLED.setBrightness(5); +// FastLED.setCorrection(0xB0E0FF); + fill_solid(blankLeds[0][0], 7*8*8, 0x0); + fill_solid(testCube[0][0], NUM_PLANES * NUM_ROWS * PIX_PER_ROW, background); + leds.begin(1.6, 72); //1.6 ocervlock factor, 72uS LED latch delay + leds.setBrightness(6); + leds.setBalance(0xDAE0FF); + + blanks.begin(); //default timing 800KHz LED clk, 75uS latch + blanks.show(); + while(Serial.read() == -1); +} //setup() + +void loop() { + startT = micros(); + // 20 calls + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + leds.show(); + stopT = micros(); + Serial.printf("LEDs/channel: %d Serial avg/20 time: %d uS %f fps\n", \ + PIX_PER_ROW*NUM_ROWS*NUM_PLANES/NUM_CHANNELS, + (stopT - startT) / 20, 20000000.0 / (stopT - startT)); + while (Serial.read() == -1); + + startT = micros(); + leds.show(); + stopT = micros(); + Serial.printf("LEDs/channel: %d Serial 1 frame time: %d uS %f fps\n", \ + PIX_PER_ROW*NUM_ROWS*NUM_PLANES/NUM_CHANNELS, + (stopT - startT), 1000000.0 / (stopT - startT)); + while (Serial.read() == -1); +return; + + +/* + startT = micros(); + // 20 calls + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + FastLED.show(); + stopT = micros(); + Serial.printf("LEDs/channel: %d FLED avg/20 time: %d uS %f fps\n", \ + PIX_PER_ROW*NUM_ROWS*NUM_PLANES/NUM_CHANNELS, + (stopT - startT) / 20, 20000000.0 / (stopT - startT)); + //Serial.printf("CPU temp: %.1f C %.1f F\n", tempmonGetTemp(), tempmonGetTemp() * 9.0 / 5.0 + 32); + while (Serial.read() == -1); + + startT = micros(); + FastLED.show(); + stopT = micros(); + Serial.printf("LEDs/channel: %d FLED 1 frame time: %d uS %f fps\n", \ + PIX_PER_ROW*NUM_ROWS*NUM_PLANES/NUM_CHANNELS, + (stopT - startT), 1000000.0 / (stopT - startT)); + while (Serial.read() == -1); +*/ +} //loop() diff --git a/examples/PlasmaAnimation/PlasmaAnimation.ino b/examples/PlasmaAnimation/PlasmaAnimation.ino new file mode 100644 index 0000000..b5db5d6 --- /dev/null +++ b/examples/PlasmaAnimation/PlasmaAnimation.ino @@ -0,0 +1,99 @@ +//PlazINT - Fast Plasma Generator using Integer Math Only +//Edmund "Skorn" Horn +//March 4,2013 +/* PlasmaAnimation.ino - ported the above code to use ObjectFLED display library. +Kurt Funderburg - Nov 2024 */ +#include +#include + +//ObjectFLED Defn. Stuff +#define COLS_LEDs 8 // X +#define ROWS_LEDs 20 // Y +#define NUM_PINS 5 // CHANNELS +#define NUM_LEDS (COLS_LEDs * ROWS_LEDs) + +byte pinList[NUM_PINS] = {1, 8, 14, 17, 24}; +const int config = CORDER_GRB; +int delayT = 20; // mS loop delay + +CRGB drawingMemory[NUM_LEDS]; +ObjectFLED leds(NUM_LEDS, drawingMemory, config, NUM_PINS, pinList, COLS_LEDs); + +//Byte val 2PI Cosine Wave, offset by 1 PI +//supports fast trig calcs and smooth LED fading/pulsing. +uint8_t const cos_wave[256] PROGMEM = +{0,0,0,0,1,1,1,2,2,3,4,5,6,6,8,9,10,11,12,14,15,17,18,20,22,23,25,27,29,31,33,35,38,40,42, +45,47,49,52,54,57,60,62,65,68,71,73,76,79,82,85,88,91,94,97,100,103,106,109,113,116,119, +122,125,128,131,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186, +189,191,194,197,199,202,204,207,209,212,214,216,218,221,223,225,227,229,231,232,234,236, +238,239,241,242,243,245,246,247,248,249,250,251,252,252,253,253,254,254,255,255,255,255, +255,255,255,255,254,254,253,253,252,252,251,250,249,248,247,246,245,243,242,241,239,238, +236,234,232,231,229,227,225,223,221,218,216,214,212,209,207,204,202,199,197,194,191,189, +186,183,180,177,174,171,168,165,162,159,156,153,150,147,144,141,138,135,131,128,125,122, +119,116,113,109,106,103,100,97,94,91,88,85,82,79,76,73,71,68,65,62,60,57,54,52,49,47,45, +42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14,12,11,10,9,8,6,6,5,4,3,2,2,1,1,1,0,0,0,0 +}; + + +//Gamma Correction Curve +uint8_t const exp_gamma[256] PROGMEM = +{0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3, +4,4,4,4,4,5,5,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,10,10,10,11,11,12,12,12,13,13,14,14,14,15,15, +16,16,17,17,18,18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33, +34,35,35,36,37,38,39,39,40,41,42,43,44,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60, +61,62,63,64,65,66,67,68,70,71,72,73,74,75,77,78,79,80,82,83,84,85,87,89,91,92,93,95,96,98, +99,100,101,102,105,106,108,109,111,112,114,115,117,118,120,121,123,125,126,128,130,131,133, +135,136,138,140,142,143,145,147,149,151,152,154,156,158,160,162,164,165,167,169,171,173,175, +177,179,181,183,185,187,190,192,194,196,198,200,202,204,207,209,211,213,216,218,220,222,225, +227,229,232,234,236,239,241,244,246,249,251,253,254,255 +}; + + +void setup() +{ +// Serial.begin(1000000); + leds.setBrightness(10); + leds.setBalance(0xdae0ff); + leds.begin(); + leds.show(); +} + +unsigned long frameCount=25500; // arbitrary seed to calculate the three time displacement variables t,t2,t3 +void loop() { + frameCount++ ; + uint16_t t = fastCosineCalc((42 * frameCount)/100); //time displacement - fiddle with these til it looks good... + uint16_t t2 = fastCosineCalc((35 * frameCount)/100); + uint16_t t3 = fastCosineCalc((38 * frameCount)/100); + + //implements 8-LED / ROW serpentine + for (uint8_t y = 0; y < ROWS_LEDs; y++) { + int pixelIndex; + pixelIndex = y * COLS_LEDs; + + for (uint8_t x = 0; x < COLS_LEDs ; x++) { + //Calculate 3 seperate plasma waves, one for each color channel + uint8_t r = fastCosineCalc(((x << 3) + (t >> 1) + fastCosineCalc((t2 + (y << 3))))); + uint8_t g = fastCosineCalc(((y << 3) + t + fastCosineCalc(((t3 >> 2) + (x << 3))))); + uint8_t b = fastCosineCalc((y << 3) + t2 + fastCosineCalc(t + (x << 3))); + if(r+g < 80) { b = max(b, 80); } + //uncomment the following to enable gamma correction + r=pgm_read_byte_near(exp_gamma+r)/2 + r/2; + g=pgm_read_byte_near(exp_gamma+g)/2 + g/2; + b=pgm_read_byte_near(exp_gamma+b)/2 + b/2; + drawingMemory[pixelIndex] = ((r << 16) & 0xff0000) | ((g << 8) & 0xff00) | b; + pixelIndex++; + } //for X + } //for Y + //digitalWrite(13, HIGH); + leds.show(); + //digitalWrite(13, LOW); + delay(delayT); +} // loop() + + +inline uint8_t fastCosineCalc( uint16_t preWrapVal) +{ + uint8_t wrapVal = (preWrapVal % 255); + if (wrapVal<0) wrapVal=255+wrapVal; + return (pgm_read_byte_near(cos_wave+wrapVal)); +} diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..bda9bb9 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,40 @@ +ObjectFLED KEYWORD1 +begin KEYWORD2 +show KEYWORD2 +busy KEYWORD2 +setBrightness KEYWORD2 +setBalance KEYWORD2 +getBrightness KEYWORD2 +getBalance KEYWORD2 +fadeToColorBy KEYWORD2 +drawSquare KEYWORD2 +CORDER_RGB LITERAL1 +CORDER_RBG LITERAL1 +CORDER_GRB LITERAL1 +CORDER_GBR LITERAL1 +CORDER_BRG LITERAL1 +CORDER_BGR LITERAL1 +CORDER_RGBW LITERAL1 +CORDER_RBGW LITERAL1 +CORDER_GRBW LITERAL1 +CORDER_GBRW LITERAL1 +CORDER_BRGW LITERAL1 +CORDER_BGRW LITERAL1 +CORDER_WRGB LITERAL1 +CORDER_WRBG LITERAL1 +CORDER_WGRB LITERAL1 +CORDER_WGBR LITERAL1 +CORDER_WBRG LITERAL1 +CORDER_WBGR LITERAL1 +CORDER_RWGB LITERAL1 +CORDER_RWBG LITERAL1 +CORDER_GWRB LITERAL1 +CORDER_GWBR LITERAL1 +CORDER_BWRG LITERAL1 +CORDER_BWGR LITERAL1 +CORDER_RGWB LITERAL1 +CORDER_RBWG LITERAL1 +CORDER_GRWB LITERAL1 +CORDER_GBWR LITERAL1 +CORDER_BRWG LITERAL1 +CORDER_BGWR LITERAL1 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..44076e7 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=ObjectFLED +version=1.0.0 +author=Kurt Funderburg +maintainer=Kurt Funderburg +sentence=Independently configure and display to various LED devices in one sketch with DMA-driven LED output. paragraph=Uses DMA transfer on all 40 or 55 pins of Teensy 4.0 or 4.1 to drive digital LEDs for display of FastLED arrays of CRGB, or other drawing buffer in RGB 3-byte format. The show() function is non-blocking, returning control to the graphics drawing code in just 6% of the time it takes to complete buffer transmission to an LED string, plane, or cube (tested with WS2812B, 1.6 overclock factor, 256 LED per pin x 16 pins = 4096 LEDs total @204 fps). +category=Display +url=https://github.com/KurtMF/ObjectFLED.git +architectures=* +