Glass Tiles

A sheet of cheap-on-closeout glass tiles emerged from the back of the Basement Laboratory workbench:

Glass Tiles - as sold
Glass Tiles – as sold

They’re intended for bathroom / kitchen backsplash panels and suchlike, rather than floors. Surprisingly, the white frit backing is diffuse, translucent, and lights up nicely with a backlight, although I lack sufficient hands for a convincing picture.

One can, with some effort, peel the tiles from their foot-square backing mesh, which leaves them covered with the resolutely sticky adhesive:

Glass Tiles - adhesive mesh
Glass Tiles – adhesive mesh

Applying the razor scraper removes most of the gum, xylene removes most of the remainder, and what’s left isn’t visible through the frit.

They’re 25 mm square and 4 mm thick, with sufficient edge imperfections to require half a millimeter of clearance on all sides

Sixteen pixels would make an adequate display:

Glass Tiles - sample layout
Glass Tiles – sample layout

Perhaps something random:

Random LED Dots - circuit board
Random LED Dots – circuit board

Now, if only I could find the matching Round Tuit™ on the bench.

Nissan Fog Lamp: Arduino Firmware

The upcycled Nissan fog lamp now has a desk stand:

Nissan Fog Lamp - table mount
Nissan Fog Lamp – table mount

A knockoff Arduino Pro Mini atop a strip of foam tape drives the WS2812 RGB LEDs:

Nissan Fog Lamp - table mount interior
Nissan Fog Lamp – table mount interior

Next time, I’ll cut the wires another inch longer.

The firmware is a tidied-up version of the vacuum tube code, minus cruft, plus fixes, and generally better at doing what it does. The Pro Mini lacks a USB output, so this came from the same code running on a Nano:

14:44:04.169 -> Algorithmic Art
14:44:04.169 ->  RGB WS2812
14:44:04.169 -> Ed Nisley - KE4ZNU - April 2020
14:44:04.169 -> Lamp test: flash full-on colors
14:44:04.169 ->  color: 00ff0000
14:44:05.165 ->  color: 0000ff00
14:44:06.160 ->  color: 000000ff
14:44:07.155 ->  color: 00ffffff
14:44:08.151 ->  color: 00000000
14:44:09.180 -> Random seed: da98f7f6
14:44:09.180 -> Primes: 7 19 3
14:44:09.180 ->  Super cycle length: 199500 steps
14:44:09.180 -> Inter-pixel phase: 1 deg = 26 steps
14:44:09.180 ->  c: 0 Steps:  3500 Init:  1538 Phase:   2 deg PWM: 255
14:44:09.180 ->  c: 1 Steps:  9500 Init:  7623 Phase:   0 deg PWM: 255
14:44:09.213 ->  c: 2 Steps:  1500 Init:  1299 Phase:   6 deg PWM: 255
14:44:19.265 -> Color 2     steps 1500  at 15101    ms 50       TS 201     
14:45:34.293 -> Color 2     steps 1500  at 90136    ms 50       TS 1701    
14:45:43.085 -> Color 1     steps 9500  at 98940    ms 50       TS 1877    
14:45:47.332 -> Color 0     steps 3500  at 103192   ms 50       TS 1962    
14:46:49.324 -> Color 2     steps 1500  at 165170   ms 50       TS 3201  
… much snippage …
17:26:52.896 -> Color 2     steps 1500  at 9769584  ms 50       TS 195201  
17:28:07.926 -> Color 2     steps 1500  at 9844618  ms 50       TS 196701  
17:29:11.000 -> Color 0     steps 3500  at 9907697  ms 50       TS 197962  
17:29:22.974 -> Color 2     steps 1500  at 9919653  ms 50       TS 198201  
17:30:27.941 -> Supercycle end, setting new color values
17:30:27.941 -> Primes: 17 7 3
17:30:27.941 ->  Super cycle length: 178500 steps
17:30:27.941 -> Inter-pixel phase: 1 deg = 23 steps
17:30:27.941 ->  c: 0 Steps:  8500 Init:  5415 Phase:   0 deg PWM: 255
17:30:27.974 ->  c: 1 Steps:  3500 Init:  3131 Phase:   2 deg PWM: 255
17:30:27.974 ->  c: 2 Steps:  1500 Init:   420 Phase:   5 deg PWM: 255
17:30:46.394 -> Color 1     steps 3500  at 10003091 ms 50       TS 369     
17:31:21.964 -> Color 2     steps 1500  at 10038658 ms 50       TS 1080  

The “Super cycle length” is the number of 50 ms steps until the colors start repeating, something over an hour in that sample. When the code reaches the end of the supercycle, it picks another set of three prime numbers, reinitializes the color settings, and away it goes.

The fog light looks pretty in action:

Nissan Fog Lamp - blue phase
Nissan Fog Lamp – blue phase

The four LEDs don’t produce the same light pattern as the halogen filament and they’re distinctly visible when you squint against the glare:

Nissan Fog Lamp - reflector LED detail
Nissan Fog Lamp – reflector LED detail

The shadow on the right comes from the larger hood support strut, the shadow on the left is the narrower strut, and the two other gaps show the beam angle gaps between the LEDs.

You’ll see plenty of residual sandpaper scratches on the lens: my surface (re)finishing hand is weak.

The LED beamwidth is so broad the “bulb” position inside the reflector doesn’t make much difference, particularly as it must, at most, wash a wall and ceiling at close range:

Nissan Fog Lamp - wall wash light
Nissan Fog Lamp – wall wash light

All in all, a much-needed dose of Quality Shop Time.

The Arduino source code as a GitHub Gist:

// Neopixel Algorithmic Art
// W2812 RGB Neopixel version
// Ed Nisley - KE4ZNU
#include <Adafruit_NeoPixel.h>
#include <Entropy.h>
//----------
// Pin assignments
const byte PIN_NEO = A3; // DO - data out to first Neopixel
const byte PIN_HEARTBEAT = 13; // DO - Arduino LED
#define PIN_MORSE 12
//----------
// Constants
// number of pixels
#define PIXELS 4
// lag between adjacent pixels in degrees of slowest period
#define PIXELPHASE 1
// update LEDs only this many ms apart (minus loop() overhead)
#define UPDATEINTERVAL 50ul
#define UPDATEMS (UPDATEINTERVAL - 0ul)
// number of steps per cycle, before applying prime factors
#define RESOLUTION 500
//----------
// Globals
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS, PIN_NEO, NEO_GRB + NEO_KHZ800);
uint32_t FullWhite = strip.Color(255,255,255);
uint32_t FullOff = strip.Color(0,0,0);
uint32_t MorseColor;
struct pixcolor_t {
unsigned int Prime;
unsigned int NumSteps;
unsigned int Step;
float StepSize;
float Phase;
byte MaxPWM;
};
unsigned long int TotalSteps;
unsigned long int SuperCycleSteps;
byte PrimeList[] = {3,5,7,11,13,17,19,29}; // small primes = faster changes
// colors in each LED and their count
enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};
struct pixcolor_t Pixel[PIXELSIZE]; // all the data for each pixel color intensity
uint32_t UniColor;
unsigned long int MillisNow;
unsigned long int MillisThen;
//-- Select three unique primes for the color generator function
// Then compute all the step parameters based on those values
void SetColorGenerators(void) {
Pixel[RED].Prime = PrimeList[random(sizeof(PrimeList))];
do {
Pixel[GREEN].Prime = PrimeList[random(sizeof(PrimeList))];
} while (Pixel[RED].Prime == Pixel[GREEN].Prime);
do {
Pixel[BLUE].Prime = PrimeList[random(sizeof(PrimeList))];
} while (Pixel[BLUE].Prime == Pixel[RED].Prime ||
Pixel[BLUE].Prime == Pixel[GREEN].Prime);
if (false) {
Pixel[RED].Prime = 1;
Pixel[GREEN].Prime = 3;
Pixel[BLUE].Prime = 5;
}
printf("Primes: %d %d %d\r\n",Pixel[RED].Prime,Pixel[GREEN].Prime,Pixel[BLUE].Prime);
TotalSteps = 0;
SuperCycleSteps = RESOLUTION;
for (byte c = 0; c < PIXELSIZE; c++) {
SuperCycleSteps *= Pixel[c].Prime;
}
printf(" Super cycle length: %lu steps\r\n",SuperCycleSteps);
Pixel[RED].MaxPWM = 255;
Pixel[GREEN].MaxPWM = 255;
Pixel[BLUE].MaxPWM = 255;
unsigned int PhaseSteps = (unsigned int) ((PIXELPHASE / 360.0) *
RESOLUTION * (unsigned int) max(max(Pixel[RED].Prime,Pixel[GREEN].Prime),Pixel[BLUE].Prime));
printf("Inter-pixel phase: %d deg = %d steps\r\n",(int)PIXELPHASE,PhaseSteps);
for (byte c = 0; c < PIXELSIZE; c++) {
Pixel[c].NumSteps = RESOLUTION * Pixel[c].Prime; // steps per cycle
Pixel[c].StepSize = TWO_PI / Pixel[c].NumSteps; // radians per step
Pixel[c].Step = random(Pixel[c].NumSteps); // current step
Pixel[c].Phase = PhaseSteps * Pixel[c].StepSize; // phase in radians for this color
printf(" c: %d Steps: %5d Init: %5d Phase: %3d deg",c,Pixel[c].NumSteps,Pixel[c].Step,(int)(Pixel[c].Phase * 360.0 / TWO_PI));
printf(" PWM: %d\r\n",Pixel[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
Serial.begin(57600);
fdevopen(&s_putc,0); // set up serial output for printf()
printf("Algorithmic Art\r\n RGB WS2812\r\nEd Nisley - KE4ZNU - April 2020\r\n");
Entropy.initialize(); // start up entropy collector
// set up pixels
strip.begin();
strip.show();
// lamp test: a brilliant white flash
printf("Lamp test: flash full-on colors\r\n");
uint32_t FullRGB = strip.Color(255,255,255);
uint32_t FullR = strip.Color(255,0,0);
uint32_t FullG = strip.Color(0,255,0);
uint32_t FullB = strip.Color(0,0,255);
uint32_t FullOff = strip.Color(0,0,0);
uint32_t TestColors[] = {FullR,FullG,FullB,FullRGB,FullOff};
for (byte i = 0; i < sizeof(TestColors)/sizeof(uint32_t) ; i++) {
printf(" color: %08lx\r\n",TestColors[i]);
for (int p=0; p < strip.numPixels(); p++) {
strip.setPixelColor(p,TestColors[i]);
}
strip.show();
delay(1000);
}
// 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();
MillisNow = MillisThen = millis();
}
//------------------
// Run the mood
void loop() {
MillisNow = millis();
if ((MillisNow - MillisThen) >= UPDATEMS) { // time for another step?
digitalWrite(PIN_HEARTBEAT,HIGH);
TotalSteps++;
strip.show(); // send out precomputed colors
for (byte c = 0; c < PIXELSIZE; c++) { // compute next increment for each color
if (++Pixel[c].Step >= Pixel[c].NumSteps) {
Pixel[c].Step = 0;
printf("Color %-5d steps %-5d at %-8ld ms %-8ld TS %-8lu\r\n",
c,Pixel[c].NumSteps,MillisNow,(MillisNow - MillisThen),TotalSteps);
}
}
// If all cycles have completed, reset the color generators
if (TotalSteps >= SuperCycleSteps) {
printf("Supercycle end, setting new color values\r\n");
SetColorGenerators();
}
for (int p = 0; p < strip.numPixels(); p++) { // for each pixel
byte Value[PIXELSIZE];
for (byte c=0; c < PIXELSIZE; c++) { // compute new colors
Value[c] = (Pixel[c].MaxPWM / 2.0) * (1.0 + sin(Pixel[c].Step * Pixel[c].StepSize - p*Pixel[c].Phase));
}
UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE]);
strip.setPixelColor(p,UniColor);
}
MillisThen = MillisNow;
digitalWrite(PIN_HEARTBEAT,LOW);
}
}
view raw AlgoArt-RGB.ino hosted with ❤ by GitHub

Nissan Fog Lamp: Desk Stand

The Nissan fog lamp looks pretty good pointing at the ceiling:

Nissan Fog Lamp - table mount
Nissan Fog Lamp – table mount

I briefly considered sandblasting the shell to knock back the corrosion, but came to my senses: this is art!

The shell has a bayonet mount intended for the cable connector, but a bout of solid modeling produced a matching twist-lock desk stand:

Nissan Fog Light Base - Slic3r preview
Nissan Fog Light Base – Slic3r preview

The locking dogs overhang little enough, relative to their diameter, to let the thing build without internal supports. Took about three hours without any intervention at all.

The little hole matches up with the slot on the bottom holding a USB cable bringing power from a wall charger:

Nissan Fog Lamp - table mount interior
Nissan Fog Lamp – table mount interior

It’s a knockoff Arduino Pro Mini without the USB interface found on a Nano, so the USB data wires don’t connect to anything.

The base might look better under a layer of (black?) epoxy, although I’m definitely a fan of those brutalist 3D printed striations.

The OpenSCAD source code as a GitHub Gist:

// Nissan Fog Light Base
// Ed Nisley KE4ZNU 2020-04-20
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
//----------------------
// Dimensions
ID = 0;
OD = 1;
LENGTH = 2;
/* [Fog Light] */
ShellBase = [49.0,55.0,10.0];
Dog = [55.0,60.0,7.0];
DogWidth = 21.0;
DogAngle = atan(DogWidth / ShellBase[ID]);
echo(str("Dog angle: ",DogAngle));
ReflectorOD = 90.0;
LensOD = 110.0;
LensAngle = -90; // peak relative to dogs
WallThick = 4.0;
BaseThick = 2*WallThick;
CableOD = 3.5;
$fn = 3*4*5;
//-------------------
// Useful shapes
module Dogs(h=Dog[LENGTH]) {
translate([0,0,h/2])
intersection() {
cube([Dog[OD],DogWidth,h],center=true);
cylinder(d=Dog[OD],h=h,center=true);
}
}
//-------------------
// Build it
difference() {
union() {
cylinder(d=(Dog[OD] + 2*WallThick),h=(BaseThick + ShellBase[LENGTH]));
intersection() {
resize([0,0,2*BaseThick])
sphere(d=LensOD);
translate([0,0,BaseThick/2])
cube([2*LensOD,2*ReflectorOD,BaseThick],center=true);
}
}
translate([0,0,BaseThick])
cylinder(d=ShellBase[OD],h=ShellBase[LENGTH] + Protrusion);
translate([0,0,BaseThick]) {
Dogs();
rotate(1.5*DogAngle)
Dogs();
rotate(2*DogAngle)
Dogs(2*ShellBase[LENGTH]);
}
rotate(LensAngle)
translate([0.75*ShellBase[ID]/2,0,-Protrusion]) {
cylinder(d=CableOD,h=2*BaseThick,$fn=8);
translate([LensOD/2,0,CableOD/2])
cube([LensOD,CableOD,CableOD + Protrusion],center=true);
}
translate([31,0,ThreadThick-Protrusion])
cube([23.0,55.0,2*ThreadThick],center=true);
}
linear_extrude(height=2*ThreadWidth + Protrusion) {
translate([32,0,-Protrusion])
rotate(-90) mirror([1,0,0])
text(text="Ed Nisley",size=6,font="Arial:style:Bold",halign="center");
translate([23,0,-Protrusion])
rotate(-90) mirror([1,0,0])
text(text="softsolder.com",size=5,font="Arial:style:Bold",halign="center");
}

CNC 3018XL: Arduino + Protoneer CNC

If the truth be known, I wanted to do this as soon as I discovered the CAMtool V3.3 board hardwired the DRV8825 PCBs in 1:32 microstep mode:

CNC 3018XL - Protoneer atop Arduino - installed
CNC 3018XL – Protoneer atop Arduino – installed

The Protoneer CNC board has jumpers, so selecting 1:8 microstep mode is no big deal.

As before, I epoxied another row of pins along the I/O header for Makerbot-style endstops:

Protoneer endstop power mod
Protoneer endstop power mod

I’ll probably regret not adding pins along the entire row, but, unlike the MPCNC, the CNC 3018XL won’t ever have hard limit switches. I plugged the Run-Hold switch LEDs into an unused +5 V pin and moved on.

I modified the DRV8825 driver PCBs for fast decay mode:

DRV8825 PCB - Fast Decay Mode wire
DRV8825 PCB – Fast Decay Mode wire

Then set the current to a bit over 1 A:

3018XL - Protoneer setup - Z 1 mm
3018XL – Protoneer setup – Z 1 mm

Six hours later I hauled the once-again-functional CNC 3018XL to my presentation for the ACM:

Spirograph - intricate sample plot - detail
Spirograph – intricate sample plot – detail

Memo to Self: Time to get another Prontoneer board …

CAMtool V3.3 vs. The Fat Fingers of Death

As is my custom, the day before showtime I talked my way through a final full-up dress rehearsal, with the HP 7475A plotter and the CNC 3018XL running their demo plots. As if to justify my attention to detail, the 3018 refused to home, with its X axis motor grinding in a manner suggesting something had gone terribly wrong with its driver.

OK, I can fix that™.

Turn off the power, verify the leadscrew turns smoothly by hand, check all the connections & connectors, then pull the DRV8825 PCB to see if anything looks obviously wrong. It didn’t, so I carefully re-plugged the driver and moved the whole affair to the Electronics Workbench for further study.

I turned on the scope and Tek current probes, then turned on the 3018 power supplies, whereupon a great cloud of Magic Smoke emerged from the CAMtool board and filled the Basement Laboratory with the acrid smell of Electrical Death.

It seems I carefully and meticulously re-plugged the DRV8825 PCB into its socket exactly one pin too high, which, among other Bad Things, connects the +24 V motor power supply to the driver GND pin.

Obviously, this did not end well:

CAMtool V3.3 - blown stepper fuse
CAMtool V3.3 – blown stepper fuse

The fuse, put under considerable stress, vented smoke & debris in all directions across the board; note the jets above the white motor connector. Surprisingly, the 1 kΩ resistor just below it is in fine shape, as is the rather blackened electrolytic cap.

The fuse measures the same 150-ish mΩ as the fuses in the other two axes, but I doubt it’s actually a fuse any more.

Astonishingly, the Arduino clone on the board worked fine, so I could extract the GRBL configuration.

Memo to Self: Never plug things in with your head upside down!

SK6812 RGBW Test Fixture: Row-by-Row Color Mods

The vacuum tube LED firmware subtracts the minimum value from the RGB channels of the SK6812 RGBW LEDs and displays it in the white channel, thereby reducing the PWM value of the RGB LEDs by their common “white” component. The main benefit is reducing the overall power by about two LEDs. More or less, kinda sorta.

I tweaked the SK6812 test fixture firmware to show how several variations of the basic RGB colors appear:

      for (int col=0; col < NUMCOLS ; col++) {              // for each column
        byte Value[PIXELSIZE];                              // figure first row colors
        for (byte p=0; p < PIXELSIZE; p++) {                //  ... for each color in pixel
          Value[p] = StepColor(p,-col*Pixels[p].TubePhase);
        }
        // just RGB
        int row = 0;
        uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE],0);
        strip.setPixelColor(col + NUMCOLS*row++,UniColor);

        byte MinWhite = min(min(Value[RED],Value[GREEN]),Value[BLUE]);

        // only common white
        UniColor = strip.Color(0,0,0,MinWhite);
        strip.setPixelColor(col + NUMCOLS*row++,UniColor);

        // RGB minus common white + white
        UniColor = strip.Color(Value[RED]-MinWhite,Value[GREEN]-MinWhite,Value[BLUE]-MinWhite,MinWhite);
        strip.setPixelColor(col + NUMCOLS*row++,UniColor);

         // RGB minus common white
        UniColor = strip.Color(Value[RED]-MinWhite,Value[GREEN]-MinWhite,Value[BLUE]-MinWhite,0);
        strip.setPixelColor(col + NUMCOLS*row++,UniColor);

        // inverse RGB
        UniColor = strip.Color(255 - Value[RED],255 - Value[GREEN],255 - Value[BLUE],0);
        strip.setPixelColor(col + NUMCOLS*row++,UniColor);

Which looks like this:

SK6812 Test Fixture - RGBW color variations - diffuser
SK6812 Test Fixture – RGBW color variations – diffuser

The pure RGB colors appear along the bottom row, with the variations proceeding upward to the inverse RGB in the top row. The dust specks show it’s actually in focus.

The color variations seem easier to see without the diffuser:

SK6812 Test Fixture - RGBW color variations - bare LEDs
SK6812 Test Fixture – RGBW color variations – bare LEDs

The white LEDs are obviously “warm white”, which seems not to make much difference.

Putting a jumper from D2 to the adjacent (on an Nano, anyway) ground pin selects the original pattern, removing the jumper displays the modified pattern:

SK6812 test fixture - pattern jumper
SK6812 test fixture – pattern jumper

For whatever it’s worth, those LEDs have been running at full throttle for two years with zero failures!

The Arduino source code as a GitHub Gist:

// SK6812 RGBW LED array exerciser
// Ed Nisley - KE4ANU - February 2017
// 2020-01-25 add row-by-row color modifications
#include <Adafruit_NeoPixel.h>
//----------
// Pin assignments
const byte PIN_NEO = A3; // DO - data out to first Neopixel
const byte PIN_HEARTBEAT = 13; // DO - Arduino LED
const byte PIN_SELECT = 2; // DI - pattern select input
//----------
// Constants
#define UPDATEINTERVAL 20ul
const unsigned long UpdateMS = UPDATEINTERVAL - 1ul; // update LEDs only this many ms apart minus loop() overhead
// number of steps per cycle, before applying prime factors
#define RESOLUTION 100
// phase difference between LEDs for slowest color
#define BASEPHASE (PI/16.0)
// LEDs in each row
#define NUMCOLS 5
// number of rows
#define NUMROWS 5
#define NUMPIXELS (NUMCOLS * NUMROWS)
#define PINDEX(row,col) (row*NUMCOLS + col)
//----------
// Globals
// instantiate the Neopixel buffer array
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN_NEO, NEO_GRBW + NEO_KHZ800);
uint32_t FullWhite = strip.Color(255,255,255,255);
uint32_t FullOff = strip.Color(0,0,0,0);
struct pixcolor_t {
byte Prime;
unsigned int NumSteps;
unsigned int Step;
float StepSize;
float TubePhase;
byte MaxPWM;
};
// colors in each LED
enum pixcolors {RED, GREEN, BLUE, WHITE, PIXELSIZE};
struct pixcolor_t Pixels[PIXELSIZE]; // all the data for each pixel color intensity
unsigned long MillisNow;
unsigned long MillisThen;
//-- Figure PWM based on current state
byte StepColor(byte Color, float Phi) {
byte Value;
Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi));
// Value = (Value) ? Value : Pixels[Color].MaxPWM; // flash at dimmest points
// printf("C: %d Phi: %d Value: %d\r\n",Color,(int)(Phi*180.0/PI),Value);
return Value;
}
//-- 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_SELECT,INPUT_PULLUP);
Serial.begin(57600);
fdevopen(&s_putc,0); // set up serial output for printf()
printf("WS2812 / SK6812 array exerciser\r\nEd Nisley - KE4ZNU - February 2017\r\n");
/// set up Neopixels
strip.begin();
strip.show();
// lamp test: run a brilliant white dot along the length of the strip
printf("Lamp test: walking white\r\n");
strip.setPixelColor(0,FullWhite);
strip.show();
delay(250);
for (int i=1; i<NUMPIXELS; i++) {
digitalWrite(PIN_HEARTBEAT,HIGH);
strip.setPixelColor(i-1,FullOff);
strip.setPixelColor(i,FullWhite);
strip.show();
digitalWrite(PIN_HEARTBEAT,LOW);
delay(250);
}
strip.setPixelColor(NUMPIXELS - 1,FullOff);
strip.show();
delay(250);
// fill the array, row by row
printf(" ... fill\r\n");
for (int i=NUMROWS-1; i>=0; i--) { // for each row
digitalWrite(PIN_HEARTBEAT,HIGH);
for (int j=NUMCOLS-1; j>=0 ; j--) {
strip.setPixelColor(PINDEX(i,j),FullWhite);
strip.show();
delay(100);
}
digitalWrite(PIN_HEARTBEAT,LOW);
}
// clear to black, column by column
printf(" ... clear\r\n");
for (int j=NUMCOLS-1; j>=0; j--) { // for each column
digitalWrite(PIN_HEARTBEAT,HIGH);
for (int i=NUMROWS-1; i>=0; i--) {
strip.setPixelColor(PINDEX(i,j),FullOff);
strip.show();
delay(100);
}
digitalWrite(PIN_HEARTBEAT,LOW);
}
delay(1000);
// set up the color generators
MillisNow = MillisThen = millis();
printf("First random number: %ld\r\n",random(10));
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);
unsigned int PixelSteps = (unsigned int) ((BASEPHASE / TWO_PI) *
RESOLUTION * (unsigned int) max(max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime),Pixels[WHITE].Prime));
printf("Pixel phase offset: %d deg = %d steps\r\n",(int)(BASEPHASE*(360.0/TWO_PI)),PixelSteps);
Pixels[RED].MaxPWM = 255;
Pixels[GREEN].MaxPWM = 255;
Pixels[BLUE].MaxPWM = 255;
Pixels[WHITE].MaxPWM = 32;
for (byte c=0; c < PIXELSIZE; c++) {
Pixels[c].NumSteps = RESOLUTION * (unsigned int) Pixels[c].Prime;
Pixels[c].Step = (3*Pixels[c].NumSteps)/4;
Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps; // in radians per step
Pixels[c].TubePhase = PixelSteps * Pixels[c].StepSize; // radians per tube
printf("c: %d Steps: %5d Init: %5d",c,Pixels[c].NumSteps,Pixels[c].Step);
printf(" PWM: %3d Phi %3d deg\r\n",Pixels[c].MaxPWM,(int)(Pixels[c].TubePhase*(360.0/TWO_PI)));
}
}
//------------------
// Run the mood
void loop() {
MillisNow = millis();
if ((MillisNow - MillisThen) > UpdateMS) {
digitalWrite(PIN_HEARTBEAT,HIGH);
unsigned int AllSteps = 0;
for (byte c=0; c < PIXELSIZE; c++) { // step to next increment in each color
if (++Pixels[c].Step >= Pixels[c].NumSteps) {
Pixels[c].Step = 0;
printf("Color %d steps %5d at %8ld delta %ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow - MillisThen));
}
AllSteps += Pixels[c].Step; // will be zero only when all wrap at once
}
if (0 == AllSteps) {
printf("Grand cycle at: %ld\r\n",MillisNow);
}
if (digitalRead(PIN_SELECT)) {
for (int col=0; col < NUMCOLS ; col++) { // for each column
byte Value[PIXELSIZE]; // figure first row colors
for (byte p=0; p < PIXELSIZE; p++) { // ... for each color in pixel
Value[p] = StepColor(p,-col*Pixels[p].TubePhase);
}
// just RGB
int row = 0;
uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE],0);
strip.setPixelColor(col + NUMCOLS*row++,UniColor);
byte MinWhite = min(min(Value[RED],Value[GREEN]),Value[BLUE]);
// only common white
UniColor = strip.Color(0,0,0,MinWhite);
strip.setPixelColor(col + NUMCOLS*row++,UniColor);
// RGB minus common white + white
UniColor = strip.Color(Value[RED]-MinWhite,Value[GREEN]-MinWhite,Value[BLUE]-MinWhite,MinWhite);
strip.setPixelColor(col + NUMCOLS*row++,UniColor);
// RGB minus common white
UniColor = strip.Color(Value[RED]-MinWhite,Value[GREEN]-MinWhite,Value[BLUE]-MinWhite,0);
strip.setPixelColor(col + NUMCOLS*row++,UniColor);
// inverse RGB
UniColor = strip.Color(255 - Value[RED],255 - Value[GREEN],255 - Value[BLUE],0);
strip.setPixelColor(col + NUMCOLS*row++,UniColor);
}
}
else {
for (int k=0; k < NUMPIXELS; k++) { // for each pixel
byte Value[PIXELSIZE];
for (byte c=0; c < PIXELSIZE; c++) { // ... for each color
Value[c] = StepColor(c,-k*Pixels[c].TubePhase); // figure new PWM value
// Value[c] = (c == RED && Value[c] == 0) ? Pixels[c].MaxPWM : Value[c]; // flash highlight for tracking
}
uint32_t UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE],Value[WHITE]);
strip.setPixelColor(k,UniColor);
}
}
strip.show();
MillisThen = MillisNow;
digitalWrite(PIN_HEARTBEAT,LOW);
}
}
view raw ArrayTest-RGBW.ino hosted with ❤ by GitHub

Algorithmic Art

This evening I’ll be showing off my Algorithmic Art at the HV Open Mad Science Fair.

There’ll be glowing glassware:

Vacuum Tube LEDs - halogen lamp - purple phase
Vacuum Tube LEDs – halogen lamp – purple phase

Ancient electronics with modern hardware:

21HB5A - Guilloche platter
21HB5A – Guilloche platter

Blinking LEDs atop Brutalist analog electronics:

Astable RGB LED - green phase
Astable RGB LED – green phase

A classic HP 7475A plotter hammering math onto paper from a Raspberry Pi running Chiplotle:

HP 7475A Plotter - LED paper illumination
HP 7475A Plotter – LED paper illumination

Some take-home art:

Superformula Plots - A-size paper
Superformula Plots – A-size paper

And, as always, a good time will be had by all!