Compare commits

...

1 commit

Author SHA1 Message Date
Phillip Burgess
c30cc5cb3c Kludgey 16-channel-wide implementation 2018-04-16 16:22:35 -07:00
3 changed files with 257 additions and 37 deletions

View file

@ -41,14 +41,16 @@ so this is not a 100% drop-in replacement for all NeoPixel code right now.
// match the NeoPixel spec)...usually only affects the 1st pixel, // match the NeoPixel spec)...usually only affects the 1st pixel,
// subsequent pixels OK due to signal reshaping through the 1st. // subsequent pixels OK due to signal reshaping through the 1st.
static const int8_t defaultPins[] = { 0,1,2,3,4,5,6,7 }; static const int8_t defaultPins[] = { 0,1,2,3,4,5,6,7 };
static volatile boolean sending = 0; // Set while DMA transfer is active static volatile boolean sending[2] = {0}; // Set while DMA transfer is active
static volatile uint32_t lastBitTime; // micros() when last bit issued static volatile uint32_t lastBitTime[2]; // micros() when last bit issued
Adafruit_NeoPXL8::Adafruit_NeoPXL8( Adafruit_NeoPXL8::Adafruit_NeoPXL8(
uint16_t n, int8_t *p, neoPixelType t) : Adafruit_NeoPixel(n * 8, -1, t), uint16_t n, int8_t *p, neoPixelType t, uint8_t tn) :
brightness(256), dmaBuf(NULL) { Adafruit_NeoPixel(n * 8, -1, t), brightness(256), dmaBuf(NULL) {
memcpy(pins, p ? p : defaultPins, sizeof(pins)); memcpy(pins, p ? p : defaultPins, sizeof(pins));
tcc = tn;
} }
Adafruit_NeoPXL8::~Adafruit_NeoPXL8() { Adafruit_NeoPXL8::~Adafruit_NeoPXL8() {
@ -62,13 +64,15 @@ Adafruit_NeoPXL8::~Adafruit_NeoPXL8() {
// PORT/pin combos are NOT present on some dev boards or SAMD21 variants. // PORT/pin combos are NOT present on some dev boards or SAMD21 variants.
// If a pin is NOT in this list, it just means there's no TCC0/W[n] func // If a pin is NOT in this list, it just means there's no TCC0/W[n] func
// there, but it may still exist and have other peripheral functions. // there, but it may still exist and have other peripheral functions.
static struct { typedef struct {
EPortType port; // PORTA|PORTB EPortType port; // PORTA|PORTB
uint8_t bit; // Port bit (0-31) uint8_t bit; // Port bit (0-31)
uint8_t wo; // TCC0/WO# (0-7) uint8_t wo; // TCC0/WO# (0-7)
EPioType peripheral; // Peripheral to select for TCC0 out EPioType peripheral; // Peripheral to select for TCC0 out
} tcc0pinMap[] = { } pinMap;
#ifdef __SAMD51__ #ifdef __SAMD51__
pinMap tcc0pinMap[] = {
{ PORTA, 8, 0, PIO_TIMER_ALT }, // FLASH_IO0 on Metro M4 { PORTA, 8, 0, PIO_TIMER_ALT }, // FLASH_IO0 on Metro M4
{ PORTA, 9, 1, PIO_TIMER_ALT }, // FLASH_IO1 { PORTA, 9, 1, PIO_TIMER_ALT }, // FLASH_IO1
{ PORTA, 10, 2, PIO_TIMER_ALT }, // FLASH_IO2 { PORTA, 10, 2, PIO_TIMER_ALT }, // FLASH_IO2
@ -94,7 +98,28 @@ static struct {
{ PORTB, 17, 5, PIO_TCC_PDEC }, // D2 { PORTB, 17, 5, PIO_TCC_PDEC }, // D2
{ PORTB, 30, 6, PIO_TCC_PDEC }, // SWO { PORTB, 30, 6, PIO_TCC_PDEC }, // SWO
{ PORTB, 31, 7, PIO_TCC_PDEC }, // NC { PORTB, 31, 7, PIO_TCC_PDEC }, // NC
}, tcc1pinMap[] = {
{ PORTA, 8, 4, PIO_TCC_PDEC }, // FLASH_IO0 on Metro M4
{ PORTA, 9, 5, PIO_TCC_PDEC }, // FLASH_IO1
{ PORTA, 10, 6, PIO_TCC_PDEC }, // FLASH_IO2
{ PORTA, 11, 7, PIO_TCC_PDEC }, // FLASH_IO3
{ PORTA, 12, 2, PIO_TCC_PDEC }, // MOSI
{ PORTA, 13, 3, PIO_TCC_PDEC }, // SCK
{ PORTA, 14, 2, PIO_TCC_PDEC }, // MISO
{ PORTA, 15, 3, PIO_TCC_PDEC }, // NC
{ PORTA, 16, 0, PIO_TIMER_ALT }, // 13
{ PORTA, 17, 1, PIO_TIMER_ALT }, // 12
{ PORTA, 18, 2, PIO_TIMER_ALT }, // 10
{ PORTA, 19, 3, PIO_TIMER_ALT }, // 11
{ PORTA, 20, 4, PIO_TIMER_ALT }, // 9
{ PORTA, 21, 5, PIO_TIMER_ALT }, // 8
{ PORTA, 22, 6, PIO_TIMER_ALT }, // 1
{ PORTA, 23, 7, PIO_TIMER_ALT }, // 0
{ PORTB, 10, 0, PIO_TCC_PDEC }, // FLASH_SCK
{ PORTB, 11, 1, PIO_TCC_PDEC }, // FLASH_CS
};
#else #else
pinMap tcc0pinMap[] = {
{ PORTA, 4, 0, PIO_TIMER }, // A3 on Metro M0 { PORTA, 4, 0, PIO_TIMER }, // A3 on Metro M0
{ PORTA, 5, 1, PIO_TIMER }, // A4 { PORTA, 5, 1, PIO_TIMER }, // A4
{ PORTA, 8, 0, PIO_TIMER }, // D4 { PORTA, 8, 0, PIO_TIMER }, // D4
@ -118,24 +143,46 @@ static struct {
{ PORTB, 12, 6, PIO_TIMER_ALT }, // NC { PORTB, 12, 6, PIO_TIMER_ALT }, // NC
{ PORTB, 13, 7, PIO_TIMER_ALT }, // NC { PORTB, 13, 7, PIO_TIMER_ALT }, // NC
{ PORTB, 16, 4, PIO_TIMER_ALT }, // NC { PORTB, 16, 4, PIO_TIMER_ALT }, // NC
{ PORTB, 17, 5, PIO_TIMER_ALT }, // NC { PORTB, 17, 5, PIO_TIMER_ALT }, // NEOPIX
{ PORTB, 30, 0, PIO_TIMER }, // NC { PORTB, 30, 0, PIO_TIMER }, // NC
{ PORTB, 31, 1, PIO_TIMER } // NC { PORTB, 31, 1, PIO_TIMER } // NC
#endif
}; };
#endif
#define PINMAPSIZE (sizeof(tcc0pinMap) / sizeof(tcc0pinMap[0])) #define PINMAPSIZE (sizeof(tcc0pinMap) / sizeof(tcc0pinMap[0]))
#ifdef __SAMD51__
#define PINMAP1SIZE (sizeof(tcc1pinMap) / sizeof(tcc1pinMap[0]))
#endif
// Given a pin number, locate corresponding entry in the pin map table // Given a pin number, locate corresponding entry in the pin map table
// above, configure as a pattern generator output and return bitmask // above, configure as a pattern generator output and return bitmask
// for later data conversion (returns 0 if invalid pin). // for later data conversion (returns 0 if invalid pin).
static uint8_t configurePin(uint8_t pin) { static uint8_t configurePin(uint8_t tcc, uint8_t pin) {
if((pin >= 0) && (pin < PINS_COUNT)) { // if((pin >= 0) && (pin < PINS_COUNT)) {
if(pin >= 0) {
EPortType port = g_APinDescription[pin].ulPort; EPortType port = g_APinDescription[pin].ulPort;
uint8_t bit = g_APinDescription[pin].ulPin; uint8_t bit = g_APinDescription[pin].ulPin;
#ifdef __SAMD51__
uint8_t mapsize = tcc ? PINMAP1SIZE : PINMAPSIZE;
pinMap *pm = tcc ? tcc1pinMap : tcc0pinMap;
for(uint8_t i=0; i<mapsize; i++) {
#else
pinMap *pm = tcc0pinMap;
for(uint8_t i=0; i<PINMAPSIZE; i++) { for(uint8_t i=0; i<PINMAPSIZE; i++) {
if((port == tcc0pinMap[i].port) && (bit == tcc0pinMap[i].bit)) { #endif
pinPeripheral(pin, tcc0pinMap[i].peripheral); if((port == pm[i].port) && (bit == pm[i].bit)) {
return (1 << tcc0pinMap[i].wo); #if 0
Serial.print(tcc);
Serial.write(' ');
Serial.print(pm[i].port);
Serial.write(' ');
Serial.print(pm[i].bit);
Serial.write(' ');
Serial.println(pm[i].wo);
#endif
pinPeripheral(pin, pm[i].peripheral);
return (1 << pm[i].wo);
} }
} }
} }
@ -144,9 +191,14 @@ static uint8_t configurePin(uint8_t pin) {
// Called at end of DMA transfer. Clears 'sending' flag and notes // Called at end of DMA transfer. Clears 'sending' flag and notes
// start-of-NeoPixel-latch time. // start-of-NeoPixel-latch time.
static void dmaCallback(Adafruit_ZeroDMA* dma) { static void dmaCallback0(Adafruit_ZeroDMA* dma) {
lastBitTime = micros(); lastBitTime[0] = micros();
sending = 0; sending[0] = 0;
}
static void dmaCallback1(Adafruit_ZeroDMA* dma) {
lastBitTime[1] = micros();
sending[1] = 0;
} }
boolean Adafruit_NeoPXL8::begin(void) { boolean Adafruit_NeoPXL8::begin(void) {
@ -157,7 +209,11 @@ boolean Adafruit_NeoPXL8::begin(void) {
if((dmaBuf = (uint8_t *)malloc(bytesTotal))) { if((dmaBuf = (uint8_t *)malloc(bytesTotal))) {
int i; int i;
#ifdef __SAMD51__
dma.setTrigger(tcc ? TCC1_DMAC_ID_OVF : TCC0_DMAC_ID_OVF);
#else
dma.setTrigger(TCC0_DMAC_ID_OVF); dma.setTrigger(TCC0_DMAC_ID_OVF);
#endif
dma.setAction(DMA_TRIGGER_ACTON_BEAT); dma.setAction(DMA_TRIGGER_ACTON_BEAT);
// Get address of first byte that's on a 32-bit boundary // Get address of first byte that's on a 32-bit boundary
@ -167,7 +223,13 @@ boolean Adafruit_NeoPXL8::begin(void) {
uint8_t *startAddr = (uint8_t *)alignedAddr - EXTRASTARTBYTES; uint8_t *startAddr = (uint8_t *)alignedAddr - EXTRASTARTBYTES;
memset(startAddr, 0, EXTRASTARTBYTES); // Initialize start with zeros memset(startAddr, 0, EXTRASTARTBYTES); // Initialize start with zeros
#ifdef __SAMD51__
uint8_t *dst = tcc ?
&((uint8_t *)(&TCC1->PATT))[1] : // PAT.vec.PGV
&((uint8_t *)(&TCC0->PATT))[1];
#else
uint8_t *dst = &((uint8_t *)(&TCC0->PATT))[1]; // PAT.vec.PGV uint8_t *dst = &((uint8_t *)(&TCC0->PATT))[1]; // PAT.vec.PGV
#endif
dma.allocate(); dma.allocate();
dma.addDescriptor( dma.addDescriptor(
startAddr, // source startAddr, // source
@ -177,7 +239,11 @@ boolean Adafruit_NeoPXL8::begin(void) {
true, // increment source true, // increment source
false); // don't increment destination false); // don't increment destination
dma.setCallback(dmaCallback); #ifdef __SAMD51__
dma.setCallback(tcc ? dmaCallback1 : dmaCallback0);
#else
dma.setCallback(dmaCallback0);
#endif
#ifdef __SAMD51__ #ifdef __SAMD51__
// Set up generic clock gen 2 as source for TCC0 // Set up generic clock gen 2 as source for TCC0
@ -192,19 +258,88 @@ boolean Adafruit_NeoPXL8::begin(void) {
GCLK->GENCTRL[2].reg = genctrl.reg; GCLK->GENCTRL[2].reg = genctrl.reg;
while(GCLK->SYNCBUSY.bit.GENCTRL1 == 1); while(GCLK->SYNCBUSY.bit.GENCTRL1 == 1);
GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN = 0; if(tcc) {
while(GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN); // Wait for disable GCLK->PCHCTRL[TCC1_GCLK_ID].bit.CHEN = 0;
GCLK_PCHCTRL_Type pchctrl; while(GCLK->PCHCTRL[TCC1_GCLK_ID].bit.CHEN); // Wait for disable
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK2_Val; GCLK_PCHCTRL_Type pchctrl;
pchctrl.bit.CHEN = 1; pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK2_Val;
GCLK->PCHCTRL[TCC0_GCLK_ID].reg = pchctrl.reg; pchctrl.bit.CHEN = 1;
while(!GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN); // Wait for enable GCLK->PCHCTRL[TCC1_GCLK_ID].reg = pchctrl.reg;
while(!GCLK->PCHCTRL[TCC1_GCLK_ID].bit.CHEN); // Wait for enable
// Disable TCC before configuring it
TCC1->CTRLA.bit.ENABLE = 0;
while(TCC1->SYNCBUSY.bit.ENABLE);
TCC1->CTRLA.bit.PRESCALER = TCC_CTRLA_PRESCALER_DIV1_Val; // 1:1 Scale
TCC1->WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; // Normal PWM mode
while(TCC1->SYNCBUSY.bit.WAVE);
TCC1->CC[0].reg = 0; // No PWM out
while(TCC1->SYNCBUSY.bit.CC0);
// 2.4 GHz clock: 3 DMA xfers per NeoPixel bit = 800 KHz
TCC1->PER.reg = ((48000000 + 1200000)/ 2400000) - 1;
while(TCC1->SYNCBUSY.bit.PER);
memset(bitmask, 0, sizeof(bitmask));
uint8_t enableMask = 0x00; // Bitmask of pattern gen outputs
for(i=0; i<8; i++) {
if(bitmask[i] = configurePin(tcc, pins[i])) enableMask |= bitmask[i];
}
TCC1->PATT.vec.PGV = 0; // Set all pattern outputs to 0
while(TCC1->SYNCBUSY.bit.PATT);
TCC1->PATT.vec.PGE = enableMask; // Enable pattern outputs
while(TCC1->SYNCBUSY.bit.PATT);
TCC1->CTRLA.bit.ENABLE = 1;
while(TCC1->SYNCBUSY.bit.ENABLE);
} else {
GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN = 0;
while(GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN); // Wait for disable
GCLK_PCHCTRL_Type pchctrl;
pchctrl.bit.GEN = GCLK_PCHCTRL_GEN_GCLK2_Val;
pchctrl.bit.CHEN = 1;
GCLK->PCHCTRL[TCC0_GCLK_ID].reg = pchctrl.reg;
while(!GCLK->PCHCTRL[TCC0_GCLK_ID].bit.CHEN); // Wait for enable
// Disable TCC before configuring it
TCC0->CTRLA.bit.ENABLE = 0;
while(TCC0->SYNCBUSY.bit.ENABLE);
TCC0->CTRLA.bit.PRESCALER = TCC_CTRLA_PRESCALER_DIV1_Val; // 1:1 Scale
TCC0->WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; // Normal PWM mode
while(TCC0->SYNCBUSY.bit.WAVE);
TCC0->CC[0].reg = 0; // No PWM out
while(TCC0->SYNCBUSY.bit.CC0);
// 2.4 GHz clock: 3 DMA xfers per NeoPixel bit = 800 KHz
TCC0->PER.reg = ((48000000 + 1200000)/ 2400000) - 1;
while(TCC0->SYNCBUSY.bit.PER);
memset(bitmask, 0, sizeof(bitmask));
uint8_t enableMask = 0x00; // Bitmask of pattern gen outputs
for(i=0; i<8; i++) {
if(bitmask[i] = configurePin(tcc, pins[i])) enableMask |= bitmask[i];
}
TCC0->PATT.vec.PGV = 0; // Set all pattern outputs to 0
while(TCC0->SYNCBUSY.bit.PATT);
TCC0->PATT.vec.PGE = enableMask; // Enable pattern outputs
while(TCC0->SYNCBUSY.bit.PATT);
TCC0->CTRLA.bit.ENABLE = 1;
while(TCC0->SYNCBUSY.bit.ENABLE);
}
#else #else
// Enable GCLK for TCC0 // Enable GCLK for TCC0
GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN | GCLK->CLKCTRL.reg = (uint16_t)(GCLK_CLKCTRL_CLKEN |
GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC0_TCC1)); GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC0_TCC1));
while(GCLK->STATUS.bit.SYNCBUSY == 1); while(GCLK->STATUS.bit.SYNCBUSY == 1);
#endif
// Disable TCC before configuring it // Disable TCC before configuring it
TCC0->CTRLA.bit.ENABLE = 0; TCC0->CTRLA.bit.ENABLE = 0;
@ -219,17 +354,13 @@ boolean Adafruit_NeoPXL8::begin(void) {
while(TCC0->SYNCBUSY.bit.CC0); while(TCC0->SYNCBUSY.bit.CC0);
// 2.4 GHz clock: 3 DMA xfers per NeoPixel bit = 800 KHz // 2.4 GHz clock: 3 DMA xfers per NeoPixel bit = 800 KHz
#ifdef __SAMD51__
TCC0->PER.reg = ((48000000 + 1200000)/ 2400000) - 1;
#else
TCC0->PER.reg = ((F_CPU + 1200000)/ 2400000) - 1; TCC0->PER.reg = ((F_CPU + 1200000)/ 2400000) - 1;
#endif
while(TCC0->SYNCBUSY.bit.PER); while(TCC0->SYNCBUSY.bit.PER);
memset(bitmask, 0, sizeof(bitmask)); memset(bitmask, 0, sizeof(bitmask));
uint8_t enableMask = 0x00; // Bitmask of pattern gen outputs uint8_t enableMask = 0x00; // Bitmask of pattern gen outputs
for(i=0; i<8; i++) { for(i=0; i<8; i++) {
if(bitmask[i] = configurePin(pins[i])) enableMask |= bitmask[i]; if(bitmask[i] = configurePin(tcc, pins[i])) enableMask |= bitmask[i];
} }
TCC0->PATT.vec.PGV = 0; // Set all pattern outputs to 0 TCC0->PATT.vec.PGV = 0; // Set all pattern outputs to 0
while(TCC0->SYNCBUSY.bit.PATT); while(TCC0->SYNCBUSY.bit.PATT);
@ -238,6 +369,7 @@ boolean Adafruit_NeoPXL8::begin(void) {
TCC0->CTRLA.bit.ENABLE = 1; TCC0->CTRLA.bit.ENABLE = 1;
while(TCC0->SYNCBUSY.bit.ENABLE); while(TCC0->SYNCBUSY.bit.ENABLE);
#endif
return true; // Success! return true; // Success!
} }
@ -295,13 +427,13 @@ void Adafruit_NeoPXL8::stage(void) {
} }
void Adafruit_NeoPXL8::show(void) { void Adafruit_NeoPXL8::show(void) {
while(sending); // Wait for DMA callback while(sending[tcc]); // Wait for DMA callback
if(!staged) stage(); // Convert data if(!staged) stage(); // Convert data
dma.startJob(); dma.startJob();
staged = false; staged = false;
sending = 1; sending[tcc] = 1;
// Wait for latch, factor in EXTRASTARTBYTES transmission time too! // Wait for latch, factor in EXTRASTARTBYTES transmission time too!
while((micros() - lastBitTime) <= (LATCHTIME - (EXTRASTARTBYTES * 5 / 4))); while((micros() - lastBitTime[tcc]) <= (LATCHTIME - (EXTRASTARTBYTES * 5 / 4)));
dma.trigger(); dma.trigger();
} }
@ -316,13 +448,13 @@ void Adafruit_NeoPXL8::show(void) {
// transmitting, rather than being done at the beginning of the show() // transmitting, rather than being done at the beginning of the show()
// function (the staging conversion isn't entirely deterministic). // function (the staging conversion isn't entirely deterministic).
boolean Adafruit_NeoPXL8::canStage(void) { boolean Adafruit_NeoPXL8::canStage(void) {
return !sending; return !sending[tcc];
} }
// Returns true if DMA transfer is NOT presently occurring and // Returns true if DMA transfer is NOT presently occurring and
// NeoPixel EOD latch has fully transpired; library is idle. // NeoPixel EOD latch has fully transpired; library is idle.
boolean Adafruit_NeoPXL8::canShow(void) { boolean Adafruit_NeoPXL8::canShow(void) {
return !sending && ((micros() - lastBitTime) > 300); return !sending[tcc] && ((micros() - lastBitTime[tcc]) > 300);
} }
// Brightness is stored differently here than in normal NeoPixel library. // Brightness is stored differently here than in normal NeoPixel library.

View file

@ -8,7 +8,8 @@ class Adafruit_NeoPXL8 : public Adafruit_NeoPixel {
public: public:
Adafruit_NeoPXL8(uint16_t n, int8_t *p=NULL, neoPixelType t=NEO_GRB); Adafruit_NeoPXL8(uint16_t n, int8_t *p=NULL, neoPixelType t=NEO_GRB,
uint8_t tn=0);
~Adafruit_NeoPXL8(); ~Adafruit_NeoPXL8();
boolean begin(void), boolean begin(void),
@ -28,6 +29,7 @@ class Adafruit_NeoPXL8 : public Adafruit_NeoPixel {
uint32_t *alignedAddr; // long-aligned ptr into dmaBuf (see code) uint32_t *alignedAddr; // long-aligned ptr into dmaBuf (see code)
uint16_t brightness; uint16_t brightness;
boolean staged; // If set, data is ready for DMA trigger boolean staged; // If set, data is ready for DMA trigger
uint8_t tcc; // TCC peripheral #
}; };
#endif // _ADAFRUIT_NEOPXL8_H_ #endif // _ADAFRUIT_NEOPXL8_H_

View file

@ -0,0 +1,86 @@
// Example/diagnostic for the Adafruit_NeoPXL8 library. Each of 8 strands
// is a distinct color, helps identify which pin goes to which strand.
// For more complete usage of NeoPixel operations, see the examples
// included with the 'regular' Adafruit_NeoPixel library.
// Also requires LATEST Adafruit_NeoPixel, Adafruit_ZeroDMA and
// Adafruit_ASFcore libraries.
// May require a logic level shifter (e.g. 75HCT245) for 5V pixels,
// or use NeoPXL8 Featherwing for Adafruit Feather M0 boards.
#include <Adafruit_NeoPXL8.h>
#define NUM_LED 64 // Per strand. Total number of pixels is 8X this!
// Second argument to constructor is an optional 8-byte pin list,
// or pass NULL to use pins 0-7 on Metro Express, Arduino Zero, etc.
//int8_t pins0[8] = { 2, 13, 3, 6, 8, 9, 10, 11 }, // TCC0
// pins1[8] = { 0, 1, 12, MOSI, SCK, A4, -1, -1 }; // TCC1
// Metro M4 TCC0 pins:
//int8_t pins[8] = { 7, 4, 5, 6, 3, 2, 10, 11 };
// Alt TCC0 pins:
//int8_t pins[8] = { 9, 8, 0, 1, 13, 12, -1, SCK };
int8_t pins0[8] = { 7, 4, 5, 6, 3, 2, 10, 11 }, // TCC0
pins1[8] = { 13, 12, MOSI, SCK, 9, 8, 1, 0 }; // TCC1
Adafruit_NeoPXL8 leds0(NUM_LED, pins0, NEO_GRB, 0),
leds1(NUM_LED, pins1, NEO_GRB, 1);
void setup() {
Serial.begin(115200);
while(!Serial);
leds0.begin();
leds0.setBrightness(32);
leds1.begin();
leds1.setBrightness(32);
}
uint8_t frame = 0;
void loop() {
for(uint8_t r=0; r<8; r++) { // For each row...
for(int p=0; p<NUM_LED; p++) { // For each pixel of row...
leds0.setPixelColor(r * NUM_LED + p, rain(r, p));
leds1.setPixelColor(r * NUM_LED + p, rain(r, p));
}
}
leds0.stage();
leds1.stage();
leds0.show();
for(volatile int i=0; i<2000; i++);
leds1.show();
frame++;
}
uint8_t colors[8][3] = { // RGB colors for the 8 rows...
255, 0, 0, // Row 0: Red
255, 160, 0, // Row 1: Orange
255, 255, 0, // Row 2: Yellow
0, 255, 0, // Row 3: Green
0, 255, 255, // Row 4: Cyan
0, 0, 255, // Row 5: Blue
192, 0, 255, // Row 6: Purple
255, 0, 255 // Row 7: Magenta
};
// Gamma-correction table improves the appearance of midrange colors
#define _GAMMA_ 2.6
const int _GBASE_ = __COUNTER__ + 1; // Index of 1st __COUNTER__ ref below
#define _G1_ (uint8_t)(pow((__COUNTER__ - _GBASE_) / 255.0, _GAMMA_) * 255.0 + 0.5),
#define _G2_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ _G1_ // Expands to 8 lines
#define _G3_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ _G2_ // Expands to 64 lines
const uint8_t gamma8[] = { _G3_ _G3_ _G3_ _G3_ }; // 256 lines
// Given row number (0-7) and pixel number along row (0 - (NUM_LED-1)),
// first calculate brightness (b) of pixel, then multiply row color by
// this and run it through gamma-correction table.
uint32_t rain(uint8_t row, int pixelNum) {
uint16_t b = 256 - ((frame - row * 32 + pixelNum * 256 / NUM_LED) & 0xFF);
return ((uint32_t)gamma8[(colors[row][0] * b) >> 8] << 16) |
((uint32_t)gamma8[(colors[row][1] * b) >> 8] << 8) |
gamma8[(colors[row][2] * b) >> 8];
}