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