-
Hard Drive Platter Mood Light: Neopixel Firmware
Having accumulated a pile of useless hard drives, it seemed reasonable to harvest the platters and turn them into techie mood lights (remember mood lights?). Some doodling showed that four of Adafruit’s high-density Neopixel strips could stand up inside the 25 mm central hole, completely eliminating the need to putz around with PWM drivers and RGB LEDs: one wire from an Arduino Pro Mini and you’re done:
const byte PIN_NEO = 6; // DO - data out to first Neopixel
The firmware creates three sine waves with mutually prime periods, then updates the RGB channels with raised-sine values every 10 ms. The
PdBaseconstant defines the common conversion from milliseconds to radians:const float PdBase = 0.05 * TWO_PI / 1000.0; // scale time in ms to radians
The leading 0.05 = 1/20 means the sine wave will repeat every 20 s = 20000 ms.
Dividing that period by three small primes produces an RGB pattern that will repeat every 5x11x17 = 935
PdBasecycles = 18.7×103 s = 5.19 h:const float Period[] = {PdBase/5.0,PdBase/11.0,PdBase/17.0}; // mutually prime periodsThat’s languid enough for me, although I admit most of the colors look pretty much the same. Obviously, you can tune for best picture by dinking with a few constants.
A
Phasearray sets the starting phase to 3π/2 = -90 degrees:float Phase[] = {3.0 * HALF_PI,3.0 * HALF_PI,3.0 * HALF_PI}; // sin(3π/2 ) = -1, so LEDs are offJiggling those starting phases produces a randomized initial color that’s close to dark:
MillisNow = MillisThen = millis(); randomSeed(MillisNow + analogRead(6) + analogRead(7)); printf("Phases: "); for (byte i=0; i<3; i++) { Phase[i] += random(-1000,1000) * HALF_PI / 1000.0; printf("%d ",(int)(Phase[i]*RAD_TO_DEG)); } printf(" deg\r\n");With all that in hand, converting from time to color goes like this:
uint32_t SineColor(unsigned long t) { byte rgb[3]; for (byte i=0; i<3; i++) { rgb[i] = Intensity[i]/2.0 * (1 + sin(t * Period[i] + Phase[i])); } return strip.Color(rgb[0],rgb[1],rgb[2]); }The rest of the code scales neatly with the strip length defined in the magic instantiation:
Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN_NEO, NEO_GRB + NEO_KHZ800);
Although the colors change very slowly, shifting them all one chip toward the end of the 144 Neopixel strip at each update produces a noticeable difference that reassured me this whole mess was working:
for (int i=strip.numPixels()-1; i>0; i--) { c = strip.getPixelColor(i-1); strip.setPixelColor(i,c); } c = SineColor(MillisNow); strip.setPixelColor(0,c); strip.show();And with that in hand, It Just Worked…
However, it’s worth noting that each Neopixel draws a bit over 60 mA at full white, which works out to a smidge under 9 A for a 144 LED strip. Because they’re PWM devices, the LEDs are either full-on or full-off, so the peak current can actually be 9 A, regardless of any reduced duty cycle to limit the intensity.
The Adafruit driver includes an overall intensity control, but I added an
Intensityarray with separate values for each channel:float Intensity[] = {128.0,128.0,128.0}; // pseudo current limit - PWM is always full currentThat would allow throttling back the blue LEDs a bit to adjust the overall color temperature, but that’s definitely in the nature of fine tuning.
The Adafruit Neopixel guide recommends a honkin’ big cap right at the strip, plus a 470 Ω decoupling resistor at the first chip’s data input. I think those attempt to tamp down the problems caused by underpowered supplies and crappy wiring; running it at half intensity produced a maximum average current just under the supply’s 3 A limit.
The complete Arduino source code:
// Neopixel mood lighting for hard drive platter sculpture // Ed Nisley - KE4ANU - November 2015 #include <Adafruit_NeoPixel.h> //---------- // Pin assignments const byte PIN_NEO = 6; // DO - data out to first Neopixel const byte PIN_HEARTBEAT = 13; // DO - Arduino LED //---------- // Constants const int UPDATEMS = 10ul - 4ul; // update LEDs only this many ms apart minus loop() overhead const float PdBase = 0.05 * TWO_PI / 1000.0; // scale time in ms to radians const float Period[] = {PdBase/5.0,PdBase/11.0,PdBase/17.0}; // mutually prime periods float Phase[] = {3.0 * HALF_PI,3.0 * HALF_PI,3.0 * HALF_PI}; // sin(3π/2 ) = -1, so LEDs are off float Intensity[] = {128.0,128.0,128.0}; // pseudo current limit - PWM is always full current //---------- // Globals unsigned long MillisNow; unsigned long MillisThen; Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN_NEO, NEO_GRB + NEO_KHZ800); uint32_t FullWhite = strip.Color(255,255,255); uint32_t FullOff = strip.Color(0,0,0); //--- figure color from time in ms uint32_t SineColor(unsigned long t) { byte rgb[3]; for (byte i=0; i<3; i++) { rgb[i] = Intensity[i]/2.0 * (1 + sin(t * Period[i] + Phase[i])); } return strip.Color(rgb[0],rgb[1],rgb[2]); } //-- Helper routine for printf() int s_putc(char c, FILE *t) { Serial.write(c); } //------------------ // Set the mood void setup() { uint32_t c; 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("Mood Light with Neopixels\r\nEd Nisley - KE4ZNU - November 2015\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(500); for (int i=1; i<strip.numPixels(); i++) { digitalWrite(PIN_HEARTBEAT,HIGH); strip.setPixelColor(i-1,FullOff); strip.setPixelColor(i,FullWhite); strip.show(); digitalWrite(PIN_HEARTBEAT,LOW); delay(500); } MillisNow = MillisThen = millis(); randomSeed(MillisNow + analogRead(6) + analogRead(7)); printf("Phases: "); for (byte i=0; i<3; i++) { Phase[i] += random(-1000,1000) * HALF_PI / 1000.0; printf("%d ",(int)(Phase[i]*RAD_TO_DEG)); } printf(" deg\r\n"); c = SineColor(MillisNow); printf("Initial time: %08lx -> color: %08lx\r\n",MillisNow,c); for (int i=0; i<strip.numPixels()-1; i++) { strip.setPixelColor(i,c); } strip.show(); } //------------------ // Run the mood void loop() { byte r,g,b; uint32_t c; MillisNow = millis(); if ((MillisNow - MillisThen) > UPDATEMS) { digitalWrite(PIN_HEARTBEAT,HIGH); for (int i=strip.numPixels()-1; i>0; i--) { c = strip.getPixelColor(i-1); strip.setPixelColor(i,c); } c = SineColor(MillisNow); strip.setPixelColor(0,c); strip.show(); MillisThen = MillisNow; digitalWrite(PIN_HEARTBEAT,LOW); } }