234 lines
8.8 KiB
Plaintext
234 lines
8.8 KiB
Plaintext
|
/*
|
||
|
NeoPixel Ring goggles sketch -- for steampunk, rave or Burning Man fashion!
|
||
|
Welding or costume goggles using 50mm round lenses can be outfitted with
|
||
|
a pair of Adafruit NeoPixel Rings: http://www.adafruit.com/product/1463
|
||
|
|
||
|
Please exercise common sense. These goggles emit a LOT of stray light and
|
||
|
should NOT BE WORN ON YOUR EYES. They're for fashion and costuming only,
|
||
|
for display on a hat or on your forehead.
|
||
|
|
||
|
Draws a spinning rainbow on both eyepieces. Not a Mac beachball, honest.
|
||
|
"Eyes" glance around and blink at random.
|
||
|
|
||
|
For 'reflected' colors (rainbows rotate in opposite directions, while eyes
|
||
|
look in same direction), connect the output of the first ring to the input
|
||
|
of the second. Or you can connect the inputs of both rings to the same
|
||
|
Arduino pin if that's easier -- the rainbows will both twirl in the same
|
||
|
direction in that case.
|
||
|
|
||
|
By default, pixel #0 (the first LED) on both rings should be at the TOP of
|
||
|
the goggles. Looking at the BACK of the board, pixel #0 is immediately
|
||
|
clockwise from the OUT connection. If a different pixel is at the top,
|
||
|
that's OK, the code can compensate (TOP_LED_FIRST and TOP_LED_SECOND below).
|
||
|
*/
|
||
|
|
||
|
#include <Adafruit_NeoPixel.h>
|
||
|
|
||
|
#define PIN 4
|
||
|
|
||
|
#define TOP_LED_FIRST 0 // Change these if the first pixel is not
|
||
|
#define TOP_LED_SECOND 0 // at the top of the first and/or second ring.
|
||
|
|
||
|
#define EFFECT RAINBOW // Choose a visual effect from the names below
|
||
|
|
||
|
#define RAINBOW 0
|
||
|
#define ECTO 1
|
||
|
|
||
|
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(32, PIN, NEO_GRB + NEO_KHZ800);
|
||
|
|
||
|
const int8_t PROGMEM
|
||
|
yCoord[] = { // Vertical coordinate of each pixel. First pixel is at top.
|
||
|
127,117,90,49,0,-49,-90,-117,-127,-117,-90,-49,0,49,90,117 },
|
||
|
sine[] = { // Brightness table for ecto effect
|
||
|
0, 28, 96, 164, 192, 164, 96, 28, 0, 28, 96, 164, 192, 164, 96, 28 };
|
||
|
|
||
|
// Eyelid vertical coordinates. Eyes shut slightly below center.
|
||
|
#define upperLidTop 130
|
||
|
#define upperLidBottom -45
|
||
|
#define lowerLidTop -40
|
||
|
#define lowerLidBottom -130
|
||
|
|
||
|
// Gamma correction improves appearance of midrange colors
|
||
|
const uint8_t PROGMEM gamma8[] = {
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||
|
1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
|
||
|
3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7,
|
||
|
7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12,
|
||
|
13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20,
|
||
|
20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29,
|
||
|
30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 38, 38, 39, 40, 41, 42,
|
||
|
42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
|
||
|
58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 75,
|
||
|
76, 77, 78, 80, 81, 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96,
|
||
|
97, 99,100,102,103,105,106,108,109,111,112,114,115,117,119,120,
|
||
|
122,124,125,127,129,130,132,134,136,137,139,141,143,145,146,148,
|
||
|
150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,
|
||
|
182,184,186,188,191,193,195,197,199,202,204,206,209,211,213,215,
|
||
|
218,220,223,225,227,230,232,235,237,240,242,245,247,250,252,255
|
||
|
};
|
||
|
|
||
|
uint32_t
|
||
|
iColor[16][3]; // Background colors for eyes
|
||
|
int16_t
|
||
|
hue = 0; // Initial hue around perimeter (0-1535)
|
||
|
uint8_t
|
||
|
iBrightness[16], // Brightness map -- eye colors get scaled by these
|
||
|
brightness = 220, // Global brightness (0-255)
|
||
|
blinkFrames = 5, // Speed of current blink
|
||
|
blinkCounter = 30, // Countdown to end of next blink
|
||
|
eyePos = 192, // Current 'resting' eye (pupil) position
|
||
|
newEyePos = 192, // Next eye position when in motion
|
||
|
gazeCounter = 75, // Countdown to next eye movement
|
||
|
gazeFrames = 50; // Duration of eye movement (smaller = faster)
|
||
|
int8_t
|
||
|
eyeMotion = 0; // Distance from prior to new position
|
||
|
|
||
|
|
||
|
void setup() {
|
||
|
pixels.begin();
|
||
|
|
||
|
// Seed random number generator from an unused analog input:
|
||
|
randomSeed(analogRead(A0));
|
||
|
}
|
||
|
|
||
|
void loop() {
|
||
|
uint8_t i, r, g, b, a, c, inner, outer, ep;
|
||
|
int y1, y2, y3, y4, h;
|
||
|
int8_t y;
|
||
|
|
||
|
// Draw eye background colors
|
||
|
|
||
|
#if EFFECT == RAINBOW
|
||
|
|
||
|
// This renders a glotating rainbow...a WAY overdone LED effect but
|
||
|
// does show the color gamut nicely.
|
||
|
|
||
|
for(h=hue, i=0; i<16; i++, h += 96) {
|
||
|
a = h;
|
||
|
switch((h >> 8) % 6) {
|
||
|
case 0: iColor[i][0] = 255; iColor[i][1] = a; iColor[i][2] = 0; break;
|
||
|
case 1: iColor[i][0] = ~a; iColor[i][1] = 255; iColor[i][2] = 0; break;
|
||
|
case 2: iColor[i][0] = 0; iColor[i][1] = 255; iColor[i][2] = a; break;
|
||
|
case 3: iColor[i][0] = 0; iColor[i][1] = ~a; iColor[i][2] = 255; break;
|
||
|
case 4: iColor[i][0] = a; iColor[i][1] = 0; iColor[i][2] = 255; break;
|
||
|
case 5: iColor[i][0] = 255; iColor[i][1] = 0; iColor[i][2] = ~a; break;
|
||
|
}
|
||
|
}
|
||
|
hue += 7;
|
||
|
if(hue >= 1536) hue -= 1536;
|
||
|
|
||
|
#elif EFFECT == ECTO
|
||
|
|
||
|
// A steampunk aesthetic might fare better with this more subdued effect.
|
||
|
// Etherial green glow with just a little animation for visual spice.
|
||
|
|
||
|
a = (hue >> 4) & 15;
|
||
|
c = hue & 15;
|
||
|
for(i=0; i<16; i++) {
|
||
|
b = (a + 1) & 15;
|
||
|
iColor[i][1] = 255; // Predominantly green
|
||
|
iColor[i][0] = (pgm_read_byte(&sine[a]) * (16 - c) +
|
||
|
pgm_read_byte(&sine[b]) * c ) >> 4;
|
||
|
iColor[i][2] = iColor[i][0] >> 1;
|
||
|
a = b;
|
||
|
}
|
||
|
hue -= 3;
|
||
|
|
||
|
#endif
|
||
|
|
||
|
// Render current blink (if any) into brightness map
|
||
|
if(blinkCounter <= blinkFrames * 2) { // In mid-blink?
|
||
|
if(blinkCounter > blinkFrames) { // Eye closing
|
||
|
outer = blinkFrames * 2 - blinkCounter;
|
||
|
inner = outer + 1;
|
||
|
} else { // Eye opening
|
||
|
inner = blinkCounter;
|
||
|
outer = inner - 1;
|
||
|
}
|
||
|
y1 = upperLidTop - (upperLidTop - upperLidBottom) * outer / blinkFrames;
|
||
|
y2 = upperLidTop - (upperLidTop - upperLidBottom) * inner / blinkFrames;
|
||
|
y3 = lowerLidBottom + (lowerLidTop - lowerLidBottom) * inner / blinkFrames;
|
||
|
y4 = lowerLidBottom + (lowerLidTop - lowerLidBottom) * outer / blinkFrames;
|
||
|
for(i=0; i<16; i++) {
|
||
|
y = pgm_read_byte(&yCoord[i]);
|
||
|
if(y > y1) { // Above top lid
|
||
|
iBrightness[i] = 0;
|
||
|
} else if(y > y2) { // Blur edge of top lid in motion
|
||
|
iBrightness[i] = brightness * (y1 - y) / (y1 - y2);
|
||
|
} else if(y > y3) { // In eye
|
||
|
iBrightness[i] = brightness;
|
||
|
} else if(y > y4) { // Blur edge of bottom lid in motion
|
||
|
iBrightness[i] = brightness * (y - y4) / (y3 - y4);
|
||
|
} else { // Below bottom lid
|
||
|
iBrightness[i] = 0;
|
||
|
}
|
||
|
}
|
||
|
} else { // Not in blink -- set all 'on'
|
||
|
memset(iBrightness, brightness, sizeof(iBrightness));
|
||
|
}
|
||
|
|
||
|
if(--blinkCounter == 0) { // Init next blink?
|
||
|
blinkFrames = random(4, 8);
|
||
|
blinkCounter = blinkFrames * 2 + random(5, 180);
|
||
|
}
|
||
|
|
||
|
// Calculate current eye movement, possibly init next one
|
||
|
if(--gazeCounter <= gazeFrames) { // Is pupil in motion?
|
||
|
ep = newEyePos - eyeMotion * gazeCounter / gazeFrames; // Current pos.
|
||
|
if(gazeCounter == 0) { // Last frame?
|
||
|
eyePos = newEyePos; // Current position = new pos
|
||
|
newEyePos = random(16) * 16; // New pos. (always pixel center)
|
||
|
eyeMotion = newEyePos - eyePos; // Distance to move
|
||
|
gazeFrames = random(10, 20); // Duration of movement
|
||
|
gazeCounter = random(gazeFrames, 130); // Count to END of next movement
|
||
|
}
|
||
|
} else ep = eyePos; // Not moving -- fixed gaze
|
||
|
|
||
|
// Draw pupil -- 2 pixels wide, but sup-pixel positioning may span 3.
|
||
|
a = ep >> 4; // First candidate
|
||
|
b = (a + 1) & 0x0F; // 1 pixel CCW of a
|
||
|
c = (a + 2) & 0x0F; // 2 pixels CCW of a
|
||
|
i = ep & 0x0F; // Fraction of 'c' covered (0-15)
|
||
|
iBrightness[a] = (iBrightness[a] * i ) >> 4;
|
||
|
iBrightness[b] = 0;
|
||
|
iBrightness[c] = (iBrightness[c] * (16 - i)) >> 4;
|
||
|
|
||
|
// Merge iColor with iBrightness, issue to NeoPixels
|
||
|
for(i=0; i<16; i++) {
|
||
|
a = iBrightness[i] + 1;
|
||
|
// First eye
|
||
|
r = iColor[i][0]; // Initial background RGB color
|
||
|
g = iColor[i][1];
|
||
|
b = iColor[i][2];
|
||
|
if(a) {
|
||
|
r = (r * a) >> 8; // Scale by brightness map
|
||
|
g = (g * a) >> 8;
|
||
|
b = (b * a) >> 8;
|
||
|
}
|
||
|
pixels.setPixelColor(((i + TOP_LED_FIRST) & 15),
|
||
|
pgm_read_byte(&gamma8[r]), // Gamma correct and set pixel
|
||
|
pgm_read_byte(&gamma8[g]),
|
||
|
pgm_read_byte(&gamma8[b]));
|
||
|
|
||
|
// Second eye uses the same colors, but reflected horizontally.
|
||
|
// The same brightness map is used, but not reflected (same left/right)
|
||
|
r = iColor[15 - i][0];
|
||
|
g = iColor[15 - i][1];
|
||
|
b = iColor[15 - i][2];
|
||
|
if(a) {
|
||
|
r = (r * a) >> 8;
|
||
|
g = (g * a) >> 8;
|
||
|
b = (b * a) >> 8;
|
||
|
}
|
||
|
pixels.setPixelColor(16 + ((i + TOP_LED_SECOND) & 15),
|
||
|
pgm_read_byte(&gamma8[r]),
|
||
|
pgm_read_byte(&gamma8[g]),
|
||
|
pgm_read_byte(&gamma8[b]));
|
||
|
}
|
||
|
pixels.show();
|
||
|
|
||
|
delay(15);
|
||
|
}
|
||
|
|