Hydro Task is a device that tracks how much water the user drinks throughout the day with the purpose of encouraging the user to meet their hydration goals.

Overall Photo 

Detail Images

Reset button that allows the user to reset the device once a day

Accelerometer used to detect the position of the water bottle

Battery pack used as a power source for the Hydro Task

The OLED display shows whether the device is on or off meaning whether the user is drinking or not. This feature is controlled by the user so they can indicate to the device to record how much water is being consumed. The OLED display also shows the user’s goal and how much water they’ve drank today.

Functioning Video 

Processing Images 

1. Determining how to measure the volume of water the user drinks throughout the day

    • Once I decided to use the accelerometer to determine the position of the water bottle, I had to decided how I would use the raw values read by accelerometer to determine the position. I also had to decide how to use the bottle’s position to determine how much water the user was drinking.

My original idea was to calculate the position angle using code I borrowed from online and use those values to determine the acceleration due to gravity at that angle. Using this information along with duration the user had the water bottle tilted, I would calculate the volume by using a volumetric flow rate equation. However, after receiving feedback from the professor I understood that volumetric flow rate would not give an accurate volume measurement. I also did not understand the code I was borrowing from online so I decided to derive the equations used to calculate the water bottle’s position myself.

    • The video below demonstrates me experimenting with the different position values reported by the accelerometer using the equation I derived. I derived the equation using trigonometry. Although I could not get the position values to reflect the position angle by ranging from 0-180 degrees, the values reported still accurately reflected the object’s position.

 

This photo shows the experiment I conducted to determine how much water was displaced at each position the water bottle can be held at. I decided to use this experimentation method when I understood that the volumetric flow rate calculation would not work.

This is one of the tables I made during a trial I conducted of the position-volume experiment.

2. Deciding to use a reset button instead of a real time clock to reset the device once a day

  • My original intention was to use a RTC(real time clock) to reset the device everyday when the clock reached 12 at night. However, I ran into some challenges when programming the RTC and had to move forward with using a reset button instead.

This is the RTC I attempted to use

This is the code I borrowed from the website https://www.pjrc.com/teensy/td_libs_DS1307RTC.html in attempt to set up the RTC

This is what would happen every time I uncommented the code for the RTC. Whenever I tried to run the RTC code along with the rest of my code the device would not work. The OLED display would only show the dailyVol value(0 mL) and would not update when I tilted the device.

Since the RTC was presenting many challenges I decided to pivot and use a reset button instead. This video shows how the reset button works. When the user clicks the button the dailyVol is reset to 0. Also if the user drank more than 3000 mL of water than the streak count goes up by 1. When the reset button is read as HIGH, the string “reset:on” is displayed on the screen. Once the user resets the device they must click the button again. The device will only measure water when the reset button is read as LOW, this is indicated to the user when the screen displays the string “reset: off.”

Discussion 

One of the challenges that comes with the design process I discovered as I worked up until the deadline is finding a stopping point. This project taught me that the design process truly never ends as designs can always be innovated upon. The final critique opened the floor for new ideas and suggestions to improve the Hydro Task. One suggestion I received during the critique was “Just tinker some more with the design/prototype. Perhaps a sleeve/sack that encases the whole bottle? Should give you enough real estate for all of the necessary components instead of the wrap-around.”  I had not rethought the layout of Hydro Task’s design since the prototype. From my initial sketches, I imagined the Hydro Task to be a device that wraps all the way around the water bottle with an adjustable band. It would be interesting to experiment with the layout and see if I could make the Hydro Task smaller and more compact. The current layout was created specifically for the bottle it is demonstrated on but in the future an improved design can allow it to be compatible with bottles of other sizes. I also agree with the feedback that adding a sleeve would have given the product a more finished look. Adding a sleeve was my original intention but due to other unforeseen challenges I did not have time. I attempted to duct tape a sleeve with fabric and cut out holes for the buttons and screens but it looked worse. Another challenge with my make-shift sleeve is it was disturbing the wiring, causing some wires to detach. In the future I would like to sew a sleeve that can be attached with velcro so it can be removed in case some wires get messed up. The ergonomics were not my priority and as a result they fell short of my original expectations. My attempts to improve the design  was an after thought because the product’s function and features took priority. However, I think with some experimentation of the layout and adding a well designed sleeve the overall Hydro Task can be improved immensely. 

The main challenge I faced was determining how to measure the volume of water the user is drinks throughout the day. I tinkered with different methods including a load cell sensor. The challenge with using a load cell was it would not work with the design I imagined for the Hydro Task since I wanted it to wrap around the bottle. Therefore, I decided to use an accelerometer after learning it could be used to detect the position angle of an object. I experimented with the accelerometer in order to understand how I could use its raw values to measure the amount of water being displaced. I decided to do an experiment where I measured how much water was poured out of the water bottle at each given position. During the final critique one person commented “I really like the way you experimented with a bunch of different angles to get the rate of flow of the water!” At first I was apprehensive about using an experimental method to determine the values because I was afraid it would compromise the device’s accuracy. However, in the end I think it was the correct direction to go in for this project. After the prototype, I redid the experiment with a graduated cylinder that gave more precise measurements. I also conducted multiple trials of the experiment to ensure accuracy and was satisfied with the results of the measuring method. 

The Hydro Task is just the beginning as there is so much potential for improving  this product. Another piece of feedback I received was to add an on and off switch for the battery pack. I think a switch would improve the functionality of the device and make it more user friendly. When I attached the wires from the battery pack to the Teensy the connection was fairly loose and caused some complications with using the device, especially as I went to move the water bottle. Adding a switch and soldering these connections would have addressed this problem as well. In addition to a switch,  I would also like to add a feature that allows the user to set their own water drinking goal. Another idea to make the Hydro Task more personalized would be having the user enter in information about themselves, such as height and weight, and based on that information the device determines their water drinking goal. This feature would make the Hydro Task more personalized to the user’s needs. Overall, feedback and critique inspired more ideas and innovation for the Hydro Task.

Technical

  • Block Diagram 

  • Electrical Schematic

  • Code

    /*
     Hydro Task 
     
     Nicole Monaco
     
    This code controls the inputs and outputs that come tohgether to make up the hydro task. This includes an accelerometer, used to understand the position of the water bottle, two buttons, that the user interacts with to control the device, and an OLED display, that allows the user to track their progress throughout the day. One button is controlled by the user to indicate to the device whether or not the user is drinking from the bottle. When the button value is read as HIGH the OLED display indicates that device is "on" and the device maps the position read by the acceloromter to how much water is being displaced. How much water that is displaced is then added to the value "dailyVol" which represents how much water the user has drank that day. Everytime "dailyVol" is updated, the OLED display also updates its screen with the new value. When the dailyVol is equal to the user's hydration goal, 3000 mL, the OLED display shows a congratulation message to celebrate that the user met their drinking goal. The second button is programmed to allow the user to reset the device once a day. When the second button is read as "HIGH", "reset: on" is displayed on the top right of the OLED display and dailyVol is reset to 0. If the dailyVol was greater than or equal to 3000 mL, the desired goal, then 1 is added to the user's streak count. The streak count updates every day the user meets their goal, but is reset to 0 if they do not meet their goal. 
    
    
    Pin mapping:
        Arduino pin | role   | description
        ------------|--------|-------------
        A9            input     accelorometer (x direction)
        A8            input     accelorometer (y direction)
        A7            input     accelorometer (z direction)
        3             input     button (Pressed by the user when they are drinking water)
        4             input     reset button (Pressed by the user once a day to reset the device
    
    Sources: 
    -Used example code from the libraries <Adafruit_GFX.h> and <Adafruit_SSD1306.h> to set up the OLED display and for the animation function
    
     */
    //libraries
    #include <Wire.h>
    #include <Arduino.h>
    #include <SPI.h>
    #include <Wire.h>
    #include <Adafruit_GFX.h>
    #include <Adafruit_SSD1306.h>
    
    
    int dailyVol = 0; //dailyVol is a variable used to track the amount of water the user drinks throughout the day and is initially set to 0
    int lastangle = 778; /*"lastangle" is a value used to represent the last value read by the accelorometer. The reason this value is stored 
    is because the readings from the accelorometer vary even if the object is standing still, so the last value read is recorded to dtermine if 
    the change is signigficant enough to indicate movement. The reason it is initially set to 778 is because that is the value read by the accelerometer
    when it is standing up straight in a still position. 
    */
    
    
    int streak = 0; //Streak is a value that tracks how many days in a row the user met their hydration goal.
    int Goal = 3000; //Goal is a value that represents how much water the user should aspire to drink in a day.
    bool congratsmode = false; /*congratsmode is a boolean variable that ensures that congrats mode is only activated once a day. It is initally set the 
    false and then once the dailyVol is equal to 3000 it is set to true. When it is set to true congratsmode can no longer be activated so the congratulation 
    screen will only be displayed once. When the user clicks the reset button, congratsmode is reset to false so it can be activated again if the user meets their 
    goal.
    */
    
    //unsigned long animationTimer = 0;
    
    //Buttons
    const int buttonPin = 3; //Button used to indicate whether or not the user is drinking water to ensure values aren't being recorded when the user isn't drinking
    const int resetbutton = 4; //Button to reset the device once a day
    
    //Oled Display setup code
    
    #define SCREEN_WIDTH 128
    #define SCREEN_HEIGHT 64
    
    // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
    #define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
    Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
    
    
    void setup()   {
      Serial.begin(9600);
      
      //button setup 
      pinMode(buttonPin, INPUT);
     
      //Oled Display setup 
      if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
        Serial.println(F("SSD1306 allocation failed"));
        for (;;); // Don't proceed, loop forever
      }
      display.display();
      delay(2000); // Pause for 2 seconds
      display.clearDisplay();
    
    //animation code for congrats mode setup
    #define NUMFLAKES     10 // Number of snowflakes in the animation example
    #define LOGO_HEIGHT   16
    #define LOGO_WIDTH    16
    
      static const unsigned char PROGMEM logo_bmp[] =
      { B00000000, B11000000,
        B00000001, B11000000,
        B00000001, B11000000,
        B00000011, B11100000,
        B11110011, B11100000,
        B11111110, B11111000,
        B01111110, B11111111,
        B00110011, B10011111,
        B00011111, B11111100,
        B00001101, B01110000,
        B00011011, B10100000,
        B00111111, B11100000,
        B00111111, B11110000,
        B01111100, B11110000,
        B01110000, B01110000,
        B00000000, B00110000
      };
      //OLed Display Text Setup 
      display.setTextSize(1); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 55);
      display.println("Streak:"); //Displays the string "Streak:" on the screen 
      display.display();
      delay(100);
      display.setTextSize(1); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(45, 55);
      display.println(String(streak)); //Displays the value stored in the variable streak on the screen 
      display.display();
      delay(100);
      display.setTextSize(2); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 15);
      display.println(F("Goal:3000")); //Displays a string indicating the user's water drinking goal
      display.display();
      delay(100);
      display.setTextSize(2); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 35);
      display.println(F("Today:")); //Displays a string "Today:" that indicates how much water the user has drank today
      display.display();
      delay(100);
    }
    void loop() {
    
    //Displays the value stored in the variable dailyVol and updates the screen everytime the value dailyVol is updated 
    display.setTextSize(2); // Draw 2X-scale text
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(70, 35);
    display.println(String(dailyVol));
    display.display();
    delay(100);
    
    /*This look up table maps each position recorded by the accelorometer to how much value was displaced at that given value. 
    The values recorded from the accelorometer ranged from 788 to 636. When the code understands that the user is drinking water 
    it uses the position values read by the accelorometer to look up how much water was displaced. 
    */
    int array1[160][2] = {
      {788, 0},
      {787, 0},
      {786, 0},
      {785, 0},
      {784, 0},
      {783, 0},
      {782, 0},
      {781, 0},
      {780, 0},
      {779, 1},
      {778, 1},
      {777, 1},
      {776, 2},
      {775, 2},
      {774, 2},
      {773, 3},
      {772, 3},
      {771, 3},
      {770, 3},
      {769, 4},
      {768, 4},
      {767, 4},
      {766, 4},
      {765, 5}, 
      {764, 5},
      {763, 5},
      {762, 5},
      {761, 5}, 
      {760, 5}, 
      {759, 5},
      {758, 5},
      {757, 5},
      {756, 5},
      {755, 5}, 
      {754, 7},
      {753, 7},
      {752, 7}, 
      {751, 7},
      {750, 8}, 
      {749, 8},
      {748, 8},
      {747, 8}, 
      {746, 8},
      {745, 9},
      {744, 9},
      {743, 9},
      {742, 9},
      {741, 9},
      {740, 9},
      {739, 10},
      {739, 11},
      {738, 11},
      {737, 11},
      {736, 11},
      {735, 13},
      {734, 13},
      {733, 13}, 
      {732, 13},
      {731, 13},
      {730, 13},
      {729, 13},
      {728, 13},
      {727, 14},
      {726, 14},
      {725, 14},
      {724, 14},
      {723, 14},
      {722, 14},
      {721, 14},
      {720, 15},
      {719, 15},
      {718, 15},
      {717, 15},
      {716, 15},
      {715, 16},
      {714, 16}, 
      {713, 16},
      {712, 16},
      {711, 16},
      {710, 16},
      {709, 16},
      {708, 16}, 
      {707, 17},
      {706, 17},
      {705, 17},
      {704, 17},
      {703, 17},
      {702, 18},
      {701, 19}, 
      {700, 19},
      {699, 19},
      {698, 20},
      {697, 20},
      {696, 20},
      {695, 20},
      {694, 20},
      {692, 20},
      {691, 20},
      {690, 19},
      {689, 19},
      {688, 15},
      {687, 8},
      {686, 4},
      {685, 4},
      {684, 4},
      {683, 3},
      {682, 3},
      {681, 2},
      {680, 2},
      {679, 2},
      {678, 2},
      {677, 2},
      {676, 2},
      {675, 2},
      {674, 2},
      {673, 1},
      {672, 1}, 
      {671, 0},
      {670, 0},
      {669, 0},
      {668, 0},
      {667, 0},
      {666, 0},
      {665, 0},
      {664, 0},
      {663, 0},
      {662, 0},
      {661, 0},
      {660, 0},
      {659, 0},
      {658, 0},
      {657, 0},
      {656, 0},
      {655, 0},
      {654, 0},
      {653, 0},
      {652, 0},
      {651, 0},
      {650, 0},
      {649, 0},
      {648, 0},
      {647, 0},
      {646, 0},
      {645, 0},
      {644, 0},
      {643, 0},
      {642, 0},
      {641, 0},
      {640, 0},
      {639, 0},
      {638, 0},
      {637, 0},
      {636, 0},
      {635, 0},
      {634, 0},
      {633, 0},
    };
    
    //These pins record the values read by the accelorometer along its x, y, and z axes. 
    //To perform the calculations to determine the bottle's position only the x and y values are used.
    int value_x = analogRead(A9);
    int value_y = analogRead(A8);
    int value_z = analogRead(A7);
    
    int angle1 = sqrt(sq(value_y) + sq(value_x)); //Trig was used to determine the initial position of the waterbottle.
    
    delay(500); /*A delay is used to assist the device in determining if the change in values read is significant enough to 
    determine if there was a change in water bottle's position.
    */
    
    //the device records the same values again after 500 miliscond delay to calculate the water bottle's updates position.
    int value_x2 = analogRead(A9);
    int value_y2 = analogRead(A8);
    int angle2 = sqrt(sq(value_y2) + sq(value_x2));
    
    if (digitalRead(3) == HIGH){ //if the button is on then the device will record how much water is being displaced
      //Updates display to show the string "on" to indicate that the device is recording if the user is drinking water 
      display.drawRect(0, 0, 30, 10, BLACK);
      display.fillRect(0, 0, 30, 10, BLACK);
      display.setTextColor(WHITE, BLACK);
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 0);
      display.println(F("on"));
      display.display();
      delay(100);
      if(angle1 - angle2 > 5){ //if the change of value is greater than 5 then the water bottle must be changing its position and the values are not varying due to chance
        int index1 = map(angle2, 788, 684, 0, 155); //maps the final position value, angle2, to an index so it can be used to search the lookup table, there are 155 rows in the table
        int index2 = map(lastangle, 788, 684, 0, 155); //maps the postion stored in the value lastangle to and index
        int i = 0; //variable i is set to 0
        /*
          if for some reason the position recorded is outside the range of the lookup table, this could potentially map the index as a negative value and cause erros in the code. 
          Therefore this conditional prevents that error from occuring.
         */
        if(index2 < 0){ 
          index2 = 0;
        }
        /*This for loop loops through all the positions from the last drinking position recorded to the new drinking position and adds each displaced value between them.   
         The prupose of the for loop is to total all of the water displaced since the last recorded value. 
         */
        for(i=index2; i < index1; i = i + 1) { 
            dailyVol = dailyVol + array1[i][1];
        }
        //Updates screen everytime the dailyVol is updated 
        display.drawRect(70, 35, 50, 30, BLACK);
        display.fillRect(70, 35, 50, 30, BLACK);
        display.setTextColor(WHITE, BLACK);
        display.setTextSize(2); // Draw 2X-scale text
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(70, 35);
        display.println(String(dailyVol));
        display.display();
        delay(100);
        lastangle = angle2; //sets the varaible lastangle to the new angle position recorded 
      }
    }
    
    /*
     When the button value is read as LOW the display screen updates to read "off" in the top left corner
     */
    if (digitalRead(3) == LOW) {
      //display for when the button is off(not being pressed)
      display.drawRect(0, 0, 30, 10, BLACK);
      display.fillRect(0, 0, 30, 10, BLACK);
      display.setTextColor(WHITE, BLACK);
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 0);
      display.println(F("off"));
      display.display();
      delay(100);
    }
    
    
    static const unsigned char PROGMEM logo_bmp[] =
    { B00000000, B11000000,
      B00000001, B11000000,
      B00000001, B11000000,
      B00000011, B11100000,
      B11110011, B11100000,
      B11111110, B11111000,
      B01111110, B11111111,
      B00110011, B10011111,
      B00011111, B11111100,
      B00001101, B01110000,
      B00011011, B10100000,
      B00111111, B11100000,
      B00111111, B11110000,
      B01111100, B11110000,
      B01110000, B01110000,
      B00000000, B00110000
    };
    /*
     If the user reaches the hydration goal, if dailyVol is greater than or equal to 3000, then "congrats mode" is activated. "Congrats mode" refers to an animation 
     that is displayed on the screen to celebrate the user's acheievement.
     */
    if (dailyVol >= Goal) {
      if (congratsmode == false) { //Only goes into congrats mode once a day
      testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); //testanaimate is a helper function 
      congratsmode =  true; //congratsmode is changed to true so the animation only runs once a day, congratsmode reset to false when the reset button is pressed
      //resets the display
      display.setTextSize(1); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 55);
      display.println("Streak:");
      display.display();
      delay(100);
      display.setTextSize(1); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(45, 55);
      display.println(String(streak));
      display.display();
      delay(100);
      display.setTextSize(2); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 15);
      display.println(F("Goal:3000"));
      display.display();
      delay(100);
      display.setTextSize(2); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 35);
      display.println(F("Today:"));
      display.display();
      delay(100);
      display.setTextSize(2); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(70, 35);
      display.println(String(dailyVol));
      display.display();
      delay(100); 
      }
    }
    /*
     When the reset button is pressed, read as HIGH, congratsmode is reset to false and dailyVol is reset to 0.
     Also, if the dailyVol is greater than or equal to 3000 one is added to the streak. 
     */
    if (digitalRead(4) == HIGH){
      congratsmode = false; //resets so congrats mode can be activated again
      //The screen is updated to indicate to the user that the reset button is on
      display.drawRect(70, 0, 60, 10, BLACK);
      display.fillRect(70, 0, 60, 10, BLACK);
      display.setTextSize(1); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(70, 0);
      display.println("reset:on");
      display.display();
      delay(100);
      //if the dailyVol is greater than or equal to 3000 and then 1 is added to the streak 
      if (dailyVol >= 3000){
        streak = streak + 1;
        display.drawRect(45, 55, 50, 10, BLACK);
        display.fillRect(45, 55, 50, 10, BLACK);
        display.setTextSize(1); // Draw 2X-scale text
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(45, 55);
        display.println(String(streak));
        display.display();
        delay(100);
      }
      //if the dailyVol is not greater than or equal to 3000 and then the streak is reset to zero
      else {
        streak = 0
        display.drawRect(45, 55, 50, 10, BLACK);
        display.fillRect(45, 55, 50, 10, BLACK);
        display.setTextSize(1); // Draw 2X-scale text
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(45, 55);
        display.println(String(streak));
        display.display();
        delay(100);
      }
      //dailyVol is reset to zero and the screen is updated with the new value for dailyVol
      dailyVol = 0; 
      display.drawRect(70, 35, 50, 30, BLACK);
      display.fillRect(70, 35, 50, 30, BLACK);
      display.setTextColor(WHITE, BLACK);
      display.setTextSize(2); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(70, 35);
      display.println(String(dailyVol));
      display.display();
      delay(100);
    }
    //if the reset button is read as LOW the screen is updated to indicate that the reset button is off
    if (digitalRead(4) == LOW){
      display.drawRect(70, 0, 50, 10, BLACK);
      display.fillRect(70, 0, 50, 10, BLACK);
      display.setTextSize(1); // Draw 2X-scale text
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(70, 0);
      display.println("reset:off");
      display.display();
      delay(100);
    }
    }
    
    //helper function that creates the animiation used in congratsmode 
    
    #define XPOS   0 // Indexes into the 'icons' array in function below
    #define YPOS   1
    #define DELTAY 2
    //need to reset screen after animation
    void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
      int8_t f, icons[NUMFLAKES][3];
      // Initialize 'snowflake' positions
      for (f = 0; f < NUMFLAKES; f++) {
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
        Serial.print(F("x: "));
        Serial.print(icons[f][XPOS], DEC);
        Serial.print(F(" y: "));
        Serial.print(icons[f][YPOS], DEC);
        Serial.print(F(" dy: "));
        Serial.println(icons[f][DELTAY], DEC);
      }
      int i = 0;
      //for loop statement allows the loop to run 60 times
      for (i = 0; i < 60; i = i + 1) { 
        display.clearDisplay();
        display.setTextSize(2); // Draw 2X-scale text
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(15, 20);
        display.println("Congrats"); //Displays the string "Congrats" on the screen in addition to the animiation of the snow flakes 
        display.display();
        // Draw each snowflake:
        for (f = 0; f < NUMFLAKES; f++) {
          display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
        }
        display.display();
        delay(200);
        // Then update coordinates of each flake...
        for (f = 0; f < NUMFLAKES; f++) {
          icons[f][YPOS] += icons[f][DELTAY];
          // If snowflake is off the bottom of the screen...
          if (icons[f][YPOS] >= display.height()) {
            // Reinitialize to a random position, just off the top
            icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
            icons[f][YPOS]   = -LOGO_HEIGHT;
            icons[f][DELTAY] = random(1, 6);
          }
        }
      }
      display.clearDisplay();
    }