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 PdBase constant 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 PdBase cycles = 18.7×103 s = 5.19 h:
const float Period[] = {PdBase/5.0,PdBase/11.0,PdBase/17.0}; // mutually prime periods
That’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 Phase array 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 off
Jiggling 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 Intensity array with separate values for each channel:
float Intensity[] = {128.0,128.0,128.0}; // pseudo current limit - PWM is always full current
That 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);
}
}
















