To compile this code you will need the following libraries.
This guide will show you how to install the libraries:
// Googly Eye Goggles
// By Bill Earl
// For Adafruit Industries
//
// The googly eye effect is based on a physical model of a pendulum.
// The pendulum motion is driven by accelerations in 2 axis.
// Eye color varies with orientation of the magnetometer
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM303_U.h>
#include <Adafruit_NeoPixel.h>
#define neoPixelPin 10
// We could do this as 2 16-pixel rings wired in parallel.
// But keeping them separate lets us do the right and left
// eyes separately if we want.
Adafruit_NeoPixel strip = Adafruit_NeoPixel(32, neoPixelPin, NEO_GRB + NEO_KHZ800);
Adafruit_LSM303_Accel_Unified accel = Adafruit_LSM303_Accel_Unified(54321);
Adafruit_LSM303_Mag_Unified mag = Adafruit_LSM303_Mag_Unified(12345);
float pos = 8; // Starting center position of pupil
float increment = 2 * 3.14159 / 16; // distance between pixels in radians
float MomentumH = 0; // horizontal component of pupil rotational inertia
float MomentumV = 0; // vertical component of pupil rotational inertia
// Tuning constants. (a.k.a. "Fudge Factors)
// These can be tweaked to adjust the liveliness and sensitivity of the eyes.
const float friction = 0.995; // frictional damping constant. 1.0 is no friction.
const float swing = 60; // arbitrary divisor for gravitational force
const float gravity = 200; // arbitrary divisor for lateral acceleration
const float nod = 7.5; // accelerometer threshold for toggling modes
long nodStart = 0;
long nodTime = 2000;
bool antiGravity = false; // The pendulum will anti-gravitate to the top.
bool mirroredEyes = false; // The left eye will mirror the right.
const float halfWidth = 1.25; // half-width of pupil (in pixels)
// Pi for calculations - not the raspberry type
const float Pi = 3.14159;
void setup(void)
{
strip.begin();
strip.show(); // Initialize all pixels to 'off' sensor_t sensor;
// Initialize the sensors
accel.begin();
mag.begin();
resetModes();
}
// main processing loop
void loop(void)
{
// Read the magnetometer and determine the compass heading:
sensors_event_t event;
mag.getEvent(&event);
// Calculate the angle of the vector y,x from magnetic North
float heading = (atan2(event.magnetic.y,event.magnetic.x) * 180) / Pi;
// Normalize to 0-360 for a compass heading
if (heading < 0)
{
heading = 360 + heading;
}
// Now read the accelerometer to control the motion.
accel.getEvent(&event);
// Check for mode change commands
CheckForNods(event);
// apply a little frictional damping to keep things in control and prevent perpetual motion
MomentumH *= friction;
MomentumV *= friction;
// Calculate the horizontal and vertical effect on the virtual pendulum
// 'pos' is a pixel address, so we multiply by 'increment' to get radians.
float TorqueH = cos(pos * increment); // peaks at top and bottom of the swing
float TorqueV = sin(pos * increment); // peaks when the pendulum is horizontal
// Add the incremental acceleration to the existing momentum
// This code assumes that the accelerometer is mounted upside-down, level
// and with the X-axis pointed forward. So the Y axis reads the horizontal
// acceleration and the inverse of the Z axis is gravity.
// For other orientations of the sensor, just change the axis to match.
MomentumH += TorqueH * event.acceleration.y / swing;
if (antiGravity)
{
MomentumV += TorqueV * event.acceleration.z / gravity;
}
else
{
MomentumV -= TorqueV * event.acceleration.z / gravity;
}
// Calculate the new position
pos += MomentumH + MomentumV;
// handle the wrap-arounds at the top
while (round(pos) < 0) pos += 16.0;
while (round(pos) > 15) pos -= 16.0;
// Now re-compute the display
for (int i = 0; i < 16; i++)
{
// Compute the distance bewteen the pixel and the center
// point of the virtual pendulum.
float diff = i - pos;
// Light up nearby pixels proportional to their proximity to 'pos'
if (fabs(diff) <= halfWidth)
{
uint32_t color;
float proximity = halfWidth - fabs(diff) * 200;
// pick a color based on heading & proximity to 'pos'
color = selectColor(heading, proximity);
// do both eyes
strip.setPixelColor(i, color);
if (mirroredEyes)
{
strip.setPixelColor(31 - i, color);
}
else
{
strip.setPixelColor(i + 16, color);
}
}
else // all others are off
{
strip.setPixelColor(i, 0);
if (mirroredEyes)
{
strip.setPixelColor(31 - i, 0);
}
else
{
strip.setPixelColor(i + 16, 0);
}
}
}
// Now show it!
strip.show();
}
// choose a color based on the compass heading and proximity to "pos".
uint32_t selectColor(float heading, float proximity)
{
uint32_t color;
// Choose eye color based on the compass heading
if (heading < 60)
{
color = strip.Color(0, 0, proximity);
}
else if (heading < 120)
{
color = strip.Color(0, proximity, proximity);
}
else if (heading < 180)
{
color = strip.Color(0, proximity, 0);
}
else if (heading < 240)
{
color = strip.Color(proximity, proximity, 0);
}
else if (heading < 300)
{
color = strip.Color(proximity, 0, 0);
}
else // 300-360
{
color = strip.Color(proximity, 0, proximity);
}
}
// monitor orientation for mode-change 'gestures'
void CheckForNods(sensors_event_t event)
{
if (event.acceleration.x > nod)
{
if (millis() - nodStart > nodTime)
{
antiGravity = false;
nodStart = millis(); // reset timer
spinDown();
}
}
else if (event.acceleration.x < -(nod + 1))
{
if (millis() - nodStart > nodTime)
{
antiGravity = true;
spinUp();
nodStart = millis(); // reset timer
}
}
else if (event.acceleration.y > nod)
{
if (millis() - nodStart > nodTime)
{
mirroredEyes = false;
spinDown();
nodStart = millis(); // reset timer
}
}
else if (event.acceleration.y < -nod)
{
if (millis() - nodStart > nodTime)
{
mirroredEyes = true;
spinUp();
nodStart = millis(); // reset timer
}
}
else // no nods in progress
{
nodStart = millis(); // reset timer
}
}
// Reset to default
void resetModes()
{
antiGravity = false;
mirroredEyes = false;
/// spin-up
spin(strip.Color(255,0,0), 1, 500);
spin(strip.Color(0,255,0), 1, 500);
spin(strip.Color(0,0,255), 1, 500);
spinUp();
}
// gradual spin up
void spinUp()
{
for (int i = 300; i > 0; i -= 20)
{
spin(strip.Color(255,255,255), 1, i);
}
pos = 0;
// leave it with some momentum and let it 'coast' to a stop
MomentumH = 3;
}
// Gradual spin down
void spinDown()
{
for (int i = 1; i < 300; i++)
{
spin(strip.Color(255,255,255), 1, i += 20);
}
// Stop it dead at the top and let it swing to the bottom on its own
pos = 0;
MomentumH = MomentumV = 0;
}
// utility function for feedback on mode changes.
void spin(uint32_t color, int count, int time)
{
for (int j = 0; j < count; j++)
{
for (int i = 0; i < 16; i++)
{
strip.setPixelColor(i, color);
strip.setPixelColor(31 - i, color);
strip.show();
delay(max(time / 16, 1));
strip.setPixelColor(i, 0);
strip.setPixelColor(31 - i, 0);
strip.show();
}
}
}
Page last edited March 08, 2024
Text editor powered by tinymce.