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 }