-
Glass Tiles: Glow vs. Flash Firmware
Although it’s not obvious in a still picture, the firmware now supports both the continuously changing colors of the Nissan fog lamp (mashed with tweaks from the vacuum tube lights) and the randomly changing colors from the LED matrix, both using SK6812 LEDs rather than the failing WS2812 modules:

Glass Tile – glow vs flash Flash is a misnomer, as the tiles simply change from one color to the next, but I’ve never been adept at picking catchy names. In any event, the glass tiles on the left show nice pastel shades, in contrast to the bright primary(-ish) colors appearing on the right.
The colors are random numbers from 1 to 7, because 0 produces a somewhat ugly dark cell. The SK6812 modules have a white LED in addition to the RGB LEDs in the WS2812 modules, so I replace the “additive white” R+G+B color with the more-or-less true white (warm, for these modules) LED.
The new color goes into a cell picked at random (0 through 3, for 2×2 frames), except if the cell already holds the same color, whereupon a simple XOR flips the colors, except if the cell is already full-on white, whereupon it becomes half-on white to avoid going completely dark.
The glass tiles must change colors at a much slower pace than the 8×8 LED matrix, because there are so few cells; a random delay between 500 ms and 6 s seems about right.
They look really great in a dim room!
The Arduino source code as a GitHub Gist:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters// Neopixel lighting for glass tiles // Ed Nisley – KE4ANU – May 2020 #include <Adafruit_NeoPixel.h> #include <Entropy.h> //———- // Pin assignments const byte PIN_NEO = A3; // DO – data to first Neopixel const byte PIN_MODE = 2; // DI – select mode const byte PIN_SPEED = 3; // DI – select speed const byte PIN_SELECT = 4; // DO – drive adjacent pins low const byte PIN_HEARTBEAT = 13; // DO – Arduino LED //———- // Constants // number of pixels #define PIXELS 9 // lag between adjacent pixels in degrees of slowest period #define PIXELPHASE 45 // update LEDs only this many ms apart (minus loop() overhead) #define UPDATEINTERVAL 50ul // number of steps per cycle, before applying prime factors #define RESOLUTION 500 //———- // Globals // instantiate the Neopixel buffer array Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS, PIN_NEO, NEO_GRBW + NEO_KHZ800); struct pixcolor_t { unsigned int Prime; unsigned int NumSteps; unsigned int Step; float StepSize; float Phase; byte MaxPWM; }; unsigned int PlatterSteps; byte PrimeList[] = {3,5,7,13,19,29}; unsigned int MaxTileTime; // colors in each LED enum pixcolors {RED, GREEN, BLUE, WHITE, PIXELSIZE}; struct pixcolor_t Pixels[PIXELSIZE]; // all the data for each pixel color intensity uint32_t UniColor; enum dispmode {GLOW, FLASH}; // based on input pin unsigned long UpdateMS; unsigned long MillisNow; unsigned long MillisThen; //– Select three unique primes for the color generator function // Then compute all the step parameters based on those values void SetColorGenerators(void) { Pixels[RED].Prime = PrimeList[random(sizeof(PrimeList))]; do { Pixels[GREEN].Prime = PrimeList[random(sizeof(PrimeList))]; } while (Pixels[RED].Prime == Pixels[GREEN].Prime); do { Pixels[BLUE].Prime = PrimeList[random(sizeof(PrimeList))]; } while (Pixels[BLUE].Prime == Pixels[RED].Prime || Pixels[BLUE].Prime == Pixels[GREEN].Prime); do { Pixels[WHITE].Prime = PrimeList[random(sizeof(PrimeList))]; } while (Pixels[WHITE].Prime == Pixels[RED].Prime || Pixels[WHITE].Prime == Pixels[GREEN].Prime || Pixels[WHITE].Prime == Pixels[BLUE].Prime); if (!digitalRead(PIN_SPEED)) { // force fast for debugging Pixels[RED].Prime = 3; Pixels[GREEN].Prime = 5; Pixels[BLUE].Prime = 7; Pixels[WHITE].Prime = 11; } printf("Primes: %d %d %d %d\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime,Pixels[WHITE].Prime); Pixels[RED].MaxPWM = 255; Pixels[GREEN].MaxPWM = 255; Pixels[BLUE].MaxPWM = 255; Pixels[WHITE].MaxPWM = 255; unsigned int PhaseSteps = (unsigned int) ((PIXELPHASE / 360.0) * RESOLUTION * (unsigned int) max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime)); printf("Pixel phase offset: %d deg = %d steps\r\n",(int)PIXELPHASE,PhaseSteps); for (byte c=0; c < PIXELSIZE; c++) { Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime; // steps per cycle Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps; // radians per step Pixels[c].Step = random(Pixels[c].NumSteps); // current step Pixels[c].Phase = PhaseSteps * Pixels[c].StepSize;; // phase in radians for this color printf(" c: %d Steps: %5d Init: %5d Phase: %3d deg",c,Pixels[c].NumSteps,Pixels[c].Step,(int)(Pixels[c].Phase * 360.0 / TWO_PI)); printf(" PWM: %d\r\n",Pixels[c].MaxPWM); } } //– Helper routine for printf() int s_putc(char c, FILE *t) { Serial.write(c); } //—————— // Set the mood void setup() { pinMode(PIN_HEARTBEAT,OUTPUT); digitalWrite(PIN_HEARTBEAT,LOW); // show we arrived pinMode(PIN_MODE,INPUT_PULLUP); pinMode(PIN_SPEED,INPUT_PULLUP); pinMode(PIN_SELECT,OUTPUT); digitalWrite(PIN_SELECT,LOW); // drive adjacent pins Serial.begin(57600); fdevopen(&s_putc,0); // set up serial output for printf() printf("\r\nAlgorithmic Art Light – Glass Tiles\r\nEd Nisley – KE4ZNU – May 2020\r\n"); printf("Display mode: %s\r\n",digitalRead(PIN_MODE) == GLOW ? "Glow" : "Flash"); printf("Speed: %s\r\n",digitalRead(PIN_SPEED) ? "Normal" : "Override"); Entropy.initialize(); // start up entropy collector // set up pixels strip.begin(); strip.show(); // lamp test printf("Lamp test: flash full-on colors\r\n"); uint32_t FullRGBW = strip.Color(255,255,255,255); uint32_t FullRGB = strip.Color(255,255,255,0); uint32_t FullR = strip.Color(255,0,0,0); uint32_t FullG = strip.Color(0,255,0,0); uint32_t FullB = strip.Color(0,0,255,0); uint32_t FullW = strip.Color(0,0,0,255); uint32_t FullOff = strip.Color(0,0,0,0); uint32_t TestColors[] = {FullR,FullG,FullB,FullRGB,FullW,FullRGBW,FullOff}; for (byte i=0; i < sizeof(TestColors)/sizeof(uint32_t) ; i++) { printf(" color: %08lx\r\n",TestColors[i]); for (int j=0; j < strip.numPixels(); j++) { strip.setPixelColor(j,TestColors[i]); } strip.show(); delay(1000); } // while (1) {continue;}; // all LEDs constant for burn-in testing // get an actual random number uint32_t rn = Entropy.random(); printf("Random seed: %08lx\r\n",rn); randomSeed(rn); // set up the color generators SetColorGenerators(); MaxTileTime = (digitalRead(PIN_SPEED) ? 6 : 1) * (1000.0 / UPDATEINTERVAL); UpdateMS = UPDATEINTERVAL; MillisNow = MillisThen = millis(); } //—————— // Run the mood void loop() { MillisNow = millis(); if ((MillisNow – MillisThen) >= UpdateMS) { // time for color change? digitalWrite(PIN_HEARTBEAT,HIGH); if (digitalRead(PIN_MODE) == GLOW) { boolean CycleRun = false; // check to see if all cycles have ended for (byte c=0; c < PIXELSIZE; c++) { // compute next increment for each color if (++Pixels[c].Step >= Pixels[c].NumSteps) { Pixels[c].Step = 0; printf("Cycle %5d steps %5d at %8ld delta %8ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow – MillisThen)); } else { CycleRun = true; // this color is still cycling } } if (!CycleRun) { printf("All cycles ended: setting new color generator values\r\n"); SetColorGenerators(); } for (int i=0; i < strip.numPixels(); i++) { // for each pixel byte Value[PIXELSIZE]; for (byte c=0; c < PIXELSIZE; c++) { // … for each color Value[c] = (Pixels[c].MaxPWM / 2.0) * (1.0 + sin(Pixels[c].Step * Pixels[c].StepSize – i*Pixels[c].Phase)); } byte WhiteBias = min(min(Value[RED],Value[GREEN]),Value[BLUE]); // hack to reduce power UniColor = strip.Color((Value[RED] – WhiteBias) * Pixels[RED].MaxPWM/255, (Value[GREEN] – WhiteBias) * Pixels[GREEN].MaxPWM/255, (Value[BLUE] – WhiteBias) * Pixels[BLUE].MaxPWM/255, WhiteBias * Pixels[WHITE].MaxPWM/255); strip.setPixelColor(i,UniColor); } } else { byte c = random(1,8); // exclude 0 = all off, to avoid darkness printf("Color %d ",c); byte r = c & 0x04 ? 0xff : 0; byte g = c & 0x02 ? 0xff : 0; byte b = c & 0x01 ? 0xff : 0; byte w = 0; if (c == 7) { // use white LED instead of R+G+B r = g = b = 0; w = 0xff; } UniColor = strip.Color(r, g, b, w); byte i = random(strip.numPixels()); printf("at %d ",i); if (UniColor == strip.getPixelColor(i)) { // flip color printf("^ "); if (w) { // white becomes dim w = 0x7f; UniColor = strip.Color(r, g, b, w); } else UniColor ^= 0xffffff00l; // other colors flip } else { printf(" "); } strip.setPixelColor(i,UniColor); UpdateMS = random(10,MaxTileTime) * UPDATEINTERVAL; // pick time for next update printf("delay: %6ld ms\r\n",UpdateMS); } strip.show(); // send out precomputed colors MillisThen = MillisNow; digitalWrite(PIN_HEARTBEAT,LOW); } }