/ EyeLights_Accelerometer_Tap / EyeLights_Accelerometer_Tap / EyeLights_Accelerometer_Tap.ino
EyeLights_Accelerometer_Tap.ino
  1  // SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*
  6  ACCELEROMETER INPUT DEMO: while the LED Glasses Driver has a perfectly
  7  good clicky button for input, this code shows how one might instead use
  8  the onboard accelerometer for interactions*.
  9  
 10  Worn normally, the LED rings are simply lit a solid color.
 11  TAP the eyeglass frames to cycle among a list of available colors.
 12  LOOK DOWN to light the LED rings bright white -- for navigating steps
 13  or finding the right key. LOOK BACK UP to return to solid color.
 14  This uses only the rings, not the matrix portion.
 15  
 16  * Like, if you have big ol' monster hands, that little button can be
 17    hard to click, y'know?
 18  */
 19  
 20  #include <Adafruit_IS31FL3741.h> // For LED driver
 21  #include <Adafruit_LIS3DH.h>     // For accelerometer
 22  #include <Adafruit_Sensor.h>     // For m/s^2 accel units
 23  
 24  Adafruit_LIS3DH             accel;
 25  Adafruit_EyeLights_buffered glasses; // Buffered for smooth animation
 26  
 27  // Here's a list of colors that we cycle through when tapped, specified
 28  // as {R,G,B} values from 0-255. These are intentionally a bit dim --
 29  // both to save battery and to make the "ground light" mode more dramatic.
 30  // Rather than primary color red/green/blue sequence which is just so
 31  // over-done at this point, let's use some HALLOWEEN colors!
 32  uint8_t colors[][3] = {
 33    {27, 9, 0},  // Orange
 34    {12, 0, 24}, // Purple
 35    {5, 31, 0},  // Green
 36  };
 37  #define NUM_COLORS (sizeof colors / sizeof colors[0]) // List length
 38  uint8_t looking_down_color[] = {255, 255, 255};       // Max white
 39  
 40  uint8_t  color_index = 0;   // Begin at first color in list
 41  uint8_t *target_color;      // Pointer to color we're aiming for
 42  float    interpolated_color[] = {0.0, 0.0, 0.0}; // Current color along the way
 43  float    filtered_y;        // De-noised accelerometer reading
 44  bool     looking_down;      // Set true when glasses are oriented downward
 45  sensors_event_t event;      // For accelerometer conversion
 46  uint32_t last_tap_time = 0; // For accelerometer tap de-noising
 47  
 48  // Crude error handler, prints message to Serial console, flashes LED
 49  void err(char *str, uint8_t hz) {
 50    Serial.println(str);
 51    pinMode(LED_BUILTIN, OUTPUT);
 52    for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
 53  }
 54  
 55  void setup() { // Runs once at program start...
 56  
 57    // Initialize hardware
 58    Serial.begin(115200);
 59    if (! accel.begin())   err("LIS3DH not found", 5);
 60    if (! glasses.begin()) err("IS3741 not found", 2);
 61  
 62    // Configure accelerometer and get initial state
 63    accel.setClick(1, 100); // Set threshold for single tap
 64    accel.getEvent(&event); // Current accel in m/s^2
 65    // Check accelerometer to see if we've started in the looking-down state,
 66    // set the target color (what we're aiming for) appropriately. Only the
 67    // Y axis is needed for this.
 68    filtered_y = event.acceleration.y;
 69    looking_down = (filtered_y > 5.0);
 70    // If initially looking down, aim for the look-down color,
 71    // else aim for the first item in the color list.
 72    target_color = looking_down ? looking_down_color : colors[color_index];
 73  
 74    // Configure glasses for max brightness, enable output
 75    glasses.setLEDscaling(0xFF);
 76    glasses.setGlobalCurrent(0xFF);
 77    glasses.enable(true);
 78  }
 79  
 80  void loop() { // Repeat forever...
 81  
 82    // interpolated_color blends from the prior to the next ("target")
 83    // LED ring colors, with a pleasant ease-out effect.
 84    for(uint8_t i=0; i<3; i++) { // R, G, B
 85      interpolated_color[i] = interpolated_color[i] * 0.97 + target_color[i] * 0.03;
 86    }
 87    // Convert separate red, green, blue to "packed" 24-bit RGB value
 88    uint32_t rgb = ((int)interpolated_color[0] << 16) |
 89                   ((int)interpolated_color[1] << 8) |
 90                    (int)interpolated_color[2];
 91    // Fill both rings with packed color, then refresh the LEDs.
 92    glasses.left_ring.fill(rgb);
 93    glasses.right_ring.fill(rgb);
 94    glasses.show();
 95  
 96    // The look-down detection only needs the accelerometer's Y axis.
 97    // This works with the Glasses Driver mounted on either temple,
 98    // with the glasses arms "open" (as when worn).
 99    accel.getEvent(&event);
100    // Smooth the accelerometer reading the same way RGB colors are
101    // interpolated. This avoids false triggers from jostling around.
102    filtered_y = filtered_y * 0.97 + event.acceleration.y * 0.03;
103  
104    // The threshold between "looking down" and "looking up" depends
105    // on which of those states we're currently in. This is an example
106    // of hysteresis in software...a change of direction requires a
107    // little extra push before it takes, which avoids oscillating if
108    // there was just a single threshold both ways.
109    if (looking_down) {       // Currently in the looking-down state...
110      (void)accel.getClick(); // Discard any taps while looking down
111      if (filtered_y < 3.5) { // Have we crossed the look-up threshold?
112        target_color = colors[color_index]; // Back to list color
113        looking_down = false;               // We're looking up now!
114      }
115    } else {                  // Currently in the looking-up state...
116      if (filtered_y > 5.0) { // Crossed the look-down threshold?
117        target_color = looking_down_color; // Aim for white
118        looking_down = true;               // We're looking down now!
119      } else if (accel.getClick()) {
120        // No look up/down change, but the accelerometer registered
121        // a tap. Compare this against the last time we sensed one,
122        // and only do things if it's been more than half a second.
123        // This avoids spurious double-taps that can occur no matter
124        // how carefully the tap threshold was set.
125        uint32_t now = millis();
126        uint32_t elapsed = now - last_tap_time;
127        if (elapsed > 500) {
128          // A good tap was detected. Cycle to the next color in
129          // the list and note the time of this tap.
130          color_index = (color_index + 1) % NUM_COLORS;
131          target_color = colors[color_index];
132          last_tap_time = now;
133        }
134      }
135    }
136  }