## 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 `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();
printf("Phases: ");
for (byte i=0; i<3; i++) {
Phase[i] += random(-1000,1000) * HALF_PI / 1000.0;
}
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

//----------
// 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;

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();
printf("Phases: ");
for (byte i=0; i<3; i++) {
Phase[i] += random(-1000,1000) * HALF_PI / 1000.0;
}
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);
}
}

```