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 }