/ 2020_shake / 2020_shake.ino
2020_shake.ino
1 // SPDX-FileCopyrightText: 2020 Limor Fried for Adafruit Industries 2 // 3 // SPDX-License-Identifier: MIT 4 5 #include <Adafruit_LIS3DH.h> // For accelerometer 6 #include <Adafruit_PixelDust.h> // For simulation 7 #include <Adafruit_Protomatter.h> // For LED matrix 8 #include "2020.h" // 2020 bitmap data 9 #include "2021.h" // 2021 bitmap data 10 #include "2022.h" // etc. 11 #include "2023.h" 12 #include "2024.h" 13 #include "2025.h" 14 #include "2026.h" 15 16 #define BITMAP_WIDTH 64 // All the year bitmaps are a fixed size 17 #define BITMAP_HEIGHT 32 18 #define THIS_YEAR_BITMAP bitmap_2021 // Name of current/next year bitmap 19 #define NEXT_YEAR_BITMAP bitmap_2022 // arrays in header files 20 21 bool show_new_year = true; 22 23 #define SHAKE_ACCEL_G 2.9 // Force (in Gs) to trigger shake 24 #define SHAKE_ACCEL_MS2 (SHAKE_ACCEL_G * 9.8) // Convert to m/s^2 25 #define SHAKE_ACCEL_SQ (SHAKE_ACCEL_MS2 * SHAKE_ACCEL_MS2) // Avoid sqrt() in accel check 26 #define SHAKE_EVENTS 30 // Number of accel readings to trigger sand 27 #define SHAKE_PERIOD 2000 // Period (in ms) when SHAKE_EVENTS must happen 28 #define SAND_TIME 6000 // Time (in ms) to run simulation before restarting 29 30 uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; 31 uint8_t addrPins[] = {17, 18, 19, 20}; 32 uint8_t clockPin = 14; 33 uint8_t latchPin = 15; 34 uint8_t oePin = 16; 35 36 // 64x32 pixel matrix, 6-bit depth 37 Adafruit_Protomatter matrix( 38 64, 6, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, true); 39 40 Adafruit_LIS3DH accel = Adafruit_LIS3DH(); // Accelerometer 41 42 #define MAX_FPS 60 // Maximum redraw rate, frames/second 43 uint32_t prevTime = 0; // For frames-per-second throttle 44 uint16_t n_grains = 0; // Number of sand grains (counted on startup) 45 Adafruit_PixelDust *sand; // Sand object (allocated in setup()) 46 47 // Error handler used by setup() 48 void err(int x) { 49 uint8_t i; 50 pinMode(LED_BUILTIN, OUTPUT); // Using onboard LED 51 for(i=1;;i++) { // Loop forever... 52 digitalWrite(LED_BUILTIN, i & 1); // LED on/off blink to alert user 53 delay(x); 54 } 55 } 56 57 // SETUP - RUNS ONCE AT PROGRAM START -------------------------------------- 58 59 void setup(void) { 60 uint8_t i, j, bytes; 61 Serial.begin(115200); 62 //while (!Serial); 63 64 ProtomatterStatus status = matrix.begin(); 65 Serial.printf("Protomatter begin() status: %d\n", status); 66 67 // Count number of 'on' pixels (sand grains) in THIS_YEAR_BITMAP 68 for (int i=0; i<sizeof(THIS_YEAR_BITMAP); i++) { 69 for (int b=0; b<8; b++) { 70 if (THIS_YEAR_BITMAP[i] & (1 << b)) { 71 n_grains++; 72 } 73 } 74 } 75 Serial.printf("Bitmap has %d grains\n", n_grains); 76 77 // Allocate sand object based on matrix size and bitmap 'on' pixels 78 sand = new Adafruit_PixelDust(matrix.width(), matrix.height(), n_grains, 1); 79 if (!sand->begin()) { 80 Serial.println("Couldn't start sand"); 81 err(1000); // Slow blink = malloc error 82 } 83 84 if (!accel.begin(0x19)) { 85 Serial.println("Couldn't find accelerometer"); 86 err(250); // Fast bink = I2C error 87 } 88 Serial.println("Accelerometer OK"); 89 accel.setRange(LIS3DH_RANGE_8_G); 90 } 91 92 void loop() { 93 Serial.print("Tick"); 94 uint16_t sandColor = show_new_year ? 0xF800 : 0xFFFF; // Red or white 95 96 // Set initial sand pixel positions and draw initial matrix state 97 sand->clear(); 98 matrix.fillScreen(0); 99 int grain = 0, pixel = 0; // Sand grain and pixel indices 100 for (int i=0; i<sizeof(THIS_YEAR_BITMAP); i++) { 101 for (int b=0; b<8; b++, pixel++) { 102 if (THIS_YEAR_BITMAP[i] & (1 << (7-b))) { 103 int x = pixel % BITMAP_WIDTH; 104 int y = pixel / BITMAP_WIDTH; 105 //Serial.printf("Set pixel %d @ (%d, %d)\n", grain, x, y); 106 sand->setPosition(grain++, x, y); 107 matrix.drawPixel(x, y, sandColor); 108 } 109 } 110 } 111 matrix.show(); 112 113 // Wait for shake 114 uint32_t first_event_time = millis() - SHAKE_PERIOD * 2, last_event_time = first_event_time; 115 uint8_t num_events = 0; 116 sensors_event_t event; 117 for (;;) { 118 uint32_t t = millis(); // Current time 119 accel.getEvent(&event); 120 float mag2 = event.acceleration.x * event.acceleration.x + 121 event.acceleration.y * event.acceleration.y + 122 event.acceleration.z * event.acceleration.z; 123 if (mag2 >= SHAKE_ACCEL_SQ) { // Accel exceeds shake threshold 124 if ((t - last_event_time) > SHAKE_PERIOD) { // Long time since last event? 125 first_event_time = t; // Start of new count 126 num_events = 1; 127 } else if ((t - first_event_time) < SHAKE_PERIOD) { // Still in shake interval? 128 if (++num_events >= SHAKE_EVENTS) { // Enough events? 129 break; 130 } 131 } 132 last_event_time = t; 133 } 134 } 135 136 // Run sand simulation for a few seconds 137 uint32_t elapsed, sandStartTime = millis(); 138 139 while((elapsed = (millis() - sandStartTime)) < SAND_TIME) { 140 141 // Limit the animation frame rate to MAX_FPS. 142 uint32_t t; 143 while(((t = micros()) - prevTime) < (1000000L / MAX_FPS)); 144 prevTime = t; 145 146 // Read accelerometer... 147 sensors_event_t event; 148 accel.getEvent(&event); 149 150 // Run one frame of the simulation 151 sand->iterate(event.acceleration.x * 1024, event.acceleration.y * 1024, event.acceleration.z * 1024); 152 153 if (elapsed > SAND_TIME * 3 / 4) { 154 float scale = 1.0 - (float)(elapsed - (SAND_TIME * 3 / 4)) / (float)(SAND_TIME / 4); 155 if (scale < 0.0) scale = 0.0; 156 else if (scale > 1.0) scale = 1.0; 157 scale = pow(scale, 2.6); 158 uint16_t rb = (int)(31.0 * scale + 0.5); 159 uint16_t g = (int)(63.0 * scale + 0.5); 160 if (show_new_year) 161 sandColor = (rb * 0b100000000000); // Just show red 162 else 163 sandColor = (rb * 0b100000000001) + (g << 5); 164 } 165 166 // Update pixel data in LED driver 167 matrix.fillScreen(0); 168 dimension_t x, y; 169 for(int i=0; i<n_grains ; i++) { 170 sand->getPosition(i, &x, &y); 171 matrix.drawPixel(x, y, sandColor); 172 } 173 matrix.show(); 174 } 175 176 // If the show_new_year flag is set, don't return to shake detect, 177 // instead switch to sparkly display forever (reset to start over) 178 if (show_new_year) { 179 uint16_t frame = 0; 180 matrix.fillScreen(0); 181 for(;;) { 182 int pixel = 0; 183 for (int i=0; i<sizeof(NEXT_YEAR_BITMAP); i++) { 184 for (int b=0; b<8; b++, pixel++) { 185 if (NEXT_YEAR_BITMAP[i] & (1 << (7-b))) { 186 int x = pixel % BITMAP_WIDTH; 187 int y = pixel / BITMAP_WIDTH; 188 matrix.drawPixel(x, y, (random() & 1) ? ((((x - y + frame) / 8) & 1) ? 0xFFFF : 0x001F) : 0); 189 } 190 } 191 } 192 matrix.show(); 193 delay(18); 194 frame++; 195 } 196 } 197 }