The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

The New Hotness

  • 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
    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:

    // 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);
    }
    }
    view raw GlassTiles.ino hosted with ❤ by GitHub