Created
August 26, 2022 03:39
-
-
Save jsdf/1cdf62e7ec6397319e63b83c7c8defb4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries | |
// | |
// SPDX-License-Identifier: MIT | |
/* | |
GOOGLY EYES for Adafruit EyeLight LED glasses + driver. Pendulum physics | |
simulation using accelerometer and math. This uses only the rings, not the | |
matrix portion. Adapted from Bill Earl's STEAM-Punk Goggles project: | |
https://learn.adafruit.com/steam-punk-goggles | |
*/ | |
#include <Adafruit_IS31FL3741.h> // For LED driver | |
#include <Adafruit_LSM6DSOX.h> // For accelerometer | |
#include <Adafruit_Sensor.h> // For m/s^2 accel units | |
Adafruit_LSM6DSOX accel; | |
Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation | |
// A small class for our pendulum simulation. | |
class Pendulum { | |
public: | |
// Constructor. Pass pointer to EyeLights ring, and a 3-byte color array. | |
Pendulum(Adafruit_EyeLights_Ring_buffered *r, uint8_t *c) { | |
ring = r; | |
color = c; | |
// Initial pendulum position, plus axle friction, are randomized | |
// so rings don't spin in perfect lockstep. | |
angle = random(1000); | |
momentum = 0.0; | |
friction = 0.94 + random(300) * 0.0001; // Inverse friction, really | |
} | |
// Given a pixel index (0-23) and a scaling factor (0.0-1.0), | |
// interpolate between LED "off" color (at 0.0) and this item's fully- | |
// lit color (at 1.0) and set pixel to the result. | |
void interp(uint8_t pixel, float scale) { | |
// Convert separate red, green, blue to "packed" 24-bit RGB value | |
ring->setPixelColor(pixel, | |
(int(color[0] * scale) << 16) | | |
(int(color[1] * scale) << 8) | | |
int(color[2] * scale)); | |
} | |
// Given an accelerometer reading, run one cycle of the pendulum | |
// physics simulation and render the corresponding LED ring. | |
void iterate(sensors_event_t &event) { | |
// Minus here is because LED pixel indices run clockwise vs. trigwise. | |
// 0.006 is just an empirically-derived scaling fudge factor that looks | |
// good; smaller values for more sluggish rings, higher = more twitch. | |
momentum = momentum * friction - 0.006 * | |
(cos(angle) * event.acceleration.z + | |
sin(angle) * event.acceleration.x); | |
angle += momentum; | |
// Scale pendulum angle into pixel space | |
float midpoint = fmodf(angle * 12.0 / M_PI, 24.0); | |
// Go around the whole ring, setting each pixel based on proximity | |
// (this is also to erase the prior position)... | |
for (uint8_t i=0; i<24; i++) { | |
float dist = fabs(midpoint - (float)i); // Pixel to pendulum distance... | |
if (dist > 12.0) // If it crosses the "seam" at top, | |
dist = 24.0 - dist; // take the shorter path. | |
if (dist > 5.0) // Not close to pendulum, | |
ring->setPixelColor(i, 0); // erase pixel. | |
else if (dist < 2.0) // Close to pendulum, | |
interp(i, 1.0); // solid color | |
else // Anything in-between, | |
interp(i, (5.0 - dist) / 3.0); // interpolate | |
} | |
} | |
private: | |
Adafruit_EyeLights_Ring_buffered *ring; // -> glasses ring | |
uint8_t *color; // -> array of 3 uint8_t's [R,G,B] | |
float angle; // Current position around ring | |
float momentum; // Current 'oomph' | |
float friction; // A scaling constant to dampen motion | |
}; | |
Pendulum pendulums[] = { | |
Pendulum(&glasses.left_ring, (uint8_t[3]){0, 20, 50}), // Cerulean blue, | |
Pendulum(&glasses.right_ring, (uint8_t[3]){0, 20, 50}), // 50 is plenty bright! | |
}; | |
#define N_PENDULUMS (sizeof pendulums / sizeof pendulums[0]) | |
// Crude error handler, prints message to Serial console, flashes LED | |
void err(char *str, uint8_t hz) { | |
Serial.println(str); | |
pinMode(LED_BUILTIN, OUTPUT); | |
for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1); | |
} | |
void setup() { // Runs once at program start... | |
// Initialize hardware | |
Serial.begin(115200); | |
delay(5000); | |
Serial.println("Boot"); | |
if (!accel.begin_I2C()) { | |
Serial.println("Failed to find LSM6DSOX chip"); | |
} | |
// accel.setGyroRange(LSM6DS_GYRO_RANGE_250_DPS ); | |
Serial.print("Gyro range set to: "); | |
switch (accel.getGyroRange()) { | |
case LSM6DS_GYRO_RANGE_125_DPS: | |
Serial.println("125 degrees/s"); | |
break; | |
case LSM6DS_GYRO_RANGE_250_DPS: | |
Serial.println("250 degrees/s"); | |
break; | |
case LSM6DS_GYRO_RANGE_500_DPS: | |
Serial.println("500 degrees/s"); | |
break; | |
case LSM6DS_GYRO_RANGE_1000_DPS: | |
Serial.println("1000 degrees/s"); | |
break; | |
case LSM6DS_GYRO_RANGE_2000_DPS: | |
Serial.println("2000 degrees/s"); | |
break; | |
case ISM330DHCX_GYRO_RANGE_4000_DPS: | |
break; // unsupported range for the DSOX | |
} | |
// accel.setAccelDataRate(LSM6DS_RATE_12_5_HZ); | |
Serial.print("Accelerometer data rate set to: "); | |
switch (accel.getAccelDataRate()) { | |
case LSM6DS_RATE_SHUTDOWN: | |
Serial.println("0 Hz"); | |
break; | |
case LSM6DS_RATE_12_5_HZ: | |
Serial.println("12.5 Hz"); | |
break; | |
case LSM6DS_RATE_26_HZ: | |
Serial.println("26 Hz"); | |
break; | |
case LSM6DS_RATE_52_HZ: | |
Serial.println("52 Hz"); | |
break; | |
case LSM6DS_RATE_104_HZ: | |
Serial.println("104 Hz"); | |
break; | |
case LSM6DS_RATE_208_HZ: | |
Serial.println("208 Hz"); | |
break; | |
case LSM6DS_RATE_416_HZ: | |
Serial.println("416 Hz"); | |
break; | |
case LSM6DS_RATE_833_HZ: | |
Serial.println("833 Hz"); | |
break; | |
case LSM6DS_RATE_1_66K_HZ: | |
Serial.println("1.66 KHz"); | |
break; | |
case LSM6DS_RATE_3_33K_HZ: | |
Serial.println("3.33 KHz"); | |
break; | |
case LSM6DS_RATE_6_66K_HZ: | |
Serial.println("6.66 KHz"); | |
break; | |
} | |
// accel.setGyroDataRate(LSM6DS_RATE_12_5_HZ); | |
Serial.print("Gyro data rate set to: "); | |
switch (accel.getGyroDataRate()) { | |
case LSM6DS_RATE_SHUTDOWN: | |
Serial.println("0 Hz"); | |
break; | |
case LSM6DS_RATE_12_5_HZ: | |
Serial.println("12.5 Hz"); | |
break; | |
case LSM6DS_RATE_26_HZ: | |
Serial.println("26 Hz"); | |
break; | |
case LSM6DS_RATE_52_HZ: | |
Serial.println("52 Hz"); | |
break; | |
case LSM6DS_RATE_104_HZ: | |
Serial.println("104 Hz"); | |
break; | |
case LSM6DS_RATE_208_HZ: | |
Serial.println("208 Hz"); | |
break; | |
case LSM6DS_RATE_416_HZ: | |
Serial.println("416 Hz"); | |
break; | |
case LSM6DS_RATE_833_HZ: | |
Serial.println("833 Hz"); | |
break; | |
case LSM6DS_RATE_1_66K_HZ: | |
Serial.println("1.66 KHz"); | |
break; | |
case LSM6DS_RATE_3_33K_HZ: | |
Serial.println("3.33 KHz"); | |
break; | |
case LSM6DS_RATE_6_66K_HZ: | |
Serial.println("6.66 KHz"); | |
break; | |
} | |
if (! glasses.begin()) err("IS3741 not found", 2); | |
// Configure glasses for max brightness, enable output | |
glasses.setLEDscaling(0xFF); | |
glasses.setGlobalCurrent(0xFF); | |
glasses.enable(true); | |
} | |
static int iter = 0; | |
void loop() { // Repeat forever... | |
// /* Get a new normalized sensor event */ | |
sensors_event_t accel_event; | |
sensors_event_t gyro_event; | |
sensors_event_t temp_event; | |
accel.getEvent(&accel_event, &gyro_event, &temp_event); // Read accelerometer once | |
for (uint8_t i=0; i<N_PENDULUMS; i++) { // For each pendulum... | |
pendulums[i].iterate(accel_event); // Do math with accel data | |
} | |
glasses.show(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment