/ 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  }