# Hard Drive Platter Mood Light: Improved Trigonometry

The original Mood Light firmware used the current time in milliseconds as a factor in the `sin()` argument, assuming that the Arduino runtime would Do The Right Thing. Having been gently disabused of that notion, here’s another pass that resets the argument after every full cycle to keep the trig from going crazy. Thanks to all of you for helping out… [grin]

The hardware still looks like this, though:

Define a structure to hold everything needed to calculate each color, then make an array holding one structure per color:

```struct pixcolor_t {
byte Prime;
unsigned int NumSteps;
unsigned int Step;
float StepSize;
byte MaxPWM;
byte Value;
};

enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};

#define RESOLUTION 1000

struct pixcolor_t Pixels[PIXELSIZE];
```

The general idea is to increment the integer `Step` from 0 through `NumSteps - 1` to create the sine wave, with the total number of steps per cycle being `Prime` times the `RESOLUTION`.

The angular argument is `Step * StepSize`, with the size of each step equal to `2π / NumSteps`. Because `Step` gets reset to zero after reaching `NumSteps - 1`, the argument never exceeds 2π and the trig never falls off the rails.

Soooo, calculating the PWM value for each color goes like this:

```byte StepColor(byte Color) {

Pixels[Color].Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize));

Pixels[Color].Step = (Pixels[Color].Step >= Pixels[Color].NumSteps) ? 0 : Pixels[Color].Step + 1;

if (0 == Pixels[Color].Step) {
printf("Color %d cycle end at %d\r\n",Color,Pixels[Color].NumSteps);
}

return Pixels[Color].Value;
}
```

The `MaxPWM` parameter limits the perceived brightness, although not the peak current. Each Neopixel dissipates 300-ish mW at full throttle, they’re mounted on a plastic structure, and there’s not a lot of air flowing between those platters; running at half power makes a lot of sense.

Initializing the structure values happens in the `setup()` function, because it’s easier than filling in all the array structure entries by hand:

```	Pixels[RED].Prime = 5;
Pixels[GREEN].Prime = 7;
Pixels[BLUE].Prime = 11;

for (byte c=0; c < PIXELSIZE; c++) {
Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime;
Pixels[c].Step = random(Pixels[c].NumSteps);
Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;
Pixels[c].MaxPWM = 128;
StepColor(c);
}
```

The `Phase` value has Gone Away, because it really didn’t add anything to the proceedings. Instead, I randomize the starting `Step`, although there’s not a lot of randomness to be had early on in an Arduino program; that needs a bit more work. Adding a little PCB with a random noise source doesn’t seem cost-effective, although a photodetector peering out the side and adjusting the `MaxPWM` values might be a Good Thing.

Come to think of it, limiting the sum of the PWM values might be more useful than limiting their individual maximum values. That’s a simple matter of software…

The `main()` loop doesn’t have a lot to do. Every 25 ms it updates the three color PWM values, sets the new values into all 12 LED buffer locations, and sends the whole mess to the Neopixels. The `RESOLUTION` value acts as a gearshift between the 25 ms update rate and the speed at which complete cycles zip past. Absent the `Prime` factor, each cycle would require `25 ms * RESOLUTION` ms to complete: call it 25 seconds.

The `Prime` factors slow that down proportionally and push the repetition interval out to the product of all the factors. For the (5, 7, 11) factors shown below, that’s 5x7x11x253 s = 6×106 s = 70 days,

Now it doesn’t matter how often the `millis()` value wraps. Every now & again, `MillisThen` will be just under 232 and `MillisNow` will be just over 0, but their (unsigned) difference will be some huge number, the conditional will trip, and nobody will notice the timing glitch…

The 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 unsigned long UpdateMS = 25ul - 4ul;		// update LEDs only this many ms apart minus loop() overhead

//----------
// Globals

unsigned long MillisNow;
unsigned long MillisThen;

uint32_t FullWhite = strip.Color(255,255,255);
uint32_t FullOff = strip.Color(0,0,0);

struct pixcolor_t {
byte Prime;
unsigned int NumSteps;
unsigned int Step;
float StepSize;
byte MaxPWM;
byte Value;
};

enum pixcolors {RED, GREEN, BLUE, PIXELSIZE};

#define RESOLUTION 1000

struct pixcolor_t Pixels[PIXELSIZE];								// everything that calculates the pixel colors

byte Map[] = {0,5,6,11, 1,4,7,10, 2,3,8,9};							// pixel numbers around platter, bottom to top.

//-- Figure PWM based on current state

byte StepColor(byte Color) {

Pixels[Color].Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize));

Pixels[Color].Step = (Pixels[Color].Step >= Pixels[Color].NumSteps) ? 0 : Pixels[Color].Step + 1;

if (0 == Pixels[Color].Step) {
printf("Color %d cycle end at %d\r\n",Color,Pixels[Color].NumSteps);
}

//	printf("Step: %d Color: %d Value: %d\r\n",Pixels[Color].Step,(word)Color,(word)Pixels[Color].Value);

return Pixels[Color].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

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);
}

strip.setPixelColor(strip.numPixels() - 1,FullOff);
strip.show();
delay(500);

// and around the disks

printf(" ... using Map array\r\n");

strip.setPixelColor(Map,FullWhite);
strip.show();
delay(250);

for (int i=1; i<strip.numPixels(); i++) {
digitalWrite(PIN_HEARTBEAT,HIGH);
strip.setPixelColor(Map[i-1],FullOff);
strip.setPixelColor(Map[i],FullWhite);
strip.show();
digitalWrite(PIN_HEARTBEAT,LOW);
delay(250);
}

strip.setPixelColor(Map[strip.numPixels() - 1],FullOff);
strip.show();
delay(250);

MillisNow = MillisThen = millis();
printf("First random number: %ld\r\n",random(10));

Pixels[RED].Prime = 5;
Pixels[GREEN].Prime = 7;
Pixels[BLUE].Prime = 11;

for (byte c=0; c < PIXELSIZE; c++) {
Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime;
Pixels[c].Step = random(Pixels[c].NumSteps);
Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps;
Pixels[c].MaxPWM = 128;
StepColor(c);
}
printf("Prime scales: (%d,%d,%d)\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime);
printf("Initial step: (%d,%d,%d)\r\n",Pixels[RED].Step,Pixels[GREEN].Step,Pixels[BLUE].Step);
printf("  ...  color: (%d,%d,%d)\r\n",Pixels[RED].Value,Pixels[GREEN].Value,Pixels[BLUE].Value);

for (int i=0; i<strip.numPixels(); i++) { strip.setPixelColor(Map[i],strip.Color(Pixels[RED].Value,Pixels[GREEN].Value,Pixels[BLUE].Value)); } strip.show(); } //------------------ // Run the mood void loop() { // printf("Loop! %ld %ld\r\n",MillisNow,MillisThen); MillisNow = millis(); if ((MillisNow - MillisThen) > UpdateMS) {
digitalWrite(PIN_HEARTBEAT,HIGH);

for (byte c=0; c < PIXELSIZE; c++) {
StepColor(c);
}

for (int i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i,strip.Color(Pixels[RED].Value,Pixels[GREEN].Value,Pixels[BLUE].Value));
}
strip.show();

MillisThen = MillisNow;
digitalWrite(PIN_HEARTBEAT,LOW);
}

}
```