// 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(); } } }
To compile this code you will need the following libraries.
This guide will show you how to install the libraries:
This guide was first published on Oct 08, 2013. It was last updated on Oct 08, 2013.
This page (Code Implementation) was last updated on Oct 05, 2013.
Text editor powered by tinymce.