/ EyeLights_Googly_Rings / EyeLights_Googly_Rings / EyeLights_Googly_Rings.ino
EyeLights_Googly_Rings.ino
  1  // SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*
  6  GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics
  7  simulation using accelerometer and math. This uses only the rings, not the
  8  matrix portion. Adapted from Bill Earl's STEAM-Punk Goggles project:
  9  https://learn.adafruit.com/steam-punk-goggles
 10  */
 11  
 12  #include <Adafruit_IS31FL3741.h> // For LED driver
 13  #include <Adafruit_LIS3DH.h>     // For accelerometer
 14  #include <Adafruit_Sensor.h>     // For m/s^2 accel units
 15  
 16  Adafruit_LIS3DH             accel;
 17  Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
 18  
 19  // A small class for our pendulum simulation.
 20  class Pendulum {
 21  public:
 22    // Constructor. Pass pointer to EyeLights ring, and a 3-byte color array.
 23    Pendulum(Adafruit_EyeLights_Ring_buffered *r, uint8_t *c) {
 24      ring  = r;
 25      color = c;
 26      // Initial pendulum position, plus axle friction, are randomized
 27      // so rings don't spin in perfect lockstep.
 28      angle = random(1000);
 29      momentum = 0.0;
 30      friction = 0.94 + random(300) * 0.0001; // Inverse friction, really
 31    }
 32  
 33    // Given a pixel index (0-23) and a scaling factor (0.0-1.0),
 34    // interpolate between LED "off" color (at 0.0) and this item's fully-
 35    // lit color (at 1.0) and set pixel to the result.
 36    void interp(uint8_t pixel, float scale) {
 37      // Convert separate red, green, blue to "packed" 24-bit RGB value
 38      ring->setPixelColor(pixel,
 39          (int(color[0] * scale) << 16) |
 40          (int(color[1] * scale) <<  8) |
 41           int(color[2] * scale));
 42    }
 43  
 44    // Given an accelerometer reading, run one cycle of the pendulum
 45    // physics simulation and render the corresponding LED ring.
 46    void iterate(sensors_event_t &event) {
 47      // Minus here is because LED pixel indices run clockwise vs. trigwise.
 48      // 0.006 is just an empirically-derived scaling fudge factor that looks
 49      // good; smaller values for more sluggish rings, higher = more twitch.
 50      momentum =  momentum * friction - 0.006 *
 51        (cos(angle) * event.acceleration.z +
 52         sin(angle) * event.acceleration.x);
 53      angle += momentum;
 54  
 55      // Scale pendulum angle into pixel space
 56      float midpoint = fmodf(angle * 12.0 / M_PI, 24.0);
 57  
 58      // Go around the whole ring, setting each pixel based on proximity
 59      // (this is also to erase the prior position)...
 60      for (uint8_t i=0; i<24; i++) {
 61          float dist = fabs(midpoint - (float)i); // Pixel to pendulum distance...
 62          if (dist > 12.0)                   // If it crosses the "seam" at top,
 63              dist = 24.0 - dist;            //   take the shorter path.
 64          if (dist > 5.0)                    // Not close to pendulum,
 65              ring->setPixelColor(i, 0);     //   erase pixel.
 66          else if (dist < 2.0)               // Close to pendulum,
 67              interp(i, 1.0);                //   solid color
 68          else                               // Anything in-between,
 69              interp(i, (5.0 - dist) / 3.0); //   interpolate
 70      }
 71    }
 72  private:
 73    Adafruit_EyeLights_Ring_buffered *ring; // -> glasses ring
 74    uint8_t *color;    // -> array of 3 uint8_t's [R,G,B]
 75    float    angle;    // Current position around ring
 76    float    momentum; // Current 'oomph'
 77    float    friction; // A scaling constant to dampen motion
 78  };
 79  
 80  Pendulum pendulums[] = {
 81      Pendulum(&glasses.left_ring, (uint8_t[3]){0, 20, 50}),  // Cerulean blue,
 82      Pendulum(&glasses.right_ring, (uint8_t[3]){0, 20, 50}), // 50 is plenty bright!
 83  };
 84  #define N_PENDULUMS (sizeof pendulums / sizeof pendulums[0])
 85  
 86  // Crude error handler, prints message to Serial console, flashes LED
 87  void err(char *str, uint8_t hz) {
 88    Serial.println(str);
 89    pinMode(LED_BUILTIN, OUTPUT);
 90    for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
 91  }
 92  
 93  void setup() { // Runs once at program start...
 94  
 95    // Initialize hardware
 96    Serial.begin(115200);
 97    if (! accel.begin())   err("LIS3DH not found", 5);
 98    if (! glasses.begin()) err("IS3741 not found", 2);
 99  
100    // Configure glasses for max brightness, enable output
101    glasses.setLEDscaling(0xFF);
102    glasses.setGlobalCurrent(0xFF);
103    glasses.enable(true);
104  }
105  
106  void loop() { // Repeat forever...
107    sensors_event_t event;
108    accel.getEvent(&event); // Read accelerometer once
109    for (uint8_t i=0; i<N_PENDULUMS; i++) { // For each pendulum...
110      pendulums[i].iterate(event);          // Do math with accel data
111    }
112    glasses.show();
113  }